@sqlite-sync/core 0.9.4 → 0.10.1
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-CugbiosG.js +508 -0
- package/dist/crdt-sync-remote-source-CugbiosG.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-D0jdmcYF.d.ts} +36 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.js +47 -50
- 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-DNPkxGim.js} +39 -429
- package/dist/reset-state-DNPkxGim.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 +59 -10
- 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;
|
|
@@ -931,6 +935,8 @@ type WorkerNotificationMessage = {
|
|
|
931
935
|
} | {
|
|
932
936
|
notificationType: "state-changed";
|
|
933
937
|
state: WorkerState;
|
|
938
|
+
/** Id of the worker's persisted database. A wiped database gets a new one. */
|
|
939
|
+
storageGeneration: string;
|
|
934
940
|
} | {
|
|
935
941
|
notificationType: "reload-requested";
|
|
936
942
|
reloadEpoch: string;
|
|
@@ -998,8 +1004,17 @@ type EventsAvailable = {
|
|
|
998
1004
|
newSyncId: number;
|
|
999
1005
|
remoteEventHlcSum: string | null;
|
|
1000
1006
|
};
|
|
1007
|
+
/**
|
|
1008
|
+
* Opens a connection to the remote. Each call must return an independent source: the library calls
|
|
1009
|
+
* `disconnect` on every returned source exactly once, including one that resolves after `signal`
|
|
1010
|
+
* has aborted. `signal` aborts when the connection attempt is cancelled or the connection is closed.
|
|
1011
|
+
* Call `onReconnected` when the transport re-establishes itself without a new source: broadcasts
|
|
1012
|
+
* sent while it was down are lost, so the library pulls and pushes to catch up.
|
|
1013
|
+
*/
|
|
1001
1014
|
type CreateRemoteSourceFactory = (opts: {
|
|
1002
1015
|
onEventsAvailable: (event: EventsAvailable) => void;
|
|
1016
|
+
onReconnected: () => void;
|
|
1017
|
+
signal: AbortSignal;
|
|
1003
1018
|
}) => RemoteSource | Promise<RemoteSource>;
|
|
1004
1019
|
type RemoteSource = {
|
|
1005
1020
|
pullEvents: (request: EventsPullRequest) => Promise<EventsPullResponse>;
|
|
@@ -1017,7 +1032,6 @@ type RemoteSourceState = {
|
|
|
1017
1032
|
schemaVersionMismatched: boolean;
|
|
1018
1033
|
} | {
|
|
1019
1034
|
type: "online";
|
|
1020
|
-
source: RemoteSource;
|
|
1021
1035
|
deSynced: boolean;
|
|
1022
1036
|
schemaVersionMismatched: boolean;
|
|
1023
1037
|
};
|
|
@@ -1025,16 +1039,8 @@ type OfflineReason = "NOT_INITIALIZED" | "INITIALIZATION_FAILED" | "REMOTE_PUSH_
|
|
|
1025
1039
|
type DeSyncDetectedReason = "CHECKSUM_MISMATCH" | "ERROR_APPLYING_REMOTE_EVENT";
|
|
1026
1040
|
declare const createCrdtSyncRemoteSource: ({ bufferSize, storage, migrator, pullSyncId, pushSyncId, nodeId, remoteFactory }: CrdtSyncRemoteSourceConfig) => {
|
|
1027
1041
|
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
|
-
};
|
|
1042
|
+
goOffline: (reason: OfflineReason) => Promise<void>;
|
|
1043
|
+
syncWithRemote: () => Promise<void>;
|
|
1038
1044
|
getState: () => WorkerState;
|
|
1039
1045
|
dispose: () => Promise<void>;
|
|
1040
1046
|
addEventListener: <K extends "state-changed" | "de-sync-detected" | "remote-schema-version-mismatch">(type: K, listener: (event: TypedEvent<{
|
|
@@ -1068,10 +1074,15 @@ declare function makeCrdtTable({ db, baseTableName, crdtTableName }: {
|
|
|
1068
1074
|
baseTableName: string;
|
|
1069
1075
|
crdtTableName: string;
|
|
1070
1076
|
}): void;
|
|
1071
|
-
|
|
1077
|
+
type CrdtViewColumn = {
|
|
1078
|
+
name: string;
|
|
1079
|
+
nullable: boolean;
|
|
1080
|
+
};
|
|
1081
|
+
declare function crdtViewColumnsFromTable(table: AnyTableBuilder): CrdtViewColumn[];
|
|
1082
|
+
declare function createCrdtViewStatements({ baseTableName, crdtTableName, columns }: {
|
|
1072
1083
|
baseTableName: string;
|
|
1073
1084
|
crdtTableName: string;
|
|
1074
|
-
|
|
1085
|
+
columns: CrdtViewColumn[];
|
|
1075
1086
|
}): string[];
|
|
1076
1087
|
declare function drainCrdtChangeIntents({ tx, storage }: {
|
|
1077
1088
|
tx: InternalSQLiteTransactionWrapper<any>;
|
|
@@ -1124,6 +1135,7 @@ declare function createSyncedDb<Database, Props = undefined>(options: SyncedDbOp
|
|
|
1124
1135
|
subscribe: (onChange: () => void) => () => void;
|
|
1125
1136
|
goOnline: () => Promise<void>;
|
|
1126
1137
|
goOffline: () => Promise<void>;
|
|
1138
|
+
sync: () => void;
|
|
1127
1139
|
};
|
|
1128
1140
|
/**
|
|
1129
1141
|
* Ask the elected worker to broadcast a page reload to all tabs for this dbId.
|
|
@@ -1143,6 +1155,7 @@ declare function createSyncedDb<Database, Props = undefined>(options: SyncedDbOp
|
|
|
1143
1155
|
"state-changed": {
|
|
1144
1156
|
notificationType: "state-changed";
|
|
1145
1157
|
state: WorkerState;
|
|
1158
|
+
storageGeneration: string;
|
|
1146
1159
|
};
|
|
1147
1160
|
"de-sync-detected": {
|
|
1148
1161
|
notificationType: "de-sync-detected";
|
|
@@ -1206,5 +1219,5 @@ type ResetStore = {
|
|
|
1206
1219
|
*/
|
|
1207
1220
|
declare const RESET_REQUEST_TTL_MS: number;
|
|
1208
1221
|
//#endregion
|
|
1209
|
-
export {
|
|
1210
|
-
//# sourceMappingURL=index-
|
|
1222
|
+
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 };
|
|
1223
|
+
//# sourceMappingURL=index-D0jdmcYF.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-D0jdmcYF.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-DNPkxGim.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-CugbiosG.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,14 +734,16 @@ 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);
|
|
768
741
|
postWorkerConfig(worker, config);
|
|
769
742
|
rpc.postState().catch(noop);
|
|
770
|
-
let workerState = await statePromise;
|
|
743
|
+
let { state: workerState, storageGeneration } = await statePromise;
|
|
771
744
|
eventTarget.addEventListener("state-changed", (event) => {
|
|
772
745
|
workerState = event.payload.state;
|
|
746
|
+
storageGeneration = event.payload.storageGeneration;
|
|
773
747
|
});
|
|
774
748
|
const dispose = () => {
|
|
775
749
|
isDisposed = true;
|
|
@@ -783,13 +757,14 @@ const createWorkerDbClient = async ({ broadcastChannels, worker, config }) => {
|
|
|
783
757
|
...rpc,
|
|
784
758
|
subscribe: eventTarget.addEventListener,
|
|
785
759
|
getState: () => workerState,
|
|
760
|
+
getStorageGeneration: () => storageGeneration,
|
|
786
761
|
dispose
|
|
787
762
|
};
|
|
788
763
|
};
|
|
789
764
|
function awaitWorkerState(eventTarget) {
|
|
790
765
|
const promise = createDeferredPromise({ timeout: 15e3 });
|
|
791
766
|
const subscription = eventTarget.addEventListener("state-changed", (event) => {
|
|
792
|
-
promise.resolve(event.payload
|
|
767
|
+
promise.resolve(event.payload);
|
|
793
768
|
subscription.unsubscribe();
|
|
794
769
|
});
|
|
795
770
|
return promise.promise;
|
|
@@ -921,6 +896,24 @@ async function createSyncedDb(options) {
|
|
|
921
896
|
const reloadRequestedSubscription = workerClient.subscribe("reload-requested", () => {
|
|
922
897
|
globalThis.location?.reload();
|
|
923
898
|
});
|
|
899
|
+
const reloadIfStorageReplaced = (storageGeneration) => {
|
|
900
|
+
if (storageGeneration !== workerClientSnapshot.storageGeneration) {
|
|
901
|
+
storageReplacedSubscription.unsubscribe();
|
|
902
|
+
globalThis.location?.reload();
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
const storageReplacedSubscription = workerClient.subscribe("state-changed", (event) => {
|
|
906
|
+
reloadIfStorageReplaced(event.payload.storageGeneration);
|
|
907
|
+
});
|
|
908
|
+
reloadIfStorageReplaced(workerClient.getStorageGeneration());
|
|
909
|
+
const sync = () => {
|
|
910
|
+
workerClient.sync().catch((error) => console.warn("Failed to start sync", error));
|
|
911
|
+
};
|
|
912
|
+
const syncWhenVisible = () => {
|
|
913
|
+
if (globalThis.document?.visibilityState === "visible") sync();
|
|
914
|
+
};
|
|
915
|
+
globalThis.document?.addEventListener("visibilitychange", syncWhenVisible);
|
|
916
|
+
globalThis.addEventListener?.("online", sync);
|
|
924
917
|
perf.logEnd("createSyncedDb", "initialized", "info");
|
|
925
918
|
const exportData = createExportData({
|
|
926
919
|
reactiveDb,
|
|
@@ -934,7 +927,10 @@ async function createSyncedDb(options) {
|
|
|
934
927
|
if (isDisposed) return;
|
|
935
928
|
isDisposed = true;
|
|
936
929
|
unregisterDevtools?.();
|
|
930
|
+
globalThis.document?.removeEventListener("visibilitychange", syncWhenVisible);
|
|
931
|
+
globalThis.removeEventListener?.("online", sync);
|
|
937
932
|
reloadRequestedSubscription.unsubscribe();
|
|
933
|
+
storageReplacedSubscription.unsubscribe();
|
|
938
934
|
clientLockRelease.resolve();
|
|
939
935
|
await tabRemoteSource.dispose();
|
|
940
936
|
broadcastChannels.requests.close();
|
|
@@ -951,7 +947,8 @@ async function createSyncedDb(options) {
|
|
|
951
947
|
return unsubscribe;
|
|
952
948
|
},
|
|
953
949
|
goOnline: workerClient.goOnline.bind(workerClient),
|
|
954
|
-
goOffline: workerClient.goOffline.bind(workerClient)
|
|
950
|
+
goOffline: workerClient.goOffline.bind(workerClient),
|
|
951
|
+
sync
|
|
955
952
|
},
|
|
956
953
|
/**
|
|
957
954
|
* Ask the elected worker to broadcast a page reload to all tabs for this dbId.
|