@syncular/server 0.15.44 → 0.15.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +1 -1
  2. package/dist/admin.d.ts +2 -2
  3. package/dist/admin.js +1 -1
  4. package/dist/blob-store.d.ts +2 -2
  5. package/dist/context.d.ts +2 -2
  6. package/dist/d1-storage.js +3 -3
  7. package/dist/events-ring.d.ts +1 -1
  8. package/dist/events-ring.js +1 -1
  9. package/dist/handler.js +2 -2
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/pg-executor.d.ts +1 -1
  13. package/dist/pg-executor.js +1 -1
  14. package/dist/postgres-fanout.d.ts +1 -1
  15. package/dist/postgres-storage.js +3 -3
  16. package/dist/pull.js +1 -1
  17. package/dist/relational-rows.d.ts +9 -9
  18. package/dist/relational-rows.js +9 -9
  19. package/dist/s3-blob-store.js +1 -1
  20. package/dist/s3-segment-store.js +1 -1
  21. package/dist/schema.d.ts +4 -5
  22. package/dist/schema.js +3 -3
  23. package/dist/seed.js +1 -1
  24. package/dist/segment-store.d.ts +3 -3
  25. package/dist/signed-url.js +2 -2
  26. package/dist/sigv4.d.ts +2 -4
  27. package/dist/sigv4.js +3 -3
  28. package/dist/sqlite-blob-store.d.ts +1 -1
  29. package/dist/sqlite-blob-store.js +1 -1
  30. package/dist/sqlite-dialect.d.ts +2 -3
  31. package/dist/sqlite-dialect.js +1 -1
  32. package/dist/sqlite-image.d.ts +1 -1
  33. package/dist/sqlite-lease-store.d.ts +1 -1
  34. package/dist/sqlite-lease-store.js +1 -1
  35. package/dist/sqlite-segment-store.d.ts +2 -2
  36. package/dist/sqlite-segment-store.js +2 -2
  37. package/dist/sqlite-storage.d.ts +1 -1
  38. package/dist/sqlite-storage.js +4 -4
  39. package/dist/storage.d.ts +3 -3
  40. package/package.json +2 -2
  41. package/src/admin.ts +3 -3
  42. package/src/blob-store.ts +0 -0
  43. package/src/context.ts +2 -2
  44. package/src/d1-storage.ts +3 -3
  45. package/src/events-ring.ts +1 -1
  46. package/src/handler.ts +2 -2
  47. package/src/index.ts +1 -1
  48. package/src/pg-executor.ts +1 -1
  49. package/src/postgres-fanout.ts +1 -1
  50. package/src/postgres-storage.ts +4 -4
  51. package/src/pull.ts +1 -1
  52. package/src/relational-rows.ts +9 -9
  53. package/src/s3-blob-store.ts +1 -1
  54. package/src/s3-segment-store.ts +1 -1
  55. package/src/schema.ts +6 -7
  56. package/src/seed.ts +1 -1
  57. package/src/segment-store.ts +3 -3
  58. package/src/signed-url.ts +2 -2
  59. package/src/sigv4.ts +3 -5
  60. package/src/sqlite-blob-store.ts +1 -1
  61. package/src/sqlite-dialect.ts +2 -3
  62. package/src/sqlite-image.ts +1 -1
  63. package/src/sqlite-lease-store.ts +1 -1
  64. package/src/sqlite-segment-store.ts +2 -2
  65. package/src/sqlite-storage.ts +4 -4
  66. package/src/storage.ts +3 -3
package/README.md CHANGED
@@ -8,7 +8,7 @@ pruning (§4.6), and signed-URL token issuance (§5.4). `SPEC.md` is
8
8
  normative for everything on the wire; this README covers the **host
9
9
  surface** — in particular the ops seam and the pruning runbook.
10
10
 
11
- ## Deployment matrix (runtime adapters, TODO §4.2)
11
+ ## Deployment matrix (runtime adapters)
12
12
 
13
13
  The server core is **runtime-neutral TypeScript** — `handleSyncRequest` and
14
14
  the realtime session speak only Web `Request`/`Response`/`fetch`/Web-Crypto,
package/dist/admin.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * `SyncularAdmin` the operator-facing read surface over the server core
3
- * (TODO §2.5). A read-only, partition-scoped, JSON-able query layer over
2
+ * `SyncularAdmin` is the operator-facing read surface over the server core.
3
+ * It is a read-only, partition-scoped, JSON-able query layer over
4
4
  * `ServerStorage`, the optional segment/blob store stats, and an in-memory
5
5
  * event ring. It delivers the 80% operator value (who's connected, what's
6
6
  * flowing, horizon health, the event tail) as a handful of queries in the
package/dist/admin.js CHANGED
@@ -16,7 +16,7 @@ function percentile(sample, q) {
16
16
  }
17
17
  function required(value, what) {
18
18
  if (value === undefined) {
19
- throw new Error(`SyncularAdmin: the configured ${what} does not implement this read (TODO §2.5 optional method missing)`);
19
+ throw new Error(`SyncularAdmin: the configured ${what} does not implement this optional read`);
20
20
  }
21
21
  return value;
22
22
  }
@@ -20,7 +20,7 @@ export interface BlobRecord {
20
20
  readonly createdAtMs: number;
21
21
  }
22
22
  /**
23
- * Coarse blob-store counters for the admin/console read surface (TODO
23
+ * Coarse blob-store counters for the admin/console read surface (work item
24
24
  * §2.5), partition-scoped. Optional. `count`/`bytes` are total stored
25
25
  * blobs; a store that omits `stats()` cannot report them.
26
26
  */
@@ -53,7 +53,7 @@ export interface BlobStore {
53
53
  * Returns the deleted blobIds.
54
54
  */
55
55
  sweepOrphans(partition: string, olderThanMs: number, referencedBlobIds: ReadonlySet<string>): Promise<string[]>;
56
- /** Admin/console counters (TODO §2.5) — ADDITIVE, optional. */
56
+ /** Admin/console counters (work item §2.5) — ADDITIVE, optional. */
57
57
  stats?(partition: string): Promise<BlobStoreStats>;
58
58
  }
59
59
  /** `"sha256:" + hex` of the bytes — the content address (§5.9.1). */
package/dist/context.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The host context for `handleSyncRequest` (REVISE B2): storage, the
2
+ * The host context for `handleSyncRequest`: storage, the
3
3
  * `resolveScopes` host callback (runs in the host process — the moat),
4
4
  * clock, and a segment store. Framework-free; adapters supply `partition`
5
5
  * and `actorId` from host authentication (§1.1).
@@ -136,7 +136,7 @@ export interface SyncServerConfig {
136
136
  */
137
137
  readonly blobUploadUrls?: BlobUploadPresignConfig;
138
138
  /**
139
- * §5.3 sqlite-image builder (TODO §4.2), injected so the pull path never
139
+ * §5.3 sqlite-image builder, injected so the pull path never
140
140
  * statically imports `bun:sqlite`. Absent ⇒ the sqlite-image lane is off
141
141
  * (bit-2 clients are served the rows lane) — the Workers/edge posture. A
142
142
  * Bun/Node host wires `buildSqliteImage` from `@syncular/server`.
@@ -1,6 +1,6 @@
1
1
  var _a;
2
2
  /**
3
- * Cloudflare D1 server storage (TODO §4.2 — the Workers deployment rung).
3
+ * Cloudflare D1 server storage for Workers deployments.
4
4
  *
5
5
  * D1 *is* SQLite exposed over an async, statement-at-a-time API
6
6
  * (`prepare(sql).bind(...).all()` / `.first()` / `.run()`, plus `batch([…])`
@@ -448,7 +448,7 @@ export class D1ServerStorage {
448
448
  for (const table of schema.tables.values()) {
449
449
  const bindCount = tableColumnNames(table).length;
450
450
  if (bindCount > D1_MAX_BIND_PARAMS) {
451
- throw new Error(`table ${JSON.stringify(table.name)} needs ${bindCount} bound parameters per upsert D1 caps statements at ${D1_MAX_BIND_PARAMS} (DESIGN "D1 bind-parameter limit")`);
451
+ throw new Error(`table ${JSON.stringify(table.name)} needs ${bindCount} bound parameters per upsert; D1 caps statements at ${D1_MAX_BIND_PARAMS}`);
452
452
  }
453
453
  }
454
454
  await this.#db.exec(`${SCHEMA_META_DDL_SQLITE.replace(/\s+/g, ' ')};`);
@@ -757,7 +757,7 @@ export class D1ServerStorage {
757
757
  .all();
758
758
  return results.map((r) => r.blob_id);
759
759
  }
760
- // -- admin/console read surface (TODO §2.5) --------------------------------
760
+ // -- admin/console read surface --------------------------------------------
761
761
  async listClientRecords(partition) {
762
762
  const { results } = await this.#db
763
763
  .prepare('SELECT client_id, actor_id, cursor, subscriptions, updated_at_ms FROM sync_clients WHERE partition=? ORDER BY updated_at_ms DESC')
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Event ring buffer + sink composition (TODO §2.5) — the "event stream"
2
+ * Event ring buffer + sink composition: the "event stream"
3
3
  * without any infrastructure dependency.
4
4
  *
5
5
  * `RingBufferEvents` is a `SyncularServerEvents` sink that retains the last
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Event ring buffer + sink composition (TODO §2.5) — the "event stream"
2
+ * Event ring buffer + sink composition: the "event stream"
3
3
  * without any infrastructure dependency.
4
4
  *
5
5
  * `RingBufferEvents` is a `SyncularServerEvents` sink that retains the last
package/dist/handler.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `handleSyncRequest(bytes, ctx) → bytes` (SPEC.md §1, REVISE B2).
2
+ * `handleSyncRequest(bytes, ctx) → bytes` (SPEC.md §1).
3
3
  *
4
4
  * Internally streaming-friendly (§1.4): `createSyncResponseStream` returns
5
5
  * an async iterable of encoded chunks — one per frame — after performing
@@ -455,7 +455,7 @@ async function createStreamCore(bytes, ctx, events, startedAtMs = 0) {
455
455
  throw error;
456
456
  }
457
457
  const schema = compileSchema(ctx.schema);
458
- // Relational row tables (DESIGN-relational-server-storage.md): create/
458
+ // Relational row tables: create/
459
459
  // migrate on first contact; memoized per storage instance thereafter.
460
460
  await ctx.storage.ensureSchema(schema);
461
461
  const plan = await planRequest(request, ctx, schema);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @syncular/server — framework-free embeddable SSP2 protocol library
3
- * (SPEC.md is normative; REVISE.md B2 is the architectural mandate).
3
+ * (SPEC.md is normative).
4
4
  *
5
5
  * Core surface: `handleSyncRequest(bytes, ctx) → bytes` over host-provided
6
6
  * storage / scope-resolution / segment-store interfaces, plus a
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @syncular/server — framework-free embeddable SSP2 protocol library
3
- * (SPEC.md is normative; REVISE.md B2 is the architectural mandate).
3
+ * (SPEC.md is normative).
4
4
  *
5
5
  * Core surface: `handleSyncRequest(bytes, ctx) → bytes` over host-provided
6
6
  * storage / scope-resolution / segment-store interfaces, plus a
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The minimal Postgres executor seam (REVISE B2: zero runtime deps).
2
+ * The minimal Postgres executor seam has zero runtime dependencies.
3
3
  *
4
4
  * `PostgresServerStorage` is written entirely against this interface so the
5
5
  * server library never imports a specific driver. Production wires one of:
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The minimal Postgres executor seam (REVISE B2: zero runtime deps).
2
+ * The minimal Postgres executor seam has zero runtime dependencies.
3
3
  *
4
4
  * `PostgresServerStorage` is written entirely against this interface so the
5
5
  * server library never imports a specific driver. Production wires one of:
@@ -1,5 +1,5 @@
1
1
  /**
2
- * LISTEN/NOTIFY multi-instance fanout (TODO §4.1) — the primitive that lets
2
+ * LISTEN/NOTIFY multi-instance fanout lets
3
3
  * N server instances behind a load balancer wake each other's realtime
4
4
  * sessions when a commit lands on any one of them.
5
5
  *
@@ -191,8 +191,8 @@ function toStoredChange(record) {
191
191
  };
192
192
  }
193
193
  /**
194
- * Migration rewrite (DESIGN "optional materialization"): keyset-paged walk
195
- * of a row table inside the migration transaction; when `oldLayout` is
194
+ * Migration rewrite: keyset-paged walk of a row table inside the migration
195
+ * transaction. When `oldLayout` is
196
196
  * given every payload re-encodes under the current columns, and the
197
197
  * projection (when materialized) refreshes from the payload either way.
198
198
  */
@@ -818,7 +818,7 @@ export class PostgresServerStorage {
818
818
  const { rows } = await this.#exec.query('SELECT DISTINCT blob_id FROM sync_blob_refs WHERE partition=$1', [partition]);
819
819
  return rows.map((r) => r.blob_id);
820
820
  }
821
- // -- admin/console read surface (TODO §2.5) --------------------------------
821
+ // -- admin/console read surface --------------------------------------------
822
822
  async listClientRecords(partition) {
823
823
  const { rows } = await this.#exec.query('SELECT client_id, actor_id, cursor, subscriptions, updated_at_ms FROM sync_clients WHERE partition=$1 ORDER BY updated_at_ms DESC', [partition]);
824
824
  return rows.map((r) => ({
package/dist/pull.js CHANGED
@@ -170,7 +170,7 @@ async function* sqliteImageSegment(ctx, schema, limits, plan, asOf, digest, trac
170
170
  // builder through `sqliteImageBuilder`; when omitted we default to the
171
171
  // in-tree `buildSqliteImage` ONLY on a Bun runtime, reached by a *dynamic*
172
172
  // import so `bun:sqlite` is never a static dependency of the pull path
173
- // (TODO §4.2 neutrality enforced by test/runtime-neutrality.test.ts). On
173
+ // (runtime neutrality is enforced by test/runtime-neutrality.test.ts). On
174
174
  // Workers/edge (no `Bun`) this yields the rows lane for a bit-2 client — a
175
175
  // support floor, not a fallback (§5.3: sqlite is an *accept*, not a demand).
176
176
  const buildImage = await resolveImageBuilder(ctx);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Relational current-row storage (DESIGN-relational-server-storage.md).
2
+ * Relational current-row storage.
3
3
  *
4
4
  * Every synced table is a REAL table in the server database — the app's
5
5
  * columns with proper type affinities, queryable with plain SQL/joins/BI —
@@ -54,7 +54,7 @@ export declare const SYNC_SCOPES_COLUMN = "_sync_scopes";
54
54
  export declare const SYNC_PAYLOAD_COLUMN = "_sync_payload";
55
55
  /** SQL-standard identifier quoting (both dialects). */
56
56
  export declare function quoteIdent(name: string): string;
57
- /** §5.3-style type affinities, per dialect (DESIGN type-mapping table). */
57
+ /** §5.3-style type affinities, per dialect. */
58
58
  export declare function columnSqlType(column: RowColumn, dialect: RelationalDialect): string;
59
59
  /**
60
60
  * The full column list of a relational row table, in INSERT order:
@@ -86,11 +86,11 @@ export declare const SYNC_INDEX_PREFIX = "sync_ix_";
86
86
  */
87
87
  export declare function physicalIndexName(declaredName: string): string;
88
88
  /**
89
- * CREATE INDEX IF NOT EXISTS for the table's user-declared indexes
90
- * (DESIGN "user indexes" — the same declared names/columns the client
91
- * materializes; cross-table index-name uniqueness is the user's schema
92
- * concern, exactly as it is client-side). Server-side the physical name
93
- * carries the {@link SYNC_INDEX_PREFIX} ownership marker.
89
+ * CREATE INDEX IF NOT EXISTS for the table's user-declared indexes. These use
90
+ * the same declared names and columns the client materializes. Cross-table
91
+ * index-name uniqueness is the user's schema concern, as it is client-side.
92
+ * Server-side the physical name carries the {@link SYNC_INDEX_PREFIX}
93
+ * ownership marker.
94
94
  */
95
95
  export declare function createIndexDdl(table: CompiledTable): string[];
96
96
  /** Idempotent removal of one Syncular-owned relational projection index. */
@@ -182,8 +182,8 @@ export declare function selectRowScopesSql(table: CompiledTable, dialect: Relati
182
182
  /** DELETE one stored row. Params: [partition, rowId]. */
183
183
  export declare function deleteRowSql(table: CompiledTable, dialect: RelationalDialect): string;
184
184
  /**
185
- * The schema-version marker table gating DDL work (DESIGN "server-side
186
- * schema migration"): `ensureSchema` compares the stored version and skips
185
+ * The schema-version marker table gates DDL work. `ensureSchema` compares the
186
+ * stored version and skips
187
187
  * all introspection/DDL when it matches — one cheap read per storage
188
188
  * instance (relevant for D1's per-request instantiation).
189
189
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Relational current-row storage (DESIGN-relational-server-storage.md).
2
+ * Relational current-row storage.
3
3
  *
4
4
  * Every synced table is a REAL table in the server database — the app's
5
5
  * columns with proper type affinities, queryable with plain SQL/joins/BI —
@@ -53,7 +53,7 @@ export const SYNC_PAYLOAD_COLUMN = '_sync_payload';
53
53
  export function quoteIdent(name) {
54
54
  return `"${name.replaceAll('"', '""')}"`;
55
55
  }
56
- /** §5.3-style type affinities, per dialect (DESIGN type-mapping table). */
56
+ /** §5.3-style type affinities, per dialect. */
57
57
  export function columnSqlType(column, dialect) {
58
58
  if (dialect === 'postgres') {
59
59
  switch (column.type) {
@@ -176,11 +176,11 @@ export function physicalIndexName(declaredName) {
176
176
  return `${SYNC_INDEX_PREFIX}${fnv1a64Hex(declaredName)}`;
177
177
  }
178
178
  /**
179
- * CREATE INDEX IF NOT EXISTS for the table's user-declared indexes
180
- * (DESIGN "user indexes" — the same declared names/columns the client
181
- * materializes; cross-table index-name uniqueness is the user's schema
182
- * concern, exactly as it is client-side). Server-side the physical name
183
- * carries the {@link SYNC_INDEX_PREFIX} ownership marker.
179
+ * CREATE INDEX IF NOT EXISTS for the table's user-declared indexes. These use
180
+ * the same declared names and columns the client materializes. Cross-table
181
+ * index-name uniqueness is the user's schema concern, as it is client-side.
182
+ * Server-side the physical name carries the {@link SYNC_INDEX_PREFIX}
183
+ * ownership marker.
184
184
  */
185
185
  export function createIndexDdl(table) {
186
186
  // User indexes name app columns — nothing to index without the projection.
@@ -402,8 +402,8 @@ export function deleteRowSql(table, dialect) {
402
402
  return `DELETE FROM ${quoteIdent(table.name)} WHERE ${quoteIdent(SYNC_PARTITION_COLUMN)}=${p[0]} AND ${quoteIdent(SYNC_ROW_ID_COLUMN)}=${p[1]}`;
403
403
  }
404
404
  /**
405
- * The schema-version marker table gating DDL work (DESIGN "server-side
406
- * schema migration"): `ensureSchema` compares the stored version and skips
405
+ * The schema-version marker table gates DDL work. `ensureSchema` compares the
406
+ * stored version and skips
407
407
  * all introspection/DDL when it matches — one cheap read per storage
408
408
  * instance (relevant for D1's per-request instantiation).
409
409
  *
@@ -23,7 +23,7 @@ function parseStatsJson(json) {
23
23
  bytes: Number(parsed.bytes ?? 0),
24
24
  };
25
25
  }
26
- // Runtime-neutral base64url (TODO §4.2): `Buffer` is not present on
26
+ // Runtime-neutral base64url: `Buffer` is not present on
27
27
  // Cloudflare Workers without `nodejs_compat`, so the metadata header is
28
28
  // (de)coded with `btoa`/`atob`, available in every runtime.
29
29
  function utf8ToBase64url(text) {
@@ -32,7 +32,7 @@ function parseStatsJson(json) {
32
32
  sqliteSegments: Number(parsed.sqliteSegments ?? 0),
33
33
  };
34
34
  }
35
- // Runtime-neutral base64url (TODO §4.2): `Buffer` is not present on
35
+ // Runtime-neutral base64url: `Buffer` is not present on
36
36
  // Cloudflare Workers without `nodejs_compat`, so the object-metadata record
37
37
  // header is (de)coded with `btoa`/`atob`, available in every runtime.
38
38
  function utf8ToBase64url(text) {
package/dist/schema.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * The server is configured with a schema IR: tables, columns with the six
5
5
  * §2.4 column types, scope patterns per §3.1, and a schema version. Codegen
6
- * (B5) will emit this shape later; tests hand-write it.
6
+ * Typegen emits this shape; tests hand-write it.
7
7
  */
8
8
  import { type RowColumn } from '@syncular/core';
9
9
  /** `'prefix:{variable}'` shorthand (column name = variable) or explicit. */
@@ -12,9 +12,8 @@ export type ScopePatternSpec = string | {
12
12
  column: string;
13
13
  };
14
14
  /**
15
- * A user-declared index (the migration subset's CREATE INDEX). With
16
- * relational server storage these apply server-side too (DESIGN
17
- * "user indexes") — the same declaration the client materializes.
15
+ * A user-declared index (the migration subset's CREATE INDEX). Relational
16
+ * server storage applies the same declaration the client materializes.
18
17
  */
19
18
  export interface IndexSchema {
20
19
  readonly name: string;
@@ -32,7 +31,7 @@ export interface TableSchema {
32
31
  /** User indexes (optional) — created on the server's relational tables. */
33
32
  readonly indexes?: readonly IndexSchema[];
34
33
  /**
35
- * Server-side column materialization (DESIGN-relational-server-storage.md
34
+ * Server-side column materialization
36
35
  * "optional materialization"). When `true` (the usual default) the server's
37
36
  * row table carries the app's typed columns as a queryable projection;
38
37
  * when `false` it carries only the `_sync_*` meta columns — same storage
package/dist/schema.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * The server is configured with a schema IR: tables, columns with the six
5
5
  * §2.4 column types, scope patterns per §3.1, and a schema version. Codegen
6
- * (B5) will emit this shape later; tests hand-write it.
6
+ * Typegen emits this shape; tests hand-write it.
7
7
  */
8
8
  import { validatePortableRelationalIdentifier, } from '@syncular/core';
9
9
  const PATTERN_RE = /^([^{}]+):\{([^{}:]+)\}$/;
@@ -37,8 +37,8 @@ export function compileSchema(schema) {
37
37
  if (tables.has(table.name)) {
38
38
  throw new Error(`duplicate table ${JSON.stringify(table.name)}`);
39
39
  }
40
- // Identifier rules (DESIGN "current-row tables") live in core's
41
- // validatePortableRelationalIdentifier, shared with typegen: app tables
40
+ // Identifier rules live in core's validatePortableRelationalIdentifier,
41
+ // shared with typegen: app tables
42
42
  // share a namespace with the sync infrastructure tables (`sync_*`) and
43
43
  // carry `_sync_*` meta columns, so both prefixes are reserved, and
44
44
  // Postgres silently truncates identifiers over 63 bytes.
package/dist/seed.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The public seeding helper (RFC 0002 §2.5): push app-shaped mutations
2
+ * The public seeding helper: push app-shaped mutations
3
3
  * through the REAL push path — one `handleSyncRequest` round built from the
4
4
  * server schema — so demos, dev servers, and ops scripts seed data with one
5
5
  * supported call. This is the same §6 pipeline every client write takes
@@ -30,8 +30,8 @@ export interface SegmentFindKey {
30
30
  readonly asOfCommitSeq: number;
31
31
  }
32
32
  /**
33
- * Coarse store-level counters for the admin/console read surface (TODO
34
- * §2.5). Optional — a store that omits `stats()` simply cannot report them.
33
+ * Coarse store-level counters for the admin/console read surface. Optional:
34
+ * a store that omits `stats()` simply cannot report them.
35
35
  * All counts include expired-but-not-yet-evicted entries (the store's own
36
36
  * bytes on disk), split by media type.
37
37
  */
@@ -62,7 +62,7 @@ export interface SegmentStore {
62
62
  * rebuilding sqlite images while one exists (§5.3).
63
63
  */
64
64
  find(key: SegmentFindKey, nowMs: number): Promise<SegmentRecord | undefined>;
65
- /** Admin/console counters (TODO §2.5) — ADDITIVE, optional. */
65
+ /** Admin/console counters: ADDITIVE, optional. */
66
66
  stats?(): Promise<SegmentStoreStats>;
67
67
  }
68
68
  export declare function segmentIdFor(bytes: Uint8Array): Promise<string>;
@@ -60,8 +60,8 @@ export async function issueBlobUploadUrl(config, args) {
60
60
  return config.presign({ ...args, ttlSeconds });
61
61
  }
62
62
  const encoder = new TextEncoder();
63
- // Runtime-neutral base64url (no `Buffer` Workers/Deno/browser safe; TODO
64
- // §4.2): `btoa`/`atob` operate on binary strings, present in every runtime.
63
+ // Runtime-neutral base64url: `btoa`/`atob` operate on binary strings and are
64
+ // present in every supported runtime.
65
65
  function base64url(bytes) {
66
66
  let binary = '';
67
67
  for (const byte of bytes)
package/dist/sigv4.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Minimal AWS Signature Version 4 — exactly the subset the S3 segment
3
3
  * store needs (header-signed requests and presigned GET URLs), hand-rolled
4
- * per the dependency-light doctrine (REVISE): SigV4 is small and
4
+ * per the dependency-light doctrine: SigV4 is small and
5
5
  * well-specified, so no SDK.
6
6
  *
7
- * Runtime-neutral (TODO §4.2): the crypto primitives are Web Crypto
7
+ * Runtime-neutral: the crypto primitives are Web Crypto
8
8
  * (`crypto.subtle`), not `node:crypto`, so this module runs unchanged on
9
9
  * Cloudflare Workers / Deno / browsers as well as Bun/Node. `crypto.subtle`
10
10
  * is async, so signing is async throughout; the `S3SegmentStore` consumers
@@ -36,8 +36,6 @@ export declare function amzTimestamps(nowMs: number): {
36
36
  amzDate: string;
37
37
  dateStamp: string;
38
38
  };
39
- /** Sorted, fully re-encoded canonical query string from *decoded* pairs. */
40
- export declare function canonicalQuery(params: Iterable<readonly [string, string]>): string;
41
39
  export interface CanonicalRequestArgs {
42
40
  readonly method: string;
43
41
  /** The URI-encoded absolute path exactly as sent on the request line. */
package/dist/sigv4.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Minimal AWS Signature Version 4 — exactly the subset the S3 segment
3
3
  * store needs (header-signed requests and presigned GET URLs), hand-rolled
4
- * per the dependency-light doctrine (REVISE): SigV4 is small and
4
+ * per the dependency-light doctrine: SigV4 is small and
5
5
  * well-specified, so no SDK.
6
6
  *
7
- * Runtime-neutral (TODO §4.2): the crypto primitives are Web Crypto
7
+ * Runtime-neutral: the crypto primitives are Web Crypto
8
8
  * (`crypto.subtle`), not `node:crypto`, so this module runs unchanged on
9
9
  * Cloudflare Workers / Deno / browsers as well as Bun/Node. `crypto.subtle`
10
10
  * is async, so signing is async throughout; the `S3SegmentStore` consumers
@@ -57,7 +57,7 @@ export function amzTimestamps(nowMs) {
57
57
  return { amzDate, dateStamp: amzDate.slice(0, 8) };
58
58
  }
59
59
  /** Sorted, fully re-encoded canonical query string from *decoded* pairs. */
60
- export function canonicalQuery(params) {
60
+ function canonicalQuery(params) {
61
61
  return [...params]
62
62
  .map(([k, v]) => [uriEncode(k, true), uriEncode(v, true)])
63
63
  .sort(([ak, av], [bk, bv]) => ak < bk ? -1 : ak > bk ? 1 : av < bv ? -1 : av > bv ? 1 : 0)
@@ -3,7 +3,7 @@
3
3
  * dependency-free). Bun-specific by design (top-level `bun:sqlite` import),
4
4
  * so it lives in its own module — the runtime-neutral `BlobStore` interface,
5
5
  * `MemoryBlobStore`, `blobIdFor`, and `isBlobId` stay in `blob-store.ts` for
6
- * the Workers/edge core (TODO §4.2 neutrality discipline; enforced by
6
+ * the Workers/edge core (runtime neutrality is enforced by
7
7
  * `test/runtime-neutrality.test.ts`).
8
8
  */
9
9
  import { Database } from 'bun:sqlite';
@@ -3,7 +3,7 @@
3
3
  * dependency-free). Bun-specific by design (top-level `bun:sqlite` import),
4
4
  * so it lives in its own module — the runtime-neutral `BlobStore` interface,
5
5
  * `MemoryBlobStore`, `blobIdFor`, and `isBlobId` stay in `blob-store.ts` for
6
- * the Workers/edge core (TODO §4.2 neutrality discipline; enforced by
6
+ * the Workers/edge core (runtime neutrality is enforced by
7
7
  * `test/runtime-neutrality.test.ts`).
8
8
  */
9
9
  import { Database } from 'bun:sqlite';
@@ -11,8 +11,7 @@
11
11
  * db.prepare(sql).bind(...).all()`); a shared execution layer would have to
12
12
  * pick one calling convention and adapt the other, which is uglier than two
13
13
  * thin storage classes that each speak their driver's native shape while
14
- * importing the same SQL text and codecs from here (TODO §4.2 judgment
15
- * call — "clean parallel implementation against the storage contract").
14
+ * importing the same SQL text and codecs from here.
16
15
  * Both classes run the identical `test/storage-contract.ts`, so the
17
16
  * behavior is held key-for-key regardless.
18
17
  */
@@ -28,7 +27,7 @@ import type { StoredChange, StoredCommit, StoredPushResult, StoredRow } from './
28
27
  * The inverted scope index carries the ordered column (`commit_seq` /
29
28
  * `row_id`) last in the PRIMARY KEY, so the candidate scan is an index
30
29
  * range that returns already-ordered rows — the same covering-index shape
31
- * the Postgres storage documents (§3.1, REVISE B2 performance-by-
30
+ * the Postgres storage documents (§3.1, performance-by-
32
31
  * construction).
33
32
  */
34
33
  export declare const SQLITE_DDL = "\nCREATE TABLE IF NOT EXISTS sync_partitions(\n partition TEXT PRIMARY KEY,\n max_commit_seq INTEGER NOT NULL DEFAULT 0,\n horizon_seq INTEGER NOT NULL DEFAULT 0\n);\nCREATE TABLE IF NOT EXISTS sync_row_scopes(\n partition TEXT NOT NULL, tbl TEXT NOT NULL,\n var TEXT NOT NULL, value TEXT NOT NULL, row_id TEXT NOT NULL,\n PRIMARY KEY(partition, tbl, var, value, row_id)\n);\nCREATE TABLE IF NOT EXISTS sync_commits(\n partition TEXT NOT NULL, commit_seq INTEGER NOT NULL,\n client_id TEXT NOT NULL, client_commit_id TEXT NOT NULL,\n actor_id TEXT NOT NULL, created_at_ms INTEGER NOT NULL,\n PRIMARY KEY(partition, commit_seq)\n);\nCREATE INDEX IF NOT EXISTS sync_commits_by_time\n ON sync_commits(partition, created_at_ms);\nCREATE TABLE IF NOT EXISTS sync_changes(\n partition TEXT NOT NULL, commit_seq INTEGER NOT NULL, idx INTEGER NOT NULL,\n tbl TEXT NOT NULL, row_id TEXT NOT NULL, op INTEGER NOT NULL,\n row_version INTEGER, scopes TEXT NOT NULL, payload BLOB,\n PRIMARY KEY(partition, commit_seq, idx)\n);\nCREATE INDEX IF NOT EXISTS sync_changes_by_table\n ON sync_changes(partition, commit_seq, tbl, idx);\nCREATE TABLE IF NOT EXISTS sync_change_scopes(\n partition TEXT NOT NULL, tbl TEXT NOT NULL,\n var TEXT NOT NULL, value TEXT NOT NULL, commit_seq INTEGER NOT NULL,\n PRIMARY KEY(partition, tbl, var, value, commit_seq)\n);\nCREATE TABLE IF NOT EXISTS sync_push_results(\n partition TEXT NOT NULL, client_id TEXT NOT NULL,\n client_commit_id TEXT NOT NULL, result TEXT NOT NULL,\n PRIMARY KEY(partition, client_id, client_commit_id)\n);\nCREATE TABLE IF NOT EXISTS sync_clients(\n partition TEXT NOT NULL, client_id TEXT NOT NULL, actor_id TEXT NOT NULL,\n cursor INTEGER NOT NULL, subscriptions TEXT NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY(partition, client_id)\n);\nCREATE TABLE IF NOT EXISTS sync_blob_refs(\n partition TEXT NOT NULL, tbl TEXT NOT NULL, row_id TEXT NOT NULL,\n blob_id TEXT NOT NULL,\n PRIMARY KEY(partition, tbl, row_id, blob_id)\n);\nCREATE INDEX IF NOT EXISTS sync_blob_refs_by_blob\n ON sync_blob_refs(partition, blob_id);\n";
@@ -9,7 +9,7 @@ import { matchesEffective } from './scopes.js';
9
9
  * The inverted scope index carries the ordered column (`commit_seq` /
10
10
  * `row_id`) last in the PRIMARY KEY, so the candidate scan is an index
11
11
  * range that returns already-ordered rows — the same covering-index shape
12
- * the Postgres storage documents (§3.1, REVISE B2 performance-by-
12
+ * the Postgres storage documents (§3.1, performance-by-
13
13
  * construction).
14
14
  */
15
15
  export const SQLITE_DDL = `
@@ -13,7 +13,7 @@ export interface SqliteImageInput {
13
13
  }
14
14
  /**
15
15
  * The §5.3 image-builder capability, injected through
16
- * `SyncServerConfig.sqliteImageBuilder` (TODO §4.2). Building an image needs
16
+ * `SyncServerConfig.sqliteImageBuilder`. Building an image needs
17
17
  * a real SQLite engine (`bun:sqlite` here), which is not available on every
18
18
  * runtime — Cloudflare Workers has none. So the core takes the builder as an
19
19
  * optional capability rather than importing `bun:sqlite` on the pull path:
@@ -3,7 +3,7 @@
3
3
  * dependency-free). Bun-specific by design (top-level `bun:sqlite` import),
4
4
  * so it lives in its own module — the runtime-neutral `LeaseStore` interface,
5
5
  * `LeaseRecord`, and `MemoryLeaseStore` stay in `lease-store.ts` for the
6
- * Workers/edge core (TODO §4.2 neutrality discipline; enforced by
6
+ * Workers/edge core (runtime neutrality is enforced by
7
7
  * `test/runtime-neutrality.test.ts`).
8
8
  */
9
9
  import { Database } from 'bun:sqlite';
@@ -3,7 +3,7 @@
3
3
  * dependency-free). Bun-specific by design (top-level `bun:sqlite` import),
4
4
  * so it lives in its own module — the runtime-neutral `LeaseStore` interface,
5
5
  * `LeaseRecord`, and `MemoryLeaseStore` stay in `lease-store.ts` for the
6
- * Workers/edge core (TODO §4.2 neutrality discipline; enforced by
6
+ * Workers/edge core (runtime neutrality is enforced by
7
7
  * `test/runtime-neutrality.test.ts`).
8
8
  */
9
9
  import { Database } from 'bun:sqlite';
@@ -4,8 +4,8 @@
4
4
  * top level, so it lives in its own module — importing it opts into the Bun
5
5
  * runtime. The runtime-neutral `SegmentStore` interface, `MemorySegmentStore`,
6
6
  * and `segmentIdFor` stay in `segment-store.ts` so the Workers/edge core can
7
- * import them without pulling in `bun:sqlite` (TODO §4.2 neutrality
8
- * discipline; enforced by `test/runtime-neutrality.test.ts`).
7
+ * import them without pulling in `bun:sqlite` (runtime neutrality is enforced
8
+ * by `test/runtime-neutrality.test.ts`).
9
9
  */
10
10
  import { Database } from 'bun:sqlite';
11
11
  import { type SegmentFindKey, type SegmentMetadata, type SegmentRecord, type SegmentStore, type SegmentStoreStats } from './segment-store.js';
@@ -4,8 +4,8 @@
4
4
  * top level, so it lives in its own module — importing it opts into the Bun
5
5
  * runtime. The runtime-neutral `SegmentStore` interface, `MemorySegmentStore`,
6
6
  * and `segmentIdFor` stay in `segment-store.ts` so the Workers/edge core can
7
- * import them without pulling in `bun:sqlite` (TODO §4.2 neutrality
8
- * discipline; enforced by `test/runtime-neutrality.test.ts`).
7
+ * import them without pulling in `bun:sqlite` (runtime neutrality is enforced
8
+ * by `test/runtime-neutrality.test.ts`).
9
9
  */
10
10
  import { Database } from 'bun:sqlite';
11
11
  import { DEFAULT_SEGMENT_TTL_MS, segmentIdFor, } from './segment-store.js';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SQLite storage via `bun:sqlite` (dev-speed, dependency-free).
3
3
  *
4
- * Scope fanout is index-first (REVISE B2): both the commit log and the
4
+ * Scope fanout is index-first: both the commit log and the
5
5
  * current-row table carry a (table, variable, value) inverted index; reads
6
6
  * select candidates from the index and verify the full multi-variable
7
7
  * match against the stored scope map — never a log scan.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SQLite storage via `bun:sqlite` (dev-speed, dependency-free).
3
3
  *
4
- * Scope fanout is index-first (REVISE B2): both the commit log and the
4
+ * Scope fanout is index-first: both the commit log and the
5
5
  * current-row table carry a (table, variable, value) inverted index; reads
6
6
  * select candidates from the index and verify the full multi-variable
7
7
  * match against the stored scope map — never a log scan.
@@ -240,8 +240,8 @@ export class SqliteServerStorage {
240
240
  this.#schemaVersion = schema.version;
241
241
  }
242
242
  /**
243
- * Migration rewrite (DESIGN "optional materialization"): keyset-paged walk
244
- * of a row table; when `oldLayout` is given every payload re-encodes under
243
+ * Migration rewrite: keyset-paged walk of a row table. When `oldLayout` is
244
+ * given every payload re-encodes under
245
245
  * the current columns, and the projection (when materialized) refreshes
246
246
  * from the payload either way.
247
247
  */
@@ -498,7 +498,7 @@ export class SqliteServerStorage {
498
498
  .all(partition);
499
499
  return rows.map((r) => r.blob_id);
500
500
  }
501
- // -- admin/console read surface (TODO §2.5) --------------------------------
501
+ // -- admin/console read surface --------------------------------------------
502
502
  async listClientRecords(partition) {
503
503
  const records = this.db
504
504
  .query('SELECT client_id, actor_id, cursor, subscriptions, updated_at_ms FROM sync_clients WHERE partition=? ORDER BY updated_at_ms DESC')
package/dist/storage.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Requirements it encodes:
5
5
  * - commit log with per-partition `commitSeq` (§2.1) and a commit→scope
6
- * inverted index designed in from day one (§3.1, REVISE B2): pulls filter
6
+ * inverted index (§3.1): pulls filter
7
7
  * by scope via the index, never by scanning the log;
8
8
  * - idempotency results per §2.3 persisted in the same transaction as the
9
9
  * commit's writes (§6.4);
@@ -241,7 +241,7 @@ export interface StorageTransaction {
241
241
  export interface ServerStorage {
242
242
  /**
243
243
  * Create/migrate the relational per-app row tables for `schema`
244
- * (DESIGN-relational-server-storage.md). Idempotent and cheap when the
244
+ * Idempotent and cheap when the
245
245
  * stored schema version already matches (one marker read, memoized per
246
246
  * instance); on first use it creates the tables, on a version bump it
247
247
  * applies the migration subset (CREATE TABLE / ADD COLUMN / CREATE
@@ -305,7 +305,7 @@ export interface ServerStorage {
305
305
  listRowsReferencingBlob?(partition: string, blobId: string): Promise<BlobReferencingRow[]>;
306
306
  listReferencedBlobIds?(partition: string): Promise<string[]>;
307
307
  /**
308
- * Admin/console read surface (`SyncularAdmin`, TODO §2.5) ADDITIVE,
308
+ * Admin/console read surface (`SyncularAdmin`): ADDITIVE,
309
309
  * optional. All read-only, partition-scoped, and JSON-able. A backend
310
310
  * that omits these simply cannot serve the console; the sync path never
311
311
  * calls them.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/server",
3
- "version": "0.15.44",
3
+ "version": "0.15.45",
4
4
  "description": "Syncular server: handleSyncRequest + storage/auth interfaces for the sync protocol",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -53,7 +53,7 @@
53
53
  "!dist/**/*.test.d.ts"
54
54
  ],
55
55
  "dependencies": {
56
- "@syncular/core": "0.15.44"
56
+ "@syncular/core": "0.15.45"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@electric-sql/pglite": "^0.5.4"
package/src/admin.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * `SyncularAdmin` the operator-facing read surface over the server core
3
- * (TODO §2.5). A read-only, partition-scoped, JSON-able query layer over
2
+ * `SyncularAdmin` is the operator-facing read surface over the server core.
3
+ * It is a read-only, partition-scoped, JSON-able query layer over
4
4
  * `ServerStorage`, the optional segment/blob store stats, and an in-memory
5
5
  * event ring. It delivers the 80% operator value (who's connected, what's
6
6
  * flowing, horizon health, the event tail) as a handful of queries in the
@@ -179,7 +179,7 @@ function percentile(sample: readonly number[], q: number): number {
179
179
  function required<T>(value: T | undefined, what: string): T {
180
180
  if (value === undefined) {
181
181
  throw new Error(
182
- `SyncularAdmin: the configured ${what} does not implement this read (TODO §2.5 optional method missing)`,
182
+ `SyncularAdmin: the configured ${what} does not implement this optional read`,
183
183
  );
184
184
  }
185
185
  return value;
package/src/blob-store.ts CHANGED
Binary file
package/src/context.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The host context for `handleSyncRequest` (REVISE B2): storage, the
2
+ * The host context for `handleSyncRequest`: storage, the
3
3
  * `resolveScopes` host callback (runs in the host process — the moat),
4
4
  * clock, and a segment store. Framework-free; adapters supply `partition`
5
5
  * and `actorId` from host authentication (§1.1).
@@ -154,7 +154,7 @@ export interface SyncServerConfig {
154
154
  */
155
155
  readonly blobUploadUrls?: BlobUploadPresignConfig;
156
156
  /**
157
- * §5.3 sqlite-image builder (TODO §4.2), injected so the pull path never
157
+ * §5.3 sqlite-image builder, injected so the pull path never
158
158
  * statically imports `bun:sqlite`. Absent ⇒ the sqlite-image lane is off
159
159
  * (bit-2 clients are served the rows lane) — the Workers/edge posture. A
160
160
  * Bun/Node host wires `buildSqliteImage` from `@syncular/server`.
package/src/d1-storage.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Cloudflare D1 server storage (TODO §4.2 — the Workers deployment rung).
2
+ * Cloudflare D1 server storage for Workers deployments.
3
3
  *
4
4
  * D1 *is* SQLite exposed over an async, statement-at-a-time API
5
5
  * (`prepare(sql).bind(...).all()` / `.first()` / `.run()`, plus `batch([…])`
@@ -687,7 +687,7 @@ export class D1ServerStorage implements ServerStorage {
687
687
  const bindCount = tableColumnNames(table).length;
688
688
  if (bindCount > D1_MAX_BIND_PARAMS) {
689
689
  throw new Error(
690
- `table ${JSON.stringify(table.name)} needs ${bindCount} bound parameters per upsert D1 caps statements at ${D1_MAX_BIND_PARAMS} (DESIGN "D1 bind-parameter limit")`,
690
+ `table ${JSON.stringify(table.name)} needs ${bindCount} bound parameters per upsert; D1 caps statements at ${D1_MAX_BIND_PARAMS}`,
691
691
  );
692
692
  }
693
693
  }
@@ -1137,7 +1137,7 @@ export class D1ServerStorage implements ServerStorage {
1137
1137
  return results.map((r) => r.blob_id);
1138
1138
  }
1139
1139
 
1140
- // -- admin/console read surface (TODO §2.5) --------------------------------
1140
+ // -- admin/console read surface --------------------------------------------
1141
1141
 
1142
1142
  async listClientRecords(partition: string): Promise<ClientRecord[]> {
1143
1143
  const { results } = await this.#db
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Event ring buffer + sink composition (TODO §2.5) — the "event stream"
2
+ * Event ring buffer + sink composition: the "event stream"
3
3
  * without any infrastructure dependency.
4
4
  *
5
5
  * `RingBufferEvents` is a `SyncularServerEvents` sink that retains the last
package/src/handler.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `handleSyncRequest(bytes, ctx) → bytes` (SPEC.md §1, REVISE B2).
2
+ * `handleSyncRequest(bytes, ctx) → bytes` (SPEC.md §1).
3
3
  *
4
4
  * Internally streaming-friendly (§1.4): `createSyncResponseStream` returns
5
5
  * an async iterable of encoded chunks — one per frame — after performing
@@ -629,7 +629,7 @@ async function createStreamCore(
629
629
  throw error;
630
630
  }
631
631
  const schema = compileSchema(ctx.schema);
632
- // Relational row tables (DESIGN-relational-server-storage.md): create/
632
+ // Relational row tables: create/
633
633
  // migrate on first contact; memoized per storage instance thereafter.
634
634
  await ctx.storage.ensureSchema(schema);
635
635
  const plan = await planRequest(request, ctx, schema);
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @syncular/server — framework-free embeddable SSP2 protocol library
3
- * (SPEC.md is normative; REVISE.md B2 is the architectural mandate).
3
+ * (SPEC.md is normative).
4
4
  *
5
5
  * Core surface: `handleSyncRequest(bytes, ctx) → bytes` over host-provided
6
6
  * storage / scope-resolution / segment-store interfaces, plus a
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The minimal Postgres executor seam (REVISE B2: zero runtime deps).
2
+ * The minimal Postgres executor seam has zero runtime dependencies.
3
3
  *
4
4
  * `PostgresServerStorage` is written entirely against this interface so the
5
5
  * server library never imports a specific driver. Production wires one of:
@@ -1,5 +1,5 @@
1
1
  /**
2
- * LISTEN/NOTIFY multi-instance fanout (TODO §4.1) — the primitive that lets
2
+ * LISTEN/NOTIFY multi-instance fanout lets
3
3
  * N server instances behind a load balancer wake each other's realtime
4
4
  * sessions when a commit lands on any one of them.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Postgres server storage the production database path (TODO §4.1).
2
+ * Postgres server storage: the production database path.
3
3
  *
4
4
  * Semantics mirror `SqliteServerStorage` exactly (both run the shared
5
5
  * storage contract in `test/storage-contract.ts`); the difference is that
@@ -336,8 +336,8 @@ function toStoredChange(record: ChangeRecord): StoredChange {
336
336
  }
337
337
 
338
338
  /**
339
- * Migration rewrite (DESIGN "optional materialization"): keyset-paged walk
340
- * of a row table inside the migration transaction; when `oldLayout` is
339
+ * Migration rewrite: keyset-paged walk of a row table inside the migration
340
+ * transaction. When `oldLayout` is
341
341
  * given every payload re-encodes under the current columns, and the
342
342
  * projection (when materialized) refreshes from the payload either way.
343
343
  */
@@ -1251,7 +1251,7 @@ export class PostgresServerStorage implements ServerStorage {
1251
1251
  return rows.map((r) => r.blob_id);
1252
1252
  }
1253
1253
 
1254
- // -- admin/console read surface (TODO §2.5) --------------------------------
1254
+ // -- admin/console read surface --------------------------------------------
1255
1255
 
1256
1256
  async listClientRecords(partition: string): Promise<ClientRecord[]> {
1257
1257
  const { rows } = await this.#exec.query<{
package/src/pull.ts CHANGED
@@ -253,7 +253,7 @@ async function* sqliteImageSegment(
253
253
  // builder through `sqliteImageBuilder`; when omitted we default to the
254
254
  // in-tree `buildSqliteImage` ONLY on a Bun runtime, reached by a *dynamic*
255
255
  // import so `bun:sqlite` is never a static dependency of the pull path
256
- // (TODO §4.2 neutrality enforced by test/runtime-neutrality.test.ts). On
256
+ // (runtime neutrality is enforced by test/runtime-neutrality.test.ts). On
257
257
  // Workers/edge (no `Bun`) this yields the rows lane for a bit-2 client — a
258
258
  // support floor, not a fallback (§5.3: sqlite is an *accept*, not a demand).
259
259
  const buildImage = await resolveImageBuilder(ctx);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Relational current-row storage (DESIGN-relational-server-storage.md).
2
+ * Relational current-row storage.
3
3
  *
4
4
  * Every synced table is a REAL table in the server database — the app's
5
5
  * columns with proper type affinities, queryable with plain SQL/joins/BI —
@@ -65,7 +65,7 @@ export function quoteIdent(name: string): string {
65
65
  return `"${name.replaceAll('"', '""')}"`;
66
66
  }
67
67
 
68
- /** §5.3-style type affinities, per dialect (DESIGN type-mapping table). */
68
+ /** §5.3-style type affinities, per dialect. */
69
69
  export function columnSqlType(
70
70
  column: RowColumn,
71
71
  dialect: RelationalDialect,
@@ -204,11 +204,11 @@ export function physicalIndexName(declaredName: string): string {
204
204
  }
205
205
 
206
206
  /**
207
- * CREATE INDEX IF NOT EXISTS for the table's user-declared indexes
208
- * (DESIGN "user indexes" — the same declared names/columns the client
209
- * materializes; cross-table index-name uniqueness is the user's schema
210
- * concern, exactly as it is client-side). Server-side the physical name
211
- * carries the {@link SYNC_INDEX_PREFIX} ownership marker.
207
+ * CREATE INDEX IF NOT EXISTS for the table's user-declared indexes. These use
208
+ * the same declared names and columns the client materializes. Cross-table
209
+ * index-name uniqueness is the user's schema concern, as it is client-side.
210
+ * Server-side the physical name carries the {@link SYNC_INDEX_PREFIX}
211
+ * ownership marker.
212
212
  */
213
213
  export function createIndexDdl(table: CompiledTable): string[] {
214
214
  // User indexes name app columns — nothing to index without the projection.
@@ -478,8 +478,8 @@ export function deleteRowSql(
478
478
  }
479
479
 
480
480
  /**
481
- * The schema-version marker table gating DDL work (DESIGN "server-side
482
- * schema migration"): `ensureSchema` compares the stored version and skips
481
+ * The schema-version marker table gates DDL work. `ensureSchema` compares the
482
+ * stored version and skips
483
483
  * all introspection/DDL when it matches — one cheap read per storage
484
484
  * instance (relevant for D1's per-request instantiation).
485
485
  *
@@ -98,7 +98,7 @@ function parseStatsJson(json: string): StatsAccumulator {
98
98
  };
99
99
  }
100
100
 
101
- // Runtime-neutral base64url (TODO §4.2): `Buffer` is not present on
101
+ // Runtime-neutral base64url: `Buffer` is not present on
102
102
  // Cloudflare Workers without `nodejs_compat`, so the metadata header is
103
103
  // (de)coded with `btoa`/`atob`, available in every runtime.
104
104
  function utf8ToBase64url(text: string): string {
@@ -112,7 +112,7 @@ function parseStatsJson(json: string): StatsAccumulator {
112
112
  };
113
113
  }
114
114
 
115
- // Runtime-neutral base64url (TODO §4.2): `Buffer` is not present on
115
+ // Runtime-neutral base64url: `Buffer` is not present on
116
116
  // Cloudflare Workers without `nodejs_compat`, so the object-metadata record
117
117
  // header is (de)coded with `btoa`/`atob`, available in every runtime.
118
118
  function utf8ToBase64url(text: string): string {
package/src/schema.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * The server is configured with a schema IR: tables, columns with the six
5
5
  * §2.4 column types, scope patterns per §3.1, and a schema version. Codegen
6
- * (B5) will emit this shape later; tests hand-write it.
6
+ * Typegen emits this shape; tests hand-write it.
7
7
  */
8
8
  import {
9
9
  type RowColumn,
@@ -14,9 +14,8 @@ import {
14
14
  export type ScopePatternSpec = string | { pattern: string; column: string };
15
15
 
16
16
  /**
17
- * A user-declared index (the migration subset's CREATE INDEX). With
18
- * relational server storage these apply server-side too (DESIGN
19
- * "user indexes") — the same declaration the client materializes.
17
+ * A user-declared index (the migration subset's CREATE INDEX). Relational
18
+ * server storage applies the same declaration the client materializes.
20
19
  */
21
20
  export interface IndexSchema {
22
21
  readonly name: string;
@@ -35,7 +34,7 @@ export interface TableSchema {
35
34
  /** User indexes (optional) — created on the server's relational tables. */
36
35
  readonly indexes?: readonly IndexSchema[];
37
36
  /**
38
- * Server-side column materialization (DESIGN-relational-server-storage.md
37
+ * Server-side column materialization
39
38
  * "optional materialization"). When `true` (the usual default) the server's
40
39
  * row table carries the app's typed columns as a queryable projection;
41
40
  * when `false` it carries only the `_sync_*` meta columns — same storage
@@ -142,8 +141,8 @@ export function compileSchema(schema: ServerSchema): CompiledSchema {
142
141
  if (tables.has(table.name)) {
143
142
  throw new Error(`duplicate table ${JSON.stringify(table.name)}`);
144
143
  }
145
- // Identifier rules (DESIGN "current-row tables") live in core's
146
- // validatePortableRelationalIdentifier, shared with typegen: app tables
144
+ // Identifier rules live in core's validatePortableRelationalIdentifier,
145
+ // shared with typegen: app tables
147
146
  // share a namespace with the sync infrastructure tables (`sync_*`) and
148
147
  // carry `_sync_*` meta columns, so both prefixes are reserved, and
149
148
  // Postgres silently truncates identifiers over 63 bytes.
package/src/seed.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The public seeding helper (RFC 0002 §2.5): push app-shaped mutations
2
+ * The public seeding helper: push app-shaped mutations
3
3
  * through the REAL push path — one `handleSyncRequest` round built from the
4
4
  * server schema — so demos, dev servers, and ops scripts seed data with one
5
5
  * supported call. This is the same §6 pipeline every client write takes
@@ -44,8 +44,8 @@ export interface SegmentFindKey {
44
44
  }
45
45
 
46
46
  /**
47
- * Coarse store-level counters for the admin/console read surface (TODO
48
- * §2.5). Optional — a store that omits `stats()` simply cannot report them.
47
+ * Coarse store-level counters for the admin/console read surface. Optional:
48
+ * a store that omits `stats()` simply cannot report them.
49
49
  * All counts include expired-but-not-yet-evicted entries (the store's own
50
50
  * bytes on disk), split by media type.
51
51
  */
@@ -80,7 +80,7 @@ export interface SegmentStore {
80
80
  * rebuilding sqlite images while one exists (§5.3).
81
81
  */
82
82
  find(key: SegmentFindKey, nowMs: number): Promise<SegmentRecord | undefined>;
83
- /** Admin/console counters (TODO §2.5) — ADDITIVE, optional. */
83
+ /** Admin/console counters: ADDITIVE, optional. */
84
84
  stats?(): Promise<SegmentStoreStats>;
85
85
  }
86
86
 
package/src/signed-url.ts CHANGED
@@ -198,8 +198,8 @@ export interface SegmentTokenClaims {
198
198
 
199
199
  const encoder = new TextEncoder();
200
200
 
201
- // Runtime-neutral base64url (no `Buffer` Workers/Deno/browser safe; TODO
202
- // §4.2): `btoa`/`atob` operate on binary strings, present in every runtime.
201
+ // Runtime-neutral base64url: `btoa`/`atob` operate on binary strings and are
202
+ // present in every supported runtime.
203
203
  function base64url(bytes: Uint8Array): string {
204
204
  let binary = '';
205
205
  for (const byte of bytes) binary += String.fromCharCode(byte);
package/src/sigv4.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Minimal AWS Signature Version 4 — exactly the subset the S3 segment
3
3
  * store needs (header-signed requests and presigned GET URLs), hand-rolled
4
- * per the dependency-light doctrine (REVISE): SigV4 is small and
4
+ * per the dependency-light doctrine: SigV4 is small and
5
5
  * well-specified, so no SDK.
6
6
  *
7
- * Runtime-neutral (TODO §4.2): the crypto primitives are Web Crypto
7
+ * Runtime-neutral: the crypto primitives are Web Crypto
8
8
  * (`crypto.subtle`), not `node:crypto`, so this module runs unchanged on
9
9
  * Cloudflare Workers / Deno / browsers as well as Bun/Node. `crypto.subtle`
10
10
  * is async, so signing is async throughout; the `S3SegmentStore` consumers
@@ -94,9 +94,7 @@ export function amzTimestamps(nowMs: number): {
94
94
  }
95
95
 
96
96
  /** Sorted, fully re-encoded canonical query string from *decoded* pairs. */
97
- export function canonicalQuery(
98
- params: Iterable<readonly [string, string]>,
99
- ): string {
97
+ function canonicalQuery(params: Iterable<readonly [string, string]>): string {
100
98
  return [...params]
101
99
  .map(([k, v]) => [uriEncode(k, true), uriEncode(v, true)] as const)
102
100
  .sort(([ak, av], [bk, bv]) =>
@@ -3,7 +3,7 @@
3
3
  * dependency-free). Bun-specific by design (top-level `bun:sqlite` import),
4
4
  * so it lives in its own module — the runtime-neutral `BlobStore` interface,
5
5
  * `MemoryBlobStore`, `blobIdFor`, and `isBlobId` stay in `blob-store.ts` for
6
- * the Workers/edge core (TODO §4.2 neutrality discipline; enforced by
6
+ * the Workers/edge core (runtime neutrality is enforced by
7
7
  * `test/runtime-neutrality.test.ts`).
8
8
  */
9
9
  import { Database } from 'bun:sqlite';
@@ -11,8 +11,7 @@
11
11
  * db.prepare(sql).bind(...).all()`); a shared execution layer would have to
12
12
  * pick one calling convention and adapt the other, which is uglier than two
13
13
  * thin storage classes that each speak their driver's native shape while
14
- * importing the same SQL text and codecs from here (TODO §4.2 judgment
15
- * call — "clean parallel implementation against the storage contract").
14
+ * importing the same SQL text and codecs from here.
16
15
  * Both classes run the identical `test/storage-contract.ts`, so the
17
16
  * behavior is held key-for-key regardless.
18
17
  */
@@ -35,7 +34,7 @@ import type {
35
34
  * The inverted scope index carries the ordered column (`commit_seq` /
36
35
  * `row_id`) last in the PRIMARY KEY, so the candidate scan is an index
37
36
  * range that returns already-ordered rows — the same covering-index shape
38
- * the Postgres storage documents (§3.1, REVISE B2 performance-by-
37
+ * the Postgres storage documents (§3.1, performance-by-
39
38
  * construction).
40
39
  */
41
40
  export const SQLITE_DDL = `
@@ -62,7 +62,7 @@ export interface SqliteImageInput {
62
62
 
63
63
  /**
64
64
  * The §5.3 image-builder capability, injected through
65
- * `SyncServerConfig.sqliteImageBuilder` (TODO §4.2). Building an image needs
65
+ * `SyncServerConfig.sqliteImageBuilder`. Building an image needs
66
66
  * a real SQLite engine (`bun:sqlite` here), which is not available on every
67
67
  * runtime — Cloudflare Workers has none. So the core takes the builder as an
68
68
  * optional capability rather than importing `bun:sqlite` on the pull path:
@@ -3,7 +3,7 @@
3
3
  * dependency-free). Bun-specific by design (top-level `bun:sqlite` import),
4
4
  * so it lives in its own module — the runtime-neutral `LeaseStore` interface,
5
5
  * `LeaseRecord`, and `MemoryLeaseStore` stay in `lease-store.ts` for the
6
- * Workers/edge core (TODO §4.2 neutrality discipline; enforced by
6
+ * Workers/edge core (runtime neutrality is enforced by
7
7
  * `test/runtime-neutrality.test.ts`).
8
8
  */
9
9
  import { Database } from 'bun:sqlite';
@@ -4,8 +4,8 @@
4
4
  * top level, so it lives in its own module — importing it opts into the Bun
5
5
  * runtime. The runtime-neutral `SegmentStore` interface, `MemorySegmentStore`,
6
6
  * and `segmentIdFor` stay in `segment-store.ts` so the Workers/edge core can
7
- * import them without pulling in `bun:sqlite` (TODO §4.2 neutrality
8
- * discipline; enforced by `test/runtime-neutrality.test.ts`).
7
+ * import them without pulling in `bun:sqlite` (runtime neutrality is enforced
8
+ * by `test/runtime-neutrality.test.ts`).
9
9
  */
10
10
  import { Database } from 'bun:sqlite';
11
11
  import {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * SQLite storage via `bun:sqlite` (dev-speed, dependency-free).
3
3
  *
4
- * Scope fanout is index-first (REVISE B2): both the commit log and the
4
+ * Scope fanout is index-first: both the commit log and the
5
5
  * current-row table carry a (table, variable, value) inverted index; reads
6
6
  * select candidates from the index and verify the full multi-variable
7
7
  * match against the stored scope map — never a log scan.
@@ -396,8 +396,8 @@ export class SqliteServerStorage implements ServerStorage {
396
396
  }
397
397
 
398
398
  /**
399
- * Migration rewrite (DESIGN "optional materialization"): keyset-paged walk
400
- * of a row table; when `oldLayout` is given every payload re-encodes under
399
+ * Migration rewrite: keyset-paged walk of a row table. When `oldLayout` is
400
+ * given every payload re-encodes under
401
401
  * the current columns, and the projection (when materialized) refreshes
402
402
  * from the payload either way.
403
403
  */
@@ -808,7 +808,7 @@ export class SqliteServerStorage implements ServerStorage {
808
808
  return rows.map((r) => r.blob_id);
809
809
  }
810
810
 
811
- // -- admin/console read surface (TODO §2.5) --------------------------------
811
+ // -- admin/console read surface --------------------------------------------
812
812
 
813
813
  async listClientRecords(partition: string): Promise<ClientRecord[]> {
814
814
  const records = this.db
package/src/storage.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Requirements it encodes:
5
5
  * - commit log with per-partition `commitSeq` (§2.1) and a commit→scope
6
- * inverted index designed in from day one (§3.1, REVISE B2): pulls filter
6
+ * inverted index (§3.1): pulls filter
7
7
  * by scope via the index, never by scanning the log;
8
8
  * - idempotency results per §2.3 persisted in the same transaction as the
9
9
  * commit's writes (§6.4);
@@ -276,7 +276,7 @@ export interface StorageTransaction {
276
276
  export interface ServerStorage {
277
277
  /**
278
278
  * Create/migrate the relational per-app row tables for `schema`
279
- * (DESIGN-relational-server-storage.md). Idempotent and cheap when the
279
+ * Idempotent and cheap when the
280
280
  * stored schema version already matches (one marker read, memoized per
281
281
  * instance); on first use it creates the tables, on a version bump it
282
282
  * applies the migration subset (CREATE TABLE / ADD COLUMN / CREATE
@@ -373,7 +373,7 @@ export interface ServerStorage {
373
373
  listReferencedBlobIds?(partition: string): Promise<string[]>;
374
374
 
375
375
  /**
376
- * Admin/console read surface (`SyncularAdmin`, TODO §2.5) ADDITIVE,
376
+ * Admin/console read surface (`SyncularAdmin`): ADDITIVE,
377
377
  * optional. All read-only, partition-scoped, and JSON-able. A backend
378
378
  * that omits these simply cannot serve the console; the sync path never
379
379
  * calls them.