reflectdb 0.1.0

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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1260 -0
  3. package/dist/cjs/client/index.cjs +1252 -0
  4. package/dist/cjs/client/index.d.cts +629 -0
  5. package/dist/cjs/client/storage/indexeddb.cjs +253 -0
  6. package/dist/cjs/client/storage/indexeddb.d.cts +85 -0
  7. package/dist/cjs/core/index.cjs +202 -0
  8. package/dist/cjs/core/index.d.cts +473 -0
  9. package/dist/cjs/react/index.cjs +1512 -0
  10. package/dist/cjs/react/index.d.cts +748 -0
  11. package/dist/cjs/server/drizzle.cjs +413 -0
  12. package/dist/cjs/server/drizzle.d.cts +233 -0
  13. package/dist/cjs/server/index.cjs +4486 -0
  14. package/dist/cjs/server/index.d.cts +1988 -0
  15. package/dist/cjs/svelte/index.cjs +1414 -0
  16. package/dist/cjs/svelte/index.d.cts +645 -0
  17. package/dist/cjs/transport/bun-ws.cjs +244 -0
  18. package/dist/cjs/transport/bun-ws.d.cts +215 -0
  19. package/dist/cjs/transport/polling.cjs +285 -0
  20. package/dist/cjs/transport/polling.d.cts +210 -0
  21. package/dist/cjs/transport/sse.cjs +312 -0
  22. package/dist/cjs/transport/sse.d.cts +205 -0
  23. package/dist/cjs/transport/ws.cjs +330 -0
  24. package/dist/cjs/transport/ws.d.cts +236 -0
  25. package/dist/cjs/vanilla/index.cjs +1430 -0
  26. package/dist/cjs/vanilla/index.d.cts +657 -0
  27. package/dist/client/index.d.ts +629 -0
  28. package/dist/client/index.js +106 -0
  29. package/dist/client/storage/indexeddb.d.ts +85 -0
  30. package/dist/client/storage/indexeddb.js +213 -0
  31. package/dist/core/index.d.ts +473 -0
  32. package/dist/core/index.js +56 -0
  33. package/dist/react/index.d.ts +748 -0
  34. package/dist/react/index.js +366 -0
  35. package/dist/server/drizzle.d.ts +233 -0
  36. package/dist/server/drizzle.js +9 -0
  37. package/dist/server/index.d.ts +1988 -0
  38. package/dist/server/index.js +3959 -0
  39. package/dist/shared/esm-3tkwvysa.js +54 -0
  40. package/dist/shared/esm-b7xs9cde.js +4 -0
  41. package/dist/shared/esm-rw7jjtrv.js +58 -0
  42. package/dist/shared/esm-wkwx6bd9.js +25 -0
  43. package/dist/shared/esm-ytrd3hbq.js +1007 -0
  44. package/dist/shared/esm-z1xse19c.js +369 -0
  45. package/dist/svelte/index.d.ts +645 -0
  46. package/dist/svelte/index.js +262 -0
  47. package/dist/transport/bun-ws.d.ts +215 -0
  48. package/dist/transport/bun-ws.js +140 -0
  49. package/dist/transport/polling.d.ts +210 -0
  50. package/dist/transport/polling.js +181 -0
  51. package/dist/transport/sse.d.ts +205 -0
  52. package/dist/transport/sse.js +208 -0
  53. package/dist/transport/ws.d.ts +236 -0
  54. package/dist/transport/ws.js +226 -0
  55. package/dist/vanilla/index.d.ts +657 -0
  56. package/dist/vanilla/index.js +278 -0
  57. package/package.json +253 -0
@@ -0,0 +1,645 @@
1
+ type OpType = "insert" | "update" | "delete";
2
+ type OpStatus = "pending" | "synced" | "rejected";
3
+ /**
4
+ * Server-side conflict policy for concurrent writes to the same row.
5
+ *
6
+ * Scope note for `"merge"`: the server resolves per column using the **client
7
+ * op HLCs** recorded in reflectdb's mirror, so two clients editing different
8
+ * fields both land. The per-column clocks the client sees on a broadcast are a
9
+ * different domain — a diff-driven broadcast can't attribute a column to the
10
+ * op that produced it, so every column changed in one broadcast carries that
11
+ * broadcast's HLC. Client-side merge therefore orders *broadcasts* against each
12
+ * other and against local optimistic state; it does not reconstruct per-column
13
+ * causality between clients. Column-level convergence is a server guarantee.
14
+ */
15
+ type ConflictPolicy = "lww" | "merge" | "server" | CustomConflictPolicy;
16
+ interface CustomConflictPolicy {
17
+ policy: "custom";
18
+ resolve: ConflictResolver;
19
+ }
20
+ interface ConflictResolver {
21
+ (incoming: {
22
+ op: OpType;
23
+ payload: Record<string, unknown> | null;
24
+ hlc: string;
25
+ }, existing: {
26
+ row: Record<string, unknown>;
27
+ colClocks: Record<string, string>;
28
+ }, meta: {
29
+ table: string;
30
+ rowId: string;
31
+ userId: string;
32
+ }): {
33
+ row: Record<string, unknown>;
34
+ };
35
+ }
36
+ type ErrorReason = "outside_shape" | "readonly_query" | "rate_limited" | "buffer_full" | "server_conflict" | "custom_conflict" | "merge_stale" | "clock_drift" | "replay" | "batch_too_large" | "schema_outdated" | "auth_revoked" | "compacted" | "mutation_rejected" | "server_error" | "unknown_query";
37
+ interface HelloMessage {
38
+ type: "hello";
39
+ /** Highest protocol version the client prefers. Retained for back-compat. */
40
+ protocolVersion: number;
41
+ /** All versions the client can speak. Omit for legacy single-version clients. */
42
+ supportedVersions?: readonly number[];
43
+ clientId: string;
44
+ token: string;
45
+ }
46
+ interface BootstrapMessage {
47
+ type: "bootstrap";
48
+ syncParams?: Record<string, Record<string, unknown>>;
49
+ }
50
+ interface ResumeMessage {
51
+ type: "resume";
52
+ since: string;
53
+ }
54
+ interface OpsMessage {
55
+ type: "ops";
56
+ ops: ClientOp[];
57
+ token: string;
58
+ }
59
+ interface ClientOp {
60
+ id: string;
61
+ table: string;
62
+ op: OpType;
63
+ rowId: string;
64
+ payload: Record<string, unknown> | null;
65
+ hlc: string;
66
+ batchId?: string;
67
+ batchSize?: number;
68
+ batchSeq?: number;
69
+ }
70
+ interface SyncDeclareMessage {
71
+ type: "sync_declare";
72
+ table: string;
73
+ params?: Record<string, unknown>;
74
+ window?: number;
75
+ }
76
+ interface LoadMoreMessage {
77
+ type: "load_more";
78
+ table: string;
79
+ count: number;
80
+ }
81
+ interface UnsyncMessage {
82
+ type: "unsync";
83
+ table: string;
84
+ }
85
+ interface AuthMessage {
86
+ type: "auth";
87
+ token: string;
88
+ }
89
+ interface EphemeralMessage {
90
+ type: "ephemeral";
91
+ key: string;
92
+ userId: string;
93
+ data: Record<string, unknown>;
94
+ ttlMs?: number;
95
+ }
96
+ interface HelloAckMessage {
97
+ type: "hello_ack";
98
+ protocolVersion: number;
99
+ serverId: string;
100
+ }
101
+ interface HelloRejectMessage {
102
+ type: "hello_reject";
103
+ reason: string;
104
+ supported: number[];
105
+ }
106
+ interface SnapshotMessage {
107
+ type: "snapshot";
108
+ table: string;
109
+ rows: Record<string, unknown>[];
110
+ colClocks: Record<string, Record<string, string>>;
111
+ append?: boolean;
112
+ totalCount?: number;
113
+ /**
114
+ * Primary key column name for this table. Self-contained per message so the
115
+ * client doesn't depend on `bootstrap_complete.tableMeta` arriving first.
116
+ * When omitted (older servers), the client falls back to tableMeta, then "id".
117
+ */
118
+ pk?: string;
119
+ }
120
+ interface BootstrapCompleteMessage {
121
+ type: "bootstrap_complete";
122
+ serverHlc: string;
123
+ tableMeta: Record<string, TableMeta>;
124
+ }
125
+ interface TableMeta {
126
+ serverSet: string[];
127
+ readonly: string[];
128
+ broadcast: "consistent" | "eager" | "eager-durable";
129
+ /** Primary key column name. Defaults to "id" when omitted (older servers). */
130
+ pk?: string;
131
+ }
132
+ interface DeltaMessage {
133
+ type: "delta";
134
+ table: string;
135
+ op: OpType;
136
+ rowId: string;
137
+ payload: Record<string, unknown> | null;
138
+ hlc: string;
139
+ colClocks?: Record<string, string>;
140
+ }
141
+ interface AckMessage {
142
+ type: "ack";
143
+ opIds: string[];
144
+ }
145
+ interface RejectMessage {
146
+ type: "reject";
147
+ opId?: string;
148
+ batchId?: string;
149
+ reason: ErrorReason;
150
+ serverRow?: Record<string, unknown>;
151
+ }
152
+ interface ResumeCompleteMessage {
153
+ type: "resume_complete";
154
+ serverHlc: string;
155
+ }
156
+ interface ResumeRejectedMessage {
157
+ type: "resume_rejected";
158
+ reason: string;
159
+ serverHlc: string;
160
+ }
161
+ interface ShapeChangedMessage {
162
+ type: "shape_changed";
163
+ table: string;
164
+ reason: "auth_changed" | "params_changed" | "server_policy";
165
+ }
166
+ interface DisconnectMessage {
167
+ type: "disconnect";
168
+ reason: string;
169
+ }
170
+ interface ReauthMessage {
171
+ type: "reauth";
172
+ }
173
+ interface CountChangedMessage {
174
+ type: "count_changed";
175
+ table: string;
176
+ totalCount: number;
177
+ }
178
+ interface EphemeralEvent {
179
+ type: "ephemeral";
180
+ key: string;
181
+ clientId: string;
182
+ userId: string;
183
+ data: Record<string, unknown>;
184
+ ttlMs?: number;
185
+ }
186
+ type ClientMessage = HelloMessage | BootstrapMessage | ResumeMessage | OpsMessage | SyncDeclareMessage | LoadMoreMessage | UnsyncMessage | AuthMessage | EphemeralMessage;
187
+ type ServerMessage = HelloAckMessage | HelloRejectMessage | SnapshotMessage | BootstrapCompleteMessage | DeltaMessage | AckMessage | RejectMessage | ResumeCompleteMessage | ResumeRejectedMessage | ShapeChangedMessage | DisconnectMessage | ReauthMessage | EphemeralEvent | CountChangedMessage;
188
+ interface ClientTransport {
189
+ send(message: ClientMessage): Promise<void>;
190
+ subscribe(handler: (message: ServerMessage) => void): void;
191
+ close(): Promise<void>;
192
+ }
193
+ interface ClientStorageAdapter {
194
+ getRows(table: string): Promise<LocalRow[]>;
195
+ getAllRows(): Promise<LocalRow[]>;
196
+ putRow(table: string, rowId: string, row: LocalRow): Promise<void>;
197
+ deleteRow(table: string, rowId: string): Promise<void>;
198
+ clearTable(table: string): Promise<void>;
199
+ getPendingOps(): Promise<PendingOp[]>;
200
+ putPendingOps(ops: PendingOp[]): Promise<void>;
201
+ appendPendingOps(ops: PendingOp[]): Promise<void>;
202
+ updatePendingOps(ops: PendingOp[]): Promise<void>;
203
+ removePendingOps(opIds: string[]): Promise<void>;
204
+ clearPendingOps(): Promise<void>;
205
+ getMeta(key: string): Promise<string | null>;
206
+ setMeta(key: string, value: string): Promise<void>;
207
+ deleteMeta(key: string): Promise<void>;
208
+ clear(): Promise<void>;
209
+ }
210
+ interface PendingOp {
211
+ op: ClientOp;
212
+ status: OpStatus;
213
+ rejectedReason: ErrorReason | null;
214
+ createdAt: number;
215
+ /**
216
+ * Row state captured just before the optimistic apply. Restored on reject
217
+ * so a rejected optimistic insert collapses back to no-row (instead of
218
+ * leaving a tombstone), and a rejected update reverts to the merged row
219
+ * the client had before touching it.
220
+ */
221
+ preState?: {
222
+ data: Record<string, unknown> | null;
223
+ colClocks: Record<string, string>;
224
+ serverHlc: string | null;
225
+ } | null;
226
+ }
227
+ interface LocalRow {
228
+ table: string;
229
+ rowId: string;
230
+ data: Record<string, unknown> | null;
231
+ colClocks: Record<string, string>;
232
+ serverHlc: string | null;
233
+ }
234
+ declare class ClientStore {
235
+ private pendingOps;
236
+ private rows;
237
+ private tableMeta;
238
+ private storage;
239
+ private pendingWrites;
240
+ private writeChain;
241
+ constructor(storage?: ClientStorageAdapter);
242
+ /**
243
+ * Load durable state into memory.
244
+ *
245
+ * @param options.tables — hydrate only these tables. Rows only ever reach
246
+ * local storage through a subscription, so restoring just the tables the
247
+ * client is actually subscribed to reads no less than it needs. Omit to
248
+ * load every row of every table the client has ever synced.
249
+ */
250
+ hydrate(options?: {
251
+ tables?: string[];
252
+ }): Promise<void>;
253
+ flush(): Promise<void>;
254
+ private enqueue;
255
+ addPendingOp(op: ClientOp): void;
256
+ getPendingOps(): PendingOp[];
257
+ markSynced(opIds: string[]): void;
258
+ markRejected(opId: string, reason: ErrorReason): void;
259
+ rejectBatch(batchId: string, reason: ErrorReason): void;
260
+ private autoTrimRejected;
261
+ clearRejected(): void;
262
+ setRow(table: string, rowId: string, data: Record<string, unknown> | null, colClocks: Record<string, string>, serverHlc: string | null): void;
263
+ getRow(table: string, rowId: string): LocalRow | undefined;
264
+ /** Internal: includes tombstones for HLC comparison in applyDelta. */
265
+ private getRowEntry;
266
+ getRows(table: string, includeDeleted?: boolean): LocalRow[];
267
+ clearTable(table: string): void;
268
+ applySnapshot(table: string, rows: Record<string, unknown>[], colClocks: Record<string, Record<string, string>>, append?: boolean, pk?: string): void;
269
+ applyDelta(table: string, op: string, rowId: string, payload: Record<string, unknown> | null, hlc: string, colClocks?: Record<string, string>): void;
270
+ revertOp(opId: string, serverRow: Record<string, unknown> | null | undefined): void;
271
+ setTableMeta(meta: Record<string, {
272
+ serverSet: string[];
273
+ readonly: string[];
274
+ broadcast?: "consistent" | "eager" | "eager-durable";
275
+ pk?: string;
276
+ }>): void;
277
+ getTableMeta(table: string): {
278
+ serverSet: string[];
279
+ readonly: string[];
280
+ broadcast: "consistent" | "eager" | "eager-durable";
281
+ pk: string;
282
+ } | undefined;
283
+ applyOptimistic(op: ClientOp): void;
284
+ clear(): Promise<void>;
285
+ }
286
+ interface SyncClientConfig {
287
+ clientId: string;
288
+ transport: ClientTransport;
289
+ token: string;
290
+ storage?: ClientStorageAdapter;
291
+ onSync?: (table: string) => void;
292
+ /**
293
+ * Structured error callback. `reason` is drawn from the protocol's
294
+ * ErrorReason taxonomy when the error originates server-side; local
295
+ * failures (reauth/transport) use a "local:*" namespace so consumers
296
+ * can switch on the string.
297
+ */
298
+ onError?: (error: {
299
+ opId?: string;
300
+ batchId?: string;
301
+ table?: string;
302
+ reason: ErrorReason | `local:${string}`;
303
+ message?: string;
304
+ }) => void;
305
+ onReauth?: () => Promise<string>;
306
+ /** Max reconnect backoff in ms (before jitter). Default: 30_000. */
307
+ maxReconnectDelayMs?: number;
308
+ /**
309
+ * Load every stored row at boot instead of only the tables this client is
310
+ * subscribed to. Needed only if you read rows for a table before calling
311
+ * `sync()` on it. Default: false.
312
+ */
313
+ hydrateAllTables?: boolean;
314
+ }
315
+ type SyncClientState = "hydrating" | "disconnected" | "connecting" | "connected" | "bootstrapping" | "synced";
316
+ interface SyncOptions {
317
+ window?: number;
318
+ }
319
+ declare class SyncClient {
320
+ private config;
321
+ private transport;
322
+ private opCreator;
323
+ private store;
324
+ private state;
325
+ /** Version agreed on in hello_ack. Null until first successful handshake. */
326
+ private negotiatedProtocolVersion;
327
+ private syncedTables;
328
+ private serverHlc;
329
+ private listeners;
330
+ private version;
331
+ private tableVersions;
332
+ private tableListeners;
333
+ private connectResolve;
334
+ private connectReject;
335
+ private connectInFlight;
336
+ private ephemeralListeners;
337
+ private totalCounts;
338
+ private syncOptions;
339
+ private bootstrapScheduled;
340
+ private initialized;
341
+ private syncParams;
342
+ private reconnectTimer;
343
+ private reconnectAttempts;
344
+ private closed;
345
+ /**
346
+ * Ops already transmitted, mapped to when they were sent. Without this,
347
+ * every mutation re-sends the whole pending set: 50 rapid edits before the
348
+ * first ack land as 1+2+…+50 = 1275 ops on the wire, each one a `reserveOp`
349
+ * insert server-side. Cleared on disconnect so a reconnect resends them.
350
+ */
351
+ private inFlightOps;
352
+ /**
353
+ * How long an op may sit unacked before a later push retries it. A frame can
354
+ * be dropped without the connection dying — a full server-side queue, for
355
+ * one — and without this the op would stay marked in-flight, never re-sent,
356
+ * until the connection happens to drop. Resends are idempotent server-side.
357
+ */
358
+ private static readonly IN_FLIGHT_TTL_MS;
359
+ /** Tail of the serialized push chain; also the coalescing slot. */
360
+ private pushChain;
361
+ private pushPending;
362
+ constructor(config: SyncClientConfig);
363
+ init(): Promise<void>;
364
+ connect(): Promise<void>;
365
+ close(): Promise<void>;
366
+ getState(): SyncClientState;
367
+ /** Version negotiated with the server. Null before the first hello_ack. */
368
+ getProtocolVersion(): number | null;
369
+ sync(table: string, params?: Record<string, unknown>, options?: SyncOptions): Promise<void>;
370
+ loadMore(table: string, count: number): Promise<void>;
371
+ getTotalCount(table: string): number | null;
372
+ unsync(table: string): Promise<void>;
373
+ bootstrap(): Promise<void>;
374
+ /**
375
+ * Schedule a bootstrap in the next microtask.
376
+ * Multiple calls within the same tick are coalesced into one bootstrap.
377
+ */
378
+ scheduleBootstrap(): void;
379
+ resume(): Promise<void>;
380
+ insert(table: string, rowId: string, payload: Record<string, unknown>): ClientOp;
381
+ update(table: string, rowId: string, payload: Record<string, unknown>): ClientOp;
382
+ delete(table: string, rowId: string): ClientOp;
383
+ batch(ops: Array<{
384
+ table: string;
385
+ op: OpType;
386
+ rowId: string;
387
+ payload: Record<string, unknown> | null;
388
+ }>): ClientOp[];
389
+ /**
390
+ * Transmit pending ops.
391
+ *
392
+ * Concurrent calls are coalesced: at most one push runs and at most one
393
+ * more is queued behind it, so a burst of mutations (every `useSync`
394
+ * mutation calls this) produces one send per op rather than one send of
395
+ * everything pending per mutation. The returned promise resolves once the
396
+ * caller's own ops have been handed to the transport.
397
+ */
398
+ push(): Promise<void>;
399
+ private doPush;
400
+ /** Forget which ops are on the wire — used when the connection drops. */
401
+ private clearInFlight;
402
+ sendEphemeral(params: {
403
+ key: string;
404
+ userId: string;
405
+ data: Record<string, unknown>;
406
+ ttlMs?: number;
407
+ }): Promise<void>;
408
+ subscribeEphemeral(key: string, listener: (event: EphemeralEvent) => void): () => void;
409
+ getRows(table: string, options?: {
410
+ includeDeleted?: boolean;
411
+ }): Record<string, unknown>[];
412
+ getRow(table: string, rowId: string): Record<string, unknown> | null;
413
+ getPendingCount(): number;
414
+ getStore(): ClientStore;
415
+ /**
416
+ * Subscribe to store changes. Returns an unsubscribe function.
417
+ * Compatible with React's useSyncExternalStore.
418
+ */
419
+ subscribe(listener: () => void): () => void;
420
+ /**
421
+ * Subscribe to changes for a specific table. Returns an unsubscribe function.
422
+ * More efficient than subscribe() — only fires when the given table changes.
423
+ */
424
+ subscribeTable(table: string, listener: () => void): () => void;
425
+ /** Returns a version number that increments on every data change. */
426
+ getVersion(): number;
427
+ /** Returns a version number for a specific table. Only increments when that table changes. */
428
+ getTableVersion(table: string): number;
429
+ private notify;
430
+ /**
431
+ * Reset client state. With no argument, wipes everything (pending ops,
432
+ * rows, server HLC, subscriptions). With `tables`, only those tables'
433
+ * rows + subscriptions are dropped — useful for logging out of a single
434
+ * scope or evicting a stale dataset without losing pending ops elsewhere.
435
+ */
436
+ clear(opts?: {
437
+ tables?: string[];
438
+ }): Promise<void>;
439
+ private handleMessage;
440
+ private mergeServerHlc;
441
+ private persistMeta;
442
+ private persistSyncSubscriptions;
443
+ private handleReauth;
444
+ private scheduleReconnect;
445
+ }
446
+ interface Readable<T> {
447
+ subscribe(cb: (value: T) => void): () => void;
448
+ }
449
+ declare function createBrowserWsTransport(url: string): ClientTransport;
450
+ interface SyncStoreConfig {
451
+ url: string;
452
+ token: string;
453
+ tables?: string[];
454
+ clientId?: string;
455
+ storage?: ClientStorageAdapter;
456
+ onReauth?: () => Promise<string>;
457
+ onError?: (error: {
458
+ opId?: string;
459
+ reason: string;
460
+ }) => void;
461
+ }
462
+ interface SyncStore {
463
+ client: SyncClient;
464
+ status: Readable<SyncClientState>;
465
+ pendingCount: Readable<number>;
466
+ sync<T extends object = Record<string, unknown>>(table: string, options?: {
467
+ params?: Record<string, unknown>;
468
+ includeDeleted?: boolean;
469
+ window?: number;
470
+ }): {
471
+ rows: Readable<T[]>;
472
+ insert: (rowId: string, payload: Partial<T>) => void;
473
+ update: (rowId: string, payload: Partial<T>) => void;
474
+ remove: (rowId: string) => void;
475
+ };
476
+ row<T extends object = Record<string, unknown>>(table: string, rowId: string): Readable<T | null>;
477
+ ephemeral<T extends object>(config: {
478
+ key: string;
479
+ userId: string;
480
+ ttlMs?: number;
481
+ }): {
482
+ events: Readable<Record<string, T>>;
483
+ broadcast: (data: T) => Promise<void>;
484
+ destroy: () => void;
485
+ };
486
+ totalCount(table: string): Readable<number | null>;
487
+ loadMore(table: string, count: number): void;
488
+ connect(): Promise<void>;
489
+ close(): Promise<void>;
490
+ }
491
+ declare function createSyncStore(config: SyncStoreConfig): SyncStore;
492
+ interface DrizzleTableLike {
493
+ $inferSelect: Record<string, unknown>;
494
+ $inferInsert: Record<string, unknown>;
495
+ }
496
+ /**
497
+ * Value supplied per serverSet field in the schema's object form.
498
+ * Either a static value or a function that receives the request context.
499
+ */
500
+ type ServerSetSchemaValue = unknown | ((ctx: {
501
+ auth: unknown;
502
+ params: unknown;
503
+ }) => unknown);
504
+ /**
505
+ * Schema-side `serverSet` declaration. Two shapes:
506
+ * - `string[]` — keys only; values are supplied at `implement(...)` time.
507
+ * - `Record<string, ServerSetSchemaValue>` — keys + values collocated in schema.
508
+ */
509
+ type SchemaServerSet = readonly string[] | Readonly<Record<string, ServerSetSchemaValue>>;
510
+ interface SyncQueryDef {
511
+ /** Drizzle table — row type derived from $inferSelect */
512
+ table?: DrizzleTableLike;
513
+ /** Phantom row type for non-drizzle usage: `{} as MyRow` */
514
+ row?: Record<string, unknown>;
515
+ /** Database tables for change detection. Falls back to query key name. */
516
+ tables?: string[];
517
+ /** Phantom value for params type: `{} as { orgId: string }` */
518
+ params?: Record<string, unknown>;
519
+ /** Primary key column name. Defaults to "id". Single-column only. */
520
+ pk?: string;
521
+ conflict?: ConflictPolicy;
522
+ readonly?: readonly string[];
523
+ serverSet?: SchemaServerSet;
524
+ countHints?: boolean;
525
+ }
526
+ /**
527
+ * Read-only computed query. Compiles down to `server.query(...)` with a
528
+ * throwing mutate so the type system blocks `useSync(...).insert/update/remove`
529
+ * and the runtime rejects any direct write attempt.
530
+ */
531
+ interface SyncViewDef {
532
+ __view: true;
533
+ row?: Record<string, unknown>;
534
+ params?: Record<string, unknown>;
535
+ tables?: string[];
536
+ /** Tables to watch for change-detection. Defaults to `tables` or query key. */
537
+ deps?: string[];
538
+ }
539
+ /**
540
+ * Typed ephemeral channel. Sugar over the runtime `useEphemeral` hook —
541
+ * derives a stable key from the schema name + serialized params and gives
542
+ * `peers`/`set` a typed `state`.
543
+ */
544
+ interface SyncPresenceDef {
545
+ __presence: true;
546
+ state?: Record<string, unknown>;
547
+ params?: Record<string, unknown>;
548
+ ttlMs?: number;
549
+ }
550
+ type SyncQueryEntry = SyncQueryDef | SyncViewDef | SyncPresenceDef;
551
+ type SyncQueryMap = Record<string, SyncQueryEntry>;
552
+ /** Row type: from `table.$inferSelect` if drizzle, otherwise from `row` phantom */
553
+ type InferRow<
554
+ TQueries extends SyncQueryMap,
555
+ K extends keyof TQueries
556
+ > = TQueries[K] extends {
557
+ table: DrizzleTableLike;
558
+ } ? TQueries[K]["table"]["$inferSelect"] : TQueries[K] extends {
559
+ row: infer R extends Record<string, unknown>;
560
+ } ? R : Record<string, unknown>;
561
+ /** Params type. `undefined` when no params declared. */
562
+ type InferParams<
563
+ TQueries extends SyncQueryMap,
564
+ K extends keyof TQueries
565
+ > = TQueries[K] extends {
566
+ params: infer P extends Record<string, unknown>;
567
+ } ? P : undefined;
568
+ /** `true` when the query declares params */
569
+ type RequiresParams<
570
+ TQueries extends SyncQueryMap,
571
+ K extends keyof TQueries
572
+ > = TQueries[K] extends {
573
+ params: Record<string, unknown>;
574
+ } ? true : false;
575
+ /** Primary key column name. Defaults to "id". */
576
+ type PkOf<TDef> = TDef extends {
577
+ pk: infer P extends string;
578
+ } ? P : "id";
579
+ /** Extract serverSet field names from either array form or object form. */
580
+ type InferServerSetKeys<TDef> = TDef extends {
581
+ serverSet: readonly (infer S extends string)[];
582
+ } ? S : TDef extends {
583
+ serverSet: infer O extends Readonly<Record<string, unknown>>;
584
+ } ? keyof O & string : never;
585
+ /**
586
+ * Row minus readonly fields, serverSet fields, and the primary key.
587
+ * View defs collapse to `never` to block client-side writes at the type level.
588
+ * Presence defs aren't writable rows; they collapse to `never` too.
589
+ */
590
+ type InferWritableRow<
591
+ TQueries extends SyncQueryMap,
592
+ K extends keyof TQueries
593
+ > = TQueries[K] extends {
594
+ __view: true;
595
+ } | {
596
+ __presence: true;
597
+ } ? never : Omit<InferRow<TQueries, K>, (TQueries[K] extends {
598
+ readonly: readonly (infer R extends string)[];
599
+ } ? R : never) | InferServerSetKeys<TQueries[K]> | PkOf<TQueries[K]>>;
600
+ type SyncOptions2<
601
+ TQueries extends SyncQueryMap,
602
+ K extends keyof TQueries
603
+ > = RequiresParams<TQueries, K> extends true ? {
604
+ params: InferParams<TQueries, K>;
605
+ includeDeleted?: boolean;
606
+ window?: number;
607
+ } : {
608
+ params?: undefined;
609
+ includeDeleted?: boolean;
610
+ window?: number;
611
+ };
612
+ interface SyncResult<
613
+ TQueries extends SyncQueryMap,
614
+ K extends keyof TQueries
615
+ > {
616
+ rows: Readable<InferRow<TQueries, K>[]>;
617
+ insert: (rowId: string, payload: InferWritableRow<TQueries, K>) => void;
618
+ update: (rowId: string, payload: Partial<InferWritableRow<TQueries, K>>) => void;
619
+ remove: (rowId: string) => void;
620
+ }
621
+ interface TypedSyncStore<TQueries extends SyncQueryMap> {
622
+ client: SyncClient;
623
+ status: Readable<SyncClientState>;
624
+ pendingCount: Readable<number>;
625
+ sync<K extends keyof TQueries & string>(...args: RequiresParams<TQueries, K> extends true ? [table: K, options: SyncOptions2<TQueries, K>] : [table: K, options?: SyncOptions2<TQueries, K>]): SyncResult<TQueries, K>;
626
+ row<K extends keyof TQueries & string>(table: K, rowId: string): Readable<InferRow<TQueries, K> | null>;
627
+ ephemeral<T extends object>(config: {
628
+ key: string;
629
+ userId: string;
630
+ ttlMs?: number;
631
+ }): {
632
+ events: Readable<Record<string, T>>;
633
+ broadcast: (data: T) => Promise<void>;
634
+ destroy: () => void;
635
+ };
636
+ totalCount<K extends keyof TQueries & string>(table: K): Readable<number | null>;
637
+ loadMore<K extends keyof TQueries & string>(table: K, count: number): void;
638
+ connect(): Promise<void>;
639
+ close(): Promise<void>;
640
+ }
641
+ interface SyncSvelteHooks<TQueries extends SyncQueryMap> {
642
+ createStore: (config: SyncStoreConfig) => TypedSyncStore<TQueries>;
643
+ }
644
+ declare function createSyncSvelte<TQueries extends SyncQueryMap>(): SyncSvelteHooks<TQueries>;
645
+ export { createSyncSvelte, createSyncStore, createBrowserWsTransport, TypedSyncStore, SyncSvelteHooks, SyncStoreConfig, SyncStore, Readable };