Skip to content

Conversation

@KodeSage
Copy link

@KodeSage KodeSage commented Sep 18, 2025

Summary by CodeRabbit

  • Bug Fixes
    • More reliable CLI defaults, validation and env handling (token, storage, date parsing) and safer script updates.
    • Improved cloud/websocket/message error handling and more consistent publish/build sequencing.
  • Refactor
    • Simplified public typings and tightened null/undefined handling for more predictable behavior.
    • Increased immutability and clearer async flow in core services.
  • Chores
    • ESLint config updated with type-checked presets.
  • Tests
    • Extensive test adjustments to match stricter types and runtime behaviors.

jchris and others added 2 commits September 17, 2025 11:45
This change upgrades from basic strict/stylistic configs to type-checked
configs (strictTypeChecked, stylisticTypeChecked, recommendedTypeChecked).
The stricter type-checking rules expose 2,304 lint violations that need
to be addressed.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
cli/tsc-cmd.ts Outdated

handler: async (args) => {
handleTsc(args as unknown as string[], sthis);
await handleTsc(args as unknown as string[], sthis);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just return

cli/write-env.ts Outdated
acc[i] = param.REQUIRED;
} else {
acc[k] = v;
const k = i.slice(0, eqIndex);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm cool here with this --- but i typical try to avoid double actions
i.splice
i.splice

setRows(rows.rows.map((i) => i.value));
});
};
loadRows().catch(console.error);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not:
database.allDocs<DocWithId<{ value: string }>>().then(() => setRows(rows.rows.map((i) => i.value).catch(console.error)

const car = await dbBlockstore.loader.commitFiles(
t,
{ files } as unknown as TransactionMeta /* {
{ files } as unknown /* {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we should dive down an figure out which is the right type.
Maybe change commitFiles.

break;
default:
return opts.tracer(event);
{ opts.tracer(event); return; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it this really enforced by our lint rules --- then we should change them
that the prev version is accepted

const docs: DocWithId<DocTypes>[] = updates.map(({ id, value }) => ({ ...value, _id: id }));
for (const listener of this._listeners) {
await (async () => await listener(docs as DocWithId<DocTypes>[]))().catch((e: Error) => {
await (async () => { await listener(docs); })().catch((e: Error) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this changed we should use then chaining to express this.

if (this._noupdate_listeners.size) {
for (const listener of this._noupdate_listeners) {
await (async () => await listener([]))().catch((e: Error) => {
await (async () => { await listener([]); })().catch((e: Error) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then chaining

const rStore = await exception2Result(
async () =>
await store.active.car.save({
{ await store.active.car.save({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then chain

throw this.logger.Error().Err(res.Err()).Msg("got error from gateway.put").AsError();
}
return res.Ok();
res.Ok();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error you need to return something

constructor(base64: Base64EndeCoder, cert: CertificatePayload) {
// this.#cert = cert;
this.#cert = deepFreeze(toSortedObject(cert)) as CertificatePayload;
this.#cert = deepFreeze(toSortedObject(cert) ?? cert);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't like it -- if we don't want to cast we should change the signature of toSortedObject to f tSO(x: R): R

caKey: rCaKey.Ok(),
caSubject: {
commonName: env.DEVICE_ID_CA_COMMON_NAME ?? "Fireproof CA",
commonName: env.DEVICE_ID_CA_COMMON_NAME || "Fireproof CA",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why now toggle from ?? to ||

if (jwk.kty === "RSA") return "RS256"; // tighten if you only support PS* or specific algs
throw new Error("Unsupported JWK kty/crv for CSR verification");
// Default case for RSA or any remaining kty
return "RS256"; // tighten if you only support PS* or specific algs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would only support ES256 keys all other should fail -:)

// publicKey = await extractPublicKeyFromCertificate(certInfo.certificate);
// Step 4: Verify JWT signature with extracted public key
const alg = (certInfo.algorithm as string) || "ES256";
const alg = (certInfo.algorithm!) || "ES256";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why here not ??

readonly #uriMapper = new Set<URIMapper>();

addMapper(mapper: URIMapper): URIInterceptor {
addMapper(mapper: URIMapper): this {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should leave the Original Type

},
});
await db.put({ _id: `id-${0}`, value: `value-${0}` });
await db.put({ _id: `id-0`, value: `value-0` });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would like to leave the ${0} just this is a reminder for me.

} from "@fireproof/core-types-base";

const sthis = ensureSuperThis();
const superThis = ensureSuperThis();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we name it alway sthis never superThis

}) as SerializedMeta[],
meta: (sthis, payload) => {
const json = JSON.parse(sthis.txt.decode(payload)) as (SerializedMeta & { key?: string })[];
key = json[0].key ?? "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change the cast one line above

export function MsgIsWithConn<T extends MsgBase>(msg: T): msg is MsgWithConn<T> {
const mwc = (msg as MsgWithConn<T>).conn;
return mwc && !!(mwc as QSId).reqId && !!(mwc as QSId).resId;
return mwc && !!(mwc).reqId && !!(mwc).resId;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the () could be removed

Copy link
Contributor

@mabels mabels left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, wonderful thx alot.
Could you try to work on the comments.

@KodeSage
Copy link
Author

alright, awesome
I will do just that now @mabels
Thank you for the review.

@mabels
Copy link
Contributor

mabels commented Sep 18, 2025

alright, awesome I will do just that now @mabels Thank you for the review.

Hi if you have question you could contact me in our Discord im 'fast&fear<'

@KodeSage
Copy link
Author

alright, awesome I will do just that now @mabels Thank you for the review.

Hi if you have question you could contact me in our Discord im 'fast&fear<'

@mabels please can I get your discord handle
have some couple of questions to ask

@mabels
Copy link
Contributor

mabels commented Sep 18, 2025

alright, awesome I will do just that now @mabels Thank you for the review.

Hi if you have question you could contact me in our Discord im 'fast&fear<'

@mabels please can I get your discord handle have some couple of questions to ask

my discord handle is fast&fear<

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 26, 2025

Walkthrough

Large, cross-cutting refactor focused on stricter typings, nullish-coalescing and readonly usage, removal of some awaits/async in helpers, and test updates. Multiple public method/type arities were narrowed or adjusted across CLI, core, gateways, protocols, dashboard, and use-fireproof.

Changes

Cohort / File(s) Summary of changes
CLI – commands & tests
cli/build-cmd.ts, cli/build-cmd.test.ts, cli/cloud-token-key-cmd.ts, cli/pre-signed-url.ts, cli/set-scripts-cmd.ts, cli/tsc-cmd.ts, cli/write-env.ts
Typing tightenings, switch from
Cloud backend – core server & dispatch
cloud/backend/base/hono-server.ts, cloud/backend/base/msg-dispatch.ts, cloud/backend/base/msg-dispatcher-impl.ts, cloud/backend/base/pre-signed-url.ts, cloud/backend/base/types.ts, cloud/backend/base/connection.test.ts, cloud/backend/base/meta-merger/meta-merger.test.ts
Narrowed WS send types; register returns this; removed forced casts in request/ws decode; unified auth flow to always await validateAuth; nullish-coalescing for defaults; DrizzleDatabase generic arity reduced; tests adjusted for null-safety and optional chaining.
Cloud backend – CF / Node
cloud/backend/cf-d1/*, cloud/backend/node/*
Removed redundant casts, tightened readonly fields, adjusted token selection (one file changed to
Core – CRDT, ledger, indexer, versioning
core/base/* (crdt-clock.ts, crdt-helpers.ts, crdt.ts, indexer*.ts, ledger.ts, version.ts)
Replace
Core – Blockstore & loader
core/blockstore/* (attachable-store.ts, loader-helpers.ts, loader.ts, store.ts, transaction.ts, commitor.ts, commitor-related tests)
Removed casts, converted some async helpers to sync (parseCarFile), commitQueue/enqueue usage changed to void for fire-and-forget, seenMeta made readonly, commit signature concrete, error handling tightened, many type adjustments in commit/loader paths and tests.
Core – Device ID
core/device-id/*
Added readonly to private fields, replaced
Core – Gateways & IndexedDB
core/gateways/*, core/gateways/indexeddb/*
Consistent Promise.resolve wrapping in some serde helpers, decode adjustments to non-async helpers, removed <unknown> generics on IDBPDatabase, tightened method param unions by removing undefined, some gateway method return type changes (less error typing).
Core – Protocols (cloud)
core/protocols/cloud/*
Simplified generic arity on bind/send/request signatures, made some helpers synchronous/Promise.resolve-based, decoding adjustments, mutex readonly, many type-tightening changes affecting message shapes.
Core – Runtime & crypto
core/runtime/*
Broadened some queue/element types, allowed sync or async IV calc/verify, removed some runtime null checks, utility signature changes (hashObjectSync/Async), various nullish-coalescing swaps.
Core – Types
core/types/*
Simplified/ narrowed several public types (CIDBlock.cid → CID, reduce generics removed in many APIs, EnDeCoder encode/decode use unknown), adjust MapFn/DocLiteral and other type unions.
use-fireproof (hooks/strategies)
use-fireproof/*, use-fireproof/react/*
Swapped type casts to non-null assertions for WebCtx, removed some return values (updateDoc now void), doc mapping without explicit casts, introduced a syntax/brace issue in use-attach token reset path.
Dashboard – backend & frontend
dashboard/backend/*, dashboard/src/**/*
Strict issuer/JWKS HTTPS requirements, createHandler db type generic arity reduced, some token/JWKS flows refactored; frontend handlers converted to block bodies, minor typing narrowings.
Tests & test harness
core/tests/**/*, cloud/*/tests/**/*, cli/*test.ts
Wide test updates to reflect tightened types: removed many as casts in favor of non-null assertions/explicit types, replaced
Config & linting
eslint.config.mjs
Switch to type-checked presets, update parserOptions and ignore patterns, add TS rule overrides (prefer-readonly).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant CLI as CLI buildCmd
  participant FS as FileSystem / fs
  participant PKG as package.json
  participant PUBLISH as publish/pnpm/deno

  Note over CLI,FS: buildCmd orchestration changes (awaits and sync helpers)
  CLI->>FS: read package.json (await)
  CLI->>PKG: buildJsrConf(pj, version)  -- now synchronous call (no await)
  CLI->>FS: copy root to npm (await fs.copy(...))
  CLI->>PUBLISH: publish steps (await where added)
  Note right of PUBLISH: Timing of jsr.json generation changed because buildJsrConf is sync and may run without await when publishing
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • mabels
  • jchris

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The title “Feat/fix 2304 lint errors” loosely describes resolving a large number of lint issues but uses an unconventional slash between “Feat” and “fix” and embeds a raw count that may confuse readers; it is related to the changes but not phrased as a clear, concise summary of the primary effort. Consider renaming the title to clearly reflect the main change, for example “Fix lint errors across the codebase (issue #2304)” to use a consistent verb, remove the slash, and reference the issue number rather than a raw error count.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
cli/build-cmd.ts (1)

170-190: Bug: regex incorrectly matches due to trailing '|' in group

The check uses /.(js|mjs|cjs|)$/.exec(v). The empty alternative '|' causes unintended behavior (matches a trailing dot only) and can skip valid entries. Use a strict extension test.

Apply this diff:

-        const x = Object.entries(v).reduce((acc, [_, v]) => {
-          if (acc === "") {
-            if (/\.(js|mjs|cjs|)$/.exec(v)) {
-              return v.replace(/\.(js|mjs|cjs)$/, ".ts");
-            }
-          }
-          return acc;
-        }, "");
+        const x = Object.entries(v).reduce((acc, [_, val]) => {
+          if (acc === "" && /\.(js|mjs|cjs)$/.test(val)) {
+            return val.replace(/\.(js|mjs|cjs)$/, ".ts");
+          }
+          return acc;
+        }, "");
dashboard/backend/create-handler.ts (1)

72-84: Restore base64url-safe decoding for JWT payloads.

Using atob(payloadB64) rejects standard JWT segments (base64url uses -, _, no padding) and also throws in Node environments where atob is undefined. This now breaks issuer discovery for Clerk tokens. Please normalize to base64url and fall back to Buffer where atob is unavailable so the handler remains portable.

-      const payload = JSON.parse(atob(payloadB64)) as { iss?: string };
+      const normalizeBase64Url = (input: string) =>
+        `${input.replace(/-/g, "+").replace(/_/g, "/")}====`.slice(0, input.length + (4 - (input.length % 4 || 4)));
+      const normalizedPayload = normalizeBase64Url(payloadB64);
+      const payloadJson =
+        typeof Buffer !== "undefined"
+          ? Buffer.from(normalizedPayload, "base64").toString("utf8")
+          : globalThis.atob(normalizedPayload);
+      const payload = JSON.parse(payloadJson) as { iss?: string };
core/blockstore/store.ts (1)

172-184: Use a proper parent type instead of casting
The parents as unknown as Link<EventView<DbMetaBinary>>[] bypasses type safety—CarClockHead is CarClockLink[] (links for CarClockBinary), not DbMetaBinary. Update createDbMetaEvent to accept Link<EventView<DbMetaBinary>>[] directly (or define a generic EventHead<T>), and remove the unsafe cast. [core/blockstore/store.ts:172–175]

♻️ Duplicate comments (3)
use-fireproof/iframe-strategy.ts (1)

82-82: Same WebCtx non-null assertion pattern as redirect-strategy.ts.

These non-null assertions assume WebCtx is always present in the context, matching the pattern in redirect-strategy.ts (lines 88, 165, 210). See the verification script in the redirect-strategy.ts review to ensure proper context initialization across the codebase.

Also applies to: 86-86

core/device-id/device-id-validator.ts (1)

32-33: Do not default to RS256; fail closed on unsupported JWKs

Defaulting unknown kty to "RS256" weakens the security posture and contradicts prior guidance to only support ES256.

Enforce ES256 (P-256) only (or a strict allowlist), and reject others.

Apply this diff to deriveAlgFromJwk:

 function deriveAlgFromJwk(jwk: JWKPublic): string {
-  if (jwk.kty === "EC") {
-    switch (jwk.crv) {
-      case "P-256":
-        return "ES256";
-      case "P-384":
-        return "ES384";
-      case "P-521":
-        return "ES512";
-    }
-  }
-  if (jwk.kty === "OKP") return "EdDSA";
-  // Default case for RSA or any remaining kty
-  return "RS256"; // tighten if you only support PS* or specific algs
+  if (jwk.kty === "EC" && jwk.crv === "P-256") {
+    return "ES256";
+  }
+  // Fail closed: only ES256 is supported
+  throw new Error("Unsupported key type/curve; only ES256 (P-256) is accepted");
 }

Additionally, ignore header.alg and enforce the derived one:

-      const alg = typeof header.alg === "string" ? header.alg : deriveAlgFromJwk(publicKey);
+      const alg = deriveAlgFromJwk(publicKey);
+      if (typeof header.alg === "string" && header.alg !== alg) {
+        return { valid: false, error: "Header alg does not match key type" };
+      }

Based on learnings

core/blockstore/store.ts (1)

382-382: Remove no-op statement.

Calling res.Ok() without using or returning its value has no effect. Since the function returns Promise<void>, either remove this line or clarify its purpose. Based on learnings.

Apply this diff:

-    res.Ok();
🧹 Nitpick comments (19)
core/types/protocols/cloud/msg-types.ts (1)

538-541: LGTM! The type assertion removal is correct.

Removing the explicit cast (mwc as QSId) is appropriate since TypeScript already infers that mwc is of type QSId from the assignment on line 539. This simplifies the code while maintaining type safety.

Optional: Remove remaining parentheses.

Based on learnings

The parentheses around (mwc) in !!(mwc).reqId and !!(mwc).resId are unnecessary and can be removed for cleaner syntax:

-  return mwc && !!(mwc).reqId && !!(mwc).resId;
+  return mwc && !!mwc.reqId && !!mwc.resId;
core/device-id/device-id-verify-msg.ts (1)

123-123: Prefer nullish coalescing ?? over non-null assertion with ||.

The non-null assertion ! followed by || is unnecessary and less idiomatic than using the nullish coalescing operator ?? directly. Since certInfo.algorithm is string | undefined, you can simplify this to:

const alg = certInfo.algorithm ?? "ES256";

This approach:

  • Removes the unnecessary non-null assertion
  • Is more concise and clearer about intent
  • Matches the pattern used elsewhere in the codebase (see core/runtime/sts-service/index.ts:87-89 which uses this.#param.alg ?? "ES256")
  • Addresses the previous review comment from mabels asking "why here not ??"

Apply this diff:

-      const alg = (certInfo.algorithm!) || "ES256";
+      const alg = certInfo.algorithm ?? "ES256";
core/tests/runtime/key-bag.test.ts (1)

296-296: Note: Optional chaining with non-null assertion.

The pattern kfp1?.fingerPrint! combines optional chaining with a non-null assertion. While this works in test code where kfp1 is expected to be defined, consider whether the optional chaining is necessary if you're asserting the property is non-null.

If kfp1 is always expected to be defined at this point, consider:

-        [kfp1?.fingerPrint!]: {
+        [kfp1!.fingerPrint]: {

This makes the assertion more explicit about what's being checked.

dashboard/backend/sql-helper.ts (1)

52-52: Explicit string conversion fixes lint error.

The change from implicit string concatenation to explicit .toString() is correct. The random userId acts as a no-match sentinel when no valid query parameters are provided.

For better clarity, consider using a constant sentinel value instead of a random number:

-  let where: ReturnType<typeof and> = eq(table.userId, Math.random().toString());
+  const NEVER_MATCH_SENTINEL = "__never_match__";
+  let where: ReturnType<typeof and> = eq(table.userId, NEVER_MATCH_SENTINEL);
core/runtime/async-block-encode.ts (1)

41-41: Inconsistent type assertion with line 93.

The explicit type assertion was removed here, but line 93 in asyncBlockEncode still explicitly casts CID.create(1, codec.code, hash) as CID<T, Code, Alg, 1>. This inconsistency should be resolved—either both should rely on inference or both should have explicit assertions for type safety.

If the type assertion is unnecessary in both places, apply this diff to line 93 for consistency:

-  const cid = CID.create(1, codec.code, hash) as CID<T, Code, Alg, 1>;
+  const cid = CID.create(1, codec.code, hash);

Alternatively, if the assertion is needed for correctness, add it back to line 41:

-  const cid = CID.create(1, codec.code, hash);
+  const cid = CID.create(1, codec.code, hash) as CID<T, Code, Alg, 1>;
dashboard/src/pages/cloud/tenants/new.tsx (1)

11-11: Consider retaining type safety for the parsed JSON.

Removing the type assertion reduces compile-time type safety. While this may address a lint error, consider using a proper type definition or interface instead.

Apply this diff to maintain type safety:

+interface CreateTenantRequest {
+  tenantName?: string;
+}
+
 export function newCloudAction(ctx: AppContextType) {
   return async ({ request }: { request: Request }) => {
-    const tenantName = ((await request.json())).tenantName;
+    const tenantName = ((await request.json()) as CreateTenantRequest).tenantName;
core/device-id/device-id-signed-msg.ts (1)

17-17: Consider retaining the generic type parameter for flexibility.

Removing the generic type parameter <T extends JWTPayload> reduces type flexibility for callers who want to preserve specific payload type information.

Based on learnings

Apply this diff to restore the generic:

-  async sign(payload: JWTPayload, algorithm = "ES256") {
+  async sign<T extends JWTPayload>(payload: T, algorithm = "ES256") {
core/device-id/certor.ts (1)

30-30: Consider the signature change suggested in past reviews.

A previous reviewer (mabels) suggested changing the signature of toSortedObject to <R>(x: R): R to avoid the need for this fallback. While the current approach (?? cert) works, the signature change might be cleaner and avoid the nullish check entirely.

Based on learnings

dashboard/backend/api.ts (1)

1216-1216: Non-null assertions are safe under the existing filter guard. To eliminate the !, introduce an explicit type predicate that narrows RoleType (e.g. filter((i): i is TenantRole => i.role === "admin" && Boolean(i.tenantId))) before mapping.

core/types/blockstore/types.ts (1)

84-86: Type consistency: CID vs Link

CIDBlock.cid widened to CID, but AnyBlock.cid is Link<…>. toCIDBlock casts AnyBlock to CIDBlock, relying on structural compatibility between CID and Link. This can regress type guarantees and confuse callers expecting AnyLink everywhere.

Consider keeping CIDBlock.cid as AnyLink (or Link<…>) to stay consistent, or change AnyBlock.cid to CID across the board.

dashboard/src/components/Sidebar.tsx (1)

19-19: LGTM! Consistent arrow function formatting.

The block-bodied arrow function syntax is applied consistently across these onClick handlers.

Also applies to: 24-24, 53-53

dashboard/src/pages/databases/show.tsx (1)

96-96: LGTM! Consistent arrow function formatting.

The block-bodied arrow function syntax is applied consistently across event handlers and callbacks.

Also applies to: 98-98, 139-139, 161-161, 169-169, 275-275

core/protocols/cloud/ws-connection.ts (1)

156-156: LGTM! Type inference and formatting improvements.

Line 156: Removing the explicit MsgBase type assertion allows TypeScript to infer the correct type from ende.decode(), which is safer and cleaner.

Lines 174, 183: Block-bodied forEach callbacks maintain consistency with the broader formatting updates.

Also applies to: 174-174, 183-183

dashboard/src/pages/databases.tsx (2)

30-30: LGTM! Non-null assertion is more idiomatic.

Replacing dbName as string with dbName! is the preferred TypeScript pattern for asserting non-nullability, especially since the filter on line 20 already excludes entries where db.name could be undefined.


68-68: LGTM! Consistent arrow function formatting.

Block-bodied arrow functions applied consistently across onClick handlers.

Also applies to: 111-111

core/gateways/indexeddb/key-bag-indexeddb.ts (1)

7-7: LGTM! Tightened IDBPDatabase typing.

Removing the <unknown> generic parameter aligns with the broader IDB database typing updates across indexeddb gateways and improves type safety.

Also applies to: 20-20

core/tests/fireproof/indexer.test.ts (1)

27-27: LGTM! Simplified test type declarations.

Reducing Index<TestType, string> to Index<TestType> relies on the default generic parameter (K extends IndexKeyType = string) and simplifies the test code without changing behavior.

Also applies to: 145-145, 243-243, 275-275

core/tests/protocols/cloud/msger.test.ts (1)

101-101: LGTM! Simplified test type declarations.

Reducing OnMsgFn<MsgBase> to OnMsgFn relies on the default generic parameter (T extends MsgBase = MsgBase) and simplifies the test mock code.

Also applies to: 246-246, 365-365

core/tests/fireproof/fireproof.test.ts (1)

32-32: LGTM! Simplified test type usage.

Reducing generic arity (e.g., IndexRows<Doc, string>IndexRows<Doc>, index<Doc, string>index<Doc>) throughout the tests relies on default type parameters and simplifies the code without altering behavior.

Also applies to: 75-75, 89-89, 192-192, 216-216, 219-219, 543-543, 563-563, 569-569

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8778561 and 8777427.

📒 Files selected for processing (99)
  • cli/build-cmd.test.ts (11 hunks)
  • cli/build-cmd.ts (6 hunks)
  • cli/cloud-token-key-cmd.ts (1 hunks)
  • cli/pre-signed-url.ts (3 hunks)
  • cli/set-scripts-cmd.ts (3 hunks)
  • cli/tsc-cmd.ts (1 hunks)
  • cli/write-env.ts (1 hunks)
  • cloud/3rd-party/src/App.tsx (2 hunks)
  • cloud/backend/base/connection.test.ts (4 hunks)
  • cloud/backend/base/hono-server.ts (4 hunks)
  • cloud/backend/base/meta-merger/meta-merger.test.ts (3 hunks)
  • cloud/backend/base/msg-dispatch.ts (1 hunks)
  • cloud/backend/base/msg-dispatcher-impl.ts (1 hunks)
  • cloud/backend/base/pre-signed-url.ts (2 hunks)
  • cloud/backend/base/types.ts (1 hunks)
  • cloud/backend/cf-d1/cf-hono-server.ts (5 hunks)
  • cloud/backend/cf-d1/drizzle.cloud.d1-remote.config.ts (1 hunks)
  • cloud/backend/node/node-hono-server.ts (2 hunks)
  • cloud/backend/node/setup-backend-node.ts (1 hunks)
  • cloud/base/globalSetup.spin-minio.ts (1 hunks)
  • cloud/base/test-global-helper.ts (1 hunks)
  • cloud/todo-app/src/components/TodoFilters.tsx (1 hunks)
  • cloud/todo-app/src/components/TodoForm.tsx (1 hunks)
  • core/base/crdt-clock.ts (3 hunks)
  • core/base/crdt-helpers.ts (4 hunks)
  • core/base/crdt.ts (2 hunks)
  • core/base/indexer-helpers.ts (1 hunks)
  • core/base/indexer.ts (1 hunks)
  • core/base/ledger.ts (4 hunks)
  • core/base/version.ts (1 hunks)
  • core/blockstore/attachable-store.ts (2 hunks)
  • core/blockstore/loader-helpers.ts (1 hunks)
  • core/blockstore/loader.ts (3 hunks)
  • core/blockstore/store.ts (7 hunks)
  • core/blockstore/transaction.ts (1 hunks)
  • core/device-id/certor.ts (2 hunks)
  • core/device-id/device-id-CA.ts (3 hunks)
  • core/device-id/device-id-CSR.ts (1 hunks)
  • core/device-id/device-id-client.ts (1 hunks)
  • core/device-id/device-id-key.ts (1 hunks)
  • core/device-id/device-id-protocol.ts (2 hunks)
  • core/device-id/device-id-signed-msg.ts (1 hunks)
  • core/device-id/device-id-validator.ts (3 hunks)
  • core/device-id/device-id-verify-msg.ts (1 hunks)
  • core/gateways/base/fp-envelope-serialize.ts (1 hunks)
  • core/gateways/base/meta-key-hack.ts (2 hunks)
  • core/gateways/base/uri-interceptor.ts (1 hunks)
  • core/gateways/cloud/to-cloud.ts (1 hunks)
  • core/gateways/file-node/node-filesystem.ts (2 hunks)
  • core/gateways/indexeddb/dummy-idb.ts (9 hunks)
  • core/gateways/indexeddb/gateway-impl.ts (2 hunks)
  • core/gateways/indexeddb/key-bag-indexeddb.ts (2 hunks)
  • core/keybag/internal-keys-by-fingerprint.ts (1 hunks)
  • core/keybag/key-with-fingerprint.ts (1 hunks)
  • core/protocols/cloud/http-connection.ts (4 hunks)
  • core/protocols/cloud/msger.ts (3 hunks)
  • core/protocols/cloud/ws-connection.ts (3 hunks)
  • core/runtime/async-block-encode.ts (1 hunks)
  • core/runtime/commit-queue.ts (2 hunks)
  • core/runtime/keyed-crypto.ts (1 hunks)
  • core/runtime/register-compact-strategy.ts (1 hunks)
  • core/runtime/utils.ts (1 hunks)
  • core/tests/blockstore/keyed-crypto-indexeddb-file.test.ts (1 hunks)
  • core/tests/blockstore/keyed-crypto.test.ts (6 hunks)
  • core/tests/fireproof/all-gateway.test.ts (1 hunks)
  • core/tests/fireproof/attachable-subscription.test.ts (6 hunks)
  • core/tests/fireproof/attachable.test.ts (2 hunks)
  • core/tests/fireproof/database.test.ts (9 hunks)
  • core/tests/fireproof/fireproof.test.ts (7 hunks)
  • core/tests/fireproof/hello.test.ts (1 hunks)
  • core/tests/fireproof/indexer.test.ts (4 hunks)
  • core/tests/fireproof/query-docs.test.ts (3 hunks)
  • core/tests/protocols/cloud/msger.test.ts (3 hunks)
  • core/tests/runtime/device-id.test.ts (13 hunks)
  • core/tests/runtime/fp-envelope-serialize.test.ts (1 hunks)
  • core/tests/runtime/hash.test.ts (1 hunks)
  • core/tests/runtime/key-bag.test.ts (3 hunks)
  • core/tests/runtime/meta-key-hack.test.ts (1 hunks)
  • core/types/blockstore/types.ts (2 hunks)
  • core/types/protocols/cloud/msg-types.ts (1 hunks)
  • dashboard/backend/api.ts (3 hunks)
  • dashboard/backend/create-handler.ts (3 hunks)
  • dashboard/backend/sql-helper.ts (3 hunks)
  • dashboard/src/components/DynamicTable.tsx (1 hunks)
  • dashboard/src/components/Sidebar.tsx (2 hunks)
  • dashboard/src/pages/cloud.tsx (2 hunks)
  • dashboard/src/pages/cloud/api/token.tsx (1 hunks)
  • dashboard/src/pages/cloud/tenants/members.tsx (3 hunks)
  • dashboard/src/pages/cloud/tenants/new.tsx (1 hunks)
  • dashboard/src/pages/databases.tsx (3 hunks)
  • dashboard/src/pages/databases/show.tsx (4 hunks)
  • dashboard/src/pages/signup.tsx (1 hunks)
  • eslint.config.mjs (2 hunks)
  • use-fireproof/iframe-strategy.ts (1 hunks)
  • use-fireproof/react/use-all-docs.ts (1 hunks)
  • use-fireproof/react/use-attach.ts (2 hunks)
  • use-fireproof/react/use-changes.ts (1 hunks)
  • use-fireproof/react/use-document.ts (1 hunks)
  • use-fireproof/redirect-strategy.ts (4 hunks)
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: mabels
PR: fireproof-storage/fireproof#1016
File: cli/package.json:8-12
Timestamp: 2025-08-05T07:28:12.473Z
Learning: In PR #1016 "update tsc -> tsgo", mabels is implementing global control over TypeScript compilation across the fireproof monorepo by replacing direct "tsc" calls with "core-cli tsc" in package.json build scripts. This allows centralized management of which TypeScript compiler to use (tsc vs tsgo) through the core-cli tool.
📚 Learning: 2025-09-09T19:39:41.805Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: core/device-id/device-id-key.ts:18-29
Timestamp: 2025-09-09T19:39:41.805Z
Learning: In core/device-id/device-id-key.ts, mabels prefers using Zod's safeParse method for JWK validation instead of direct parse() calls, as it provides better error handling and aligns with the codebase's Result pattern.

Applied to files:

  • core/device-id/device-id-protocol.ts
  • core/device-id/device-id-validator.ts
📚 Learning: 2025-09-09T19:57:45.468Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: core/device-id/device-id-signed-msg.ts:17-18
Timestamp: 2025-09-09T19:57:45.468Z
Learning: In core/device-id/device-id-signed-msg.ts, mabels prefers using generic type constraints like `<T extends JWTPayload>` rather than direct type usage when maintaining type flexibility is beneficial, particularly for methods that need to preserve input type information while ensuring proper constraints.

Applied to files:

  • core/device-id/device-id-signed-msg.ts
📚 Learning: 2025-09-09T20:13:36.836Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: core/gateways/cloud/to-cloud.ts:155-160
Timestamp: 2025-09-09T20:13:36.836Z
Learning: In core/gateways/cloud/to-cloud.ts, the configHash method currently hashes this.opts directly, which includes volatile fields like strategy/token/events/context. The user (mabels) indicated this is part of a second iteration approach, suggesting they plan to address hash stability in a future iteration.

Applied to files:

  • core/gateways/cloud/to-cloud.ts
📚 Learning: 2025-09-10T10:00:01.115Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: use-fireproof/react/use-attach.ts:160-185
Timestamp: 2025-09-10T10:00:01.115Z
Learning: In use-fireproof/react/use-attach.ts, there's a potential memory leak where the onTokenChange callback can call setAttachState after component unmount. The fix requires adding cleanup to the useEffect with a disposed flag to guard state updates, and ideally refactoring WebToCloudCtx.onTokenChange to return an unsubscribe function.

Applied to files:

  • use-fireproof/react/use-attach.ts
📚 Learning: 2025-09-10T09:53:26.979Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: use-fireproof/react/use-attach.ts:186-199
Timestamp: 2025-09-10T09:53:26.979Z
Learning: In use-fireproof/react/use-attach.ts, the current attach flow calls database.attach immediately via KeyedResolvOnce, which can run before a token is available and won't re-run after resetToken. The attach logic should be moved into the onTokenChange success path to ensure it only runs when a valid token exists and can re-attach after token reset.

Applied to files:

  • use-fireproof/react/use-attach.ts
📚 Learning: 2025-09-15T06:39:20.409Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1138
File: use-fireproof/react/use-attach.ts:166-168
Timestamp: 2025-09-15T06:39:20.409Z
Learning: In use-fireproof/react/use-attach.ts, the KeyedResolvOnce with composite key (database.ledger.refId() + config.attach.configHash(database.ledger)) is intentionally designed to prevent double attach scenarios which could cause serious trouble. Reattachment is not supported - the reset functionality only manages token state, not re-attachment. The underlying layers also have mechanisms to address double attach issues.

Applied to files:

  • use-fireproof/react/use-attach.ts
📚 Learning: 2025-09-09T20:18:00.903Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: core/keybag/coerce-keyed-item.ts:44-75
Timestamp: 2025-09-09T20:18:00.903Z
Learning: The `coerceKeyedItem` function in `core/keybag/coerce-keyed-item.ts` is only used for loading from backend storage as a read-only operation, not for writing back changes. Therefore, tracking modification flags in this context is unnecessary.

Applied to files:

  • core/tests/runtime/key-bag.test.ts
  • core/keybag/internal-keys-by-fingerprint.ts
📚 Learning: 2025-07-31T06:20:06.137Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1014
File: core/tests/runtime/key-bag.test.ts:45-45
Timestamp: 2025-07-31T06:20:06.137Z
Learning: In migration tests for the Fireproof keybag system, unsafe type casting (e.g., `as unknown as V2KeysItem`) is acceptable when simulating legacy V1 keybag data that might exist in persistent storage. This allows testing of backward compatibility and migration behavior from V1 to V2 formats, even though production code should not allow setting V1 keybags directly.

Applied to files:

  • core/tests/runtime/key-bag.test.ts
📚 Learning: 2025-08-05T07:27:02.871Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1016
File: cli/main.ts:15-18
Timestamp: 2025-08-05T07:27:02.871Z
Learning: In cli/main.ts, the special-case handling for the "tsc" command (checking process.argv[2] === "tsc" and calling handleTsc directly) is an intentional workaround for current limitations with the cmd-ts framework. The user mabels is working with the cmd-ts author (schniz) to fix this properly at the framework level.

Applied to files:

  • cli/tsc-cmd.ts
📚 Learning: 2025-08-05T07:29:57.380Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1016
File: cli/tsc-cmd.ts:36-38
Timestamp: 2025-08-05T07:29:57.380Z
Learning: In cli/tsc-cmd.ts, the unsafe type assertion `args as unknown as string[]` in the tscCmd handler function is intentionally kept as a placeholder/reminder while waiting for cmd-ts framework fixes. The code is currently unused and serves as a reference for future implementation once the framework limitations are resolved.

Applied to files:

  • cli/tsc-cmd.ts
📚 Learning: 2025-08-05T07:33:19.459Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1016
File: cli/tsc-cmd.ts:27-34
Timestamp: 2025-08-05T07:33:19.459Z
Learning: The cmd-ts framework has built-in circuit breakers that override custom handling of --help and --version flags, preventing pass-through argument approaches for CLI wrappers. This is why the special-case handling for "tsc" command in cli/main.ts bypasses cmd-ts subcommand parsing entirely.

Applied to files:

  • cli/tsc-cmd.ts
📚 Learning: 2025-09-09T20:45:43.368Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: core/device-id/device-id-CA.ts:126-139
Timestamp: 2025-09-09T20:45:43.368Z
Learning: In core/device-id/device-id-key.ts, the exportPrivateJWK() method returns a JWKPrivate object (JWK format), not a KeyLike. When using this with jose's SignJWT.sign(), the JWK must be imported using importJWK() to convert it to a KeyLike that the signing method accepts.

Applied to files:

  • core/device-id/device-id-key.ts
  • core/tests/runtime/device-id.test.ts
📚 Learning: 2025-09-09T19:32:49.609Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: core/device-id/device-id-CSR.ts:14-15
Timestamp: 2025-09-09T19:32:49.609Z
Learning: In core/device-id/device-id-CSR.ts, the Extensions type already has all properties defined as optional in the ExtensionsSchema Zod definition, so `extensions: Extensions = {}` is valid TypeScript and does not need to be changed to `Partial<Extensions>`.

Applied to files:

  • core/device-id/device-id-CSR.ts
🧬 Code graph analysis (36)
cloud/backend/base/connection.test.ts (1)
core/protocols/cloud/msger.ts (1)
  • conn (250-256)
core/device-id/device-id-client.ts (1)
core/device-id/device-id-signed-msg.ts (1)
  • DeviceIdSignMsg (6-39)
core/base/crdt.ts (1)
core/base/ledger.ts (1)
  • opts (97-99)
core/device-id/certor.ts (1)
core/runtime/utils.ts (1)
  • deepFreeze (607-614)
core/gateways/base/fp-envelope-serialize.ts (1)
core/types/blockstore/types.ts (1)
  • DbMetaBinary (702-704)
use-fireproof/iframe-strategy.ts (3)
use-fireproof/react/use-attach.ts (1)
  • WebCtx (20-20)
core/types/base/types.ts (1)
  • SuperThis (150-162)
core/types/protocols/cloud/gateway-control.ts (2)
  • ToCloudOpts (79-79)
  • TokenAndClaims (10-17)
core/tests/protocols/cloud/msger.test.ts (1)
core/protocols/cloud/msger.ts (2)
  • OnMsgFn (57-57)
  • UnReg (58-58)
core/device-id/device-id-verify-msg.ts (1)
core/runtime/sts-service/index.ts (1)
  • alg (88-90)
core/tests/fireproof/hello.test.ts (1)
core/base/indexer.ts (1)
  • index (52-70)
core/device-id/device-id-signed-msg.ts (1)
core/types/base/jwt-payload.zod.ts (1)
  • JWTPayload (14-14)
cli/build-cmd.test.ts (1)
cli/build-cmd.ts (1)
  • PackageJson (105-114)
use-fireproof/react/use-document.ts (1)
core/gateways/cloud/to-cloud.ts (1)
  • reset (191-195)
core/gateways/indexeddb/gateway-impl.ts (1)
core/types/base/types.ts (1)
  • PARAM (42-68)
dashboard/backend/api.ts (1)
dashboard/backend/invites.ts (1)
  • sqlInviteTickets (17-61)
cloud/backend/cf-d1/cf-hono-server.ts (1)
cloud/backend/base/hono-server.ts (1)
  • ws (204-211)
dashboard/backend/create-handler.ts (1)
dashboard/backend/cf-serve.ts (1)
  • Env (6-10)
core/base/indexer.ts (1)
core/base/ledger.ts (1)
  • crdt (118-120)
core/gateways/cloud/to-cloud.ts (1)
core/types/protocols/cloud/gateway-control.ts (1)
  • TokenAndClaims (10-17)
use-fireproof/redirect-strategy.ts (1)
use-fireproof/react/use-attach.ts (1)
  • WebCtx (20-20)
core/tests/fireproof/query-docs.test.ts (1)
core/types/base/types.ts (2)
  • IndexRowsWithoutDocs (308-310)
  • IndexRowsWithDocs (312-315)
cloud/backend/base/hono-server.ts (1)
cloud/backend/base/msg-dispatch.ts (1)
  • Promisable (51-51)
core/tests/runtime/key-bag.test.ts (1)
core/types/base/keybag-storage.zod.ts (1)
  • KeyedV2StorageKeyItem (61-61)
cloud/backend/base/msg-dispatcher-impl.ts (1)
core/types/protocols/cloud/msg-types.ts (1)
  • buildErrorMsg (631-653)
cloud/3rd-party/src/App.tsx (1)
core/types/base/types.ts (1)
  • DocWithId (238-238)
core/tests/runtime/fp-envelope-serialize.test.ts (1)
core/gateways/base/fp-envelope-serialize.ts (1)
  • fpDeserialize (177-208)
cli/tsc-cmd.ts (1)
core/base/ledger.ts (1)
  • sthis (115-117)
core/keybag/internal-keys-by-fingerprint.ts (1)
core/types/base/keybag-storage.zod.ts (1)
  • KeysItem (19-19)
core/protocols/cloud/msger.ts (1)
vendor/p-limit/index.js (1)
  • pLimit (3-91)
cli/pre-signed-url.ts (1)
core/base/ledger.ts (1)
  • sthis (115-117)
core/tests/runtime/device-id.test.ts (5)
core/runtime/utils.ts (1)
  • ensureSuperThis (192-208)
core/device-id/device-id-CSR.ts (1)
  • DeviceIdCSR (6-59)
core/device-id/device-id-CA.ts (1)
  • DeviceIdCA (45-206)
core/device-id/device-id-key.ts (1)
  • DeviceIdKey (5-61)
core/types/base/fp-ca-cert-payload.zod.ts (1)
  • Certificate (64-64)
cloud/backend/node/node-hono-server.ts (2)
core/types/protocols/cloud/msg-types.ts (4)
  • MsgBase (175-180)
  • MsgWithConn (191-191)
  • MsgIsWithConn (538-541)
  • qsidKey (163-165)
cloud/backend/base/types.ts (1)
  • WSEventsConnId (69-74)
cloud/backend/base/meta-merger/meta-merger.test.ts (1)
core/base/ledger.ts (1)
  • sthis (115-117)
core/keybag/key-with-fingerprint.ts (1)
core/types/base/types.ts (1)
  • KeyMaterial (784-787)
core/tests/fireproof/fireproof.test.ts (2)
core/types/base/types.ts (1)
  • IndexRows (317-319)
core/base/indexer.ts (3)
  • query (198-274)
  • index (52-70)
  • Index (77-368)
core/blockstore/store.ts (2)
core/types/base/types.ts (1)
  • PARAM (42-68)
core/types/blockstore/types.ts (1)
  • DbMetaBinary (702-704)
core/tests/fireproof/indexer.test.ts (1)
core/base/indexer.ts (1)
  • Index (77-368)
🪛 ast-grep (0.39.5)
use-fireproof/redirect-strategy.ts

[warning] 105-105: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: styleNode.innerHTML = DOMPurify.sanitize(this.overlayCss)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

return drizzle(
createClient({
url: sthis.env.get("FP_TEST_SQL_URL") as string,
url: sthis.env.get("FP_TEST_SQL_URL")!,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add explicit validation instead of non-null assertion.

The non-null assertion (!) will cause LibSQL to throw a cryptic error if FP_TEST_SQL_URL is not set. In test setup, fail fast with a clear error message to improve developer experience.

Apply this diff to add explicit validation:

         const { createClient } = await import("@libsql/client");
+        const url = sthis.env.get("FP_TEST_SQL_URL");
+        if (!url) {
+          throw new Error("FP_TEST_SQL_URL environment variable is required for tests");
+        }
         return drizzle(
           createClient({
-            url: sthis.env.get("FP_TEST_SQL_URL")!,
+            url,
           }),
         );
🤖 Prompt for AI Agents
In cloud/backend/base/meta-merger/meta-merger.test.ts around line 54, the test
currently uses a non-null assertion on sthis.env.get("FP_TEST_SQL_URL") which
yields a cryptic LibSQL error when the env var is missing; replace the non-null
assertion with an explicit validation: read the env var into a local const, if
it's undefined or empty throw a clear Error (e.g. "FP_TEST_SQL_URL must be set
for tests") before passing it to the client setup so the test fails fast with a
helpful message.

const a4f = new AwsClient({
...env.aws,
region: env.aws.region || "us-east-1",
region: env.aws.region ?? "us-east-1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that region can handle empty strings.

Changing from || to ?? means an empty string for env.aws.region will no longer default to "us-east-1". This could cause AWS API errors if an empty region string is passed.

Run this script to verify valid region values:


🏁 Script executed:

#!/bin/bash
# Search for aws.region assignments and usage patterns
rg -nP --type=ts -C3 'aws\.region'

Length of output: 435


Handle empty region values
Switching from || to ?? means an empty env.aws.region will no longer default to "us-east-1", risking AWS API errors. Revert to env.aws.region || "us-east-1" or add an explicit empty‐string check (e.g. env.aws.region?.trim() || "us-east-1").

🤖 Prompt for AI Agents
In cloud/backend/base/pre-signed-url.ts around line 68, the change to use the
nullish coalescing operator (??) will not default when env.aws.region is an
empty string, which can cause AWS errors; update the expression to either use
the logical OR fallback (env.aws.region || "us-east-1") or add an explicit
empty-string-safe check such as (env.aws.region?.trim() || "us-east-1") so that
empty or whitespace-only values also fall back to "us-east-1".


export function setTestEnv(project: unknown, env: Record<string, string>) {
const val = JSON.parse(process.env.FP_TEST_ENV || "{}");
const val = JSON.parse(process.env.FP_TEST_ENV ?? "{}") as Record<string, string>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unsafe type assertion without runtime validation.

The type assertion as Record<string, string> assumes the parsed JSON will always contain only string values. If FP_TEST_ENV contains numbers, booleans, nested objects, or arrays, this assertion will be incorrect, potentially causing type-related runtime errors downstream.

Consider adding runtime validation or using a type guard. If the assertion is intentional, document the expected JSON structure:

  const val = JSON.parse(process.env.FP_TEST_ENV || "{}") as Record<string, string>;
+  // Expected format: { "key": "string_value", ... }

Alternatively, add validation:

const parsed = JSON.parse(process.env.FP_TEST_ENV || "{}");
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
  throw new TypeError("FP_TEST_ENV must be a JSON object");
}
const val = parsed as Record<string, string>;
🤖 Prompt for AI Agents
In cloud/base/test-global-helper.ts around line 6, the code unsafely asserts
JSON.parse(process.env.FP_TEST_ENV ?? "{}") as Record<string,string>; add
runtime validation to ensure the parsed value is a non-null plain object (not an
array) and that every property value is a string; if validation fails, throw a
TypeError with a clear message (or coerce values to strings if intended), and
then safely cast or return the validated object so downstream code can rely on
string values.

⚠️ Potential issue | 🔴 Critical

Critical: Nullish coalescing breaks on empty string.

Switching from || to ?? introduces a runtime bug. If process.env.FP_TEST_ENV is set to an empty string "", the nullish coalescing operator (??) will not trigger the fallback, and JSON.parse("") will throw a SyntaxError. The original || operator correctly treated empty strings as falsy and used the fallback.

Apply this diff to preserve the original fallback behavior:

-  const val = JSON.parse(process.env.FP_TEST_ENV ?? "{}") as Record<string, string>;
+  const val = JSON.parse(process.env.FP_TEST_ENV || "{}") as Record<string, string>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const val = JSON.parse(process.env.FP_TEST_ENV ?? "{}") as Record<string, string>;
const val = JSON.parse(process.env.FP_TEST_ENV || "{}") as Record<string, string>;
🤖 Prompt for AI Agents
In cloud/base/test-global-helper.ts around line 6, the change from || to ??
causes JSON.parse to receive an empty string when process.env.FP_TEST_ENV ===
"", leading to a SyntaxError; revert the nullish coalescing to the original
logical OR so empty strings are treated as falsy and the fallback "{}" is used
(i.e., restore using || to ensure JSON.parse always gets a valid JSON string).

Comment on lines 25 to 27
async readdir(path: PathLike, options?: ObjectEncodingOptions): Promise<string[]> {
return this.fs?.readdir(path, options) as Promise<string[]>;
return this.fs?.readdir(path, options)!;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Non-null assertion on optional chaining defeats type safety.

Using ! after ?. is contradictory: optional chaining already returns undefined when this.fs is undefined, but the non-null assertion tells TypeScript to ignore that possibility. If start() hasn't been called, this method returns undefined (not Promise<string[]>), causing runtime errors in callers.

Either ensure fs is always initialized before use:

  async readdir(path: PathLike, options?: ObjectEncodingOptions): Promise<string[]> {
-   return this.fs?.readdir(path, options)!;
+   if (!this.fs) throw new Error("NodeFileSystem not started. Call start() first.");
+   return this.fs.readdir(path, options);
  }

Or make fs non-optional if start() is guaranteed to be called during construction:

  fs?: {
+  fs!: {
    mkdir: typeof mkdir;
    ...
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async readdir(path: PathLike, options?: ObjectEncodingOptions): Promise<string[]> {
return this.fs?.readdir(path, options) as Promise<string[]>;
return this.fs?.readdir(path, options)!;
}
async readdir(path: PathLike, options?: ObjectEncodingOptions): Promise<string[]> {
if (!this.fs) throw new Error("NodeFileSystem not started. Call start() first.");
return this.fs.readdir(path, options);
}
🤖 Prompt for AI Agents
In core/gateways/file-node/node-filesystem.ts around lines 25-27, the method
uses optional chaining with a non-null assertion (this.fs?.readdir(...)!) which
defeats type safety and can return undefined at runtime; change the
implementation to either (a) assert and ensure fs is initialized before use by
making fs non-optional or calling/awaiting an internal start/ensureStarted
method so readdir can safely call this.fs.readdir(...) and keep the
Promise<string[]> return type, or (b) keep fs optional and explicitly handle the
undefined case by rejecting/throwing a clear error (e.g., "Filesystem not
started") or returning a rejected Promise when this.fs is undefined, and update
the method signature if you intend to return undefined. Ensure callers get a
predictable Promise<string[]> or a thrown/rejected error rather than undefined.

Comment on lines 38 to 40
stat(path: PathLike): Promise<Stats> {
return this.fs?.stat(path) as Promise<Stats>;
return this.fs?.stat(path)!;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Same non-null assertion issue on optional chaining.

Identical problem: this.fs?.stat(path)! returns undefined when fs is uninitialized, violating the Promise<Stats> return type.

Apply the same fix:

  stat(path: PathLike): Promise<Stats> {
-   return this.fs?.stat(path)!;
+   if (!this.fs) throw new Error("NodeFileSystem not started. Call start() first.");
+   return this.fs.stat(path);
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stat(path: PathLike): Promise<Stats> {
return this.fs?.stat(path) as Promise<Stats>;
return this.fs?.stat(path)!;
}
stat(path: PathLike): Promise<Stats> {
if (!this.fs) throw new Error("NodeFileSystem not started. Call start() first.");
return this.fs.stat(path);
}
🤖 Prompt for AI Agents
In core/gateways/file-node/node-filesystem.ts around lines 38 to 40, the method
uses optional chaining with a non-null assertion (this.fs?.stat(path)!) which
can return undefined when this.fs is uninitialized and violates the
Promise<Stats> return type; replace the optional chaining with an explicit
check: if this.fs is not set, throw a clear error (or return a rejected Promise)
indicating the filesystem is uninitialized, otherwise call this.fs.stat(path)
(no optional chaining) so the method always returns a Promise<Stats>.

Comment on lines +44 to +47
{
files: ["**/*.js", "*.mjs"],
extends: [tseslint.configs.disableTypeChecked],
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix glob pattern inconsistency for .mjs files.

The pattern "*.mjs" only matches .mjs files in the root directory, while "**/*.js" matches .js files in all subdirectories. This inconsistency could cause .mjs files in subdirectories to incorrectly run type-checked rules (which would fail since they don't have type information).

Apply this diff to match .mjs files consistently:

   {
-    files: ["**/*.js", "*.mjs"],
+    files: ["**/*.js", "**/*.mjs"],
     extends: [tseslint.configs.disableTypeChecked],
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
files: ["**/*.js", "*.mjs"],
extends: [tseslint.configs.disableTypeChecked],
},
{
files: ["**/*.js", "**/*.mjs"],
extends: [tseslint.configs.disableTypeChecked],
},
🤖 Prompt for AI Agents
In eslint.config.mjs around lines 44 to 47, the glob for .mjs files is currently
"*.mjs" which only matches root files; change it to "**/*.mjs" so both patterns
consistently match files in all subdirectories (i.e., replace "*.mjs" with
"**/*.mjs" in that files entry) to prevent .mjs files in subfolders from being
incorrectly subjected to type-checked rules.

Comment on lines 82 to 84
if (this.opts.onTokenChange) {
return this.opts.onTokenChange(on);
this.opts.onTokenChange(on); return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Restore unsubscribe propagation in onTokenChange.

Previously we returned the value from opts.onTokenChange, so custom handlers could supply their own unsubscribe. Dropping the return now always yields undefined, leaking subscriptions for any consumer relying on the old contract. Please restore the return.

-    if (this.opts.onTokenChange) {
-      this.opts.onTokenChange(on); return;
-    }
+    if (this.opts.onTokenChange) {
+      return this.opts.onTokenChange(on);
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (this.opts.onTokenChange) {
return this.opts.onTokenChange(on);
this.opts.onTokenChange(on); return;
}
if (this.opts.onTokenChange) {
return this.opts.onTokenChange(on);
}
🤖 Prompt for AI Agents
In use-fireproof/react/use-attach.ts around lines 82 to 84, the handler call to
this.opts.onTokenChange currently invokes the callback but does not return its
value, which breaks the previous contract where custom handlers could return an
unsubscribe function; restore the return by returning the result of
this.opts.onTokenChange(on) so any unsubscribe propagated by the caller is
preserved.

(newDoc?: DocSet<T>, opts = { replace: false, reset: false }) => {
if (!newDoc) {
return opts.reset ? reset() : refresh();
opts.reset ? reset() : refresh(); return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fire-and-forget refresh() call breaks async contract.

When opts.reset is false, this line calls refresh() (which returns Promise<void>) but immediately discards the promise and returns undefined without awaiting. This means:

  1. The function returns before the database fetch in refresh() completes (lines 26-30)
  2. Callers cannot await the document refresh
  3. Race conditions may occur if callers access doc state immediately after calling updateDoc

The AI summary confirms this affects call-sites that previously relied on the returned value. If the fire-and-forget pattern is intentional for React's state management, it should be clearly documented. Otherwise, make updateDoc async and await the refresh.

Apply this diff to properly await the async operation:

-    if (!newDoc) {
-      opts.reset ? reset() : refresh(); return;
+    if (!newDoc) {
+      if (opts.reset) {
+        reset();
+      } else {
+        await refresh();
+      }
+      return;
     }

Note: If you apply this fix, you must also update the updateDoc declaration to be async:

-  const updateDoc = useCallback(
-    (newDoc?: DocSet<T>, opts = { replace: false, reset: false }) => {
+  const updateDoc = useCallback(
+    async (newDoc?: DocSet<T>, opts = { replace: false, reset: false }) => {
🤖 Prompt for AI Agents
In use-fireproof/react/use-document.ts around line 84, the code currently calls
refresh() without awaiting, breaking the async contract and causing callers to
return before the fetch completes; change the branch to await the async call
(e.g., if opts.reset await reset(); else await refresh(); return;) and update
the updateDoc function declaration to be async, then update any callers/types to
handle the returned Promise so callers can await the document refresh.

Comment on lines +103 to 104
let overlayNode = document.body.querySelector("#fpOverlay")!;
if (!overlayNode) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Non-null assertion contradicts null check.

Line 103 uses a non-null assertion (!) on querySelector("#fpOverlay"), but line 104 immediately checks if (!overlayNode). If the element doesn't exist, the code will throw a runtime error at line 103 before reaching the null check on line 104, defeating the purpose of the guard.

Apply this diff to remove the non-null assertion:

-    let overlayNode = document.body.querySelector("#fpOverlay")!;
+    let overlayNode = document.body.querySelector("#fpOverlay");
     if (!overlayNode) {
🤖 Prompt for AI Agents
In use-fireproof/redirect-strategy.ts around lines 103 to 104, the code uses a
non-null assertion on document.body.querySelector("#fpOverlay") and then
immediately checks if the result is falsy; remove the non-null assertion so the
querySelector result can be null-checked safely (i.e., assign without "!" and
keep the if (!overlayNode) guard), ensuring no runtime exception if the element
is absent.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 28

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (11)
core/gateways/file/key-bag-file.ts (1)

45-57: Unsafe type assertion: JSON.parse can return null.

The type assertion as NonNullable<unknown> on line 49 is unsafe because JSON.parse() can legitimately return null when the stored file contains the JSON literal null. If this occurs, the method will return null at runtime despite the type system claiming it returns NonNullable<unknown> | undefined, violating the contract and potentially causing downstream issues.

Apply this diff to handle null explicitly:

   async get(id: string): Promise<NonNullable<unknown> | undefined> {
     const ctx = await this._prepare(id);
     try {
       const p = await ctx.sysFS.readfile(ctx.fName);
-      const ki = JSON.parse(this.sthis.txt.decode(p)) as NonNullable<unknown>;
-      return ki;
+      const parsed = JSON.parse(this.sthis.txt.decode(p));
+      if (parsed == null) {
+        return undefined;
+      }
+      return parsed as NonNullable<unknown>;
     } catch (e) {
       if (isNotFoundError(e)) {
         return undefined;
       }
       throw this.logger.Error().Err(e).Any("file", ctx).Msg("read bag failed").AsError();
     }
   }

Alternatively, if null values should never be stored, throw an error instead of silently returning undefined.

core/runtime/keyed-crypto.ts (1)

36-38: Fix IV_VERIFY=disable logic (currently forces a decode failure).

When IV verification is disabled via URL param, verify() returns false, so decode throws. It should short‑circuit to true.

-    verify: async function (ko: CryptoAction, crypto: CryptoRuntime, iv: Uint8Array, data: Uint8Array): Promise<boolean> {
-      return ko.url.getParam(PARAM.IV_VERIFY) !== "disable" && UInt8ArrayEqual(iv, await this.calc(ko, crypto, data));
-    },
+    verify: async function (ko: CryptoAction, crypto: CryptoRuntime, iv: Uint8Array, data: Uint8Array): Promise<boolean> {
+      if (ko.url.getParam(PARAM.IV_VERIFY) === "disable") return true;
+      return UInt8ArrayEqual(iv, await this.calc(ko, crypto, data));
+    },
core/keybag/key-bag.ts (2)

48-52: Per-URL cache never stored: map isn’t populated

namedKeyItemsPerUrl.get(... ) ?? new KeyedResolvOnce(...) creates a new cache but never inserts it into the map. Caching/sharing across instances won’t work.

Apply:

 static async create(rt: KeyBagRuntime) {
   const urlHash = await hashStringAsync(rt.url.toJSON());
-  const namedKeyItems = namedKeyItemsPerUrl.get(urlHash) ?? new KeyedResolvOnce<KeyBagItem>();
-  return new KeyBag(rt, namedKeyItems);
+  let namedKeyItems = namedKeyItemsPerUrl.get(urlHash);
+  if (!namedKeyItems) {
+    namedKeyItems = new KeyedResolvOnce<KeyBagItem>();
+    namedKeyItemsPerUrl.set(urlHash, namedKeyItems);
+  }
+  return new KeyBag(rt, namedKeyItems);
 }

176-199: Await the cached JWT item and handle errors before parsing

-      const ret = this.#namedKeyItems.get(name).once((): Result<KeyedJwtKeyBagItem> => {
-        throw new Error("Should never called");
-      });
+      const ret = await this.#namedKeyItems.get(name).once((): Result<KeyedJwtKeyBagItem> => {
+        throw new Error("Should never be called");
+      });
+      if (ret.isErr()) {
+        return ret;
+      }
       const p = KeyedJwtKeyBagItemSchema.safeParse(ret.Ok());

Optional: validate claims instead of using as JWTPayload or propagate decode/verify errors.

cloud/backend/base/connection.test.ts (1)

379-391: Replace return with break on line 382
if (done) return; aborts the entire test after the first stream—use break to exit just the inner loop and continue with remaining streams.

core/base/crdt-helpers.ts (1)

47-55: Rename local helper to avoid shadowing global toString (linter error).

The function name shadows a restricted global and triggers Biome’s noShadowRestrictedNames. Rename and update call sites.

-function toString(key: IndexKeyType, logger: Logger): string {
+function keyToString(key: IndexKeyType, logger: Logger): string {
   switch (typeof key) {
     case "string":
     case "number":
       return key.toString();
     default:
       throw logger.Error().Msg("Invalid key type").AsError();
   }
 }
@@
-      await batch.put(toString(update.id, logger), link);
+      await batch.put(keyToString(update.id, logger), link);
@@
-    result = await put(toPailFetcher(tblocks), head, toString(updates[0].id, logger), link);
+    result = await put(toPailFetcher(tblocks), head, keyToString(updates[0].id, logger), link);

Based on static analysis hints.

Also applies to: 124-130

core/runtime/utils.ts (1)

536-544: Fix: setPresetEnv writes to possibly undefined object.

When the symbol isn’t set yet, env is undefined and writes throw. Default it.

-export function setPresetEnv(o: Record<string, string>, symbol = "FP_PRESET_ENV") {
-  const key = Symbol.for(symbol);
-  const env = (globalThis as unknown as Record<symbol, Record<string, string>>)[key];
+export function setPresetEnv(o: Record<string, string>, symbol = "FP_PRESET_ENV") {
+  const key = Symbol.for(symbol);
+  const g = globalThis as unknown as Record<symbol, Record<string, string>>;
+  const env = (g[key] ??= {});
   for (const [k, v] of Object.entries(o)) {
     env[k] = v;
   }
-  (globalThis as unknown as Record<symbol, Record<string, string>>)[key] = env;
+  g[key] = env;
   // console.log("setPresetEnv", key, env);
   return env;
 }
core/tests/fireproof/fireproof.test.ts (1)

253-276: Consider failing tests explicitly on errors instead of logging.

Several promises now use .catch(console.error), which silently swallows errors. In test code, this can mask failures that should cause the test to fail.

Consider replacing with .catch((e) => { throw e; }) or using expect(...).rejects patterns to ensure test failures are visible:

-      db.put({ foo: "fast" }).catch(console.error);
+      await db.put({ foo: "fast" });

Or if fire-and-forget is intended:

-      db.put({ foo: "fast" }).catch(console.error);
+      void db.put({ foo: "fast" }).catch((e) => { 
+        // Log but also fail the test if this happens
+        console.error(e);
+        throw e;
+      });
core/device-id/device-id-verify-msg.ts (1)

118-129: Constrain JWT alg during verification

Pass the expected algorithm to jwtVerify to prevent alg confusion; also guard against "none".

-      const key = await importJWK(certInfo.certificate.asCert().certificate.subjectPublicKeyInfo, alg);
-      return jwtVerify(jwt, key, {
+      const key = await importJWK(certInfo.certificate.asCert().certificate.subjectPublicKeyInfo, alg);
+      if (alg === "none") {
+        throw new Error("Disallowed JWT alg 'none'");
+      }
+      return jwtVerify(jwt, key, {
+        algorithms: [alg],
         clockTolerance: this.#options.clockTolerance,
         maxTokenAge: this.#options.maxAge,
       });
core/gateways/base/meta-key-hack.ts (1)

115-116: Result.Err misuse: passing Result instead of Error (compile/runtime bug)

These paths pass a Result to Result.Err instead of the contained Error.

-  if (metas.isErr()) {
-    return Result.Err(metas);
-  }
+  if (metas.isErr()) {
+    return Result.Err(metas.Err());
+  }
-        if (r.isErr()) {
-          return Promise.resolve(Result.Err(r));
-        }
+        if (r.isErr()) {
+          return Promise.resolve(Result.Err(r.Err()));
+        }
-        if (obj.isErr()) {
-          return Promise.resolve(Result.Err(obj));
-        }
+        if (obj.isErr()) {
+          return Promise.resolve(Result.Err(obj.Err()));
+        }

Also applies to: 133-135, 203-205

core/protocols/cloud/ws-connection.ts (1)

154-171: Blob-only cast breaks Node/browsers delivering non-Blob event.data

event.data can be Blob, ArrayBuffer, Buffer, or string depending on environment. Forcing Blob reduces compatibility.

-      const msg = this.msgP.ende.decode(await top_uint8(event.data as Blob));
+      const msg = this.msgP.ende.decode(await top_uint8(event.data));
♻️ Duplicate comments (1)
core/types/base/jwk-public.zod.ts (1)

14-14: Verify the pipe pattern for URL validation.

This is the same validator pattern change as in core/types/base/jwk-private.zod.ts line 14. Please see the review comment on that file for details.

🧹 Nitpick comments (42)
cloud/todo-app/src/main.tsx (1)

7-7: Remove the unnecessary type cast.

The as Element cast is redundant because HTMLElement (returned by getElementById after the ! assertion) already extends Element, which satisfies createRoot's parameter type.

Apply this diff to remove the unnecessary cast:

-createRoot(document.getElementById("root")! as Element).render(
+createRoot(document.getElementById("root")!).render(

Additionally, consider replacing the non-null assertion with explicit null handling for safer runtime behavior:

-// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-createRoot(document.getElementById("root")! as Element).render(
+const rootElement = document.getElementById("root");
+if (!rootElement) {
+  throw new Error("Root element not found");
+}
+createRoot(rootElement).render(
core/tests/fireproof/concurrent.test.ts (1)

16-31: Good practice using async IIFE; consider propagating errors to the test.

The refactor from an async callback to a non-async wrapper with an async IIFE is correct, as setTimeout doesn't properly handle async callbacks. The .catch(console.error) ensures errors are logged rather than silently swallowed.

However, errors caught by .catch(console.error) won't fail the test immediately—they'll only prevent dones from reaching loops, causing a timeout. Consider propagating errors to fail the test faster.

Optional improvement to propagate errors immediately:

-          })().catch(console.error);
+          })().catch((error) => {
+            console.error(error);
+            future.reject(error);
+          });
core/base/write-queue.ts (1)

16-16: Consider removing commented-out code.

The commented-out property task can be removed to keep the codebase clean, unless there's a specific reason to retain it for future reference.

Apply this diff to remove the commented code:

 interface WriteQueueItem<T extends DocTypes> {
-  // readonly task?: DocUpdate<T>;
   readonly tasks?: DocUpdate<T>[];
   resolve(result: MetaType): void;
   reject(error: Error): void;
 }
core/tests/blockstore/transaction.test.ts (1)

29-32: Standardize transaction callback pattern.

In core/tests/blockstore/transaction.test.ts (lines 29–32), revert to an async (tblocks) => and return the object directly:

const txR = await blocks.transaction(async (tblocks) => {
  expect(tblocks).toBeTruthy();
  expect(tblocks instanceof CarTransactionImpl).toBeTruthy();
  return { head: [] };
});

This aligns with the required Promise<M> return and removes the manual Promise.resolve.

core/device-id/device-id-client.ts (1)

19-21: Consider stronger typing for better type safety.

The signature changed from generic T to NonNullable<unknown>, but DeviceIdSignMsg.sign() expects JWTPayload. This weakens type safety as callers can now pass any non-nullable value, which may not match the JWTPayload structure required by the underlying method.

Based on learnings and the relevant code in device-id-signed-msg.ts, consider using JWTPayload directly or <T extends JWTPayload> for better type safety:

-  sign(payload: NonNullable<unknown>, algorithm?: string): Promise<string> {
+  sign(payload: JWTPayload, algorithm?: string): Promise<string> {
     return this.#x.sign(payload, algorithm);
   }

Alternatively, if type flexibility is needed:

-  sign(payload: NonNullable<unknown>, algorithm?: string): Promise<string> {
+  sign<T extends JWTPayload>(payload: T, algorithm?: string): Promise<string> {
     return this.#x.sign(payload, algorithm);
   }
core/tests/fireproof/utils.test.ts (1)

1-1: Consider narrowing the scope of the ESLint disable.

Disabling @typescript-eslint/restrict-template-expressions file-wide bypasses type safety for all template expressions in this test file. If only specific lines require this (e.g., lines 88, 104), consider using inline // eslint-disable-next-line comments instead.

core/types/base/prolly-trees.d.ts (1)

48-48: Type safety regression in bf() signature.

Changing from a generic signature to (entry: unknown, dist: number) => Promise<boolean> reduces type safety. The previous generic allowed callers to maintain type information through the chunker function.

Consider preserving the generic parameter to maintain type safety:

-  declare function bf(factor: number): (entry: unknown, dist: number) => Promise<boolean>;
+  declare function bf<T>(factor: number): (entry: T, dist: number) => Promise<boolean>;
core/protocols/dashboard/msg-api.ts (2)

12-12: Consider preserving type safety with a constrained generic parameter.

The method signature change from async request<S, Q>(req: Q) to async request<S>(req: unknown) removes compile-time type safety for the request parameter. While this may address lint errors, it means the method now accepts any value without type validation.

Consider alternatives that maintain type safety:

Option 1: Use a constrained generic to preserve type information while allowing flexibility:

-async request<S>(req: unknown): Promise<Result<S>> {
+async request<S, Q extends Record<string, unknown> = Record<string, unknown>>(req: Q): Promise<Result<S>> {

Option 2: Add runtime validation for the unknown parameter:

async request<S>(req: unknown): Promise<Result<S>> {
  return exception2Result(async () => {
    // Validate req structure
    if (!req || typeof req !== 'object') {
      throw new Error('Invalid request: expected object');
    }
    const res = await fetch(this.apiUrl, {
      // ... rest of implementation
    });

The current implementation relies on call-site type checking via satisfies (line 34), which provides some safety but is less robust than method-level type constraints.


26-26: Minor: String(res.status) is explicit but unnecessary.

Template literals automatically convert res.status (a number) to a string. The explicit String() wrapper doesn't change behavior but adds verbosity.

You can simplify to:

-throw new Error(`Request failed: ${String(res.status)} ${res.statusText} ${this.apiUrl}`);
+throw new Error(`Request failed: ${res.status} ${res.statusText} ${this.apiUrl}`);

However, if this was done for linting or style consistency, it's acceptable as-is.

core/types/base/fp-device-id-payload.zod.ts (1)

13-13: Use top-level z.email() instead of z.string().pipe(z.email())
Replace z.string().pipe(z.email()).optional() with z.email().optional() to align with Zod v4 idioms.

core/types/base/fp-ca-cert-payload.zod.ts (1)

12-13: Use direct ISO datetime validators instead of pipe composition

  • In core/types/base/fp-ca-cert-payload.zod.ts (12-13), replace
    z.string().pipe(z.iso.datetime())
    with either
    z.string().datetime()  
    // or  
    z.iso.datetime()
core/base/crdt.ts (1)

139-144: Unify applyMeta implementations for consistency.

The blockstore applyMeta is declared async, while indexBlockstore returns Promise.resolve() from a synchronous function. Align both—e.g. make indexBlockstore.applyMeta async (meta) => { … }—to keep the patterns uniform.

core/runtime/keyed-crypto.ts (7)

101-103: Honor both opts.noIVVerify and URL param consistently.

Currently only opts is honored in decode. Consider skipping verify when either flag disables it.

-    if (!this.opts.noIVVerify && !(await getGenerateIVFn(this.ko.url, this.opts).verify(this.ko, this.ko.crypto, iv, result))) {
+    const skipVerify = this.opts.noIVVerify || this.ko.url.getParam(PARAM.IV_VERIFY) === "disable";
+    if (!skipVerify && !(await getGenerateIVFn(this.ko.url, this.opts).verify(this.ko, this.ko.crypto, iv, result))) {
       throw this.ko.logger.Error().Msg("iv missmatch").AsError();
     }

162-170: Remove unnecessary return await Promise.resolve(...).

Simplify to direct returns; cleaner and avoids no-return-await lint.

   async encode(data: Uint8Array): Promise<Uint8Array> {
-    return await Promise.resolve(data);
+    return data;
   }
   async decode(data: Uint8Array): Promise<IvKeyIdData> {
-    return await Promise.resolve({
+    return {
       iv: this.empty,
       keyId: this.empty,
       data: data,
-    });
+    };
   }

22-23: Basic sanity check for random IVs.

random.verify always returns true; validate IV length to catch truncation/corruption early.

-    verify: (ko: CryptoAction, crypto: CryptoRuntime, iv: Uint8Array, data: Uint8Array): boolean => {
-      return true;
-    },
+    verify: (ko: CryptoAction, crypto: CryptoRuntime, iv: Uint8Array, _data: Uint8Array): boolean => {
+      return iv.length === ko.ivLength;
+    },

71-74: Validate IV length before use.

Protect against accidental wrong‑length IVs from custom strategies.

   async encode(data: Uint8Array): Promise<Uint8Array> {
-    const calcIv = this.iv ?? (await getGenerateIVFn(this.ko.url, this.opts).calc(this.ko, this.ko.crypto, data));
+    const calcIv = this.iv ?? (await getGenerateIVFn(this.ko.url, this.opts).calc(this.ko, this.ko.crypto, data));
+    if (calcIv.length !== this.ko.ivLength) {
+      throw this.ko.logger
+        .Error()
+        .Int("ivLen", calcIv.length)
+        .Int("expected", this.ko.ivLength)
+        .Msg("invalid IV length")
+        .AsError();
+    }
     const { iv } = this.ko.algo(calcIv);

11-12: Prefer a MaybePromise<T> alias for clarity.

Improves readability and reuse across sync/async signatures.

-  calc(ko: CryptoAction, crypto: CryptoRuntime, data: Uint8Array): Uint8Array | Promise<Uint8Array>;
-  verify(ko: CryptoAction, crypto: CryptoRuntime, iv: Uint8Array, data: Uint8Array): boolean | Promise<boolean>;
+  calc(ko: CryptoAction, crypto: CryptoRuntime, data: Uint8Array): MaybePromise<Uint8Array>;
+  verify(ko: CryptoAction, crypto: CryptoRuntime, iv: Uint8Array, data: Uint8Array): MaybePromise<boolean>;

Add once near the top-level:

type MaybePromise<T> = T | Promise<T>;

102-102: Typo: “missmatch” → “mismatch”.

-      throw this.ko.logger.Error().Msg("iv missmatch").AsError();
+      throw this.ko.logger.Error().Msg("iv mismatch").AsError();

181-181: Use a stable/secure source for noCrypto fingerprint (optional).

Math.random is predictable and varies per process. Consider using CryptoRuntime.randomBytes or a stable ID.

Example:

// in constructor:
this._fingerPrint = "noCrypto:" + base58btc.encode(cyrt.randomBytes(8));
// and declare: private readonly _fingerPrint: string;
core/keybag/key-bag.ts (1)

220-222: Shared cache type mixing can break getNamedKey

Using one KeyedResolvOnce for different shapes (NamedKeyItem, DeviceIdResult, Result) risks returning a non-NamedKeyItem here (e.g., after setJwt), making kItem.seq undefined.

Consider:

  • Separate caches per item kind or namespace keys to prevent collisions.
  • Alternatively, ensure KeyedResolvOnce stores a single uniform wrapper type.
cloud/3rd-party/src/main.tsx (1)

7-7: Consider simplifying the type assertion.

Since HTMLElement already extends Element, the explicit as Element cast after the non-null assertion may be redundant. The non-null assertion alone should suffice:

-createRoot(document.getElementById("root")! as Element).render(
+createRoot(document.getElementById("root")!).render(

Alternatively, for better type safety, consider adding a runtime check instead of using the non-null assertion:

const rootElement = document.getElementById("root");
if (!rootElement) {
  throw new Error("Root element not found");
}
createRoot(rootElement).render(
  <StrictMode>
    <App />
  </StrictMode>,
);
core/tests/fireproof/charwise-boolean.test.ts (1)

11-20: Consider creating a typed wrapper to reduce repetition.

While the current approach works, the repeated eslint-disable comments and as any casts create noise. Consider extracting a small typed wrapper:

// At the top of the file, after imports
const charwiseHelper = {
  encode: (value: unknown): string => {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
    return (charwise as any).encode(value) as string;
  },
  decode: (encoded: string): unknown => {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
    return (charwise as any).decode(encoded);
  },
};

// Then use throughout:
const trueEncoded = charwiseHelper.encode(true);
expect(charwiseHelper.decode(trueEncoded)).toBe(true);

This localizes the type-safety suppressions and improves readability.

core/gateways/base/interceptor-gateway.ts (1)

25-25: Consider removing redundant Promise.resolve wrapping.

Wrapping Result.Ok({op}) with await Promise.resolve(...) doesn't change the value—only delays execution by one microtask. Since these methods are already async, directly returning the Result is equivalent and clearer.

Apply this diff to simplify:

-return await Promise.resolve(Result.Ok({ op }));
+return Result.Ok({ op });

Also applies to: 29-29, 33-33, 37-37, 41-41, 45-45, 49-49, 57-57

core/gateways/file-deno/deno-filesystem.ts (1)

18-18: Consider removing redundant Promise.resolve wrapping.

Wrapping the assignment with await Promise.resolve(...) doesn't change the resulting this.fs value—it only defers the assignment by one microtask. Direct assignment is clearer and equivalent.

Apply this diff to simplify:

-this.fs = await Promise.resolve(Deno as unknown as DenoFileSystem["fs"]);
+this.fs = Deno as unknown as DenoFileSystem["fs"];
core/blockstore/attachable-store.ts (2)

286-288: ESLint directive may be unnecessary.

The require-await directive suggests the function might not need to be async. Consider whether prepare can be synchronous or if it truly needs async capabilities for future use.

If prepare doesn't perform any async operations, consider making it synchronous:

-      // eslint-disable-next-line @typescript-eslint/require-await
-      prepare: async () => gup,
+      prepare: () => gup,

449-452: ESLint directive may be unnecessary.

Similar to line 287, this require-await directive suggests the function might not need to be async.

Consider making the detach function synchronous if it only performs synchronous operations:

-            // eslint-disable-next-line @typescript-eslint/require-await
-            detach: async () => {
-              this._remotes.unget(key);
-            },
+            detach: () => {
+              this._remotes.unget(key);
+            },
core/base/crdt-helpers.ts (2)

209-216: Avoid unknown cast when calling commitFiles (type safety).

{ files } as unknown weakens guarantees and can hide mistakes. Prefer a precise type import for the expected payload, or narrow to { files: DocFiles }.

-    const car = await dbBlockstore.loader.commitFiles(
-      t,
-      { files } as unknown /* {
+    const car = await dbBlockstore.loader.commitFiles(
+      t,
+      { files } as { files: DocFiles } /* {

If the loader expects a different shape, introduce a dedicated type alias exported from the loader module and use it here.


73-110: Sanitizer: clarify NaN/Infinity handling (optional).

Condition value === null || (!Number.isNaN(value) && value !== undefined) treats Infinity as valid but excludes NaN. If intentional, add a comment; otherwise explicitly gate numbers:

-        if (value === null || (!Number.isNaN(value) && value !== undefined)) {
+        if (
+          value === null ||
+          (value !== undefined && (typeof value !== "number" || Number.isFinite(value)))
+        ) {

This preserves nulls, drops NaN/Infinity, and keeps all non-number types.

Also applies to: 139-143

core/base/indexer-helpers.ts (2)

40-47: Comparison logic ok; consider symmetric Infinity check (optional).

You special-case aRef === Infinity but not bRef === Infinity. simpleCompare will handle it, but add symmetry if you need explicit ordering rules.


82-93: Deduplicate charwise casts with small helpers.

Inline casts to charwise.encode/decode are repeated and noisy. Create local wrappers to centralize typing.

+// helpers to tame untyped charwise
+const cw = {
+  enc: (v: unknown) => (charwise as { encode: (value: unknown) => string }).encode(v),
+  dec: (v: string) => (charwise as { decode: (value: string) => unknown }).decode(v),
+};
@@
-        key: [(charwise as { encode: (value: unknown) => unknown }).encode(k) as K, key],
+        key: [cw.enc(k) as K, key],
@@
-    if (!mapCalled && mapReturn !== undefined) {
+    if (!mapCalled && mapReturn !== undefined) {
       indexEntries.push({
-        key: [(charwise as { encode: (value: unknown) => unknown }).encode(mapReturn) as K, key],
+        key: [cw.enc(mapReturn) as K, key],
         value: null,
       });
     }
@@
-    const decodedKey = (charwise as { decode: (value: string) => unknown }).decode(key as string);
+    const decodedKey = cw.dec(key as string);
@@
-export function encodeRange(range: [IndexKeyType, IndexKeyType]): [string, string] {
-  return [
-    (charwise as { encode: (value: unknown) => string }).encode(range[0]),
-    (charwise as { encode: (value: unknown) => string }).encode(range[1]),
-  ];
+export function encodeRange(range: [IndexKeyType, IndexKeyType]): [string, string] {
+  return [cw.enc(range[0]), cw.enc(range[1])];
 }
@@
-export function encodeKey(key: DocFragment): string {
-  return (charwise as { encode: (value: unknown) => string }).encode(key);
+export function encodeKey(key: DocFragment): string {
+  return cw.enc(key);
 }

Also applies to: 188-216, 230-239

core/base/crdt-clock.ts (1)

219-229: Validation concurrency is fine; keep logger-based error construction.

Switching to concurrent Promise.all improves latency. Consider preserving logger.Error() on missing blockstore for consistency, but not required.

core/device-id/device-id-verify-msg.ts (2)

112-116: Use Error objects with Result.Err for consistency

Result.Err should receive an Error. Avoid passing strings.

-      return this.createVerifyWithCertificateError(Result.Err("Certificate thumbprint validation failed"), {
+      return this.createVerifyWithCertificateError(Result.Err(new Error("Certificate thumbprint validation failed")), {
         certificateExtracted: true,
         certificateInfo: certInfo,
       });
-      return this.createVerifyWithCertificateError(Result.Err("Certificate chain validation not implemented"), {
+      return this.createVerifyWithCertificateError(Result.Err(new Error("Certificate chain validation not implemented")), {
         certificateExtracted: true,
         certificateInfo: certInfo,
       });

Also applies to: 153-157


274-279: Type narrowing for trustedCA

Minor: declare trustedCA with an explicit type to reflect the throw-based narrowing.

-      let trustedCA = null;
-      trustedCA = this.findTrustedCA(cert, this.#trustedCAs);
+      let trustedCA: CertificatePayload | undefined = this.findTrustedCA(cert, this.#trustedCAs);
core/blockstore/transaction.ts (1)

637-653: Guard compact remove path against empty compacts

Avoid indexing into an empty compact list.

-      const previousCompactCid = cHeader.compact[cHeader.compact.length - 1];
+      const previousCompactCid = cHeader.compact[cHeader.compact.length - 1];
       cHeader.compact
         .map((c) => c.toString())
         .forEach((k) => {
           this.seenCompacted.add(k);
         });
       this.carLog.update(uniqueCids([...this.carLog.asArray(), ...cHeader.cars, cids], this.seenCompacted));
-      await this.removeCidsForCompact(previousCompactCid[0], this.attachedStores.local()).catch((e: unknown) => e);
+      if (previousCompactCid && previousCompactCid.length > 0) {
+        await this.removeCidsForCompact(previousCompactCid[0], this.attachedStores.local()).catch((e: unknown) => e);
+      }
core/gateways/base/meta-key-hack.ts (2)

105-112: Prefer Ok() over unwrap() after isErr() guard

unwrap() may throw; Ok() respects the Result API used elsewhere.

-  const v2 = toV2SerializedMetaKey(rJsObj.unwrap());
+  const v2 = toV2SerializedMetaKey(rJsObj.Ok());

200-203: Use Error for unknown-version branch

Align with Error-based Err payloads.

-            return Promise.resolve(Result.Err(`unknown version:[${version as string}]`));
+            return Promise.resolve(Result.Err(new Error(`unknown version:[${String(version)}]`)));
core/blockstore/loader.ts (1)

652-653: Ignore removeCidsForCompact error result

Consider logging rejected promise results for visibility or using catch+log explicitly.

-      await this.removeCidsForCompact(previousCompactCid[0], this.attachedStores.local()).catch((e: unknown) => e);
+      await this.removeCidsForCompact(previousCompactCid[0], this.attachedStores.local())
+        .catch((e: unknown) => this.logger.Warn().Err(e).Msg("removeCidsForCompact failed"));
core/protocols/cloud/ws-connection.ts (3)

173-182: close() should not double-close underlying ws during onclose

Minor: calling ws.close() inside onclose is benign but unnecessary. Optionally skip when already closing/closed.

-    this.ws.close();
+    if (this.ws.readyState !== WebSocket.CLOSING && this.ws.readyState !== WebSocket.CLOSED) {
+      this.ws.close();
+    }

191-195: send() return value is not used; keep but clarify intent

Returning the input as Promise is unconventional; consider void return or Promise to avoid misuse.

-  send<S extends MsgBase>(msg: MsgBase): Promise<S> {
+  send<S extends MsgBase>(msg: MsgBase): Promise<void> {
     // console.log("send", msg);
     this.ws.send(this.msgP.ende.encode(msg));
-    return Promise.resolve(msg as unknown as S);
+    return Promise.resolve();
   }

Note: request() and bind() already ignore the return value.


124-132: onmessage path: ensure early error path returns

After emitting the "before onOpen" error, consider early return to avoid processing the event twice.

-      if (!this.isReady) {
-        this.toMsg(buildErrorMsg(this, {} as MsgBase, this.logger.Error().Msg("Received message before onOpen").AsError()));
-      }
+      if (!this.isReady) {
+        this.toMsg(buildErrorMsg(this, {} as MsgBase, this.logger.Error().Msg("Received message before onOpen").AsError()));
+        return;
+      }
core/gateways/cloud/gateway.ts (2)

177-189: Add abort/timeout to fetch to avoid hanging I/O.

The GET/DELETE fetches have no timeout/abort; a stalled network can hang the request permanently.

Apply a minimal abort pattern:

-    const rDownload = await exception2Result(async () => fetch(downloadUrl, { method: "GET" }));
+    const rDownload = await exception2Result(async () => {
+      const ctrl = new AbortController();
+      const t = setTimeout(() => ctrl.abort(), 30000);
+      try {
+        return await fetch(downloadUrl, { method: "GET", signal: ctrl.signal });
+      } finally {
+        clearTimeout(t);
+      }
+    });
-    const rDelete = await exception2Result(async () => fetch(deleteUrl, { method: "DELETE" }));
+    const rDelete = await exception2Result(async () => {
+      const ctrl = new AbortController();
+      const t = setTimeout(() => ctrl.abort(), 30000);
+      try {
+        return await fetch(deleteUrl, { method: "DELETE", signal: ctrl.signal });
+      } finally {
+        clearTimeout(t);
+      }
+    });

Also applies to: 191-205


479-480: Remove redundant await/Promise.resolve.

These functions already return Promises; extra wrapping adds noise.

-    return await Promise.resolve(Result.Ok(wal as FPEnvelope<S>));
+    return Result.Ok(wal as FPEnvelope<S>);
-    return await Promise.resolve(Result.Ok(undefined));
+    return Result.Ok(undefined);

Also applies to: 487-488, 495-496

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8777427 and 020a5b0.

📒 Files selected for processing (107)
  • cli/build-cmd.test.ts (11 hunks)
  • cli/build-cmd.ts (10 hunks)
  • cli/main.ts (0 hunks)
  • cloud/3rd-party/src/App.tsx (3 hunks)
  • cloud/3rd-party/src/main.tsx (1 hunks)
  • cloud/backend/base/connection.test.ts (20 hunks)
  • cloud/backend/base/hono-server.ts (6 hunks)
  • cloud/backend/base/meta-merger/meta-by-tenant-ledger.ts (1 hunks)
  • cloud/backend/base/meta-merger/meta-merger.ts (2 hunks)
  • cloud/backend/base/meta-merger/meta-send.ts (1 hunks)
  • cloud/backend/base/msg-dispatch.ts (4 hunks)
  • cloud/backend/base/ws-sockets.test.ts (3 hunks)
  • cloud/base/test-super-this.ts (2 hunks)
  • cloud/todo-app/src/App.tsx (1 hunks)
  • cloud/todo-app/src/components/TodoForm.tsx (3 hunks)
  • cloud/todo-app/src/main.tsx (1 hunks)
  • core/base/apply-head-queue.ts (2 hunks)
  • core/base/compact-strategies.ts (2 hunks)
  • core/base/crdt-clock.ts (7 hunks)
  • core/base/crdt-helpers.ts (8 hunks)
  • core/base/crdt.ts (3 hunks)
  • core/base/database.ts (2 hunks)
  • core/base/indexer-helpers.ts (7 hunks)
  • core/base/indexer.ts (5 hunks)
  • core/base/ledger.ts (10 hunks)
  • core/base/write-queue.ts (1 hunks)
  • core/blockstore/attachable-store.ts (6 hunks)
  • core/blockstore/commitor.ts (2 hunks)
  • core/blockstore/loader-helpers.ts (2 hunks)
  • core/blockstore/loader.ts (22 hunks)
  • core/blockstore/register-store-protocol.ts (3 hunks)
  • core/blockstore/store-factory.ts (1 hunks)
  • core/blockstore/store.ts (16 hunks)
  • core/blockstore/transaction.ts (6 hunks)
  • core/device-id/certor.ts (2 hunks)
  • core/device-id/device-id-client.ts (2 hunks)
  • core/device-id/device-id-key.ts (3 hunks)
  • core/device-id/device-id-verify-msg.ts (3 hunks)
  • core/gateways/base/def-serde-gateway.ts (1 hunks)
  • core/gateways/base/fp-envelope-serialize.ts (5 hunks)
  • core/gateways/base/interceptor-gateway.ts (2 hunks)
  • core/gateways/base/meta-key-hack.ts (6 hunks)
  • core/gateways/base/uri-interceptor.ts (1 hunks)
  • core/gateways/cloud/gateway.ts (10 hunks)
  • core/gateways/file-deno/deno-filesystem.ts (1 hunks)
  • core/gateways/file/gateway-impl.ts (2 hunks)
  • core/gateways/file/key-bag-file.ts (1 hunks)
  • core/gateways/file/sys-file-system-factory.ts (1 hunks)
  • core/keybag/coerce-keyed-item.ts (1 hunks)
  • core/keybag/internal-keys-by-fingerprint.ts (3 hunks)
  • core/keybag/key-bag-memory.ts (2 hunks)
  • core/keybag/key-bag-setup.ts (5 hunks)
  • core/keybag/key-bag.ts (3 hunks)
  • core/keybag/key-with-fingerprint.ts (2 hunks)
  • core/protocols/cloud/http-connection.ts (6 hunks)
  • core/protocols/cloud/msger.ts (15 hunks)
  • core/protocols/cloud/ws-connection.ts (6 hunks)
  • core/protocols/dashboard/msg-api.ts (2 hunks)
  • core/runtime/async-block-encode.ts (2 hunks)
  • core/runtime/commit-queue.ts (4 hunks)
  • core/runtime/files.ts (1 hunks)
  • core/runtime/keyed-crypto.ts (8 hunks)
  • core/runtime/register-compact-strategy.ts (1 hunks)
  • core/runtime/sts-service/index.ts (2 hunks)
  • core/runtime/task-manager.ts (1 hunks)
  • core/runtime/utils.ts (18 hunks)
  • core/tests/blockstore/interceptor-gateway.test.ts (4 hunks)
  • core/tests/blockstore/keyed-crypto-indexeddb-file.test.ts (5 hunks)
  • core/tests/blockstore/keyed-crypto.test.ts (14 hunks)
  • core/tests/blockstore/loader.test.ts (7 hunks)
  • core/tests/blockstore/standalone.test.ts (1 hunks)
  • core/tests/blockstore/store.test.ts (3 hunks)
  • core/tests/blockstore/transaction.test.ts (3 hunks)
  • core/tests/fireproof/all-gateway.test.ts (7 hunks)
  • core/tests/fireproof/attachable-subscription.test.ts (13 hunks)
  • core/tests/fireproof/attachable.test.ts (11 hunks)
  • core/tests/fireproof/charwise-boolean.test.ts (4 hunks)
  • core/tests/fireproof/compact-strategy.test.ts (1 hunks)
  • core/tests/fireproof/concurrent.test.ts (1 hunks)
  • core/tests/fireproof/crdt.test.ts (11 hunks)
  • core/tests/fireproof/database.test.ts (22 hunks)
  • core/tests/fireproof/deleted-docs-handling.test.ts (3 hunks)
  • core/tests/fireproof/fireproof.test.ts (16 hunks)
  • core/tests/fireproof/indexer.test.ts (7 hunks)
  • core/tests/fireproof/multiple-ledger.test.ts (2 hunks)
  • core/tests/fireproof/query-docs.test.ts (4 hunks)
  • core/tests/fireproof/query-limit-issue.test.ts (2 hunks)
  • core/tests/fireproof/utils.test.ts (2 hunks)
  • core/tests/gateway/file/loader-config.test.ts (14 hunks)
  • core/tests/gateway/indexeddb/create-db-on-write.test.ts (6 hunks)
  • core/tests/gateway/indexeddb/loader-config.test.ts (4 hunks)
  • core/tests/protocols/cloud/msger.test.ts (22 hunks)
  • core/tests/runtime/device-id.test.ts (6 hunks)
  • core/tests/runtime/key-bag.test.ts (6 hunks)
  • core/tests/runtime/meta-key-hack.test.ts (3 hunks)
  • core/types/base/fp-ca-cert-payload.zod.ts (1 hunks)
  • core/types/base/fp-device-id-payload.zod.ts (2 hunks)
  • core/types/base/jwk-private.zod.ts (1 hunks)
  • core/types/base/jwk-public.zod.ts (1 hunks)
  • core/types/base/prolly-trees.d.ts (3 hunks)
  • core/types/base/types.ts (3 hunks)
  • core/types/blockstore/types.ts (3 hunks)
  • core/types/protocols/cloud/msg-types-data.ts (6 hunks)
  • core/types/protocols/cloud/msg-types-wal.ts (6 hunks)
  • core/types/protocols/cloud/msg-types.ts (15 hunks)
  • dashboard/backend/api.ts (7 hunks)
  • dashboard/backend/cf-serve.ts (1 hunks)
⛔ Files not processed due to max files limit (13)
  • dashboard/backend/create-fp-token.ts
  • dashboard/backend/deno-serve.ts
  • dashboard/backend/invites.ts
  • dashboard/src/pages/cloud/api/token-auto.tsx
  • dashboard/src/pages/signup.tsx
  • eslint.config.mjs
  • tsconfig.json
  • use-fireproof/react/img-file.ts
  • use-fireproof/react/use-attach.ts
  • use-fireproof/tests/use-fireproof-stability.test.tsx
  • vendor/merge-package.ts
  • vendor/p-limit/index.js
  • vendor/p-limit/index.test-d.ts
💤 Files with no reviewable changes (1)
  • cli/main.ts
✅ Files skipped from review due to trivial changes (2)
  • core/tests/fireproof/deleted-docs-handling.test.ts
  • core/gateways/base/uri-interceptor.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • core/device-id/certor.ts
  • core/base/indexer.ts
  • core/keybag/key-with-fingerprint.ts
  • cloud/3rd-party/src/App.tsx
  • core/keybag/internal-keys-by-fingerprint.ts
  • cloud/backend/base/msg-dispatch.ts
  • core/tests/blockstore/keyed-crypto.test.ts
  • core/tests/fireproof/query-docs.test.ts
  • core/tests/fireproof/attachable-subscription.test.ts
  • dashboard/backend/api.ts
  • core/tests/fireproof/attachable.test.ts
  • core/base/ledger.ts
  • core/tests/fireproof/all-gateway.test.ts
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-09T20:18:00.903Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: core/keybag/coerce-keyed-item.ts:44-75
Timestamp: 2025-09-09T20:18:00.903Z
Learning: The `coerceKeyedItem` function in `core/keybag/coerce-keyed-item.ts` is only used for loading from backend storage as a read-only operation, not for writing back changes. Therefore, tracking modification flags in this context is unnecessary.

Applied to files:

  • core/keybag/coerce-keyed-item.ts
  • core/keybag/key-bag.ts
📚 Learning: 2025-05-13T14:08:24.069Z
Learnt from: mabels
PR: fireproof-storage/fireproof#801
File: src/blockstore/loader.ts:401-403
Timestamp: 2025-05-13T14:08:24.069Z
Learning: In the `handleMetaStream` function in `src/blockstore/loader.ts`, recursive calls intentionally omit the `opts` parameter to avoid calling the `.first` callback multiple times, even though this also means the error handler won't be called for subsequent read errors.

Applied to files:

  • core/blockstore/loader.ts
📚 Learning: 2025-09-09T20:45:43.368Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: core/device-id/device-id-CA.ts:126-139
Timestamp: 2025-09-09T20:45:43.368Z
Learning: In core/device-id/device-id-key.ts, the exportPrivateJWK() method returns a JWKPrivate object (JWK format), not a KeyLike. When using this with jose's SignJWT.sign(), the JWK must be imported using importJWK() to convert it to a KeyLike that the signing method accepts.

Applied to files:

  • core/device-id/device-id-key.ts
📚 Learning: 2025-09-09T19:57:45.468Z
Learnt from: mabels
PR: fireproof-storage/fireproof#1130
File: core/device-id/device-id-signed-msg.ts:17-18
Timestamp: 2025-09-09T19:57:45.468Z
Learning: In core/device-id/device-id-signed-msg.ts, mabels prefers using generic type constraints like `<T extends JWTPayload>` rather than direct type usage when maintaining type flexibility is beneficial, particularly for methods that need to preserve input type information while ensuring proper constraints.

Applied to files:

  • core/device-id/device-id-client.ts
🧬 Code graph analysis (54)
core/tests/blockstore/transaction.test.ts (3)
core/blockstore/loader-helpers.ts (1)
  • anyBlock2FPBlock (130-132)
core/types/blockstore/types.ts (1)
  • AnyLink (28-28)
core/blockstore/transaction.ts (1)
  • CarTransactionImpl (29-79)
core/gateways/base/def-serde-gateway.ts (1)
core/base/ledger.ts (1)
  • sthis (116-118)
core/runtime/keyed-crypto.ts (2)
core/types/blockstore/types.ts (2)
  • CryptoAction (206-223)
  • IvKeyIdData (102-106)
core/types/base/types.ts (1)
  • PARAM (42-68)
core/blockstore/register-store-protocol.ts (1)
core/base/ledger.ts (1)
  • sthis (116-118)
core/base/database.ts (1)
core/types/base/types.ts (5)
  • DocSet (240-240)
  • CRDTMeta (321-323)
  • DocResponse (398-402)
  • DocTypes (223-223)
  • BulkResponse (404-408)
cloud/backend/base/meta-merger/meta-by-tenant-ledger.ts (1)
core/types/base/types.ts (1)
  • CRDTEntry (414-418)
cloud/backend/base/hono-server.ts (2)
cloud/backend/base/msg-dispatch.ts (1)
  • Promisable (51-51)
core/types/protocols/cloud/msg-types.ts (3)
  • buildErrorMsg (631-653)
  • ErrorMsg (202-202)
  • MsgBase (175-180)
core/tests/fireproof/query-limit-issue.test.ts (1)
core/base/ledger.ts (1)
  • fireproof (342-344)
cloud/backend/base/ws-sockets.test.ts (1)
core/protocols/cloud/msger.ts (1)
  • Msger (152-167)
core/tests/blockstore/store.test.ts (1)
core/types/base/types.ts (1)
  • NotFoundError (30-32)
core/base/crdt-clock.ts (2)
core/base/crdt-helpers.ts (1)
  • toPailFetcher (57-71)
core/blockstore/loader-helpers.ts (1)
  • anyBlock2FPBlock (130-132)
core/tests/fireproof/crdt.test.ts (3)
core/base/indexer.ts (1)
  • index (52-70)
core/blockstore/loader-helpers.ts (1)
  • parseCarFile (157-187)
core/types/base/types.ts (1)
  • CRDTMeta (321-323)
core/base/crdt.ts (2)
core/types/base/types.ts (2)
  • TransactionMeta (697-697)
  • IndexTransactionMeta (325-327)
core/base/indexer.ts (1)
  • index (52-70)
core/tests/gateway/file/loader-config.test.ts (2)
core/types/base/types.ts (1)
  • PARAM (42-68)
core/blockstore/attachable-store.ts (3)
  • fileStore (234-236)
  • metaStore (237-239)
  • carStore (231-233)
core/gateways/file/gateway-impl.ts (1)
core/types/base/types.ts (1)
  • PARAM (42-68)
core/tests/gateway/indexeddb/create-db-on-write.test.ts (1)
core/types/base/types.ts (1)
  • Database (625-667)
core/tests/blockstore/interceptor-gateway.test.ts (1)
core/gateways/base/uri-interceptor.ts (2)
  • URIInterceptor (20-76)
  • uri (32-38)
core/types/protocols/cloud/msg-types-data.ts (1)
core/types/protocols/cloud/msg-types.ts (2)
  • buildReqSignedUrl (694-704)
  • buildRes (744-774)
core/blockstore/loader-helpers.ts (2)
core/types/blockstore/types.ts (4)
  • BlockItem (883-892)
  • FPBlock (894-898)
  • ReadyCarBlockItem (856-861)
  • CarHeader (136-140)
core/runtime/async-block-encode.ts (1)
  • asyncBlockEncode (72-91)
core/tests/fireproof/multiple-ledger.test.ts (1)
core/base/ledger.ts (2)
  • name (160-163)
  • fireproof (342-344)
core/blockstore/store-factory.ts (1)
core/runtime/files.ts (2)
  • encodeFile (11-26)
  • decodeFile (40-58)
core/tests/fireproof/fireproof.test.ts (2)
core/types/base/types.ts (1)
  • IndexRows (317-319)
core/base/indexer.ts (3)
  • query (196-272)
  • index (52-70)
  • Index (77-366)
core/runtime/utils.ts (2)
dashboard/src/app-context.tsx (1)
  • AppContext (26-52)
core/types/base/types.ts (1)
  • PARAM (42-68)
core/tests/fireproof/database.test.ts (1)
core/types/base/types.ts (2)
  • DocResponse (398-402)
  • DocFileMeta (242-250)
core/blockstore/loader.ts (3)
core/blockstore/loader-helpers.ts (1)
  • parseCarFile (157-187)
core/types/base/types.ts (2)
  • CarTransaction (442-455)
  • TransactionMeta (697-697)
core/blockstore/transaction.ts (2)
  • compact (146-148)
  • compact (274-307)
core/blockstore/store.ts (6)
core/types/base/types.ts (1)
  • PARAM (42-68)
core/base/indexer.ts (1)
  • ready (89-93)
core/blockstore/loader.ts (2)
  • ready (309-327)
  • carLogIncludesGroup (56-69)
core/blockstore/transaction.ts (2)
  • ready (134-136)
  • ready (218-220)
core/base/crdt.ts (1)
  • ready (210-229)
core/base/ledger.ts (2)
  • ready (132-134)
  • ready (205-211)
core/runtime/sts-service/index.ts (1)
core/base/ledger.ts (1)
  • sthis (116-118)
core/base/write-queue.ts (1)
core/types/base/types.ts (2)
  • DocUpdate (252-257)
  • MetaType (333-333)
core/tests/gateway/indexeddb/loader-config.test.ts (2)
core/blockstore/attachable-store.ts (2)
  • fileStore (234-236)
  • metaStore (237-239)
core/types/blockstore/types.ts (1)
  • WALStore (485-496)
core/gateways/cloud/gateway.ts (1)
core/gateways/base/uri-interceptor.ts (1)
  • uri (32-38)
core/protocols/cloud/http-connection.ts (2)
core/types/protocols/cloud/msg-types.ts (5)
  • MsgBase (175-180)
  • MsgWithError (59-59)
  • RequestOpts (85-92)
  • MsgIsError (222-224)
  • buildErrorMsg (631-653)
core/protocols/cloud/msger.ts (1)
  • ActiveStream (69-77)
core/device-id/device-id-verify-msg.ts (2)
core/runtime/sts-service/index.ts (1)
  • alg (86-88)
core/device-id/certor.ts (1)
  • Certor (11-66)
core/protocols/cloud/msger.ts (4)
core/types/protocols/cloud/msg-types.ts (10)
  • FPJWKCloudAuthType (133-138)
  • MsgRawConnection (66-83)
  • MsgIsConnected (547-549)
  • MsgWithConn (191-191)
  • MsgWithOptionalConn (193-193)
  • RequestOpts (85-92)
  • MsgWithError (59-59)
  • ResOpen (533-536)
  • buildReqOpen (509-521)
  • MsgBase (175-180)
core/types/base/types.ts (1)
  • SuperThis (150-162)
core/protocols/cloud/http-connection.ts (1)
  • HttpConnection (41-206)
vendor/p-limit/index.js (1)
  • pLimit (3-91)
core/types/base/prolly-trees.d.ts (2)
core/types/blockstore/types.ts (1)
  • AnyLink (28-28)
core/types/base/types.ts (5)
  • KeyLiteral (266-266)
  • IndexKeyType (267-267)
  • DocFragment (217-217)
  • IndexKey (268-268)
  • IndexUpdateString (276-280)
core/tests/fireproof/concurrent.test.ts (2)
core/base/ledger.ts (1)
  • fireproof (342-344)
core/base/crdt.ts (1)
  • allDocs (248-255)
core/tests/runtime/device-id.test.ts (1)
core/types/base/fp-ca-cert-payload.zod.ts (1)
  • Certificate (64-64)
cloud/backend/base/connection.test.ts (5)
core/protocols/cloud/msger.ts (1)
  • conn (249-255)
core/types/protocols/cloud/msg-types.ts (2)
  • buildReqOpen (509-521)
  • MsgIsResOpen (565-567)
core/types/protocols/cloud/msg-types-data.ts (2)
  • buildReqGetData (22-24)
  • MsgIsResGetData (35-37)
core/blockstore/store.ts (1)
  • stream (212-296)
core/types/protocols/cloud/msg-types-wal.ts (2)
  • buildReqGetWAL (26-28)
  • MsgIsResGetWAL (35-37)
core/blockstore/transaction.ts (2)
core/types/blockstore/types.ts (3)
  • AnyLink (28-28)
  • FPBlock (894-898)
  • isCarBlockItemReady (875-877)
core/types/base/types.ts (1)
  • Falsy (36-36)
core/tests/blockstore/loader.test.ts (3)
core/types/blockstore/types.ts (1)
  • FPBlock (894-898)
core/blockstore/loader-helpers.ts (1)
  • parseCarFile (157-187)
core/types/base/types.ts (2)
  • CRDTMeta (321-323)
  • IndexTransactionMeta (325-327)
core/base/crdt-helpers.ts (2)
core/types/base/types.ts (2)
  • IndexKeyType (267-267)
  • DocFileMeta (242-250)
core/blockstore/loader-helpers.ts (1)
  • fileBlock2FPBlock (126-128)
core/protocols/cloud/ws-connection.ts (1)
core/types/protocols/cloud/msg-types.ts (3)
  • MsgBase (175-180)
  • MsgWithError (59-59)
  • RequestOpts (85-92)
core/tests/runtime/meta-key-hack.test.ts (4)
core/runtime/utils.ts (1)
  • ensureSuperThis (193-211)
core/blockstore/register-store-protocol.ts (1)
  • registerStoreProtocol (76-112)
core/gateways/base/meta-key-hack.ts (1)
  • AddKeyToDbMetaGateway (216-256)
core/types/blockstore/types.ts (1)
  • DbMetaEvent (423-427)
core/tests/blockstore/keyed-crypto-indexeddb-file.test.ts (1)
core/types/base/keybag-storage.zod.ts (1)
  • KeyedV2StorageKeyItem (61-61)
core/types/protocols/cloud/msg-types.ts (2)
core/protocols/cloud/msger.ts (1)
  • conn (249-255)
core/gateways/cloud/gateway.ts (1)
  • buildReqSignedUrl (93-140)
core/keybag/key-bag.ts (3)
core/types/base/keybag-storage.zod.ts (1)
  • KeyedJwtKeyBagItem (63-63)
core/types/base/jwt-payload.zod.ts (1)
  • JWTPayload (14-14)
core/keybag/key-bag-fingerprint-item.ts (1)
  • InternalKeyBagFingerprintItem (12-35)
core/base/indexer-helpers.ts (3)
core/blockstore/loader-helpers.ts (1)
  • anyBlock2FPBlock (130-132)
core/types/blockstore/types.ts (2)
  • AnyBlock (78-81)
  • AnyLink (28-28)
core/types/base/types.ts (1)
  • DocFragment (217-217)
core/types/protocols/cloud/msg-types-wal.ts (1)
core/types/protocols/cloud/msg-types.ts (2)
  • buildReqSignedUrl (694-704)
  • buildRes (744-774)
core/tests/runtime/key-bag.test.ts (2)
core/types/base/key-bag-if.ts (1)
  • JWTResult (14-18)
core/types/base/keybag-storage.zod.ts (1)
  • KeyedV2StorageKeyItem (61-61)
cli/build-cmd.test.ts (1)
cli/build-cmd.ts (3)
  • PackageJson (102-111)
  • buildJsrConf (227-253)
  • version (76-78)
core/gateways/base/fp-envelope-serialize.ts (2)
core/types/blockstore/types.ts (2)
  • DbMetaBinary (702-704)
  • WALState (476-483)
core/types/base/types.ts (1)
  • SuperThis (150-162)
core/tests/protocols/cloud/msger.test.ts (3)
core/types/protocols/cloud/msg-types.ts (4)
  • MsgBase (175-180)
  • RequestOpts (85-92)
  • MsgWithError (59-59)
  • MsgRawConnection (66-83)
core/protocols/cloud/msger.ts (3)
  • OnMsgFn (59-59)
  • UnReg (60-60)
  • ExchangedGestalt (62-65)
core/types/base/types.ts (2)
  • UnReg (422-422)
  • SuperThis (150-162)
core/tests/fireproof/indexer.test.ts (1)
core/base/indexer.ts (2)
  • Index (77-366)
  • index (52-70)
core/gateways/base/meta-key-hack.ts (2)
core/blockstore/store.ts (1)
  • url (106-108)
core/types/blockstore/types.ts (1)
  • Loadable (668-700)
core/device-id/device-id-client.ts (1)
core/device-id/device-id-signed-msg.ts (1)
  • DeviceIdSignMsg (6-39)
🪛 Biome (2.1.2)
core/base/crdt-helpers.ts

[error] 47-47: Do not shadow the global "toString" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

Comment on lines +1 to +2
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Suppressing lints instead of addressing root cause.

Adding ESLint disables is counterproductive to the PR's goal of fixing lint errors. These suppressions mask type safety issues on line 11.

Consider declaring a typed interface for globalThis extensions and adding runtime guards instead:

+interface FireproofGlobalThis {
+  FP_ENV?: Record<string, string>;
+}
+
 /* eslint-disable @typescript-eslint/no-unsafe-argument */
 /* eslint-disable @typescript-eslint/no-unsafe-member-access */
 
 import { ensureSuperThis } from "@fireproof/core-runtime";

Then on line 11, use the interface and add defensive checks (see next comment).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
interface FireproofGlobalThis {
FP_ENV?: Record<string, string>;
}
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { ensureSuperThis } from "@fireproof/core-runtime";

presetEnv: new Map<string, string>(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Object.entries({ ...(globalThis as any).FP_ENV, ...JSON.parse(process.env.FP_TEST_ENV || "{}") }),
Object.entries({ ...(globalThis as any).FP_ENV, ...JSON.parse(process.env.FP_TEST_ENV ?? "{}") }),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Runtime safety: Spreading undefined or invalid JSON will throw.

Two critical issues:

  1. If FP_ENV is undefined or not an object, the spread operator fails at runtime.
  2. If FP_TEST_ENV contains malformed JSON, JSON.parse throws an uncaught exception.

Apply this diff to add defensive checks:

-        Object.entries({ ...(globalThis as any).FP_ENV, ...JSON.parse(process.env.FP_TEST_ENV ?? "{}") }),
+        Object.entries({
+          ...((globalThis as FireproofGlobalThis).FP_ENV ?? {}),
+          ...((() => {
+            try {
+              return JSON.parse(process.env.FP_TEST_ENV ?? "{}");
+            } catch {
+              return {};
+            }
+          })()),
+        }),

This ensures:

  • FP_ENV defaults to {} if missing
  • Invalid JSON in FP_TEST_ENV returns {} instead of throwing
  • Type assertion is more specific (using the interface from the previous comment)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Object.entries({ ...(globalThis as any).FP_ENV, ...JSON.parse(process.env.FP_TEST_ENV ?? "{}") }),
Object.entries({
...((globalThis as FireproofGlobalThis).FP_ENV ?? {}),
...((() => {
try {
return JSON.parse(process.env.FP_TEST_ENV ?? "{}");
} catch {
return {};
}
})()),
}),
🤖 Prompt for AI Agents
In cloud/base/test-super-this.ts around line 11, the current spread of
(globalThis as any).FP_ENV and JSON.parse(process.env.FP_TEST_ENV ?? "{}") is
unsafe: ensure FP_ENV defaults to an empty object and malformed JSON doesn't
throw by replacing the spread with a safe merge that uses (globalThis as
any).FP_ENV ?? {} and a guarded parse (try/catch) that returns {} on error; also
tighten the type assertion to the specific FP_ENV interface from the previous
comment instead of any. Implement: compute a const safeFpEnv: FPEnvInterface =
(globalThis as any).FP_ENV ?? {}; compute a const safeTestEnv by parsing
process.env.FP_TEST_ENV inside try/catch returning {} on failure; then spread
Object.entries({ ...safeFpEnv, ...safeTestEnv }), so runtime exceptions are
prevented.

</div>

<TodoForm value={newTodoText} onChange={setNewTodoText} onSubmit={handleAddTodo} />
<TodoForm value={newTodoText} onChange={setNewTodoText} onSubmit={() => void handleAddTodo()} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unhandled promise rejections risk.

The void operator converts the returned promises to void, creating a fire-and-forget pattern. If database.put or database.del operations fail, these errors will become unhandled promise rejections with no user feedback.

Consider one of these approaches:

Option 1: Add .catch() handlers

-<TodoForm value={newTodoText} onChange={setNewTodoText} onSubmit={() => void handleAddTodo()} />
+<TodoForm 
+  value={newTodoText} 
+  onChange={setNewTodoText} 
+  onSubmit={() => { 
+    handleAddTodo().catch((err) => {
+      console.error('Failed to add todo:', err);
+      // Optionally: show user-facing error message
+    });
+  }} 
+/>

Option 2: Use a wrapper with error handling

const safeAsync = (fn: () => Promise<void>) => {
  fn().catch((err) => {
    console.error('Operation failed:', err);
    // Optionally: set error state for user feedback
  });
};

Then use: onSubmit={() => safeAsync(handleAddTodo)}

Also applies to: 113-118

🤖 Prompt for AI Agents
In cloud/todo-app/src/App.tsx around line 107 (and also lines 113–118), the
current use of the void operator creates fire-and-forget async calls that can
lead to unhandled promise rejections; replace those usages with a safe async
wrapper or attach .catch handlers so failures are handled and surfaced (e.g.,
create a safeAsync(fn) helper that calls the async function and catches/logs
errors and optionally updates an error state for user feedback, then change
onSubmit and other event props from using "void handle..." to calling
safeAsync(handle...) or to handleAddTodo().catch(...)); apply the same change to
all similar invocations on lines 113–118.

Comment on lines 158 to 170
// const noLoader = this.head.length === 1 && !updates?.length
if (!this.blockstore) {
throw this.logger.Error().Msg("missing blockstore").AsError();
}
await validateBlocks(this.logger, newHead, this.blockstore);
if (!this.transaction) {
this.transaction = this.blockstore.openTransaction({ noLoader, add: false });
}
const tblocks = this.transaction;
this.transaction ??= this.blockstore.openTransaction({ noLoader, add: false });
const transaction = this.transaction;

const advancedHead = await advanceBlocks(this.logger, newHead, tblocks, this.head);
const result = await root(toPailFetcher(tblocks), advancedHead);
const advancedHead = await advanceBlocks(this.logger, newHead, transaction, this.head);
const result = await root(toPailFetcher(transaction), advancedHead);

const fpBlocks = await Promise.all(result.additions.map(anyBlock2FPBlock));
for (const fp of fpBlocks) {
tblocks.putSync(fp);
transaction.putSync(fp);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Transaction lifecycle may leak when noLoader is true (unbounded growth risk).

You open and retain this.transaction but only commit/reset when !noLoader. On repeated remote updates (noLoader = true), the transaction persists and accumulates puts without commit/reset.

Options:

  • Commit in both paths (pass { noLoader: true } to commitTransaction if supported), then clear this.transaction.
  • Or use a short‑lived local transaction for remote paths (don’t store on this).
  • Or add an explicit abort/reset for the noLoader branch.

Please confirm intended semantics and adjust to avoid retained state across calls.

Also applies to: 177-181


🏁 Script executed:

#!/bin/bash
set -e
# Locate the crdt-clock file
file=$(fd --max-depth 3 'crdt-clock.ts' | head -n1)
echo "Inspecting: $file"

echo -e "\n--- Around validateBlocks ---"
rg -n -C3 'validateBlocks' "$file"

echo -e "\n--- Around openTransaction ---"
rg -n -C3 'openTransaction' "$file"

echo -e "\n--- This.transaction usages ---"
rg -n -C3 'this\.transaction' "$file"

echo -e "\n--- Commit or reset mentions ---"
rg -n -C3 'commit\|reset\|abort' "$file"

Length of output: 1865


🏁 Script executed:

#!/bin/bash
file="core/base/crdt-clock.ts"
echo "Searching noLoader occurrences in $file"
rg -n 'noLoader' "$file"

Length of output: 395


Ensure transaction lifecycle covers noLoader path

  • core/base/crdt-clock.ts (lines 158–181): when noLoader===true, this.transaction is opened but never committed or cleared, causing unbounded state growth.
  • Fix by committing in both paths (e.g. await blockstore.commitTransaction(transaction, {head: advancedHead}, {add:false, noLoader:true}); this.transaction=undefined), or use a short-lived local transaction / explicitly abort/reset in the noLoader branch.
🤖 Prompt for AI Agents
In core/base/crdt-clock.ts around lines 158 to 170, the code opens
this.transaction with noLoader but never commits or clears it, causing unbounded
state growth; ensure the transaction lifecycle is closed for the noLoader path
by committing (e.g. await blockstore.commitTransaction(transaction, { head:
advancedHead }, { add: false, noLoader: true })) and then clearing
this.transaction = undefined, or alternatively convert to a short-lived local
transaction and explicitly abort/reset it on error; make sure both branches
(noLoader true and false) always commit or abort and clear this.transaction to
avoid leaking an open transaction.

Comment on lines 189 to 196
for (const filename in files) {
if (File === files[filename].constructor) {
const file = files[filename] as File;
const file = files[filename];

// totalSize += file.size
const { cid, blocks: fileBlocks } = await store.encodeFile(file);
didPut.push(filename);
for (const block of fileBlocks) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard against environments without File (ReferenceError risk).

File may be undefined in some runtimes (Node without web shims). Add a type check and use instanceof.

-  for (const filename in files) {
-    if (File === files[filename].constructor) {
+  for (const filename in files) {
+    if (typeof File !== "undefined" && files[filename] instanceof File) {
       const file = files[filename];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const filename in files) {
if (File === files[filename].constructor) {
const file = files[filename] as File;
const file = files[filename];
// totalSize += file.size
const { cid, blocks: fileBlocks } = await store.encodeFile(file);
didPut.push(filename);
for (const block of fileBlocks) {
for (const filename in files) {
if (typeof File !== "undefined" && files[filename] instanceof File) {
const file = files[filename];
// totalSize += file.size
const { cid, blocks: fileBlocks } = await store.encodeFile(file);
didPut.push(filename);
for (const block of fileBlocks) {
🤖 Prompt for AI Agents
In core/base/crdt-helpers.ts around lines 189 to 196, the code directly
references the global File constructor and compares constructors, which throws a
ReferenceError in environments where File is undefined; replace that check with
a safe typeof guard and an instanceof check (e.g. if (typeof File !==
"undefined" && files[filename] instanceof File) { ... }) so the block only runs
when File exists and the item is truly a File.

@@ -1,9 +1,9 @@
declare module "prolly-trees/base" {
declare type AnyLink = Link<unknown, number, number, Version>;
declare type AnyLink = string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify type consistency across module boundaries.

This declaration simplifies AnyLink to string, but the relevant code snippet shows that in core/types/blockstore/types.ts (line 27), AnyLink is still defined as Link<unknown, number, number, Version>. This creates a type mismatch between the prolly-trees module declarations and the actual blockstore types.

Run the following script to verify how AnyLink is used across the codebase and identify potential type conflicts:


🏁 Script executed:

#!/bin/bash
# Description: Search for AnyLink type definitions and usages to verify consistency

# Find all AnyLink type definitions
echo "=== AnyLink type definitions ==="
rg -nP --type=ts -A2 'type\s+AnyLink\s*='

# Find imports of AnyLink from different modules
echo -e "\n=== AnyLink imports ==="
rg -nP --type=ts 'import.*\bAnyLink\b'

# Find AnyLink usage in prolly-trees related code
echo -e "\n=== Prolly-trees AnyLink usage ==="
rg -nP --type=ts -C3 -g '*prolly*' '\bAnyLink\b'

Length of output: 1691


Align AnyLink declaration in prolly-trees with blockstore types.
prolly-trees/base/prolly-trees.d.ts declares AnyLink = string, conflicting with export type AnyLink = Link<unknown, number, number, Version> in core/types/blockstore/types.ts and breaking consumers. Import and re-export the blockstore AnyLink instead of redefining it.

🤖 Prompt for AI Agents
In core/types/base/prolly-trees.d.ts around line 2, the file currently
redeclares AnyLink as a plain string which conflicts with
core/types/blockstore/types.ts's exported Link-based AnyLink; remove the local
declaration and instead import the AnyLink type from core/types/blockstore/types
(or from its module path) and re-export it (export type { AnyLink } or export {
AnyLink } as appropriate) so consumers use the blockstore definition; ensure the
import path is correct and update any references to avoid duplicate
declarations.

range(a: string, b: string): Promise<{ result: ProllyIndexRow<K, T>[] }>;
get(key: string): Promise<{ result: ProllyIndexRow<K, T>[] }>;
bulk(bulk: (IndexUpdate<K> | IndexUpdateString)[]): PromiseLike<{
bulk(bulk: IndexUpdateString[]): PromiseLike<{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify bulk() breaking change.

Narrowing the bulk parameter from (IndexUpdate<K> | IndexUpdateString)[] to IndexUpdateString[] removes support for IndexUpdate<K> objects. This is a breaking change that could cause runtime errors if existing code passes IndexUpdate<K> values.

Run the following script to identify call sites that may be affected:


🏁 Script executed:

#!/bin/bash
# Description: Find bulk() calls and IndexUpdate usage

# Find bulk() method calls
echo "=== bulk() call sites ==="
rg -nP --type=ts -C3 '\.bulk\s*\('

# Find IndexUpdate type usage (not IndexUpdateString)
echo -e "\n=== IndexUpdate<K> usage ==="
rg -nP --type=ts -C2 'IndexUpdate<[^>]+>'

Length of output: 14008


Restore bulk() support for IndexUpdate<K>
Narrowing the parameter to IndexUpdateString[] breaks calls in core/base/indexer-helpers.ts (and other callers) that pass (IndexUpdate<K> | IndexUpdateString)[]. Revert the signature in core/types/base/prolly-trees.d.ts to accept (IndexUpdate<K> | IndexUpdateString)[].

🤖 Prompt for AI Agents
In core/types/base/prolly-trees.d.ts around line 19, the bulk() method was
narrowed to accept only IndexUpdateString[] which breaks callers that pass a mix
of IndexUpdate<K> and IndexUpdateString; change the declaration back to accept
(IndexUpdate<K> | IndexUpdateString)[] (preserving the generic K) so the
signature is generic and callers passing either type compile again.

Comment on lines 865 to 873
export function isBlockReady(ifp: unknown): ifp is FPBlock {
const fp = ifp as FPBlock;
return fp.item && fp.item.status === "ready";
return fp.item.status === "ready";
}

export function isBlockNotReady(ifp: unknown): ifp is FPBlock {
const fp = ifp as FPBlock;
return fp.item && fp.item.status !== "ready";
return fp.item.status !== "ready";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Type guards lack runtime validation.

Both isBlockReady and isBlockNotReady accept unknown input but use type assertions (as FPBlock) instead of runtime validation. If the input is not actually an FPBlock, or if fp.item is undefined, these functions will throw runtime errors instead of returning false.

Type guard functions must validate their inputs at runtime to safely narrow types.

Apply this diff to add proper runtime validation:

 export function isBlockReady(ifp: unknown): ifp is FPBlock {
+  if (!ifp || typeof ifp !== 'object') return false;
   const fp = ifp as FPBlock;
+  if (!fp.item || typeof fp.item !== 'object') return false;
   return fp.item.status === "ready";
 }

 export function isBlockNotReady(ifp: unknown): ifp is FPBlock {
+  if (!ifp || typeof ifp !== 'object') return false;
   const fp = ifp as FPBlock;
+  if (!fp.item || typeof fp.item !== 'object') return false;
   return fp.item.status !== "ready";
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function isBlockReady(ifp: unknown): ifp is FPBlock {
const fp = ifp as FPBlock;
return fp.item && fp.item.status === "ready";
return fp.item.status === "ready";
}
export function isBlockNotReady(ifp: unknown): ifp is FPBlock {
const fp = ifp as FPBlock;
return fp.item && fp.item.status !== "ready";
return fp.item.status !== "ready";
}
export function isBlockReady(ifp: unknown): ifp is FPBlock {
if (!ifp || typeof ifp !== 'object') return false;
const fp = ifp as FPBlock;
if (!fp.item || typeof fp.item !== 'object') return false;
return fp.item.status === "ready";
}
export function isBlockNotReady(ifp: unknown): ifp is FPBlock {
if (!ifp || typeof ifp !== 'object') return false;
const fp = ifp as FPBlock;
if (!fp.item || typeof fp.item !== 'object') return false;
return fp.item.status !== "ready";
}
🤖 Prompt for AI Agents
In core/types/blockstore/types.ts around lines 865 to 873, the two type guard
functions use naive assertions and can throw when given non-FPBlock inputs;
update both to perform runtime checks: verify the input is a non-null object,
that it has an "item" property which is a non-null object, and that
"item.status" exists and is a string before returning the predicate (true for
"ready" in isBlockReady, false otherwise) — if any check fails return false;
keep the type predicate signatures (ifp is FPBlock) and ensure no property
access occurs before these checks so the guards never throw on invalid input.

Comment on lines 875 to 881
export function isCarBlockItemReady(ifp: unknown): ifp is FPBlock<ReadyCarBlockItem> {
const fp = ifp as FPBlock<ReadyCarBlockItem>;
return fp.item && fp.item.type === "car" && fp.item.status === "ready";
return true;
}

export function isCarBlockItemStale(ifp: unknown): ifp is FPBlock<StaleCarBlockItem> {
const fp = ifp as FPBlock<StaleCarBlockItem>;
return fp.item && fp.item.type === "car" && fp.item.status === "stale";
return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Type guards unconditionally return true.

Both isCarBlockItemReady and isCarBlockItemStale are type guard functions that unconditionally return true without validating the input. This completely breaks type safety—calling code will assume the parameter is of a specific type (e.g., FPBlock<ReadyCarBlockItem>), but it could be anything, leading to runtime crashes when accessing properties.

These functions must properly validate the input. Apply this diff to implement correct type guards:

 export function isCarBlockItemReady(ifp: unknown): ifp is FPBlock<ReadyCarBlockItem> {
-  return true;
+  if (!ifp || typeof ifp !== 'object') return false;
+  const fp = ifp as FPBlock;
+  if (!fp.item || typeof fp.item !== 'object') return false;
+  return fp.item.type === "car" && fp.item.status === "ready";
 }

 export function isCarBlockItemStale(ifp: unknown): ifp is FPBlock<StaleCarBlockItem> {
-  return true;
+  if (!ifp || typeof ifp !== 'object') return false;
+  const fp = ifp as FPBlock;
+  if (!fp.item || typeof fp.item !== 'object') return false;
+  return fp.item.type === "car" && fp.item.status === "stale";
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function isCarBlockItemReady(ifp: unknown): ifp is FPBlock<ReadyCarBlockItem> {
const fp = ifp as FPBlock<ReadyCarBlockItem>;
return fp.item && fp.item.type === "car" && fp.item.status === "ready";
return true;
}
export function isCarBlockItemStale(ifp: unknown): ifp is FPBlock<StaleCarBlockItem> {
const fp = ifp as FPBlock<StaleCarBlockItem>;
return fp.item && fp.item.type === "car" && fp.item.status === "stale";
return true;
}
export function isCarBlockItemReady(ifp: unknown): ifp is FPBlock<ReadyCarBlockItem> {
if (!ifp || typeof ifp !== 'object') return false;
const fp = ifp as FPBlock;
if (!fp.item || typeof fp.item !== 'object') return false;
return fp.item.type === "car" && fp.item.status === "ready";
}
export function isCarBlockItemStale(ifp: unknown): ifp is FPBlock<StaleCarBlockItem> {
if (!ifp || typeof ifp !== 'object') return false;
const fp = ifp as FPBlock;
if (!fp.item || typeof fp.item !== 'object') return false;
return fp.item.type === "car" && fp.item.status === "stale";
}

Comment on lines 528 to 531
export function MsgIsReqOpen(imsg: MsgBase): imsg is ReqOpen {
const msg = imsg as MsgWithConn<ReqOpen>;
return msg.type === "reqOpen" && !!msg.conn && !!msg.conn.reqId;
return !!msg.conn.reqId;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard predicates can throw; also restore type check in MsgIsReqOpen.

Accessing msg.conn.* without checking existence risks runtime TypeErrors. MsgIsReqOpen should also verify msg.type.

-export function MsgIsReqOpen(imsg: MsgBase): imsg is ReqOpen {
-  const msg = imsg as MsgWithConn<ReqOpen>;
-  return !!msg.conn.reqId;
-}
+export function MsgIsReqOpen(imsg: MsgBase): imsg is ReqOpen {
+  const anyMsg = imsg as Partial<MsgWithConn<ReqOpen>>;
+  return anyMsg.type === "reqOpen" && !!anyMsg.conn && !!anyMsg.conn.reqId;
+}
-export function MsgIsWithConn<T extends MsgBase>(msg: T): msg is MsgWithConn<T> {
-  const mwc = (msg as MsgWithConn<T>).conn;
-  return !!mwc.reqId && !!mwc.resId;
-}
+export function MsgIsWithConn<T extends MsgBase>(msg: T): msg is MsgWithConn<T> {
+  const mwc = (msg as Partial<MsgWithConn<T>>).conn;
+  return !!mwc && !!mwc.reqId && !!mwc.resId;
+}
-export function MsgIsTenantLedger<T extends MsgBase>(msg: T): msg is MsgWithTenantLedger<MsgWithConn<T>> {
-  if (MsgIsWithConnAuth(msg)) {
-    const t = (msg as MsgWithTenantLedger<MsgWithConn<T>>).tenant;
-    return !!t.tenant && !!t.ledger;
-  }
-  return false;
-}
+export function MsgIsTenantLedger<T extends MsgBase>(msg: T): msg is MsgWithTenantLedger<MsgWithConn<T>> {
+  if (MsgIsWithConnAuth(msg)) {
+    const t = (msg as Partial<MsgWithTenantLedger<MsgWithConn<T>>>).tenant;
+    return !!t && !!t.tenant && !!t.ledger;
+  }
+  return false;
+}

Also applies to: 538-545, 655-661

🤖 Prompt for AI Agents
In core/types/protocols/cloud/msg-types.ts around lines 528-531 (and similarly
for 538-545 and 655-661), the guard predicates access msg.conn.* without
verifying msg.conn exists and do not check msg.type; update each predicate to
first verify imsg.type === the expected message type, then cast to
MsgWithConn<...> only after confirming msg.conn is truthy, and finally check
Boolean(msg.conn.reqId) (or the appropriate property) to avoid TypeError; ensure
the predicate returns false if conn is missing or type mismatches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants