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
|
@@ -250,11 +250,15 @@ interface DrizzleTableLike {
|
|
|
250
250
|
/**
|
|
251
251
|
* Value supplied per serverSet field in the schema's object form.
|
|
252
252
|
* Either a static value or a function that receives the request context.
|
|
253
|
+
*
|
|
254
|
+
* The static arm is enumerated rather than written as `unknown`: a union with
|
|
255
|
+
* `unknown` collapses to `unknown`, which strips the contextual type from the
|
|
256
|
+
* callback arm and makes every `(ctx) => …` an implicit `any` under `strict`.
|
|
253
257
|
*/
|
|
254
|
-
type ServerSetSchemaValue =
|
|
258
|
+
type ServerSetSchemaValue = ((ctx: {
|
|
255
259
|
auth: unknown;
|
|
256
260
|
params: unknown;
|
|
257
|
-
}) => unknown)
|
|
261
|
+
}) => unknown) | string | number | bigint | boolean | symbol | null | undefined | Date | readonly unknown[] | Record<string, unknown>;
|
|
258
262
|
/**
|
|
259
263
|
* Schema-side `serverSet` declaration. Two shapes:
|
|
260
264
|
* - `string[]` — keys only; values are supplied at `implement(...)` time.
|
|
@@ -319,6 +323,88 @@ type InferServerSetKeys<TDef> = TDef extends {
|
|
|
319
323
|
serverSet: infer O extends Readonly<Record<string, unknown>>;
|
|
320
324
|
} ? keyof O & string : never;
|
|
321
325
|
/**
|
|
326
|
+
* Ephemeral (presence, cursors, typing) storage and fan-out seam.
|
|
327
|
+
*
|
|
328
|
+
* The default adapter keeps state in the server process, which is correct for a
|
|
329
|
+
* single node and invisible across a fleet: two clients on different instances
|
|
330
|
+
* never see each other. An adapter backed by shared infrastructure (Redis, or a
|
|
331
|
+
* hosted service) fixes both halves — the shared store answers "who is here"
|
|
332
|
+
* for a client that just joined, and the bus carries each event to the peers
|
|
333
|
+
* holding the other sockets.
|
|
334
|
+
*/
|
|
335
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
336
|
+
interface EphemeralState {
|
|
337
|
+
clientId: string;
|
|
338
|
+
userId: string;
|
|
339
|
+
key: string;
|
|
340
|
+
data: Record<string, unknown>;
|
|
341
|
+
updatedAt: number;
|
|
342
|
+
ttlMs?: number;
|
|
343
|
+
}
|
|
344
|
+
/** One fan-out target: subscribers of `query`, narrowed to `room` when set. */
|
|
345
|
+
interface EphemeralTarget {
|
|
346
|
+
query: string;
|
|
347
|
+
room: string | null;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* An ephemeral event as it crosses the bus between server instances.
|
|
351
|
+
*
|
|
352
|
+
* Recipients are resolved on the origin instance and carried here as `targets`,
|
|
353
|
+
* because the receiving instance holds no session for the sender and so cannot
|
|
354
|
+
* re-derive them. `serverId` lets an instance drop its own echo on buses that
|
|
355
|
+
* deliver published messages back to the publisher.
|
|
356
|
+
*/
|
|
357
|
+
interface EphemeralBroadcast {
|
|
358
|
+
serverId: string;
|
|
359
|
+
key: string;
|
|
360
|
+
clientId: string;
|
|
361
|
+
userId: string;
|
|
362
|
+
data: Record<string, unknown>;
|
|
363
|
+
ttlMs?: number;
|
|
364
|
+
targets: EphemeralTarget[];
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Backing store and fan-out bus for ephemeral state.
|
|
368
|
+
*
|
|
369
|
+
* `publish`/`subscribe` are optional: an adapter that omits them is a
|
|
370
|
+
* single-process store, and the handler still fans out to its own sockets.
|
|
371
|
+
*/
|
|
372
|
+
interface EphemeralAdapter {
|
|
373
|
+
/**
|
|
374
|
+
* Record one entry, keyed by `clientId`.
|
|
375
|
+
*
|
|
376
|
+
* Peer identity is the connection, not the account: two tabs from one login
|
|
377
|
+
* are two cursors, and the client bindings key peers the same way. `userId`
|
|
378
|
+
* rides along for display and authorization, not as the entry's identity.
|
|
379
|
+
*
|
|
380
|
+
* Returns false when the adapter is at capacity, which the handler surfaces
|
|
381
|
+
* as `ephemeral_full` rather than evicting silently.
|
|
382
|
+
*/
|
|
383
|
+
set(room: string, key: string, clientId: string, userId: string, data: Record<string, unknown>, ttlMs?: number): MaybePromise<boolean>;
|
|
384
|
+
/** Live entries for one channel, keyed by clientId. */
|
|
385
|
+
get(room: string, key: string): MaybePromise<Record<string, EphemeralState>>;
|
|
386
|
+
/**
|
|
387
|
+
* Live entries for every channel in a room, keyed by channel key then
|
|
388
|
+
* clientId. Backs the snapshot a client receives when it joins.
|
|
389
|
+
*/
|
|
390
|
+
getRoom(room: string): MaybePromise<Record<string, Record<string, EphemeralState>>>;
|
|
391
|
+
remove(room: string, key: string, clientId: string): MaybePromise<void>;
|
|
392
|
+
/** Drop everything a disconnecting client published. */
|
|
393
|
+
removeClient(clientId: string): MaybePromise<void>;
|
|
394
|
+
/** Sweep entries past their TTL. Called on a timer by the handler. */
|
|
395
|
+
cleanupExpired(): MaybePromise<void>;
|
|
396
|
+
/** Current entry count, for the capacity gate and `ephemeral_full`. */
|
|
397
|
+
size(): MaybePromise<number>;
|
|
398
|
+
destroy(): MaybePromise<void>;
|
|
399
|
+
/** Hand an event to peer instances. Absent on single-process adapters. */
|
|
400
|
+
publish?(event: EphemeralBroadcast): MaybePromise<void>;
|
|
401
|
+
/**
|
|
402
|
+
* Register the handler's delivery callback for events published by peers.
|
|
403
|
+
* Called once during wiring. Absent on single-process adapters.
|
|
404
|
+
*/
|
|
405
|
+
subscribe?(onEvent: (event: EphemeralBroadcast) => void): MaybePromise<void>;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
322
408
|
* Adapter contract for `server.tx({ atomic: true })` — pluggable so non-drizzle
|
|
323
409
|
* data layers (kysely, prisma, raw SQL) can wrap their own BEGIN/COMMIT/ROLLBACK
|
|
324
410
|
* without reflectdb hard-coding a drizzle dependency.
|
|
@@ -456,6 +542,16 @@ declare class ResultCache {
|
|
|
456
542
|
private cache;
|
|
457
543
|
/**
|
|
458
544
|
* Update the cached result set and return the diff.
|
|
545
|
+
*
|
|
546
|
+
* Rows are snapshotted, not referenced. A query that hands back live objects
|
|
547
|
+
* — an in-memory store, a game loop mutating rows in place, an ORM returning
|
|
548
|
+
* tracked entities — would otherwise have the cache holding the very objects
|
|
549
|
+
* the next write mutates, so every later diff compares a row against itself,
|
|
550
|
+
* finds nothing changed, and the client silently stops receiving updates.
|
|
551
|
+
*
|
|
552
|
+
* The copy is shallow: mutating a nested object inside a row in place is
|
|
553
|
+
* still invisible to change detection.
|
|
554
|
+
*
|
|
459
555
|
* @param idField - The field name used as the row identifier (default: "id")
|
|
460
556
|
*/
|
|
461
557
|
set(clientId: string, queryName: string, rows: Record<string, unknown>[], idField?: string): DiffResult;
|
|
@@ -467,6 +563,21 @@ declare class ResultCache {
|
|
|
467
563
|
* already has rows it never received.
|
|
468
564
|
*/
|
|
469
565
|
diffOnly(clientId: string, queryName: string, rows: Record<string, unknown>[], idField?: string): DiffResult;
|
|
566
|
+
/**
|
|
567
|
+
* Forget one row of one client's cached result.
|
|
568
|
+
*
|
|
569
|
+
* The cache mirrors what a client is believed to hold, and a writer is
|
|
570
|
+
* excluded from the broadcast of its own write because it already applied
|
|
571
|
+
* that write optimistically. For a delete that means the row is gone on the
|
|
572
|
+
* client while the cache still holds it — and if the same rowId is later
|
|
573
|
+
* re-created, the next diff reports it as an *update* against the dead row
|
|
574
|
+
* and sends only the fields that happen to differ. The client, whose local
|
|
575
|
+
* row is just its own optimistic payload, would silently never receive the
|
|
576
|
+
* unchanged server-owned columns. Dropping the row here keeps the mirror
|
|
577
|
+
* honest, so a re-created rowId diffs as a fresh insert carrying every
|
|
578
|
+
* column.
|
|
579
|
+
*/
|
|
580
|
+
evictRow(clientId: string, queryName: string, rowId: string): void;
|
|
470
581
|
clear(clientId: string, queryName: string): void;
|
|
471
582
|
clearClient(clientId: string): void;
|
|
472
583
|
private getOrCreateClientCache;
|
|
@@ -662,6 +773,8 @@ declare class MessageHandler<TAuth extends AuthContext = AuthContext> {
|
|
|
662
773
|
private broadcast;
|
|
663
774
|
private ops;
|
|
664
775
|
private ephemeralManager;
|
|
776
|
+
/** Bus subscription is wired once, on the first adapter that offers one. */
|
|
777
|
+
private ephemeralBusReady;
|
|
665
778
|
private ephemeralCleanupTimer;
|
|
666
779
|
private eagerBuffer;
|
|
667
780
|
private replay;
|
|
@@ -701,6 +814,13 @@ declare class MessageHandler<TAuth extends AuthContext = AuthContext> {
|
|
|
701
814
|
setEphemeralRateLimit(perSecond: number): void;
|
|
702
815
|
setMaxBatchSize(size: number): void;
|
|
703
816
|
setMaxEphemeralEntries(max: number): void;
|
|
817
|
+
/**
|
|
818
|
+
* Swap the ephemeral store. An adapter that implements `subscribe` also
|
|
819
|
+
* makes this instance a bus participant, so presence spans the fleet.
|
|
820
|
+
*/
|
|
821
|
+
setEphemeralAdapter(adapter: EphemeralAdapter): void;
|
|
822
|
+
/** Drop everything a client published, whatever the adapter costs. */
|
|
823
|
+
private forgetEphemeralClient;
|
|
704
824
|
setMinSchemaVersion(version: number): void;
|
|
705
825
|
setMaxConnectionsPerUser(max: number | null): void;
|
|
706
826
|
private enforceConnectionCap;
|
|
@@ -761,6 +881,23 @@ declare class MessageHandler<TAuth extends AuthContext = AuthContext> {
|
|
|
761
881
|
*/
|
|
762
882
|
private allowEphemeral;
|
|
763
883
|
private handleEphemeral;
|
|
884
|
+
/**
|
|
885
|
+
* Deliver one ephemeral event to this instance's sockets.
|
|
886
|
+
*
|
|
887
|
+
* `exclude` is the sender when the event originated here, and undefined
|
|
888
|
+
* when it arrived over the bus — the sender's socket lives elsewhere.
|
|
889
|
+
*/
|
|
890
|
+
private fanOutEphemeral;
|
|
891
|
+
/** An event published by a peer instance. State is already in the shared store. */
|
|
892
|
+
private deliverRemoteEphemeral;
|
|
893
|
+
/**
|
|
894
|
+
* Replay a room's live presence to a client that just subscribed.
|
|
895
|
+
*
|
|
896
|
+
* Without this a joiner sees nobody until each peer happens to move again —
|
|
897
|
+
* the stored state existed but was never served. Replayed as ordinary
|
|
898
|
+
* `ephemeral` events, so clients need no new message type to benefit.
|
|
899
|
+
*/
|
|
900
|
+
private sendPresenceSnapshot;
|
|
764
901
|
/** Public reserve-or-replay gate for REST idempotency. Returns true when fresh. */
|
|
765
902
|
reserveOpId(opId: string): Promise<boolean>;
|
|
766
903
|
runCompaction(minOpAge: number): Promise<number>;
|
|
@@ -915,6 +1052,18 @@ interface ServerConfig<TDb = unknown> {
|
|
|
915
1052
|
* this is absent. See `TxAtomicAdapter`.
|
|
916
1053
|
*/
|
|
917
1054
|
txAtomic?: TxAtomicAdapter;
|
|
1055
|
+
/**
|
|
1056
|
+
* Ephemeral (presence, cursors, typing) storage and fan-out.
|
|
1057
|
+
*
|
|
1058
|
+
* The default is in-process: correct on one node, invisible across a fleet,
|
|
1059
|
+
* since each instance holds its own map and its own sockets. Supply an
|
|
1060
|
+
* adapter backed by shared infrastructure to make presence span instances.
|
|
1061
|
+
*/
|
|
1062
|
+
ephemeral?: {
|
|
1063
|
+
adapter?: EphemeralAdapter;
|
|
1064
|
+
/** Entry ceiling for the default in-process store. Default: 10_000. */
|
|
1065
|
+
maxEntries?: number;
|
|
1066
|
+
};
|
|
918
1067
|
}
|
|
919
1068
|
interface QueryContext<TAuth extends AuthContext = AuthContext> {
|
|
920
1069
|
auth: TAuth;
|
|
@@ -1472,6 +1621,11 @@ interface BunStatement<Row> {
|
|
|
1472
1621
|
run(...params: SqlValue[]): {
|
|
1473
1622
|
changes: number;
|
|
1474
1623
|
};
|
|
1624
|
+
/**
|
|
1625
|
+
* Releases the statement's sqlite handle. Optional so a hand-rolled stand-in
|
|
1626
|
+
* need not implement it; `bun:sqlite` always does.
|
|
1627
|
+
*/
|
|
1628
|
+
finalize?(): void;
|
|
1475
1629
|
}
|
|
1476
1630
|
interface BunDatabase {
|
|
1477
1631
|
run(sql: string, ...params: SqlValue[][]): {
|
|
@@ -1564,6 +1718,18 @@ declare class BroadcastEngine<TAuth extends AuthContext = AuthContext> {
|
|
|
1564
1718
|
private withLock;
|
|
1565
1719
|
private executeWithTimeout;
|
|
1566
1720
|
/**
|
|
1721
|
+
* Forget a row the writer itself removed, in every query that depends on
|
|
1722
|
+
* `tableName`.
|
|
1723
|
+
*
|
|
1724
|
+
* The writer is skipped by `broadcastChanges` because it applied its own op
|
|
1725
|
+
* optimistically, which leaves its cached result claiming a row the client
|
|
1726
|
+
* has already dropped. Re-creating that same rowId would then diff as an
|
|
1727
|
+
* update against the dead row and send only the columns that differ, so the
|
|
1728
|
+
* writer would never receive the server-owned columns that happen to match
|
|
1729
|
+
* the row it deleted.
|
|
1730
|
+
*/
|
|
1731
|
+
forgetWriterRow(tableName: string, clientId: string, rowId: string): void;
|
|
1732
|
+
/**
|
|
1567
1733
|
* After a write to `tableName`, re-run every dependent query and send each
|
|
1568
1734
|
* subscriber the rows that changed for them.
|
|
1569
1735
|
*
|
|
@@ -1647,6 +1813,13 @@ interface OpProcessorDeps<TAuth extends AuthContext> {
|
|
|
1647
1813
|
receiveClientHlc(hlc: string): void;
|
|
1648
1814
|
send(clientId: string, message: ServerMessage): Promise<void>;
|
|
1649
1815
|
broadcastChanges(tableName: string, excludeClientId: string): Promise<void>;
|
|
1816
|
+
/**
|
|
1817
|
+
* Drop a row the writer deleted from the writer's own cached result set.
|
|
1818
|
+
* The writer is excluded from the broadcast of its own op, so without this
|
|
1819
|
+
* the cache keeps a row the client no longer holds — and a later re-insert
|
|
1820
|
+
* of the same rowId diffs as a partial update against that dead row.
|
|
1821
|
+
*/
|
|
1822
|
+
forgetWriterRow(tableName: string, clientId: string, rowId: string): void;
|
|
1650
1823
|
}
|
|
1651
1824
|
/**
|
|
1652
1825
|
* Applies client ops: replay reservation, enforcement, conflict resolution,
|
|
@@ -1795,6 +1968,16 @@ interface TypedServerConfig<
|
|
|
1795
1968
|
maxBroadcastConcurrency?: number;
|
|
1796
1969
|
/** Adapter that wraps `server.tx({ atomic: true })` writes in a transaction. */
|
|
1797
1970
|
txAtomic?: TxAtomicAdapter;
|
|
1971
|
+
/**
|
|
1972
|
+
* Ephemeral (presence, cursors, typing) storage and fan-out. Defaults to an
|
|
1973
|
+
* in-process store, which is invisible across a fleet — supply an adapter
|
|
1974
|
+
* backed by shared infrastructure to make presence span instances.
|
|
1975
|
+
*/
|
|
1976
|
+
ephemeral?: {
|
|
1977
|
+
adapter?: EphemeralAdapter;
|
|
1978
|
+
/** Entry ceiling for the default in-process store. Default: 10_000. */
|
|
1979
|
+
maxEntries?: number;
|
|
1980
|
+
};
|
|
1798
1981
|
}
|
|
1799
1982
|
type ImplementParams<
|
|
1800
1983
|
TQueries extends SyncQueryMap,
|
|
@@ -1818,14 +2001,22 @@ type IsServerSetArrayForm<
|
|
|
1818
2001
|
serverSet: readonly string[];
|
|
1819
2002
|
} ? true : false;
|
|
1820
2003
|
/** Value: static or a function receiving auth/params context */
|
|
2004
|
+
/**
|
|
2005
|
+
* A serverSet entry: a static value, or a function of the request context.
|
|
2006
|
+
*
|
|
2007
|
+
* The static arm is spelled out rather than written as `unknown`, because
|
|
2008
|
+
* `unknown | ((ctx) => …)` collapses to `unknown` and the callback's parameter
|
|
2009
|
+
* then has no contextual type — every `(ctx) => …` would be an implicit `any`
|
|
2010
|
+
* under `strict`.
|
|
2011
|
+
*/
|
|
1821
2012
|
type ServerSetValue<
|
|
1822
2013
|
TAuth extends AuthContext,
|
|
1823
2014
|
TQueries extends SyncQueryMap,
|
|
1824
2015
|
K extends keyof TQueries
|
|
1825
|
-
> =
|
|
2016
|
+
> = ((ctx: {
|
|
1826
2017
|
auth: TAuth;
|
|
1827
2018
|
params: ImplementParams<TQueries, K>;
|
|
1828
|
-
}) => unknown)
|
|
2019
|
+
}) => unknown) | string | number | bigint | boolean | symbol | null | undefined | Date | readonly unknown[] | Record<string, unknown>;
|
|
1829
2020
|
/**
|
|
1830
2021
|
* `serverSet` shape on `implement(...)`:
|
|
1831
2022
|
* - schema declares no `serverSet` → field disallowed.
|
|
@@ -1985,4 +2176,4 @@ declare function createSyncServer<
|
|
|
1985
2176
|
TDb = unknown,
|
|
1986
2177
|
TAuth extends AuthContext = AuthContext
|
|
1987
2178
|
>(config: TypedServerConfig<TQueries, TDb>): TypedSyncServer<TQueries, TAuth, TDb>;
|
|
1988
|
-
export {
|
|
2179
|
+
export { AuthCallback, AuthorizeAction, BatchContext, BroadcastEngine, BroadcastEngineDeps, ClientSession, ConflictInput, ConflictResult, DefineTableOpts, DiffResult, DrizzleTableOpts, EnforcementContext, EnforcementResult, ExistingRow, HandlerConfig, ImplementOptions, MessageHandler, MutateResult, MutationContext, MutationError, OpLogEntry, OpProcessor, OpProcessorDeps, OpResult, PipelineContext, PipelineResult, PostgresClient, PostgresStorageConfig, QueryCallback, QueryContext, QueryOptions, QueryRegistration, QuerySubscription, RateLimiter, ResolvedOp, RestConfig, ResultCache, RoomCallback, RoomResolution, SERVER_HLC_META_KEY, ScopeFilter, ServerClock, ServerConfig, SessionManager, SqliteStorageConfig, StorageAdapter, SyncEvent, SyncServer, TableAdapter, TxAtomicAdapter, TxFn, TxOptions, TxProxy, TypedServerConfig, TypedSyncServer, createPostgresStorage, createRateLimiter, createServer, createSqliteStorage, createSyncServer, defineTable, drizzleTable, drizzleTxAtomic, enforceBatchSize, enforceClockDrift, enforceReadonly, enforceServerSet, processOp, resolveConflict, resolveRoomKey, stableStringify };
|
|
@@ -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/svelte/index.ts
|
|
108
108
|
var exports_svelte = {};
|
|
109
109
|
__export(exports_svelte, {
|
|
110
|
-
|
|
110
|
+
createBrowserWsTransport: () => createBrowserWsTransport,
|
|
111
111
|
createSyncStore: () => createSyncStore,
|
|
112
|
-
|
|
112
|
+
createSyncSvelte: () => createSyncSvelte
|
|
113
113
|
});
|
|
114
114
|
module.exports = __toCommonJS(exports_svelte);
|
|
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) {
|
|
@@ -1323,21 +1338,21 @@ function createSyncStore(config) {
|
|
|
1323
1338
|
}
|
|
1324
1339
|
}
|
|
1325
1340
|
const unsub = client.subscribeEphemeral(cfg.key, (event) => {
|
|
1326
|
-
current = { ...current, [event.
|
|
1341
|
+
current = { ...current, [event.clientId]: event.data };
|
|
1327
1342
|
notifySubscribers();
|
|
1328
1343
|
if (cfg.ttlMs) {
|
|
1329
|
-
const existing = timers.get(event.
|
|
1344
|
+
const existing = timers.get(event.clientId);
|
|
1330
1345
|
if (existing) {
|
|
1331
1346
|
clearTimeout(existing);
|
|
1332
1347
|
}
|
|
1333
1348
|
const timer = setTimeout(() => {
|
|
1334
1349
|
const next = { ...current };
|
|
1335
|
-
delete next[event.
|
|
1350
|
+
delete next[event.clientId];
|
|
1336
1351
|
current = next;
|
|
1337
|
-
timers.delete(event.
|
|
1352
|
+
timers.delete(event.clientId);
|
|
1338
1353
|
notifySubscribers();
|
|
1339
1354
|
}, cfg.ttlMs);
|
|
1340
|
-
timers.set(event.
|
|
1355
|
+
timers.set(event.clientId, timer);
|
|
1341
1356
|
}
|
|
1342
1357
|
});
|
|
1343
1358
|
const events = {
|
|
@@ -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[];
|
|
@@ -496,11 +507,15 @@ interface DrizzleTableLike {
|
|
|
496
507
|
/**
|
|
497
508
|
* Value supplied per serverSet field in the schema's object form.
|
|
498
509
|
* Either a static value or a function that receives the request context.
|
|
510
|
+
*
|
|
511
|
+
* The static arm is enumerated rather than written as `unknown`: a union with
|
|
512
|
+
* `unknown` collapses to `unknown`, which strips the contextual type from the
|
|
513
|
+
* callback arm and makes every `(ctx) => …` an implicit `any` under `strict`.
|
|
499
514
|
*/
|
|
500
|
-
type ServerSetSchemaValue =
|
|
515
|
+
type ServerSetSchemaValue = ((ctx: {
|
|
501
516
|
auth: unknown;
|
|
502
517
|
params: unknown;
|
|
503
|
-
}) => unknown)
|
|
518
|
+
}) => unknown) | string | number | bigint | boolean | symbol | null | undefined | Date | readonly unknown[] | Record<string, unknown>;
|
|
504
519
|
/**
|
|
505
520
|
* Schema-side `serverSet` declaration. Two shapes:
|
|
506
521
|
* - `string[]` — keys only; values are supplied at `implement(...)` time.
|
|
@@ -642,4 +657,4 @@ interface SyncSvelteHooks<TQueries extends SyncQueryMap> {
|
|
|
642
657
|
createStore: (config: SyncStoreConfig) => TypedSyncStore<TQueries>;
|
|
643
658
|
}
|
|
644
659
|
declare function createSyncSvelte<TQueries extends SyncQueryMap>(): SyncSvelteHooks<TQueries>;
|
|
645
|
-
export {
|
|
660
|
+
export { Readable, SyncStore, SyncStoreConfig, SyncSvelteHooks, TypedSyncStore, createBrowserWsTransport, createSyncStore, createSyncSvelte };
|
|
@@ -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);
|
|
@@ -212,4 +212,4 @@ declare function createBunWsServerTransport(cfg?: BunWsServerConfig): {
|
|
|
212
212
|
pong?(ws: BunServerWebSocket<BunWsTransportData>): void;
|
|
213
213
|
};
|
|
214
214
|
};
|
|
215
|
-
export {
|
|
215
|
+
export { BunWsServerConfig, BunWsTransportData, createBunWsServerTransport };
|
|
@@ -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);
|
|
@@ -108,9 +108,9 @@ var init_types = __esm(() => {
|
|
|
108
108
|
init_types();
|
|
109
109
|
var exports_polling = {};
|
|
110
110
|
__export(exports_polling, {
|
|
111
|
-
|
|
111
|
+
createPollingClientTransport: () => createPollingClientTransport,
|
|
112
112
|
createPollingServerTransport: () => createPollingServerTransport,
|
|
113
|
-
|
|
113
|
+
pollingBodyTooLarge: () => pollingBodyTooLarge
|
|
114
114
|
});
|
|
115
115
|
module.exports = __toCommonJS(exports_polling);
|
|
116
116
|
function pollingBodyTooLarge(body, limit = 1e6) {
|
|
@@ -207,4 +207,4 @@ interface PollingClientConfig {
|
|
|
207
207
|
headers?: Record<string, string>;
|
|
208
208
|
}
|
|
209
209
|
declare function createPollingClientTransport(config: PollingClientConfig): ClientTransport;
|
|
210
|
-
export {
|
|
210
|
+
export { PollingClientConfig, PollingServerConfig, createPollingClientTransport, createPollingServerTransport, pollingBodyTooLarge };
|
|
@@ -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);
|
|
@@ -108,8 +108,8 @@ var init_types = __esm(() => {
|
|
|
108
108
|
init_types();
|
|
109
109
|
var exports_sse = {};
|
|
110
110
|
__export(exports_sse, {
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
createSseClientTransport: () => createSseClientTransport,
|
|
112
|
+
createSseServerTransport: () => createSseServerTransport
|
|
113
113
|
});
|
|
114
114
|
module.exports = __toCommonJS(exports_sse);
|
|
115
115
|
function createSseServerTransport(cfg = {}) {
|
|
@@ -202,4 +202,4 @@ interface SseClientConfig {
|
|
|
202
202
|
headers?: Record<string, string>;
|
|
203
203
|
}
|
|
204
204
|
declare function createSseClientTransport(config: SseClientConfig): ClientTransport;
|
|
205
|
-
export {
|
|
205
|
+
export { SseClientConfig, SseServerConfig, createSseClientTransport, createSseServerTransport };
|
|
@@ -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);
|
|
@@ -108,9 +108,9 @@ var init_types = __esm(() => {
|
|
|
108
108
|
init_types();
|
|
109
109
|
var exports_ws = {};
|
|
110
110
|
__export(exports_ws, {
|
|
111
|
-
|
|
111
|
+
createWsClientTransport: () => createWsClientTransport,
|
|
112
112
|
createWsServerTransport: () => createWsServerTransport,
|
|
113
|
-
|
|
113
|
+
isOriginAllowed: () => isOriginAllowed
|
|
114
114
|
});
|
|
115
115
|
module.exports = __toCommonJS(exports_ws);
|
|
116
116
|
function trySend(clientId, ws, data, maxBufferedBytes) {
|
|
@@ -233,4 +233,4 @@ interface WebSocketLike {
|
|
|
233
233
|
/** Bytes queued but not yet flushed. Used for outbound backpressure. */
|
|
234
234
|
bufferedAmount?: number;
|
|
235
235
|
}
|
|
236
|
-
export {
|
|
236
|
+
export { WebSocketLike, WsClientConfig, WsServerConfig, WsServerTransportConfig, createWsClientTransport, createWsServerTransport, isOriginAllowed };
|