reflectdb 0.1.0 → 0.1.2

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 (61) hide show
  1. package/README.md +500 -40
  2. package/dist/cjs/client/index.cjs +32 -17
  3. package/dist/cjs/client/index.d.cts +18 -3
  4. package/dist/cjs/client/storage/indexeddb.d.cts +1 -1
  5. package/dist/cjs/core/index.cjs +28 -28
  6. package/dist/cjs/core/index.d.cts +21 -9
  7. package/dist/cjs/react/index.cjs +48 -33
  8. package/dist/cjs/react/index.d.cts +23 -5
  9. package/dist/cjs/server/drizzle.cjs +2 -2
  10. package/dist/cjs/server/drizzle.d.cts +1 -1
  11. package/dist/cjs/server/ephemeral/index.cjs +180 -0
  12. package/dist/cjs/server/ephemeral/index.d.cts +102 -0
  13. package/dist/cjs/server/ephemeral/redis.cjs +226 -0
  14. package/dist/cjs/server/ephemeral/redis.d.cts +136 -0
  15. package/dist/cjs/server/index.cjs +238 -87
  16. package/dist/cjs/server/index.d.cts +196 -5
  17. package/dist/cjs/svelte/index.cjs +34 -19
  18. package/dist/cjs/svelte/index.d.cts +18 -3
  19. package/dist/cjs/transport/bun-ws.cjs +9 -9
  20. package/dist/cjs/transport/bun-ws.d.cts +1 -1
  21. package/dist/cjs/transport/polling.cjs +11 -11
  22. package/dist/cjs/transport/polling.d.cts +1 -1
  23. package/dist/cjs/transport/sse.cjs +11 -11
  24. package/dist/cjs/transport/sse.d.cts +1 -1
  25. package/dist/cjs/transport/ws.cjs +11 -11
  26. package/dist/cjs/transport/ws.d.cts +1 -1
  27. package/dist/cjs/vanilla/index.cjs +34 -19
  28. package/dist/cjs/vanilla/index.d.cts +18 -3
  29. package/dist/client/index.d.ts +18 -3
  30. package/dist/client/index.js +7 -7
  31. package/dist/client/storage/indexeddb.d.ts +1 -1
  32. package/dist/client/storage/indexeddb.js +1 -1
  33. package/dist/core/index.d.ts +21 -9
  34. package/dist/core/index.js +20 -20
  35. package/dist/react/index.d.ts +23 -5
  36. package/dist/react/index.js +23 -23
  37. package/dist/server/drizzle.d.ts +1 -1
  38. package/dist/server/drizzle.js +3 -3
  39. package/dist/server/ephemeral/index.d.ts +102 -0
  40. package/dist/server/ephemeral/index.js +9 -0
  41. package/dist/server/ephemeral/redis.d.ts +136 -0
  42. package/dist/server/ephemeral/redis.js +186 -0
  43. package/dist/server/index.d.ts +196 -5
  44. package/dist/server/index.js +184 -163
  45. package/dist/shared/{esm-ytrd3hbq.js → esm-8qbr4y0d.js} +18 -3
  46. package/dist/shared/{esm-wkwx6bd9.js → esm-ck88h30s.js} +10 -10
  47. package/dist/shared/esm-g0marxk7.js +134 -0
  48. package/dist/svelte/index.d.ts +18 -3
  49. package/dist/svelte/index.js +9 -9
  50. package/dist/transport/bun-ws.d.ts +1 -1
  51. package/dist/transport/bun-ws.js +1 -1
  52. package/dist/transport/polling.d.ts +1 -1
  53. package/dist/transport/polling.js +3 -3
  54. package/dist/transport/sse.d.ts +1 -1
  55. package/dist/transport/sse.js +3 -3
  56. package/dist/transport/ws.d.ts +1 -1
  57. package/dist/transport/ws.js +3 -3
  58. package/dist/vanilla/index.d.ts +18 -3
  59. package/dist/vanilla/index.js +9 -9
  60. package/package.json +66 -44
  61. /package/dist/shared/{esm-b7xs9cde.js → esm-k7kedp3y.js} +0 -0
@@ -41,16 +41,16 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
41
41
  // src/core/types.ts
42
42
  var exports_types = {};
43
43
  __export(exports_types, {
44
- reasonFromError: () => reasonFromError,
45
- isErrorReason: () => isErrorReason,
46
- TransportSendError: () => TransportSendError,
47
- TOMBSTONE_RETENTION_MS: () => TOMBSTONE_RETENTION_MS,
48
- SUPPORTED_PROTOCOL_VERSIONS: () => SUPPORTED_PROTOCOL_VERSIONS,
49
- SERVER_TOMBSTONE_RETENTION_MS: () => SERVER_TOMBSTONE_RETENTION_MS,
50
- PROTOCOL_VERSION: () => PROTOCOL_VERSION,
51
- MutationError: () => MutationError,
44
+ MAX_BATCH_SIZE: () => MAX_BATCH_SIZE,
52
45
  MAX_CLOCK_DRIFT_MS: () => MAX_CLOCK_DRIFT_MS,
53
- MAX_BATCH_SIZE: () => MAX_BATCH_SIZE
46
+ MutationError: () => MutationError,
47
+ PROTOCOL_VERSION: () => PROTOCOL_VERSION,
48
+ SERVER_TOMBSTONE_RETENTION_MS: () => SERVER_TOMBSTONE_RETENTION_MS,
49
+ SUPPORTED_PROTOCOL_VERSIONS: () => SUPPORTED_PROTOCOL_VERSIONS,
50
+ TOMBSTONE_RETENTION_MS: () => TOMBSTONE_RETENTION_MS,
51
+ TransportSendError: () => TransportSendError,
52
+ isErrorReason: () => isErrorReason,
53
+ reasonFromError: () => reasonFromError
54
54
  });
55
55
  function isErrorReason(value) {
56
56
  return typeof value === "string" && VALID_ERROR_REASONS.has(value);
@@ -107,9 +107,9 @@ var init_types = __esm(() => {
107
107
  // src/vanilla/index.ts
108
108
  var exports_vanilla = {};
109
109
  __export(exports_vanilla, {
110
- createSyncVanilla: () => createSyncVanilla,
110
+ createBrowserWsTransport: () => createBrowserWsTransport,
111
111
  createSync: () => createSync,
112
- createBrowserWsTransport: () => createBrowserWsTransport
112
+ createSyncVanilla: () => createSyncVanilla
113
113
  });
114
114
  module.exports = __toCommonJS(exports_vanilla);
115
115
 
@@ -415,7 +415,7 @@ class ClientStore {
415
415
  if (existing?.serverHlc && compareHlc(hlc, existing.serverHlc) <= 0) {
416
416
  return;
417
417
  }
418
- this.setRow(table, rowId, payload ?? {}, colClocks ?? { _row: hlc }, hlc);
418
+ this.setRow(table, rowId, this.withPk(table, rowId, payload ?? {}), colClocks ?? { _row: hlc }, hlc);
419
419
  return;
420
420
  }
421
421
  const incomingClocks = colClocks ?? {};
@@ -436,7 +436,13 @@ class ClientStore {
436
436
  }
437
437
  const newRowHlc = existing.serverHlc && compareHlc(existing.serverHlc, hlc) >= 0 ? existing.serverHlc : hlc;
438
438
  mergedClocks._row = newRowHlc;
439
- this.setRow(table, rowId, mergedData, mergedClocks, newRowHlc);
439
+ this.setRow(table, rowId, this.withPk(table, rowId, mergedData), mergedClocks, newRowHlc);
440
+ }
441
+ withPk(table, rowId, data) {
442
+ const pk = this.tableMeta.get(table)?.pk ?? "id";
443
+ if (data[pk] !== undefined)
444
+ return data;
445
+ return { ...data, [pk]: rowId };
440
446
  }
441
447
  revertOp(opId, serverRow) {
442
448
  const pending = this.pendingOps.find((p) => p.op.id === opId);
@@ -481,10 +487,19 @@ class ClientStore {
481
487
  delete payload[field];
482
488
  }
483
489
  }
490
+ if (op.op === "insert" && payload) {
491
+ payload = this.withPk(op.table, op.rowId, payload);
492
+ }
484
493
  if (op.op === "delete") {
485
494
  this.setRow(op.table, op.rowId, null, { _row: op.hlc }, op.hlc);
486
495
  } else if (op.op === "insert") {
487
- this.setRow(op.table, op.rowId, payload ?? {}, { _row: op.hlc }, op.hlc);
496
+ const existing = this.getRow(op.table, op.rowId);
497
+ if (existing?.data) {
498
+ const merged = { ...existing.data, ...payload };
499
+ this.setRow(op.table, op.rowId, merged, existing.colClocks, existing.serverHlc);
500
+ } else {
501
+ this.setRow(op.table, op.rowId, payload ?? {}, { _row: op.hlc }, op.hlc);
502
+ }
488
503
  } else {
489
504
  const existing = this.getRow(op.table, op.rowId);
490
505
  if (existing?.data) {
@@ -1341,20 +1356,20 @@ function createSync(config) {
1341
1356
  }
1342
1357
  }
1343
1358
  const unsub = client.subscribeEphemeral(ephConfig.key, (event) => {
1344
- events = { ...events, [event.userId]: event.data };
1359
+ events = { ...events, [event.clientId]: event.data };
1345
1360
  if (ephConfig.ttlMs) {
1346
- const existing = timers.get(event.userId);
1361
+ const existing = timers.get(event.clientId);
1347
1362
  if (existing) {
1348
1363
  clearTimeout(existing);
1349
1364
  }
1350
1365
  const timer = setTimeout(() => {
1351
1366
  const next = { ...events };
1352
- delete next[event.userId];
1367
+ delete next[event.clientId];
1353
1368
  events = next;
1354
- timers.delete(event.userId);
1369
+ timers.delete(event.clientId);
1355
1370
  notifyChange();
1356
1371
  }, ephConfig.ttlMs);
1357
- timers.set(event.userId, timer);
1372
+ timers.set(event.clientId, timer);
1358
1373
  }
1359
1374
  notifyChange();
1360
1375
  });
@@ -267,6 +267,17 @@ declare class ClientStore {
267
267
  clearTable(table: string): void;
268
268
  applySnapshot(table: string, rows: Record<string, unknown>[], colClocks: Record<string, Record<string, string>>, append?: boolean, pk?: string): void;
269
269
  applyDelta(table: string, op: string, rowId: string, payload: Record<string, unknown> | null, hlc: string, colClocks?: Record<string, string>): void;
270
+ /**
271
+ * Guarantee a materialized row carries its own primary key.
272
+ *
273
+ * A delta's payload is not always a whole row: eager broadcasts forward the
274
+ * writer's payload verbatim, and the typed API omits the pk from what a
275
+ * client may write — so an eagerly-broadcast insert describes a row with no
276
+ * id in it. Rows are addressed by `rowId` everywhere in the protocol, so the
277
+ * key is never in doubt; only the materialized object was missing it, and
278
+ * `rows.map(r => r.id)` came back undefined.
279
+ */
280
+ private withPk;
270
281
  revertOp(opId: string, serverRow: Record<string, unknown> | null | undefined): void;
271
282
  setTableMeta(meta: Record<string, {
272
283
  serverSet: string[];
@@ -504,11 +515,15 @@ interface DrizzleTableLike {
504
515
  /**
505
516
  * Value supplied per serverSet field in the schema's object form.
506
517
  * Either a static value or a function that receives the request context.
518
+ *
519
+ * The static arm is enumerated rather than written as `unknown`: a union with
520
+ * `unknown` collapses to `unknown`, which strips the contextual type from the
521
+ * callback arm and makes every `(ctx) => …` an implicit `any` under `strict`.
507
522
  */
508
- type ServerSetSchemaValue = unknown | ((ctx: {
523
+ type ServerSetSchemaValue = ((ctx: {
509
524
  auth: unknown;
510
525
  params: unknown;
511
- }) => unknown);
526
+ }) => unknown) | string | number | bigint | boolean | symbol | null | undefined | Date | readonly unknown[] | Record<string, unknown>;
512
527
  /**
513
528
  * Schema-side `serverSet` declaration. Two shapes:
514
529
  * - `string[]` — keys only; values are supplied at `implement(...)` time.
@@ -654,4 +669,4 @@ interface SyncVanillaHooks<TQueries extends SyncQueryMap> {
654
669
  createSync: (config: VanillaSyncConfig) => TypedVanillaSync<TQueries>;
655
670
  }
656
671
  declare function createSyncVanilla<TQueries extends SyncQueryMap>(): SyncVanillaHooks<TQueries>;
657
- export { createSyncVanilla, createSync2 as createSync, createBrowserWsTransport, VanillaSyncConfig, VanillaSync, TypedVanillaSync, TableBinding, SyncVanillaHooks, EphemeralBinding };
672
+ export { EphemeralBinding, SyncVanillaHooks, TableBinding, TypedVanillaSync, VanillaSync, VanillaSyncConfig, createBrowserWsTransport, createSync2 as createSync, createSyncVanilla };
@@ -285,6 +285,17 @@ declare class ClientStore {
285
285
  clearTable(table: string): void;
286
286
  applySnapshot(table: string, rows: Record<string, unknown>[], colClocks: Record<string, Record<string, string>>, append?: boolean, pk?: string): void;
287
287
  applyDelta(table: string, op: string, rowId: string, payload: Record<string, unknown> | null, hlc: string, colClocks?: Record<string, string>): void;
288
+ /**
289
+ * Guarantee a materialized row carries its own primary key.
290
+ *
291
+ * A delta's payload is not always a whole row: eager broadcasts forward the
292
+ * writer's payload verbatim, and the typed API omits the pk from what a
293
+ * client may write — so an eagerly-broadcast insert describes a row with no
294
+ * id in it. Rows are addressed by `rowId` everywhere in the protocol, so the
295
+ * key is never in doubt; only the materialized object was missing it, and
296
+ * `rows.map(r => r.id)` came back undefined.
297
+ */
298
+ private withPk;
288
299
  revertOp(opId: string, serverRow: Record<string, unknown> | null | undefined): void;
289
300
  setTableMeta(meta: Record<string, {
290
301
  serverSet: string[];
@@ -475,11 +486,15 @@ interface DrizzleTableLike {
475
486
  /**
476
487
  * Value supplied per serverSet field in the schema's object form.
477
488
  * Either a static value or a function that receives the request context.
489
+ *
490
+ * The static arm is enumerated rather than written as `unknown`: a union with
491
+ * `unknown` collapses to `unknown`, which strips the contextual type from the
492
+ * callback arm and makes every `(ctx) => …` an implicit `any` under `strict`.
478
493
  */
479
- type ServerSetSchemaValue = unknown | ((ctx: {
494
+ type ServerSetSchemaValue = ((ctx: {
480
495
  auth: unknown;
481
496
  params: unknown;
482
- }) => unknown);
497
+ }) => unknown) | string | number | bigint | boolean | symbol | null | undefined | Date | readonly unknown[] | Record<string, unknown>;
483
498
  /**
484
499
  * Schema-side `serverSet` declaration. Two shapes:
485
500
  * - `string[]` — keys only; values are supplied at `implement(...)` time.
@@ -626,4 +641,4 @@ interface TypedSyncClient<TQueries extends SyncQueryMap> {
626
641
  subscribeEphemeral(key: string, listener: (event: EphemeralEvent) => void): () => void;
627
642
  }
628
643
  declare function createSyncClient<TQueries extends SyncQueryMap>(config: TypedSyncClientConfig<TQueries>): TypedSyncClient<TQueries>;
629
- export { pushSafely, createSyncClient, createOpCreator, createMemoryStorage, TypedSyncClientConfig, TypedSyncClient, SyncOptions, SyncClientState, SyncClientConfig, SyncClient, PendingOp, OpCreator, LocalRow, ClientStore, ClientStorageAdapter };
644
+ export { ClientStorageAdapter, ClientStore, LocalRow, OpCreator, PendingOp, SyncClient, SyncClientConfig, SyncClientState, SyncOptions, TypedSyncClient, TypedSyncClientConfig, createMemoryStorage, createOpCreator, createSyncClient, pushSafely };
@@ -3,10 +3,10 @@ import {
3
3
  SyncClient,
4
4
  createOpCreator,
5
5
  pushSafely
6
- } from "../shared/esm-ytrd3hbq.js";
6
+ } from "../shared/esm-8qbr4y0d.js";
7
7
  import"../shared/esm-rw7jjtrv.js";
8
8
  import"../shared/esm-3tkwvysa.js";
9
- import"../shared/esm-b7xs9cde.js";
9
+ import"../shared/esm-k7kedp3y.js";
10
10
  // src/client/storage/memory.ts
11
11
  function createMemoryStorage() {
12
12
  const rows = new Map;
@@ -97,10 +97,10 @@ function createSyncClient(config) {
97
97
  return client;
98
98
  }
99
99
  export {
100
- pushSafely,
101
- createSyncClient,
102
- createOpCreator,
103
- createMemoryStorage,
100
+ ClientStore,
104
101
  SyncClient,
105
- ClientStore
102
+ createMemoryStorage,
103
+ createOpCreator,
104
+ createSyncClient,
105
+ pushSafely
106
106
  };
@@ -82,4 +82,4 @@ declare function createIndexedDBStorage(config: {
82
82
  configVersion: number;
83
83
  }, storage: ClientStorageAdapter) => void | Promise<void>;
84
84
  }): ClientStorageAdapter;
85
- export { createIndexedDBStorage, IndexedDBMigrationContext };
85
+ export { IndexedDBMigrationContext, createIndexedDBStorage };
@@ -1,4 +1,4 @@
1
- import"../../shared/esm-b7xs9cde.js";
1
+ import"../../shared/esm-k7kedp3y.js";
2
2
 
3
3
  // src/client/storage/indexeddb.ts
4
4
  function createIndexedDBStorage(config) {
@@ -321,11 +321,15 @@ interface DrizzleTableLike {
321
321
  /**
322
322
  * Value supplied per serverSet field in the schema's object form.
323
323
  * Either a static value or a function that receives the request context.
324
+ *
325
+ * The static arm is enumerated rather than written as `unknown`: a union with
326
+ * `unknown` collapses to `unknown`, which strips the contextual type from the
327
+ * callback arm and makes every `(ctx) => …` an implicit `any` under `strict`.
324
328
  */
325
- type ServerSetSchemaValue = unknown | ((ctx: {
329
+ type ServerSetSchemaValue = ((ctx: {
326
330
  auth: unknown;
327
331
  params: unknown;
328
- }) => unknown);
332
+ }) => unknown) | string | number | bigint | boolean | symbol | null | undefined | Date | readonly unknown[] | Record<string, unknown>;
329
333
  /**
330
334
  * Schema-side `serverSet` declaration. Two shapes:
331
335
  * - `string[]` — keys only; values are supplied at `implement(...)` time.
@@ -370,8 +374,8 @@ declare function view<
370
374
  deps?: string[];
371
375
  tables?: string[];
372
376
  }): SyncViewDef & {
373
- row?: TRow;
374
- params?: TParams;
377
+ row: TRow;
378
+ params: TParams;
375
379
  };
376
380
  /**
377
381
  * Typed ephemeral channel. Sugar over the runtime `useEphemeral` hook —
@@ -392,8 +396,8 @@ declare function presence<
392
396
  params?: TParams;
393
397
  ttlMs?: number;
394
398
  }): SyncPresenceDef & {
395
- state?: TState;
396
- params?: TParams;
399
+ state: TState;
400
+ params: TParams;
397
401
  };
398
402
  type SyncQueryEntry = SyncQueryDef | SyncViewDef | SyncPresenceDef;
399
403
  type SyncQueryMap = Record<string, SyncQueryEntry>;
@@ -403,12 +407,20 @@ type ColumnOf<TDef> = TDef extends {
403
407
  } ? keyof TDef["table"]["$inferSelect"] & string : TDef extends {
404
408
  row: infer R extends Record<string, unknown>;
405
409
  } ? keyof R & string : string;
406
- /** Constrain serverSet and readonly to valid column names. Accepts either array or object form for serverSet. View/presence entries don't have these fields, so the constraint is empty. */
410
+ /**
411
+ * Constrain serverSet and readonly to valid column names. Accepts either array
412
+ * or object form for serverSet.
413
+ *
414
+ * View/presence entries carry neither field, so they get `unknown` — the
415
+ * identity of intersection. `Record<string, never>` reads like "no constraint"
416
+ * but means "an object with no properties", which every view and presence entry
417
+ * fails, making both helpers unusable inside `defineSyncQueries`.
418
+ */
407
419
  type FieldsConstraint<TDef> = TDef extends {
408
420
  __view: true;
409
421
  } | {
410
422
  __presence: true;
411
- } ? Record<string, never> : {
423
+ } ? unknown : {
412
424
  serverSet?: readonly ColumnOf<TDef>[] | Readonly<Partial<Record<ColumnOf<TDef>, ServerSetSchemaValue>>>;
413
425
  readonly?: readonly ColumnOf<TDef>[];
414
426
  };
@@ -470,4 +482,4 @@ type InferWritableRow<
470
482
  } ? never : Omit<InferRow<TQueries, K>, (TQueries[K] extends {
471
483
  readonly: readonly (infer R extends string)[];
472
484
  } ? R : never) | InferServerSetKeys<TQueries[K]> | PkOf<TQueries[K]>>;
473
- export { view, unpackHlc, t, sendHlc, receiveHlc, reasonFromError, presence, packHlc, isErrorReason, defineSyncQueries, createHlc, compareHlc, UnsyncMessage, TransportSendError, TableMeta, TOMBSTONE_RETENTION_MS, SyncViewDef, SyncQueryMap, SyncQueryEntry, SyncQueryDef, SyncPresenceDef, SyncOp, SyncMessage, SyncError, SyncDeclareMessage, SnapshotMessage, ShapeConfig, ShapeChangedMessage, ServerTransport, ServerMessage, SUPPORTED_PROTOCOL_VERSIONS, SERVER_TOMBSTONE_RETENTION_MS, ResumeRejectedMessage, ResumeMessage, ResumeCompleteMessage, RequiresParams, RejectMessage, ReauthMessage, RateLimitConfig, PROTOCOL_VERSION, OpsMessage, OpType, OpStatus, MutationError, MAX_CLOCK_DRIFT_MS, MAX_BATCH_SIZE, LoadMoreMessage, InferWritableRow, InferState, InferRow, InferParams, HelloRejectMessage, HelloMessage, HelloAckMessage, HLC, ErrorReason, EphemeralMessage, EphemeralEvent, DrizzleTableLike, DisconnectMessage, Dialect, DeltaMessage, CustomConflictPolicy, CountChangedMessage, ConflictResolver, ConflictPolicy, CompactionConfig, ClientTransport, ClientOp, ClientMessage, BootstrapMessage, BootstrapCompleteMessage, AuthMessage, AuthContext, AckMessage };
485
+ export { AckMessage, AuthContext, AuthMessage, BootstrapCompleteMessage, BootstrapMessage, ClientMessage, ClientOp, ClientTransport, CompactionConfig, ConflictPolicy, ConflictResolver, CountChangedMessage, CustomConflictPolicy, DeltaMessage, Dialect, DisconnectMessage, DrizzleTableLike, EphemeralEvent, EphemeralMessage, ErrorReason, HLC, HelloAckMessage, HelloMessage, HelloRejectMessage, InferParams, InferRow, InferState, InferWritableRow, LoadMoreMessage, MAX_BATCH_SIZE, MAX_CLOCK_DRIFT_MS, MutationError, OpStatus, OpType, OpsMessage, PROTOCOL_VERSION, RateLimitConfig, ReauthMessage, RejectMessage, RequiresParams, ResumeCompleteMessage, ResumeMessage, ResumeRejectedMessage, SERVER_TOMBSTONE_RETENTION_MS, SUPPORTED_PROTOCOL_VERSIONS, ServerMessage, ServerTransport, ShapeChangedMessage, ShapeConfig, SnapshotMessage, SyncDeclareMessage, SyncError, SyncMessage, SyncOp, SyncPresenceDef, SyncQueryDef, SyncQueryEntry, SyncQueryMap, SyncViewDef, TOMBSTONE_RETENTION_MS, TableMeta, TransportSendError, UnsyncMessage, compareHlc, createHlc, defineSyncQueries, isErrorReason, packHlc, presence, reasonFromError, receiveHlc, sendHlc, t, unpackHlc, view };
@@ -18,7 +18,7 @@ import {
18
18
  isErrorReason,
19
19
  reasonFromError
20
20
  } from "../shared/esm-3tkwvysa.js";
21
- import"../shared/esm-b7xs9cde.js";
21
+ import"../shared/esm-k7kedp3y.js";
22
22
  // src/core/schema.ts
23
23
  function view(opts) {
24
24
  return { __view: true, ...opts };
@@ -33,24 +33,24 @@ function t() {
33
33
  return;
34
34
  }
35
35
  export {
36
- view,
37
- unpackHlc,
38
- t,
39
- sendHlc,
40
- receiveHlc,
41
- reasonFromError,
42
- presence,
43
- packHlc,
44
- isErrorReason,
45
- defineSyncQueries,
46
- createHlc,
47
- compareHlc,
48
- TransportSendError,
49
- TOMBSTONE_RETENTION_MS,
50
- SUPPORTED_PROTOCOL_VERSIONS,
51
- SERVER_TOMBSTONE_RETENTION_MS,
52
- PROTOCOL_VERSION,
53
- MutationError,
36
+ MAX_BATCH_SIZE,
54
37
  MAX_CLOCK_DRIFT_MS,
55
- MAX_BATCH_SIZE
38
+ MutationError,
39
+ PROTOCOL_VERSION,
40
+ SERVER_TOMBSTONE_RETENTION_MS,
41
+ SUPPORTED_PROTOCOL_VERSIONS,
42
+ TOMBSTONE_RETENTION_MS,
43
+ TransportSendError,
44
+ compareHlc,
45
+ createHlc,
46
+ defineSyncQueries,
47
+ isErrorReason,
48
+ packHlc,
49
+ presence,
50
+ reasonFromError,
51
+ receiveHlc,
52
+ sendHlc,
53
+ t,
54
+ unpackHlc,
55
+ view
56
56
  };
@@ -268,6 +268,17 @@ declare class ClientStore {
268
268
  clearTable(table: string): void;
269
269
  applySnapshot(table: string, rows: Record<string, unknown>[], colClocks: Record<string, Record<string, string>>, append?: boolean, pk?: string): void;
270
270
  applyDelta(table: string, op: string, rowId: string, payload: Record<string, unknown> | null, hlc: string, colClocks?: Record<string, string>): void;
271
+ /**
272
+ * Guarantee a materialized row carries its own primary key.
273
+ *
274
+ * A delta's payload is not always a whole row: eager broadcasts forward the
275
+ * writer's payload verbatim, and the typed API omits the pk from what a
276
+ * client may write — so an eagerly-broadcast insert describes a row with no
277
+ * id in it. Rows are addressed by `rowId` everywhere in the protocol, so the
278
+ * key is never in doubt; only the materialized object was missing it, and
279
+ * `rows.map(r => r.id)` came back undefined.
280
+ */
281
+ private withPk;
271
282
  revertOp(opId: string, serverRow: Record<string, unknown> | null | undefined): void;
272
283
  setTableMeta(meta: Record<string, {
273
284
  serverSet: string[];
@@ -531,6 +542,9 @@ declare function usePendingCount2(): number;
531
542
  *
532
543
  * ```tsx
533
544
  * function CursorTracker({ userId }: { userId: string }) {
545
+ * `events` is keyed by the peer's `clientId`, so two tabs from one account
546
+ * appear as two peers.
547
+ *
534
548
  * const { events, broadcast } = useEphemeral({
535
549
  * key: "cursor",
536
550
  * userId,
@@ -546,8 +560,8 @@ declare function usePendingCount2(): number;
546
560
  *
547
561
  * return (
548
562
  * <div>
549
- * {Object.entries(events).map(([uid, data]) => (
550
- * <div key={uid}>User {uid}: {JSON.stringify(data)}</div>
563
+ * {Object.entries(events).map(([peerId, data]) => (
564
+ * <div key={peerId}>Peer {peerId}: {JSON.stringify(data)}</div>
551
565
  * ))}
552
566
  * </div>
553
567
  * );
@@ -579,11 +593,15 @@ interface DrizzleTableLike {
579
593
  /**
580
594
  * Value supplied per serverSet field in the schema's object form.
581
595
  * Either a static value or a function that receives the request context.
596
+ *
597
+ * The static arm is enumerated rather than written as `unknown`: a union with
598
+ * `unknown` collapses to `unknown`, which strips the contextual type from the
599
+ * callback arm and makes every `(ctx) => …` an implicit `any` under `strict`.
582
600
  */
583
- type ServerSetSchemaValue = unknown | ((ctx: {
601
+ type ServerSetSchemaValue = ((ctx: {
584
602
  auth: unknown;
585
603
  params: unknown;
586
- }) => unknown);
604
+ }) => unknown) | string | number | bigint | boolean | symbol | null | undefined | Date | readonly unknown[] | Record<string, unknown>;
587
605
  /**
588
606
  * Schema-side `serverSet` declaration. Two shapes:
589
607
  * - `string[]` — keys only; values are supplied at `implement(...)` time.
@@ -745,4 +763,4 @@ type UseSyncOptions<
745
763
  window?: number;
746
764
  };
747
765
  declare function createSyncReact<TQueries extends SyncQueryMap>(queries?: TQueries): SyncReactHooks<TQueries>;
748
- export { useTotalCount2 as useTotalCount, useSyncStatus2 as useSyncStatus, useSyncClient, useSync2 as useSync, useRow2 as useRow, usePendingCount2 as usePendingCount, useLoadMore2 as useLoadMore, useEphemeral2 as useEphemeral, derivePresenceKey, createSyncReact, SyncProviderProps, SyncProvider2 as SyncProvider, SyncContext };
766
+ export { SyncContext, SyncProvider2 as SyncProvider, SyncProviderProps, createSyncReact, derivePresenceKey, useEphemeral2 as useEphemeral, useLoadMore2 as useLoadMore, usePendingCount2 as usePendingCount, useRow2 as useRow, useSync2 as useSync, useSyncClient, useSyncStatus2 as useSyncStatus, useTotalCount2 as useTotalCount };
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  SyncClient,
3
3
  pushSafely
4
- } from "../shared/esm-ytrd3hbq.js";
4
+ } from "../shared/esm-8qbr4y0d.js";
5
5
  import"../shared/esm-rw7jjtrv.js";
6
6
  import"../shared/esm-3tkwvysa.js";
7
- import"../shared/esm-b7xs9cde.js";
7
+ import"../shared/esm-k7kedp3y.js";
8
8
 
9
9
  // src/react/context.tsx
10
10
  import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
11
- import { jsxDEV } from "react/jsx-dev-runtime";
11
+ import { jsx } from "react/jsx-runtime";
12
12
  var SyncContext = createContext(null);
13
13
  function createBrowserWsTransport(url) {
14
14
  let ws = null;
@@ -124,10 +124,10 @@ function SyncProvider({
124
124
  }, [url, clientId, storage, managedTables]);
125
125
  if (!ctx)
126
126
  return null;
127
- return /* @__PURE__ */ jsxDEV(SyncContext.Provider, {
127
+ return /* @__PURE__ */ jsx(SyncContext.Provider, {
128
128
  value: ctx,
129
129
  children
130
- }, undefined, false, undefined, this);
130
+ });
131
131
  }
132
132
  function useSyncClient() {
133
133
  const ctx = useContext(SyncContext);
@@ -201,21 +201,21 @@ function useEphemeral(config) {
201
201
  const timersRef = useRef2(new Map);
202
202
  useEffect2(() => {
203
203
  const unsub = client.subscribeEphemeral(config.key, (event) => {
204
- setEvents((prev) => ({ ...prev, [event.userId]: event.data }));
204
+ setEvents((prev) => ({ ...prev, [event.clientId]: event.data }));
205
205
  if (config.ttlMs) {
206
- const existing = timersRef.current.get(event.userId);
206
+ const existing = timersRef.current.get(event.clientId);
207
207
  if (existing) {
208
208
  clearTimeout(existing);
209
209
  }
210
210
  const timer = setTimeout(() => {
211
211
  setEvents((prev) => {
212
212
  const next = { ...prev };
213
- delete next[event.userId];
213
+ delete next[event.clientId];
214
214
  return next;
215
215
  });
216
- timersRef.current.delete(event.userId);
216
+ timersRef.current.delete(event.clientId);
217
217
  }, config.ttlMs);
218
- timersRef.current.set(event.userId, timer);
218
+ timersRef.current.set(event.clientId, timer);
219
219
  }
220
220
  });
221
221
  return () => {
@@ -332,8 +332,8 @@ function createSyncReact(queries) {
332
332
  userId,
333
333
  ttlMs
334
334
  });
335
- const peers = useMemo3(() => Object.entries(events).map(([uid, state]) => ({
336
- userId: uid,
335
+ const peers = useMemo3(() => Object.entries(events).map(([peerId, state]) => ({
336
+ userId: peerId,
337
337
  state
338
338
  })), [events]);
339
339
  return { peers, set: broadcast };
@@ -351,16 +351,16 @@ function createSyncReact(queries) {
351
351
  };
352
352
  }
353
353
  export {
354
- useTotalCount,
355
- useSyncStatus,
356
- useSyncClient,
357
- useSync,
358
- useRow,
359
- usePendingCount,
360
- useLoadMore,
361
- useEphemeral,
362
- derivePresenceKey,
363
- createSyncReact,
354
+ SyncContext,
364
355
  SyncProvider,
365
- SyncContext
356
+ createSyncReact,
357
+ derivePresenceKey,
358
+ useEphemeral,
359
+ useLoadMore,
360
+ usePendingCount,
361
+ useRow,
362
+ useSync,
363
+ useSyncClient,
364
+ useSyncStatus,
365
+ useTotalCount
366
366
  };
@@ -230,4 +230,4 @@ declare function drizzleTable<
230
230
  * has a single source of truth.
231
231
  */
232
232
  declare const drizzleTxAtomic: TxAtomicAdapter;
233
- export { drizzleTxAtomic, drizzleTable, DrizzleTableOpts };
233
+ export { DrizzleTableOpts, drizzleTable, drizzleTxAtomic };
@@ -2,8 +2,8 @@ import {
2
2
  drizzleTable,
3
3
  drizzleTxAtomic
4
4
  } from "../shared/esm-z1xse19c.js";
5
- import"../shared/esm-b7xs9cde.js";
5
+ import"../shared/esm-k7kedp3y.js";
6
6
  export {
7
- drizzleTxAtomic,
8
- drizzleTable
7
+ drizzleTable,
8
+ drizzleTxAtomic
9
9
  };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Ephemeral (presence, cursors, typing) storage and fan-out seam.
3
+ *
4
+ * The default adapter keeps state in the server process, which is correct for a
5
+ * single node and invisible across a fleet: two clients on different instances
6
+ * never see each other. An adapter backed by shared infrastructure (Redis, or a
7
+ * hosted service) fixes both halves — the shared store answers "who is here"
8
+ * for a client that just joined, and the bus carries each event to the peers
9
+ * holding the other sockets.
10
+ */
11
+ type MaybePromise<T> = T | Promise<T>;
12
+ interface EphemeralState {
13
+ clientId: string;
14
+ userId: string;
15
+ key: string;
16
+ data: Record<string, unknown>;
17
+ updatedAt: number;
18
+ ttlMs?: number;
19
+ }
20
+ /** One fan-out target: subscribers of `query`, narrowed to `room` when set. */
21
+ interface EphemeralTarget {
22
+ query: string;
23
+ room: string | null;
24
+ }
25
+ /**
26
+ * An ephemeral event as it crosses the bus between server instances.
27
+ *
28
+ * Recipients are resolved on the origin instance and carried here as `targets`,
29
+ * because the receiving instance holds no session for the sender and so cannot
30
+ * re-derive them. `serverId` lets an instance drop its own echo on buses that
31
+ * deliver published messages back to the publisher.
32
+ */
33
+ interface EphemeralBroadcast {
34
+ serverId: string;
35
+ key: string;
36
+ clientId: string;
37
+ userId: string;
38
+ data: Record<string, unknown>;
39
+ ttlMs?: number;
40
+ targets: EphemeralTarget[];
41
+ }
42
+ /**
43
+ * Backing store and fan-out bus for ephemeral state.
44
+ *
45
+ * `publish`/`subscribe` are optional: an adapter that omits them is a
46
+ * single-process store, and the handler still fans out to its own sockets.
47
+ */
48
+ interface EphemeralAdapter {
49
+ /**
50
+ * Record one entry, keyed by `clientId`.
51
+ *
52
+ * Peer identity is the connection, not the account: two tabs from one login
53
+ * are two cursors, and the client bindings key peers the same way. `userId`
54
+ * rides along for display and authorization, not as the entry's identity.
55
+ *
56
+ * Returns false when the adapter is at capacity, which the handler surfaces
57
+ * as `ephemeral_full` rather than evicting silently.
58
+ */
59
+ set(room: string, key: string, clientId: string, userId: string, data: Record<string, unknown>, ttlMs?: number): MaybePromise<boolean>;
60
+ /** Live entries for one channel, keyed by clientId. */
61
+ get(room: string, key: string): MaybePromise<Record<string, EphemeralState>>;
62
+ /**
63
+ * Live entries for every channel in a room, keyed by channel key then
64
+ * clientId. Backs the snapshot a client receives when it joins.
65
+ */
66
+ getRoom(room: string): MaybePromise<Record<string, Record<string, EphemeralState>>>;
67
+ remove(room: string, key: string, clientId: string): MaybePromise<void>;
68
+ /** Drop everything a disconnecting client published. */
69
+ removeClient(clientId: string): MaybePromise<void>;
70
+ /** Sweep entries past their TTL. Called on a timer by the handler. */
71
+ cleanupExpired(): MaybePromise<void>;
72
+ /** Current entry count, for the capacity gate and `ephemeral_full`. */
73
+ size(): MaybePromise<number>;
74
+ destroy(): MaybePromise<void>;
75
+ /** Hand an event to peer instances. Absent on single-process adapters. */
76
+ publish?(event: EphemeralBroadcast): MaybePromise<void>;
77
+ /**
78
+ * Register the handler's delivery callback for events published by peers.
79
+ * Called once during wiring. Absent on single-process adapters.
80
+ */
81
+ subscribe?(onEvent: (event: EphemeralBroadcast) => void): MaybePromise<void>;
82
+ }
83
+ declare class EphemeralManager implements EphemeralAdapter {
84
+ private store;
85
+ private clientIndex;
86
+ private entryCount;
87
+ private maxEntries;
88
+ constructor(maxEntries?: number);
89
+ set(room: string, key: string, clientId: string, userId: string, data: Record<string, unknown>, ttlMs?: number): boolean;
90
+ private addToClientIndex;
91
+ private removeFromClientIndex;
92
+ get(room: string, key: string): Record<string, EphemeralState>;
93
+ getRoom(room: string): Record<string, Record<string, EphemeralState>>;
94
+ remove(room: string, key: string, clientId: string): void;
95
+ removeClient(clientId: string): void;
96
+ cleanupExpired(): void;
97
+ size(): number;
98
+ destroy(): void;
99
+ }
100
+ /** Convenience factory mirroring the other adapters. */
101
+ declare function createMemoryEphemeral(maxEntries?: number): EphemeralManager;
102
+ export { EphemeralAdapter, EphemeralBroadcast, EphemeralManager, EphemeralState, EphemeralTarget, MaybePromise, createMemoryEphemeral };
@@ -0,0 +1,9 @@
1
+ import {
2
+ EphemeralManager,
3
+ createMemoryEphemeral
4
+ } from "../../shared/esm-g0marxk7.js";
5
+ import"../../shared/esm-k7kedp3y.js";
6
+ export {
7
+ EphemeralManager,
8
+ createMemoryEphemeral
9
+ };