@sqlite-sync/core 0.9.4 → 0.10.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.
- package/dist/admit-client-events-BDrJw4SK.js +35 -0
- package/dist/admit-client-events-BDrJw4SK.js.map +1 -0
- package/dist/crdt-sync-remote-source-Dtvyrk0R.js +504 -0
- package/dist/crdt-sync-remote-source-Dtvyrk0R.js.map +1 -0
- package/dist/{utils-Dr5JwcTP.js → crdt-table-schema-2w_AIpsV.js} +9 -2
- package/dist/crdt-table-schema-2w_AIpsV.js.map +1 -0
- package/dist/{index-Dbbd4flq.d.ts → index-B4s8GFWF.d.ts} +33 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.js +30 -48
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +3 -3
- package/dist/{make-crdt-table-CvpTHmRY.js → make-crdt-table-D8VQ-ptj.js} +23 -14
- package/dist/make-crdt-table-D8VQ-ptj.js.map +1 -0
- package/dist/{reset-state-4Hn95CKG.js → reset-state-BVWEcT_z.js} +39 -429
- package/dist/reset-state-BVWEcT_z.js.map +1 -0
- package/dist/server.d.ts +30 -2
- package/dist/server.js +103 -3
- package/dist/server.js.map +1 -1
- package/dist/{system-schema-CkglcfTS.js → system-schema-C2JZDv6L.js} +3 -10
- package/dist/system-schema-C2JZDv6L.js.map +1 -0
- package/dist/worker.d.ts +3 -2
- package/dist/worker.js +52 -8
- package/dist/worker.js.map +1 -1
- package/package.json +2 -1
- package/dist/make-crdt-table-CvpTHmRY.js.map +0 -1
- package/dist/reset-state-4Hn95CKG.js.map +0 -1
- package/dist/system-schema-CkglcfTS.js.map +0 -1
- package/dist/utils-Dr5JwcTP.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crdt-table-schema-2w_AIpsV.js","names":[],"sources":["../src/utils.ts","../src/sqlite-crdt/crdt-table-schema.ts"],"sourcesContent":["export type DeferredPromise<T> = {\n promise: Promise<T>;\n resolve: (value: T) => void;\n reject: (error: Error) => void;\n};\n\ntype DeferredPromiseOptions = {\n timeout?: number;\n onTimeout?: () => void;\n};\n\nexport function createDeferredPromise<T>(opts?: DeferredPromiseOptions): DeferredPromise<T> {\n let resolve!: (value: T | PromiseLike<T>) => void;\n let reject!: (reason?: unknown) => void;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const promise = new Promise<T>((_resolve, _reject) => {\n resolve = (value) => {\n if (timeoutId) clearTimeout(timeoutId);\n _resolve(value);\n };\n reject = (reason) => {\n if (timeoutId) clearTimeout(timeoutId);\n _reject(reason);\n };\n\n if (opts?.timeout) {\n timeoutId = setTimeout(rejectTimeout, opts.timeout, _reject, opts);\n }\n });\n\n return { promise, resolve, reject };\n}\n\nfunction rejectTimeout(reject: (reason?: unknown) => void, opts: DeferredPromiseOptions) {\n reject(new Error(`Promise timed out after ${opts.timeout}ms`));\n tryCatch(() => opts?.onTimeout?.());\n}\n\nexport const generateId = () => {\n return crypto.randomUUID();\n};\n\nexport type DistributiveOmit<T, K extends keyof T> = T extends any ? Omit<T, K> : never;\n\nexport function ensureSingletonExecution<T, TArgs extends any[]>(\n fn: (...args: TArgs) => Promise<T>,\n opts: { queueReExecution?: boolean } = { queueReExecution: true },\n) {\n let executingPromise: Promise<T> | null = null;\n let shouldReExecute = false;\n\n const wrappedFn = (...args: TArgs) => {\n if (executingPromise) {\n shouldReExecute = true;\n return executingPromise;\n }\n\n executingPromise = fn(...args).finally(() => {\n executingPromise = null;\n\n if (shouldReExecute && opts?.queueReExecution) {\n shouldReExecute = false;\n wrappedFn(...args);\n }\n });\n return executingPromise;\n };\n\n wrappedFn.promise = () => executingPromise;\n wrappedFn.isExecuting = () => !!executingPromise;\n\n return wrappedFn;\n}\n\nexport function createAutoFlushBuffer<T>({ size, flush }: { size: number; flush: (items: T[]) => void }) {\n const buffer: T[] = [];\n\n return {\n add(item: T) {\n buffer.push(item);\n if (buffer.length >= size) {\n flush(buffer);\n buffer.length = 0;\n }\n },\n flush() {\n flush(buffer);\n buffer.length = 0;\n },\n };\n}\n\nexport function createAsyncAutoFlushBuffer<T>({\n size,\n flush,\n}: {\n size: number;\n flush: (items: T[]) => void | Promise<void>;\n}) {\n const buffer: T[] = [];\n\n return {\n async add(item: T) {\n buffer.push(item);\n if (buffer.length >= size) {\n await this.flush();\n }\n },\n async flush() {\n const itemsToFlush = buffer.splice(0);\n if (itemsToFlush.length === 0) {\n return;\n }\n await flush(itemsToFlush);\n },\n };\n}\n\nexport class TypedBroadcastChannel<TMessage> {\n private readonly channel: BroadcastChannel;\n\n constructor(name: string) {\n this.channel = new BroadcastChannel(name);\n }\n\n postMessage(message: TMessage) {\n this.channel.postMessage(message);\n }\n\n set onmessage(callback: ((event: MessageEvent<TMessage>) => void) | null) {\n this.channel.onmessage = callback;\n }\n\n close() {\n this.channel.close();\n }\n}\n\nexport class TypedEvent<T = unknown> extends Event {\n readonly payload: T;\n constructor(type: string, payload: T) {\n super(type);\n this.payload = payload;\n }\n}\n\nexport type TypedEventTarget<T extends Record<string, unknown>> = {\n addEventListener: <K extends keyof T & string>(\n type: K,\n listener: (event: TypedEvent<T[K]>) => void,\n ) => { unsubscribe: () => void };\n removeEventListener: <K extends keyof T & string>(type: K, listener: (event: TypedEvent<T[K]>) => void) => void;\n dispatchEvent: <K extends keyof T & string>(type: K, payload: T[K]) => void;\n};\n\nexport const createTypedEventTarget = <T extends Record<string, unknown>>(): TypedEventTarget<T> => {\n const eventTarget = new EventTarget();\n\n const addEventListener = <K extends keyof T & string>(type: K, listener: (event: TypedEvent<T[K]>) => void) => {\n eventTarget.addEventListener(type, listener as (e: Event) => void);\n\n return {\n unsubscribe: () => {\n eventTarget.removeEventListener(type, listener as (e: Event) => void);\n },\n };\n };\n\n const removeEventListener = <K extends keyof T & string>(type: K, listener: (event: TypedEvent<T[K]>) => void) => {\n eventTarget.removeEventListener(type, listener as (e: Event) => void);\n };\n\n const dispatchEvent = <K extends keyof T & string>(type: K, payload: T[K]) => {\n eventTarget.dispatchEvent(new TypedEvent(type, payload));\n };\n\n return {\n addEventListener,\n removeEventListener,\n dispatchEvent,\n };\n};\n\ntype TryCatchResult<T> =\n | {\n success: true;\n data: T;\n }\n | {\n success: false;\n error: unknown;\n };\n\nexport function tryCatch<T>(fn: () => T): TryCatchResult<T> {\n try {\n return {\n success: true,\n data: fn(),\n };\n } catch (error) {\n return {\n success: false,\n error,\n };\n }\n}\n\nexport async function tryCatchAsync<T>(fn: () => Promise<T>): Promise<TryCatchResult<T>> {\n try {\n return {\n success: true,\n data: await fn(),\n };\n } catch (error) {\n return {\n success: false,\n error,\n };\n }\n}\n\nexport function jsonSafeParse<T>(json: string) {\n return tryCatch(() => JSON.parse(json) as T);\n}\n\n/** Quote a SQLite identifier (table/column name), handling dot-separated schema qualifiers. */\nexport function quoteId(name: string): string {\n return name\n .split(\".\")\n .map((s) => `\"${s.replace(/\"/g, '\"\"')}\"`)\n .join(\".\");\n}\n\nexport function noop(..._args: unknown[]): void {}\n\nexport type ParsedTableName = {\n schema: \"main\" | (string & {});\n table: string;\n fullIdentifier: string;\n};\n\nexport function parseTableName(tableName: string): ParsedTableName {\n if (!tableName?.trim()) {\n throw new Error(\"Parse table: missing table name\");\n }\n const parts = tableName.split(\".\");\n if (parts.length > 2) {\n throw new Error(\"Parse table: too many dot-delimited segments in table name\");\n }\n\n return parts.length === 1\n ? {\n schema: \"main\",\n table: parts[0],\n fullIdentifier: parts[0],\n }\n : {\n schema: parts[0],\n table: parts[1],\n fullIdentifier: `${parts[0]}.${parts[1]}`,\n };\n}\n","import type { SchemaModule } from \"kysely\";\n\nexport type NewCrdtEvent = {\n type: CrdtEventType;\n dataset: string;\n item_id: string;\n payload: Record<string, unknown>;\n};\n\nexport type CrdtEventType = \"item-created\" | \"item-updated\" | \"item-deleted\";\n\nexport type CrdtEventStatus = \"pending\" | \"applied\" | \"failed\" | \"deduped\";\n\n/** Persisted on applied events that were accepted but did not mutate materialized state. */\nexport const CRDT_EVENT_NO_OP_PAYLOAD = \"no-op\";\n\nexport function isNoOpCrdtEventPayload(payload: string) {\n return payload === CRDT_EVENT_NO_OP_PAYLOAD;\n}\n\nexport type CrdtEventOrigin = \"remote\" | \"own\" | \"own-applied\" | \"local\";\n\nexport type PersistedCrdtEvent = {\n schema_version: number;\n sync_id: number;\n status: CrdtEventStatus;\n type: CrdtEventType;\n timestamp: string;\n origin: CrdtEventOrigin;\n source_node_id: string;\n dataset: string;\n item_id: string;\n payload: string;\n};\n\nexport type CrdtUpdateLogItem = {\n dataset: string;\n item_id: string;\n payload: string;\n};\n\nexport type CrdtUpdateLogPayload = Record<string, string>;\n\nexport const crdtSchema = {\n persistedEventsTable: createPersistedEventsTable,\n crdtUpdateLogTable: createCrdtUpdateLogTableQuery,\n};\n\nfunction createPersistedEventsTable(schema: SchemaModule, tableName: string) {\n return schema\n .createTable(tableName)\n .ifNotExists()\n .addColumn(\"sync_id\", \"integer\", (col) => col.notNull().primaryKey())\n .addColumn(\"schema_version\", \"integer\", (col) => col.notNull())\n .addColumn(\"status\", \"text\", (col) => col.notNull())\n .addColumn(\"type\", \"text\", (col) => col.notNull())\n .addColumn(\"timestamp\", \"text\", (col) => col.notNull())\n .addColumn(\"origin\", \"text\", (col) => col.notNull())\n .addColumn(\"dataset\", \"text\", (col) => col.notNull())\n .addColumn(\"item_id\", \"text\", (col) => col.notNull())\n .addColumn(\"payload\", \"text\", (col) => col.notNull());\n}\n\nfunction createCrdtUpdateLogTableQuery(schema: SchemaModule, tableName: string) {\n return schema\n .createTable(tableName)\n .ifNotExists()\n .addColumn(\"dataset\", \"text\", (col) => col.notNull())\n .addColumn(\"item_id\", \"text\", (col) => col.notNull())\n .addColumn(\"payload\", \"text\", (col) => col.notNull())\n .addPrimaryKeyConstraint(`pk_${tableName}`, [\"item_id\", \"dataset\"]);\n}\n"],"mappings":"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"}
|
|
@@ -237,7 +237,8 @@ declare class TableBuilder<Cols extends TableColumns, BaseName extends string |
|
|
|
237
237
|
get $row(): InferRow<Cols>;
|
|
238
238
|
/**
|
|
239
239
|
* Validate a CRDT event payload against the declared columns.
|
|
240
|
-
* `item-created` payloads must contain every column without a default (nullable columns may be omitted)
|
|
240
|
+
* `item-created` payloads must contain every column without a default (nullable columns may be omitted)
|
|
241
|
+
* and their `tombstone` value is ignored, since applying a create always sets it to 0;
|
|
241
242
|
* `item-updated` payloads are partial and must not touch `id` or set `tombstone` to a deleted value.
|
|
242
243
|
*/
|
|
243
244
|
validatePayload(payload: Record<string, unknown>, options?: {
|
|
@@ -291,7 +292,10 @@ declare class HLCCounter {
|
|
|
291
292
|
constructor(nodeId: string, getTimestamp: () => number, maxDrift?: number);
|
|
292
293
|
getCurrentHLC(): HLC;
|
|
293
294
|
getNextHLC(): HLC;
|
|
295
|
+
/** Restore already-persisted causal state, regardless of wall-clock drift. */
|
|
296
|
+
restoreHLC(hlc: HLC): void;
|
|
294
297
|
mergeHLC(hlc: HLC): void;
|
|
298
|
+
private rollOverCounter;
|
|
295
299
|
}
|
|
296
300
|
declare function serializeHLC(hlc: HLC): string;
|
|
297
301
|
declare function deserializeHLC(serialized: string): HLC;
|
|
@@ -574,7 +578,7 @@ type EnqueueEventsResult = {
|
|
|
574
578
|
/** Resolves when the enqueued events have been processed (applied/deduped/failed). */
|
|
575
579
|
processed: Promise<void>;
|
|
576
580
|
};
|
|
577
|
-
type StorageHLC = Pick<HLCCounter, "getNextHLC" | "mergeHLC">;
|
|
581
|
+
type StorageHLC = Pick<HLCCounter, "getNextHLC" | "mergeHLC" | "restoreHLC">;
|
|
578
582
|
type DbSyncerStorage = {
|
|
579
583
|
nodeId: string;
|
|
580
584
|
initialLocalSyncId: number;
|
|
@@ -766,13 +770,13 @@ type AdmitClientEventsResult<T> = {
|
|
|
766
770
|
admitted: T[];
|
|
767
771
|
skipped: T[];
|
|
768
772
|
};
|
|
769
|
-
type SchemaForAdmitClientEvents = Pick<SyncDbSchema, "tables" | "tablesConfig"
|
|
770
|
-
|
|
771
|
-
};
|
|
773
|
+
type SchemaForAdmitClientEvents = Pick<SyncDbSchema, "tables" | "tablesConfig">;
|
|
774
|
+
type ClientDatasetRejectionReason = "server-only-dataset" | "undeclared-dataset";
|
|
772
775
|
/**
|
|
773
|
-
* Splits a client push into events the hub should persist and events
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
+
* Splits a client push into events the hub should persist and events it must drop. Only
|
|
777
|
+
* datasets that exactly match the base table name of a declared table without
|
|
778
|
+
* `{ writes: "server" }` are admitted; server-only tables, crdt view names, and undeclared
|
|
779
|
+
* datasets are skipped.
|
|
776
780
|
*/
|
|
777
781
|
declare function admitClientEvents<T extends {
|
|
778
782
|
dataset: string;
|
|
@@ -998,8 +1002,17 @@ type EventsAvailable = {
|
|
|
998
1002
|
newSyncId: number;
|
|
999
1003
|
remoteEventHlcSum: string | null;
|
|
1000
1004
|
};
|
|
1005
|
+
/**
|
|
1006
|
+
* Opens a connection to the remote. Each call must return an independent source: the library calls
|
|
1007
|
+
* `disconnect` on every returned source exactly once, including one that resolves after `signal`
|
|
1008
|
+
* has aborted. `signal` aborts when the connection attempt is cancelled or the connection is closed.
|
|
1009
|
+
* Call `onReconnected` when the transport re-establishes itself without a new source: broadcasts
|
|
1010
|
+
* sent while it was down are lost, so the library pulls and pushes to catch up.
|
|
1011
|
+
*/
|
|
1001
1012
|
type CreateRemoteSourceFactory = (opts: {
|
|
1002
1013
|
onEventsAvailable: (event: EventsAvailable) => void;
|
|
1014
|
+
onReconnected: () => void;
|
|
1015
|
+
signal: AbortSignal;
|
|
1003
1016
|
}) => RemoteSource | Promise<RemoteSource>;
|
|
1004
1017
|
type RemoteSource = {
|
|
1005
1018
|
pullEvents: (request: EventsPullRequest) => Promise<EventsPullResponse>;
|
|
@@ -1017,7 +1030,6 @@ type RemoteSourceState = {
|
|
|
1017
1030
|
schemaVersionMismatched: boolean;
|
|
1018
1031
|
} | {
|
|
1019
1032
|
type: "online";
|
|
1020
|
-
source: RemoteSource;
|
|
1021
1033
|
deSynced: boolean;
|
|
1022
1034
|
schemaVersionMismatched: boolean;
|
|
1023
1035
|
};
|
|
@@ -1025,16 +1037,8 @@ type OfflineReason = "NOT_INITIALIZED" | "INITIALIZATION_FAILED" | "REMOTE_PUSH_
|
|
|
1025
1037
|
type DeSyncDetectedReason = "CHECKSUM_MISMATCH" | "ERROR_APPLYING_REMOTE_EVENT";
|
|
1026
1038
|
declare const createCrdtSyncRemoteSource: ({ bufferSize, storage, migrator, pullSyncId, pushSyncId, nodeId, remoteFactory }: CrdtSyncRemoteSourceConfig) => {
|
|
1027
1039
|
goOnline: () => Promise<void>;
|
|
1028
|
-
goOffline:
|
|
1029
|
-
|
|
1030
|
-
promise(): Promise<void> | null;
|
|
1031
|
-
isExecuting(): boolean;
|
|
1032
|
-
};
|
|
1033
|
-
syncWithRemote: {
|
|
1034
|
-
(): Promise<void>;
|
|
1035
|
-
promise(): Promise<void> | null;
|
|
1036
|
-
isExecuting(): boolean;
|
|
1037
|
-
};
|
|
1040
|
+
goOffline: (reason: OfflineReason) => Promise<void>;
|
|
1041
|
+
syncWithRemote: () => Promise<void>;
|
|
1038
1042
|
getState: () => WorkerState;
|
|
1039
1043
|
dispose: () => Promise<void>;
|
|
1040
1044
|
addEventListener: <K extends "state-changed" | "de-sync-detected" | "remote-schema-version-mismatch">(type: K, listener: (event: TypedEvent<{
|
|
@@ -1068,10 +1072,15 @@ declare function makeCrdtTable({ db, baseTableName, crdtTableName }: {
|
|
|
1068
1072
|
baseTableName: string;
|
|
1069
1073
|
crdtTableName: string;
|
|
1070
1074
|
}): void;
|
|
1071
|
-
|
|
1075
|
+
type CrdtViewColumn = {
|
|
1076
|
+
name: string;
|
|
1077
|
+
nullable: boolean;
|
|
1078
|
+
};
|
|
1079
|
+
declare function crdtViewColumnsFromTable(table: AnyTableBuilder): CrdtViewColumn[];
|
|
1080
|
+
declare function createCrdtViewStatements({ baseTableName, crdtTableName, columns }: {
|
|
1072
1081
|
baseTableName: string;
|
|
1073
1082
|
crdtTableName: string;
|
|
1074
|
-
|
|
1083
|
+
columns: CrdtViewColumn[];
|
|
1075
1084
|
}): string[];
|
|
1076
1085
|
declare function drainCrdtChangeIntents({ tx, storage }: {
|
|
1077
1086
|
tx: InternalSQLiteTransactionWrapper<any>;
|
|
@@ -1124,6 +1133,7 @@ declare function createSyncedDb<Database, Props = undefined>(options: SyncedDbOp
|
|
|
1124
1133
|
subscribe: (onChange: () => void) => () => void;
|
|
1125
1134
|
goOnline: () => Promise<void>;
|
|
1126
1135
|
goOffline: () => Promise<void>;
|
|
1136
|
+
sync: () => void;
|
|
1127
1137
|
};
|
|
1128
1138
|
/**
|
|
1129
1139
|
* Ask the elected worker to broadcast a page reload to all tabs for this dbId.
|
|
@@ -1206,5 +1216,5 @@ type ResetStore = {
|
|
|
1206
1216
|
*/
|
|
1207
1217
|
declare const RESET_REQUEST_TTL_MS: number;
|
|
1208
1218
|
//#endregion
|
|
1209
|
-
export {
|
|
1210
|
-
//# sourceMappingURL=index-
|
|
1219
|
+
export { createExportData as $, SyncDbSchema as $t, PendingCrdtEvent as A, PersistedCrdtEvent as An, ExecuteParams as At, DefinedSyncSchema as B, Logger as Bt, WorkerConfig as C, createStoredValue as Cn, jsonSafeParse as Ct, CrdtStorageMutator as D, CrdtEventType as Dn, KvStoreItem as Dt, createCrdtSyncProducer as E, CrdtEventStatus as En, tryCatchAsync as Et, verifySyncSchema as F, PreparedStatement as Ft, SQLiteReactiveDb as G, introspectDb as Gt, AdmitClientEventsResult as H, ColumnMetadata as Ht, CrdtEventValidationError as I, QueryBuilderOutput as It, parametersAreEqual as J, compareHLC as Jt, SharedLiveQuery as K, HLC as Kt, NewCrdtEventValidationResult as L, SQLiteDbWrapper as Lt, createSQLiteCrdtApplyFunction as M, dummyKysely as Mn, InternalSQLiteWrapper as Mt, SchemaVerificationIssue as N, KyselyQueryFactory as Nt, SnapshotOptions as O, CrdtUpdateLogItem as On, createKvStoreTableQuery as Ot, formatSchemaVerificationIssues as P, KyselyStatementFactory as Pt, SyncedDbExport as Q, ReadonlyTable as Qt, validateNewCrdtEvent as R, SQLiteTransactionWrapper as Rt, EventsPullResponse as S, StoredValue as Sn, generateId as St, WorkerState as T, CrdtEventOrigin as Tn, tryCatch as Tt, ClientDatasetRejectionReason as U, DatabaseIntrospection as Ut, defineSyncSchema as V, startPerformanceLogger as Vt, admitClientEvents as W, TableMetadata as Wt, ImportDataOptions as X, serializeHLC as Xt, xxhash as Y, deserializeHLC as Yt, ImportDataResult as Z, CrdtTableConfig as Zt, CreateRemoteSourceFactory as _, Migrations as _n, TypedBroadcastChannel as _t, SyncedDbDatabase as a, ColumnMeta as an, OwnCrdtSnapshot as at, EventsPushResponse as b, createMigrations as bn, createDeferredPromise as bt, SyncedDbTransaction as c, PayloadValidationResult as cn, SystemMigration as ct, CRDT_CHANGE_INTENTS_TABLE as d, TableBuilder as dn, applyWorkerDbSchema as dt, AiAccess as en, createImportData as et, crdtViewColumnsFromTable as f, TableColumns as fn, baseSystemMigrations as ft, CrdtSyncRemoteSource as g, MigratableEvent as gn, DistributiveOmit as gt, makeCrdtTable as h, t as hn, DeferredPromise as ht, SyncedDb as i, ColumnKind as in, OwnCrdtEvent as it, createCrdtApplyFunction as j, isNoOpCrdtEventPayload as jn, ExecuteResult as jt, createCrdtStorageMutator as k, CrdtUpdateLogPayload as kn, createSQLiteKvStore as kt, UnsafeSyncedDbSqlExecutor as l, SqliteStorageType as ln, SystemMigrationContext as lt, drainCrdtChangeIntents as m, WriteOrigin as mn, runSystemMigrations as mt, ResetRequest as n, AnyTableBuilder as nn, CrdtStorage as nt, SyncedDbOptions as o, ExportImportMode as on, createCrdtStorage as ot, createCrdtViewStatements as p, TableOptions as pn, createSystemDbConfig as pt, SharedLiveQuerySnapshot as q, HLCCounter as qt, ResetStore as r, ColumnBuilder as rn, GetEventsBatch as rt, SyncedDbSqlExecutor as s, InferRow as sn, SystemDbConfig as st, RESET_REQUEST_TTL_MS as t, AnyColumnBuilder as tn, CrdtChangeIntent as tt, createSyncedDb as u, SyncSchemaTables as un, applyMemoryDbSchema as ut, EventsPullRequest as v, MigrationsDb as vn, TypedEvent as vt, WorkerNotificationMessage as w, CRDT_EVENT_NO_OP_PAYLOAD as wn, quoteId as wt, createCrdtSyncRemoteSource as x, createMigrator as xn, createTypedEventTarget as xt, EventsPushRequest as y, SyncDbMigrator as yn, TypedEventTarget as yt, DefineSyncSchemaConfig as z, LogLevel as zt };
|
|
1220
|
+
//# sourceMappingURL=index-B4s8GFWF.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $t as
|
|
1
|
+
import { $ as createExportData, $t as SyncDbSchema, A as PendingCrdtEvent, An as PersistedCrdtEvent, At as ExecuteParams, B as DefinedSyncSchema, Bt as Logger, C as WorkerConfig, Cn as createStoredValue, Ct as jsonSafeParse, D as CrdtStorageMutator, Dn as CrdtEventType, Dt as KvStoreItem, E as createCrdtSyncProducer, En as CrdtEventStatus, Et as tryCatchAsync, F as verifySyncSchema, Ft as PreparedStatement, G as SQLiteReactiveDb, Gt as introspectDb, H as AdmitClientEventsResult, Ht as ColumnMetadata, I as CrdtEventValidationError, It as QueryBuilderOutput, J as parametersAreEqual, Jt as compareHLC, K as SharedLiveQuery, Kt as HLC, L as NewCrdtEventValidationResult, Lt as SQLiteDbWrapper, M as createSQLiteCrdtApplyFunction, Mn as dummyKysely, N as SchemaVerificationIssue, Nt as KyselyQueryFactory, O as SnapshotOptions, On as CrdtUpdateLogItem, Ot as createKvStoreTableQuery, P as formatSchemaVerificationIssues, Q as SyncedDbExport, Qt as ReadonlyTable, R as validateNewCrdtEvent, Rt as SQLiteTransactionWrapper, Sn as StoredValue, St as generateId, T as WorkerState, Tn as CrdtEventOrigin, Tt as tryCatch, Ut as DatabaseIntrospection, V as defineSyncSchema, Vt as startPerformanceLogger, W as admitClientEvents, Wt as TableMetadata, X as ImportDataOptions, Xt as serializeHLC, Y as xxhash, Yt as deserializeHLC, Z as ImportDataResult, Zt as CrdtTableConfig, _n as Migrations, _t as TypedBroadcastChannel, a as SyncedDbDatabase, an as ColumnMeta, at as OwnCrdtSnapshot, b as EventsPushResponse, bn as createMigrations, bt as createDeferredPromise, c as SyncedDbTransaction, cn as PayloadValidationResult, ct as SystemMigration, dn as TableBuilder, dt as applyWorkerDbSchema, en as AiAccess, et as createImportData, fn as TableColumns, g as CrdtSyncRemoteSource, gn as MigratableEvent, gt as DistributiveOmit, h as makeCrdtTable, hn as t, ht as DeferredPromise, i as SyncedDb, in as ColumnKind, it as OwnCrdtEvent, j as createCrdtApplyFunction, jn as isNoOpCrdtEventPayload, jt as ExecuteResult, k as createCrdtStorageMutator, kn as CrdtUpdateLogPayload, kt as createSQLiteKvStore, l as UnsafeSyncedDbSqlExecutor, ln as SqliteStorageType, lt as SystemMigrationContext, mn as WriteOrigin, n as ResetRequest, nn as AnyTableBuilder, nt as CrdtStorage, o as SyncedDbOptions, on as ExportImportMode, ot as createCrdtStorage, pn as TableOptions, q as SharedLiveQuerySnapshot, qt as HLCCounter, r as ResetStore, rn as ColumnBuilder, s as SyncedDbSqlExecutor, sn as InferRow, st as SystemDbConfig, t as RESET_REQUEST_TTL_MS, tn as AnyColumnBuilder, u as createSyncedDb, un as SyncSchemaTables, ut as applyMemoryDbSchema, v as EventsPullRequest, vn as MigrationsDb, vt as TypedEvent, w as WorkerNotificationMessage, wn as CRDT_EVENT_NO_OP_PAYLOAD, wt as quoteId, x as createCrdtSyncRemoteSource, xn as createMigrator, xt as createTypedEventTarget, y as EventsPushRequest, yn as SyncDbMigrator, yt as TypedEventTarget, z as DefineSyncSchemaConfig, zt as LogLevel } from "./index-B4s8GFWF.js";
|
|
2
2
|
export { type AdmitClientEventsResult, type AiAccess, type AnyColumnBuilder, type AnyTableBuilder, CRDT_EVENT_NO_OP_PAYLOAD, ColumnBuilder, type ColumnKind, type ColumnMeta, type ColumnMetadata, type CrdtEventOrigin, type CrdtEventStatus, type CrdtEventType, CrdtEventValidationError, type CrdtStorage, type CrdtStorageMutator, type CrdtSyncRemoteSource, type CrdtTableConfig, type CrdtUpdateLogItem, type CrdtUpdateLogPayload, type DatabaseIntrospection, type DeferredPromise, type DefineSyncSchemaConfig, type DefinedSyncSchema, type DistributiveOmit, type EventsPullRequest, type EventsPushRequest, type EventsPushResponse, type ExecuteParams, type ExecuteResult, type ExportImportMode, type HLC, HLCCounter, type ImportDataOptions, type ImportDataResult, type InferRow, type KvStoreItem, type KyselyQueryFactory, type LogLevel, type Logger, type MigratableEvent, type Migrations, type MigrationsDb, type NewCrdtEventValidationResult, type OwnCrdtEvent, type OwnCrdtSnapshot, type PayloadValidationResult, type PendingCrdtEvent, type PersistedCrdtEvent, type PreparedStatement, type QueryBuilderOutput, RESET_REQUEST_TTL_MS, type ReadonlyTable, type ResetRequest, type ResetStore, SQLiteDbWrapper, SQLiteReactiveDb, type SQLiteTransactionWrapper, type SchemaVerificationIssue, type SharedLiveQuery, type SharedLiveQuerySnapshot, type SnapshotOptions, type SqliteStorageType, type StoredValue, type SyncDbMigrator, type SyncDbSchema, type SyncSchemaTables, type SyncedDb, type SyncedDbDatabase, type SyncedDbExport, type SyncedDbOptions, type SyncedDbSqlExecutor, type SyncedDbTransaction, type SystemDbConfig, type SystemMigration, type SystemMigrationContext, TableBuilder, type TableColumns, type TableMetadata, type TableOptions, TypedBroadcastChannel, TypedEvent, type TypedEventTarget, type UnsafeSyncedDbSqlExecutor, type WorkerConfig, type WorkerNotificationMessage, type WorkerState, type WriteOrigin, admitClientEvents, applyMemoryDbSchema, applyWorkerDbSchema, compareHLC, createCrdtApplyFunction, createCrdtStorage, createCrdtStorageMutator, createCrdtSyncProducer, createCrdtSyncRemoteSource, createDeferredPromise, createExportData, createImportData, createKvStoreTableQuery, createMigrations, createMigrator, createSQLiteCrdtApplyFunction, createSQLiteKvStore, createStoredValue, createSyncedDb, createTypedEventTarget, defineSyncSchema, deserializeHLC, dummyKysely, formatSchemaVerificationIssues, generateId, introspectDb, isNoOpCrdtEventPayload, jsonSafeParse, makeCrdtTable, parametersAreEqual, quoteId, serializeHLC, startPerformanceLogger, t, tryCatch, tryCatchAsync, validateNewCrdtEvent, verifySyncSchema, xxhash };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { a as
|
|
4
|
-
import { a as
|
|
1
|
+
import { C as introspectDb, D as dummyKysely, E as createImportData, S as startPerformanceLogger, T as createExportData, _ as formatSchemaVerificationIssues, a as createBroadcastChannels, b as createMigrator, c as isWorkerNotificationMessage, d as syncDbClientLockName, g as createSQLiteCrdtApplyFunction, h as createCrdtApplyFunction, m as createCrdtStorage, o as isWorkerErrorResponseMessage, p as createCrdtSyncProducer, t as RESET_REQUEST_TTL_MS, u as isWorkerResponseMessage, v as verifySyncSchema, w as xxhash, x as SQLiteDbWrapper, y as createMigrations } from "./reset-state-BVWEcT_z.js";
|
|
2
|
+
import { a as createDeferredPromise, c as generateId, f as quoteId, i as TypedEvent, l as jsonSafeParse, m as tryCatchAsync, n as isNoOpCrdtEventPayload, o as createTypedEventTarget, p as tryCatch, r as TypedBroadcastChannel, t as CRDT_EVENT_NO_OP_PAYLOAD, u as noop } from "./crdt-table-schema-2w_AIpsV.js";
|
|
3
|
+
import { a as HLCCounter, c as serializeHLC, n as CrdtEventValidationError, o as compareHLC, r as validateNewCrdtEvent, s as deserializeHLC, t as createCrdtSyncRemoteSource } from "./crdt-sync-remote-source-Dtvyrk0R.js";
|
|
4
|
+
import { a as memoryDbConfig, c as createKvStoreTableQuery, l as createSQLiteKvStore, n as applyWorkerDbSchema, t as applyMemoryDbSchema, u as createStoredValue } from "./system-schema-C2JZDv6L.js";
|
|
5
|
+
import { n as buildWriteOriginByName, t as admitClientEvents } from "./admit-client-events-BDrJw4SK.js";
|
|
6
|
+
import { a as makeCrdtTable, o as registerCrdtIntentDrainer } from "./make-crdt-table-D8VQ-ptj.js";
|
|
5
7
|
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
|
6
8
|
//#region src/bound-map.ts
|
|
7
9
|
var BoundMap = class {
|
|
@@ -83,12 +85,14 @@ var SQLiteReactiveDb = class SQLiteReactiveDb {
|
|
|
83
85
|
});
|
|
84
86
|
sharedLiveQueries = /* @__PURE__ */ new Map();
|
|
85
87
|
createLiveQuery(query) {
|
|
88
|
+
let rowsVersion = 0;
|
|
86
89
|
const fetchRows = (parameters) => {
|
|
87
90
|
let statement = this.liveQueryStatements.get(query.sql);
|
|
88
91
|
if (!statement) {
|
|
89
92
|
statement = this.db.prepare(query.sql);
|
|
90
93
|
this.liveQueryStatements.set(query.sql, statement);
|
|
91
94
|
}
|
|
95
|
+
rowsVersion = this.mutationVersion;
|
|
92
96
|
return statement.execute(parameters);
|
|
93
97
|
};
|
|
94
98
|
let rows = null;
|
|
@@ -110,6 +114,7 @@ var SQLiteReactiveDb = class SQLiteReactiveDb {
|
|
|
110
114
|
sql: query.sql,
|
|
111
115
|
onDataChange: refresh
|
|
112
116
|
});
|
|
117
|
+
if (rows && this.mutationVersion !== rowsVersion) refresh();
|
|
113
118
|
return () => {
|
|
114
119
|
subscription.unsubscribe();
|
|
115
120
|
subscriber = null;
|
|
@@ -128,7 +133,6 @@ var SQLiteReactiveDb = class SQLiteReactiveDb {
|
|
|
128
133
|
return existingEntry;
|
|
129
134
|
}
|
|
130
135
|
const liveQuery = this.createLiveQuery(query);
|
|
131
|
-
let hasDetachedFromLiveQuery = false;
|
|
132
136
|
const entry = {
|
|
133
137
|
sql: query.sql,
|
|
134
138
|
parameters: query.parameters,
|
|
@@ -144,21 +148,14 @@ var SQLiteReactiveDb = class SQLiteReactiveDb {
|
|
|
144
148
|
this.cancelSharedLiveQueryCleanup(entry);
|
|
145
149
|
this.registerSharedLiveQueryEntry(entry);
|
|
146
150
|
entry.listeners.add(onchange);
|
|
147
|
-
if (!entry.unsubscribeFromLiveQuery) {
|
|
148
|
-
entry.
|
|
149
|
-
|
|
150
|
-
});
|
|
151
|
-
if (hasDetachedFromLiveQuery) {
|
|
152
|
-
hasDetachedFromLiveQuery = false;
|
|
153
|
-
liveQuery.refresh();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
151
|
+
if (!entry.unsubscribeFromLiveQuery) entry.unsubscribeFromLiveQuery = liveQuery.subscribe(() => {
|
|
152
|
+
for (const listener of entry.listeners) listener();
|
|
153
|
+
});
|
|
156
154
|
return () => {
|
|
157
155
|
entry.listeners.delete(onchange);
|
|
158
156
|
if (entry.listeners.size === 0) {
|
|
159
157
|
entry.unsubscribeFromLiveQuery?.();
|
|
160
158
|
entry.unsubscribeFromLiveQuery = null;
|
|
161
|
-
hasDetachedFromLiveQuery = true;
|
|
162
159
|
this.scheduleSharedLiveQueryCleanup(entry);
|
|
163
160
|
}
|
|
164
161
|
};
|
|
@@ -289,6 +286,7 @@ var SQLiteReactiveDb = class SQLiteReactiveDb {
|
|
|
289
286
|
}
|
|
290
287
|
useSnapshot(snapshot) {
|
|
291
288
|
this.db.useSnapshot(snapshot);
|
|
289
|
+
this.mutationVersion++;
|
|
292
290
|
this.notifyTableSubscribers();
|
|
293
291
|
}
|
|
294
292
|
dispose() {
|
|
@@ -333,33 +331,6 @@ function parametersAreEqual(a, b) {
|
|
|
333
331
|
return true;
|
|
334
332
|
}
|
|
335
333
|
//#endregion
|
|
336
|
-
//#region src/schema/admit-client-events.ts
|
|
337
|
-
function buildWriteOriginByName(schema) {
|
|
338
|
-
const writeOriginByName = /* @__PURE__ */ new Map();
|
|
339
|
-
for (const { crdtTableName, baseTableName } of schema.tablesConfig) {
|
|
340
|
-
const writeOrigin = schema.tables[crdtTableName]?.writeOrigin ?? "any";
|
|
341
|
-
writeOriginByName.set(crdtTableName, writeOrigin);
|
|
342
|
-
writeOriginByName.set(baseTableName, writeOrigin);
|
|
343
|
-
}
|
|
344
|
-
return writeOriginByName;
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Splits a client push into events the hub should persist and events for tables with
|
|
348
|
-
* `{ writes: "server" }`. Unknown datasets stay admitted. Matches either the crdt or the base
|
|
349
|
-
* table name.
|
|
350
|
-
*/
|
|
351
|
-
function admitClientEvents(opts) {
|
|
352
|
-
const writeOriginByName = opts.syncDbSchema.writeOriginByName ?? buildWriteOriginByName(opts.syncDbSchema);
|
|
353
|
-
const admitted = [];
|
|
354
|
-
const skipped = [];
|
|
355
|
-
for (const event of opts.events) if (writeOriginByName.get(event.dataset) === "server") skipped.push(event);
|
|
356
|
-
else admitted.push(event);
|
|
357
|
-
return {
|
|
358
|
-
admitted,
|
|
359
|
-
skipped
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
//#endregion
|
|
363
334
|
//#region src/schema/define-sync-schema.ts
|
|
364
335
|
const RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set([
|
|
365
336
|
"change_history",
|
|
@@ -506,24 +477,25 @@ var TableBuilder = class TableBuilder {
|
|
|
506
477
|
}
|
|
507
478
|
/**
|
|
508
479
|
* Validate a CRDT event payload against the declared columns.
|
|
509
|
-
* `item-created` payloads must contain every column without a default (nullable columns may be omitted)
|
|
480
|
+
* `item-created` payloads must contain every column without a default (nullable columns may be omitted)
|
|
481
|
+
* and their `tombstone` value is ignored, since applying a create always sets it to 0;
|
|
510
482
|
* `item-updated` payloads are partial and must not touch `id` or set `tombstone` to a deleted value.
|
|
511
483
|
*/
|
|
512
484
|
validatePayload(payload, options) {
|
|
513
485
|
const event = options?.event ?? "item-created";
|
|
514
486
|
const errors = [];
|
|
515
|
-
for (const key of Object.keys(payload)) if (!(
|
|
487
|
+
for (const key of Object.keys(payload)) if (!Object.hasOwn(this.columns, key)) errors.push(`Unknown column "${key}"`);
|
|
516
488
|
if (event === "item-updated") {
|
|
517
489
|
if ("id" in payload) errors.push(`Column "id" is immutable and cannot appear in an update payload`);
|
|
518
490
|
if (payload.tombstone === true || payload.tombstone === 1) errors.push(`Column "tombstone" cannot be set to true/1 in an update payload; use an item-deleted event`);
|
|
519
491
|
}
|
|
520
492
|
if (event === "item-created") for (const [name, meta] of Object.entries(this.columns)) {
|
|
521
|
-
if (name
|
|
493
|
+
if (Object.hasOwn(payload, name) || name === "tombstone" || meta.nullable || meta.hasDefault) continue;
|
|
522
494
|
errors.push(`Missing required column "${name}"`);
|
|
523
495
|
}
|
|
524
496
|
for (const [name, value] of Object.entries(payload)) {
|
|
525
|
-
const meta = this.columns[name];
|
|
526
|
-
if (!meta || event === "item-updated" && name === "id") continue;
|
|
497
|
+
const meta = Object.hasOwn(this.columns, name) ? this.columns[name] : void 0;
|
|
498
|
+
if (!meta || event === "item-updated" && name === "id" || event === "item-created" && name === "tombstone") continue;
|
|
527
499
|
const error = validateValue(name, value, meta);
|
|
528
500
|
if (error) errors.push(error);
|
|
529
501
|
}
|
|
@@ -762,6 +734,7 @@ const createWorkerDbClient = async ({ broadcastChannels, worker, config }) => {
|
|
|
762
734
|
postState: () => queryWorker("postState", []),
|
|
763
735
|
goOnline: () => queryWorker("goOnline", []),
|
|
764
736
|
goOffline: () => queryWorker("goOffline", []),
|
|
737
|
+
sync: () => queryWorker("sync", []),
|
|
765
738
|
requestReload: (options) => queryWorker("requestReload", [options])
|
|
766
739
|
};
|
|
767
740
|
const statePromise = awaitWorkerState(eventTarget);
|
|
@@ -921,6 +894,13 @@ async function createSyncedDb(options) {
|
|
|
921
894
|
const reloadRequestedSubscription = workerClient.subscribe("reload-requested", () => {
|
|
922
895
|
globalThis.location?.reload();
|
|
923
896
|
});
|
|
897
|
+
const sync = () => {
|
|
898
|
+
workerClient.sync().catch((error) => console.warn("Failed to start sync", error));
|
|
899
|
+
};
|
|
900
|
+
const syncWhenVisible = () => {
|
|
901
|
+
if (globalThis.document?.visibilityState === "visible") sync();
|
|
902
|
+
};
|
|
903
|
+
globalThis.document?.addEventListener("visibilitychange", syncWhenVisible);
|
|
924
904
|
perf.logEnd("createSyncedDb", "initialized", "info");
|
|
925
905
|
const exportData = createExportData({
|
|
926
906
|
reactiveDb,
|
|
@@ -934,6 +914,7 @@ async function createSyncedDb(options) {
|
|
|
934
914
|
if (isDisposed) return;
|
|
935
915
|
isDisposed = true;
|
|
936
916
|
unregisterDevtools?.();
|
|
917
|
+
globalThis.document?.removeEventListener("visibilitychange", syncWhenVisible);
|
|
937
918
|
reloadRequestedSubscription.unsubscribe();
|
|
938
919
|
clientLockRelease.resolve();
|
|
939
920
|
await tabRemoteSource.dispose();
|
|
@@ -951,7 +932,8 @@ async function createSyncedDb(options) {
|
|
|
951
932
|
return unsubscribe;
|
|
952
933
|
},
|
|
953
934
|
goOnline: workerClient.goOnline.bind(workerClient),
|
|
954
|
-
goOffline: workerClient.goOffline.bind(workerClient)
|
|
935
|
+
goOffline: workerClient.goOffline.bind(workerClient),
|
|
936
|
+
sync
|
|
955
937
|
},
|
|
956
938
|
/**
|
|
957
939
|
* Ask the elected worker to broadcast a page reload to all tabs for this dbId.
|