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.
- package/README.md +500 -40
- package/dist/cjs/client/index.cjs +32 -17
- package/dist/cjs/client/index.d.cts +18 -3
- package/dist/cjs/client/storage/indexeddb.d.cts +1 -1
- package/dist/cjs/core/index.cjs +28 -28
- package/dist/cjs/core/index.d.cts +21 -9
- package/dist/cjs/react/index.cjs +48 -33
- package/dist/cjs/react/index.d.cts +23 -5
- package/dist/cjs/server/drizzle.cjs +2 -2
- package/dist/cjs/server/drizzle.d.cts +1 -1
- package/dist/cjs/server/ephemeral/index.cjs +180 -0
- package/dist/cjs/server/ephemeral/index.d.cts +102 -0
- package/dist/cjs/server/ephemeral/redis.cjs +226 -0
- package/dist/cjs/server/ephemeral/redis.d.cts +136 -0
- package/dist/cjs/server/index.cjs +238 -87
- package/dist/cjs/server/index.d.cts +196 -5
- package/dist/cjs/svelte/index.cjs +34 -19
- package/dist/cjs/svelte/index.d.cts +18 -3
- package/dist/cjs/transport/bun-ws.cjs +9 -9
- package/dist/cjs/transport/bun-ws.d.cts +1 -1
- package/dist/cjs/transport/polling.cjs +11 -11
- package/dist/cjs/transport/polling.d.cts +1 -1
- package/dist/cjs/transport/sse.cjs +11 -11
- package/dist/cjs/transport/sse.d.cts +1 -1
- package/dist/cjs/transport/ws.cjs +11 -11
- package/dist/cjs/transport/ws.d.cts +1 -1
- package/dist/cjs/vanilla/index.cjs +34 -19
- package/dist/cjs/vanilla/index.d.cts +18 -3
- package/dist/client/index.d.ts +18 -3
- package/dist/client/index.js +7 -7
- package/dist/client/storage/indexeddb.d.ts +1 -1
- package/dist/client/storage/indexeddb.js +1 -1
- package/dist/core/index.d.ts +21 -9
- package/dist/core/index.js +20 -20
- package/dist/react/index.d.ts +23 -5
- package/dist/react/index.js +23 -23
- package/dist/server/drizzle.d.ts +1 -1
- package/dist/server/drizzle.js +3 -3
- package/dist/server/ephemeral/index.d.ts +102 -0
- package/dist/server/ephemeral/index.js +9 -0
- package/dist/server/ephemeral/redis.d.ts +136 -0
- package/dist/server/ephemeral/redis.js +186 -0
- package/dist/server/index.d.ts +196 -5
- package/dist/server/index.js +184 -163
- package/dist/shared/{esm-ytrd3hbq.js → esm-8qbr4y0d.js} +18 -3
- package/dist/shared/{esm-wkwx6bd9.js → esm-ck88h30s.js} +10 -10
- package/dist/shared/esm-g0marxk7.js +134 -0
- package/dist/svelte/index.d.ts +18 -3
- package/dist/svelte/index.js +9 -9
- package/dist/transport/bun-ws.d.ts +1 -1
- package/dist/transport/bun-ws.js +1 -1
- package/dist/transport/polling.d.ts +1 -1
- package/dist/transport/polling.js +3 -3
- package/dist/transport/sse.d.ts +1 -1
- package/dist/transport/sse.js +3 -3
- package/dist/transport/ws.d.ts +1 -1
- package/dist/transport/ws.js +3 -3
- package/dist/vanilla/index.d.ts +18 -3
- package/dist/vanilla/index.js +9 -9
- package/package.json +66 -44
- /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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
+
createBrowserWsTransport: () => createBrowserWsTransport,
|
|
111
111
|
createSync: () => createSync,
|
|
112
|
-
|
|
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.
|
|
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.
|
|
1359
|
+
events = { ...events, [event.clientId]: event.data };
|
|
1345
1360
|
if (ephConfig.ttlMs) {
|
|
1346
|
-
const existing = timers.get(event.
|
|
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.
|
|
1367
|
+
delete next[event.clientId];
|
|
1353
1368
|
events = next;
|
|
1354
|
-
timers.delete(event.
|
|
1369
|
+
timers.delete(event.clientId);
|
|
1355
1370
|
notifyChange();
|
|
1356
1371
|
}, ephConfig.ttlMs);
|
|
1357
|
-
timers.set(event.
|
|
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 =
|
|
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 {
|
|
672
|
+
export { EphemeralBinding, SyncVanillaHooks, TableBinding, TypedVanillaSync, VanillaSync, VanillaSyncConfig, createBrowserWsTransport, createSync2 as createSync, createSyncVanilla };
|
package/dist/client/index.d.ts
CHANGED
|
@@ -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 =
|
|
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 {
|
|
644
|
+
export { ClientStorageAdapter, ClientStore, LocalRow, OpCreator, PendingOp, SyncClient, SyncClientConfig, SyncClientState, SyncOptions, TypedSyncClient, TypedSyncClientConfig, createMemoryStorage, createOpCreator, createSyncClient, pushSafely };
|
package/dist/client/index.js
CHANGED
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
SyncClient,
|
|
4
4
|
createOpCreator,
|
|
5
5
|
pushSafely
|
|
6
|
-
} from "../shared/esm-
|
|
6
|
+
} from "../shared/esm-8qbr4y0d.js";
|
|
7
7
|
import"../shared/esm-rw7jjtrv.js";
|
|
8
8
|
import"../shared/esm-3tkwvysa.js";
|
|
9
|
-
import"../shared/esm-
|
|
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
|
-
|
|
101
|
-
createSyncClient,
|
|
102
|
-
createOpCreator,
|
|
103
|
-
createMemoryStorage,
|
|
100
|
+
ClientStore,
|
|
104
101
|
SyncClient,
|
|
105
|
-
|
|
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 {
|
|
85
|
+
export { IndexedDBMigrationContext, createIndexedDBStorage };
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
374
|
-
params
|
|
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
|
|
396
|
-
params
|
|
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
|
-
/**
|
|
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
|
-
} ?
|
|
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 {
|
|
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 };
|
package/dist/core/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
isErrorReason,
|
|
19
19
|
reasonFromError
|
|
20
20
|
} from "../shared/esm-3tkwvysa.js";
|
|
21
|
-
import"../shared/esm-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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(([
|
|
550
|
-
* <div key={
|
|
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 =
|
|
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 {
|
|
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 };
|
package/dist/react/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SyncClient,
|
|
3
3
|
pushSafely
|
|
4
|
-
} from "../shared/esm-
|
|
4
|
+
} from "../shared/esm-8qbr4y0d.js";
|
|
5
5
|
import"../shared/esm-rw7jjtrv.js";
|
|
6
6
|
import"../shared/esm-3tkwvysa.js";
|
|
7
|
-
import"../shared/esm-
|
|
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 {
|
|
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__ */
|
|
127
|
+
return /* @__PURE__ */ jsx(SyncContext.Provider, {
|
|
128
128
|
value: ctx,
|
|
129
129
|
children
|
|
130
|
-
}
|
|
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.
|
|
204
|
+
setEvents((prev) => ({ ...prev, [event.clientId]: event.data }));
|
|
205
205
|
if (config.ttlMs) {
|
|
206
|
-
const existing = timersRef.current.get(event.
|
|
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.
|
|
213
|
+
delete next[event.clientId];
|
|
214
214
|
return next;
|
|
215
215
|
});
|
|
216
|
-
timersRef.current.delete(event.
|
|
216
|
+
timersRef.current.delete(event.clientId);
|
|
217
217
|
}, config.ttlMs);
|
|
218
|
-
timersRef.current.set(event.
|
|
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(([
|
|
336
|
-
userId:
|
|
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
|
-
|
|
355
|
-
useSyncStatus,
|
|
356
|
-
useSyncClient,
|
|
357
|
-
useSync,
|
|
358
|
-
useRow,
|
|
359
|
-
usePendingCount,
|
|
360
|
-
useLoadMore,
|
|
361
|
-
useEphemeral,
|
|
362
|
-
derivePresenceKey,
|
|
363
|
-
createSyncReact,
|
|
354
|
+
SyncContext,
|
|
364
355
|
SyncProvider,
|
|
365
|
-
|
|
356
|
+
createSyncReact,
|
|
357
|
+
derivePresenceKey,
|
|
358
|
+
useEphemeral,
|
|
359
|
+
useLoadMore,
|
|
360
|
+
usePendingCount,
|
|
361
|
+
useRow,
|
|
362
|
+
useSync,
|
|
363
|
+
useSyncClient,
|
|
364
|
+
useSyncStatus,
|
|
365
|
+
useTotalCount
|
|
366
366
|
};
|
package/dist/server/drizzle.d.ts
CHANGED
package/dist/server/drizzle.js
CHANGED
|
@@ -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 };
|