@sqlite-sync/core 0.13.1 → 0.13.3
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/{crdt-sync-producer-BY-WobgO.js → crdt-sync-producer-t2jHxO38.js} +36 -13
- package/dist/crdt-sync-producer-t2jHxO38.js.map +1 -0
- package/dist/{index-CH4lf3iX.d.ts → index-B3s_6aG6.d.ts} +8 -4
- package/dist/{index.common-C8W9VKSY.js → index.common-Dp8CNMk4.js} +4 -4
- package/dist/{index.common-C8W9VKSY.js.map → index.common-Dp8CNMk4.js.map} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/index.native.d.ts +1 -1
- package/dist/index.native.js +4 -4
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +2 -2
- package/dist/local.d.ts +2 -2
- package/dist/local.js +8 -6
- package/dist/local.js.map +1 -1
- package/dist/{reset-state-CPOi_45B.js → reset-state-Bz1sfbf3.js} +2 -2
- package/dist/{reset-state-CPOi_45B.js.map → reset-state-Bz1sfbf3.js.map} +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{sqlite-reactive-db-DLS1xRZp.js → sqlite-reactive-db-BwY3FoIy.js} +2 -2
- package/dist/{sqlite-reactive-db-DLS1xRZp.js.map → sqlite-reactive-db-BwY3FoIy.js.map} +1 -1
- package/dist/{system-schema-CXf3FmxN.js → system-schema-uHQdQTCk.js} +20 -4
- package/dist/{system-schema-CXf3FmxN.js.map → system-schema-uHQdQTCk.js.map} +1 -1
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +4 -4
- package/dist/worker.js.map +1 -1
- package/dist/{ws-remote-source-DFBhDc6Z.d.ts → ws-remote-source-Dgd9dmMl.d.ts} +2 -2
- package/package.json +1 -1
- package/dist/crdt-sync-producer-BY-WobgO.js.map +0 -1
package/dist/index.native.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as parametersAreEqual, $t as
|
|
1
|
+
import { $ as parametersAreEqual, $t as startPerformanceLogger, A as WorkerState, An as MigrationsDb, At as tryCatch, B as verifySyncSchemaWithDriver, Bn as CrdtUpdateLogItem, C as EventsPullRequest, Cn as TableBuilder, Ct as TypedEvent, Dn as t, Dt as generateId, E as createCrdtSyncRemoteSource, En as WriteOrigin, Et as createTypedEventTarget, F as PendingCrdtEvent, Fn as createStoredValue, Ft as createSQLiteKvStore, G as DefinedSyncSchema, H as NewCrdtEventValidationResult, Hn as PersistedCrdtEvent, Ht as QueryBuilderOutput, I as createCrdtApplyFunction, In as CRDT_EVENT_NO_OP_PAYLOAD, It as ExecuteParams, Jt as SqliteDriverRows, K as defineSyncSchema, Kt as SqliteDriver, L as createSQLiteCrdtApplyFunction, Ln as CrdtEventOrigin, Lt as ExecuteResult, M as CrdtStorageMutator, Mn as createMigrations, Mt as KvStoreItem, N as SnapshotOptions, Nn as createMigrator, O as WorkerConfig, On as MigratableEvent, Ot as jsonSafeParse, P as createCrdtStorageMutator, Pn as StoredValue, Pt as createKvStoreTableQuery, Q as SharedLiveQuerySnapshot, Qt as Logger, R as SchemaVerificationIssue, Rn as CrdtEventStatus, Sn as SyncSchemaTables, St as TypedBroadcastChannel, T as EventsPushResponse, Tn as TableOptions, Tt as createDeferredPromise, U as validateNewCrdtEvent, Un as isNoOpCrdtEventPayload, Ut as SQLiteDbWrapper, V as CrdtEventValidationError, Vn as CrdtUpdateLogPayload, Vt as PreparedStatement, W as DefineSyncSchemaConfig, Wn as dummyKysely, Wt as SQLiteTransactionWrapper, X as SQLiteReactiveDb, Xt as SqliteDriverValue, Y as admitClientEvents, Yt as SqliteDriverStatement, Z as SharedLiveQuery, Zt as LogLevel, _n as ColumnMeta, an as HLCCounter, at as createImportData, b as makeCrdtTable, bn as PayloadValidationResult, bt as DeferredPromise, c as ResetStore, cn as serializeHLC, dn as SyncDbSchema, dt as createCrdtStorage, en as ColumnMetadata, et as xxhash, f as SyncedDbDatabase, fn as AiAccess, ft as SystemDbConfig, gn as ColumnKind, gt as applyWorkerDbSchema, h as UnsafeSyncedDbSqlExecutor, hn as ColumnBuilder, ht as applyMemoryDbSchema, in as HLC, it as createExportData, j as createCrdtSyncProducer, jn as SyncDbMigrator, jt as tryCatchAsync, k as WorkerNotificationMessage, kn as Migrations, kt as quoteId, l as SyncedDb, ln as CrdtTableConfig, lt as OwnCrdtEvent, m as SyncedDbTransaction, mn as AnyTableBuilder, mt as SystemMigrationContext, nn as TableMetadata, nt as ImportDataResult, on as compareHLC, p as SyncedDbSqlExecutor, pn as AnyColumnBuilder, pt as SystemMigration, q as AdmitClientEventsResult, qt as SqliteDriverHooks, rn as introspectDb, rt as SyncedDbExport, s as ResetRequest, sn as deserializeHLC, st as CrdtStorage, tn as DatabaseIntrospection, tt as ImportDataOptions, u as SyncedDbOptions, un as ReadonlyTable, ut as OwnCrdtSnapshot, vn as ExportImportMode, w as EventsPushRequest, wn as TableColumns, wt as TypedEventTarget, x as CrdtSyncRemoteSource, xn as SqliteStorageType, xt as DistributiveOmit, yn as InferRow, z as formatSchemaVerificationIssues, zn as CrdtEventType, zt as KyselyQueryFactory } from "./index-B3s_6aG6.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, type ReadonlyTable, type ResetRequest, type ResetStore, SQLiteDbWrapper, SQLiteReactiveDb, type SQLiteTransactionWrapper, type SchemaVerificationIssue, type SharedLiveQuery, type SharedLiveQuerySnapshot, type SnapshotOptions, type SqliteDriver, type SqliteDriverHooks, type SqliteDriverRows, type SqliteDriverStatement, type SqliteDriverValue, 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, createTypedEventTarget, defineSyncSchema, deserializeHLC, dummyKysely, formatSchemaVerificationIssues, generateId, introspectDb, isNoOpCrdtEventPayload, jsonSafeParse, makeCrdtTable, parametersAreEqual, quoteId, serializeHLC, startPerformanceLogger, t, tryCatch, tryCatchAsync, validateNewCrdtEvent, verifySyncSchemaWithDriver, xxhash };
|
package/dist/index.native.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { b as
|
|
1
|
+
import { b as introspectDb, g as SQLiteDbWrapper, h as createStoredValue, i as applyWorkerDbSchema, m as createSQLiteKvStore, p as createKvStoreTableQuery, r as applyMemoryDbSchema, x as dummyKysely, y as startPerformanceLogger } from "./system-schema-uHQdQTCk.js";
|
|
2
2
|
import { h as tryCatchAsync, i as TypedEvent, l as generateId, m as tryCatch, n as isNoOpCrdtEventPayload, o as createDeferredPromise, p as quoteId, r as TypedBroadcastChannel, s as createTypedEventTarget, t as CRDT_EVENT_NO_OP_PAYLOAD, u as jsonSafeParse } from "./crdt-table-schema-D6Yf61Ex.js";
|
|
3
|
-
import { a as formatSchemaVerificationIssues, c as createMigrator, i as createSQLiteCrdtApplyFunction, l as createExportData, n as createCrdtStorage, o as verifySyncSchemaWithDriver, r as createCrdtApplyFunction, s as createMigrations, t as createCrdtSyncProducer, u as createImportData } from "./crdt-sync-producer-
|
|
3
|
+
import { a as formatSchemaVerificationIssues, c as createMigrator, i as createSQLiteCrdtApplyFunction, l as createExportData, n as createCrdtStorage, o as verifySyncSchemaWithDriver, r as createCrdtApplyFunction, s as createMigrations, t as createCrdtSyncProducer, u as createImportData } from "./crdt-sync-producer-t2jHxO38.js";
|
|
4
4
|
import { c as deserializeHLC, i as validateNewCrdtEvent, l as serializeHLC, o as HLCCounter, r as CrdtEventValidationError, s as compareHLC, t as createCrdtSyncRemoteSource, u as xxhash } from "./crdt-sync-remote-source-yeTEkOjH.js";
|
|
5
|
-
import { n as parametersAreEqual, t as SQLiteReactiveDb } from "./sqlite-reactive-db-
|
|
5
|
+
import { n as parametersAreEqual, t as SQLiteReactiveDb } from "./sqlite-reactive-db-BwY3FoIy.js";
|
|
6
6
|
import { t as admitClientEvents } from "./admit-client-events-BDrJw4SK.js";
|
|
7
|
-
import { a as defineSyncSchema, i as t, n as ColumnBuilder, r as TableBuilder, t as createCrdtStorageMutator } from "./index.common-
|
|
7
|
+
import { a as defineSyncSchema, i as t, n as ColumnBuilder, r as TableBuilder, t as createCrdtStorageMutator } from "./index.common-Dp8CNMk4.js";
|
|
8
8
|
import { a as makeCrdtTable } from "./make-crdt-table-DYCXOYB7.js";
|
|
9
9
|
export { CRDT_EVENT_NO_OP_PAYLOAD, ColumnBuilder, CrdtEventValidationError, HLCCounter, SQLiteDbWrapper, SQLiteReactiveDb, TableBuilder, TypedBroadcastChannel, TypedEvent, admitClientEvents, applyMemoryDbSchema, applyWorkerDbSchema, compareHLC, createCrdtApplyFunction, createCrdtStorage, createCrdtStorageMutator, createCrdtSyncProducer, createCrdtSyncRemoteSource, createDeferredPromise, createExportData, createImportData, createKvStoreTableQuery, createMigrations, createMigrator, createSQLiteCrdtApplyFunction, createSQLiteKvStore, createStoredValue, createTypedEventTarget, defineSyncSchema, deserializeHLC, dummyKysely, formatSchemaVerificationIssues, generateId, introspectDb, isNoOpCrdtEventPayload, jsonSafeParse, makeCrdtTable, parametersAreEqual, quoteId, serializeHLC, startPerformanceLogger, t, tryCatch, tryCatchAsync, validateNewCrdtEvent, verifySyncSchemaWithDriver, xxhash };
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { CRDT_CHANGE_INTENTS_TABLE, type CrdtChangeIntent, type InternalSQLiteWrapper, type KyselyStatementFactory, baseSystemMigrations, compileKyselyStatement, crdtViewColumnsFromTable, createCrdtViewStatements, createSystemDbConfig, drainCrdtChangeIntents, runSystemMigrations };
|
|
1
|
+
import { Bt as KyselyStatementFactory, Gt as compileKyselyStatement, Nt as STATE_HASH_KV_KEY, Rt as InternalSQLiteWrapper, _ as crdtViewColumnsFromTable, _t as baseSystemMigrations, g as CRDT_CHANGE_INTENTS_TABLE, ot as CrdtChangeIntent, v as createCrdtViewStatements, vt as createSystemDbConfig, y as drainCrdtChangeIntents, yt as runSystemMigrations } from "./index-B3s_6aG6.js";
|
|
2
|
+
export { CRDT_CHANGE_INTENTS_TABLE, type CrdtChangeIntent, type InternalSQLiteWrapper, type KyselyStatementFactory, STATE_HASH_KV_KEY, baseSystemMigrations, compileKyselyStatement, crdtViewColumnsFromTable, createCrdtViewStatements, createSystemDbConfig, drainCrdtChangeIntents, runSystemMigrations };
|
package/dist/internal.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as baseSystemMigrations,
|
|
1
|
+
import { _ as compileKyselyStatement, a as baseSystemMigrations, d as STATE_HASH_KV_KEY, l as runSystemMigrations, o as createSystemDbConfig } from "./system-schema-uHQdQTCk.js";
|
|
2
2
|
import { i as drainCrdtChangeIntents, n as crdtViewColumnsFromTable, r as createCrdtViewStatements, t as CRDT_CHANGE_INTENTS_TABLE } from "./make-crdt-table-DYCXOYB7.js";
|
|
3
|
-
export { CRDT_CHANGE_INTENTS_TABLE, baseSystemMigrations, compileKyselyStatement, crdtViewColumnsFromTable, createCrdtViewStatements, createSystemDbConfig, drainCrdtChangeIntents, runSystemMigrations };
|
|
3
|
+
export { CRDT_CHANGE_INTENTS_TABLE, STATE_HASH_KV_KEY, baseSystemMigrations, compileKyselyStatement, crdtViewColumnsFromTable, createCrdtViewStatements, createSystemDbConfig, drainCrdtChangeIntents, runSystemMigrations };
|
package/dist/local.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as EventsPullRequest,
|
|
2
|
-
import { t as createWsRemoteSource } from "./ws-remote-source-
|
|
1
|
+
import { C as EventsPullRequest, Jt as SqliteDriverRows, Kt as SqliteDriver, Qt as Logger, S as CreateRemoteSourceFactory, T as EventsPushResponse, Xt as SqliteDriverValue, Yt as SqliteDriverStatement, dn as SyncDbSchema, l as SyncedDb, qt as SqliteDriverHooks, w as EventsPushRequest } from "./index-B3s_6aG6.js";
|
|
2
|
+
import { t as createWsRemoteSource } from "./ws-remote-source-Dgd9dmMl.js";
|
|
3
3
|
//#region src/local/local-synced-db.d.ts
|
|
4
4
|
type LocalSyncedDbOptions<Database> = {
|
|
5
5
|
dbId: string;
|
package/dist/local.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as createKvStateHashStore, m as createSQLiteKvStore, n as applyLocalDbSchema, p as createKvStoreTableQuery, s as localDbConfig, t as LOCAL_KV_TABLE_NAME } from "./system-schema-uHQdQTCk.js";
|
|
2
2
|
import { l as generateId, s as createTypedEventTarget } from "./crdt-table-schema-D6Yf61Ex.js";
|
|
3
|
-
import { a as formatSchemaVerificationIssues, c as createMigrator, l as createExportData, n as createCrdtStorage, o as verifySyncSchemaWithDriver, t as createCrdtSyncProducer, u as createImportData } from "./crdt-sync-producer-
|
|
3
|
+
import { a as formatSchemaVerificationIssues, c as createMigrator, l as createExportData, n as createCrdtStorage, o as verifySyncSchemaWithDriver, t as createCrdtSyncProducer, u as createImportData } from "./crdt-sync-producer-t2jHxO38.js";
|
|
4
4
|
import { o as HLCCounter, t as createCrdtSyncRemoteSource, u as xxhash } from "./crdt-sync-remote-source-yeTEkOjH.js";
|
|
5
|
-
import { t as SQLiteReactiveDb } from "./sqlite-reactive-db-
|
|
5
|
+
import { t as SQLiteReactiveDb } from "./sqlite-reactive-db-BwY3FoIy.js";
|
|
6
6
|
import { a as makeCrdtTable, o as registerCrdtIntentDrainer } from "./make-crdt-table-DYCXOYB7.js";
|
|
7
7
|
import { n as getOrCreateSQLiteSyncDevtoolsRegistry, r as validateDbId, t as createSyncedDbDatabase } from "./synced-db-database-B5M7MrBD.js";
|
|
8
8
|
import { i as runWorkerEventLogGc, r as formatStorageVersion, t as createWsRemoteSource } from "./ws-remote-source-BbPWrHB6.js";
|
|
@@ -73,7 +73,7 @@ async function createLocalSyncedDb(options) {
|
|
|
73
73
|
const nodeId = generateId();
|
|
74
74
|
const crdtStorage = createCrdtStorage({
|
|
75
75
|
nodeId,
|
|
76
|
-
initialLocalSyncId: getMaxSyncId(db),
|
|
76
|
+
initialLocalSyncId: Math.max(getMaxSyncId(db), pushSyncId.current),
|
|
77
77
|
migrator,
|
|
78
78
|
hlc: new HLCCounter(nodeId, () => Date.now()),
|
|
79
79
|
db,
|
|
@@ -109,10 +109,12 @@ async function createLocalSyncedDb(options) {
|
|
|
109
109
|
migrator,
|
|
110
110
|
remoteFactory: options.createRemoteSource
|
|
111
111
|
});
|
|
112
|
+
let stateSnapshot = remoteSource.getState();
|
|
112
113
|
remoteSource.addEventListener("state-changed", () => {
|
|
114
|
+
stateSnapshot = remoteSource.getState();
|
|
113
115
|
notify({
|
|
114
116
|
notificationType: "state-changed",
|
|
115
|
-
state:
|
|
117
|
+
state: stateSnapshot,
|
|
116
118
|
storageGeneration: storageGeneration.current
|
|
117
119
|
});
|
|
118
120
|
});
|
|
@@ -159,7 +161,7 @@ async function createLocalSyncedDb(options) {
|
|
|
159
161
|
const syncedDb = {
|
|
160
162
|
db: createSyncedDbDatabase(reactiveDb),
|
|
161
163
|
state: {
|
|
162
|
-
getState: () =>
|
|
164
|
+
getState: () => stateSnapshot,
|
|
163
165
|
subscribe: (onChange) => {
|
|
164
166
|
const { unsubscribe } = eventTarget.addEventListener("state-changed", onChange);
|
|
165
167
|
return unsubscribe;
|
package/dist/local.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.js","names":[],"sources":["../src/local/local-synced-db.ts"],"sourcesContent":["import { validateDbId } from \"../db-id\";\nimport { getOrCreateSQLiteSyncDevtoolsRegistry } from \"../devtools-registry\";\nimport { createExportData, createImportData } from \"../export-import\";\nimport { xxhash } from \"../hash\";\nimport { HLCCounter } from \"../hlc\";\nimport type { Logger } from \"../logger\";\nimport { SQLiteReactiveDb } from \"../memory-db/sqlite-reactive-db\";\nimport { createMigrator } from \"../migrations/migrator\";\nimport {\n applyLocalDbSchema,\n LOCAL_KV_TABLE_NAME,\n type LocalDbSchema,\n localDbConfig,\n} from \"../migrations/system-schema\";\nimport { formatSchemaVerificationIssues, verifySyncSchemaWithDriver } from \"../schema/verify-sync-schema\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { createCrdtStorage } from \"../sqlite-crdt/crdt-storage\";\nimport { createCrdtSyncProducer } from \"../sqlite-crdt/crdt-sync-producer\";\nimport {\n type CreateRemoteSourceFactory,\n createCrdtSyncRemoteSource,\n PUSH_BATCH_SIZE,\n} from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport { makeCrdtTable, registerCrdtIntentDrainer } from \"../sqlite-crdt/make-crdt-table\";\nimport type { SqliteDriver } from \"../sqlite-driver\";\nimport { createKvStateHashStore, createKvStoreTableQuery, createSQLiteKvStore } from \"../sqlite-kv-store\";\nimport type { SyncedDb } from \"../sync-db\";\nimport { createSyncedDbDatabase } from \"../synced-db-database\";\nimport { createTypedEventTarget, generateId } from \"../utils\";\nimport { runWorkerEventLogGc } from \"../worker-db/event-log-gc\";\nimport { formatStorageVersion } from \"../worker-db/storage-version\";\nimport type { WorkerNotificationEvents, WorkerNotificationMessage } from \"../worker-db/worker-common\";\n\nexport type LocalSyncedDbOptions<Database> = {\n dbId: string;\n syncDbSchema: SyncDbSchema<Database>;\n /** Opens the persisted database. Called again after a reset deleted it. */\n openDriver: () => SqliteDriver | Promise<SqliteDriver>;\n /** Deletes the persisted database. Only called while no connection to it is open. */\n deleteDatabase: () => void | Promise<void>;\n createRemoteSource?: CreateRemoteSourceFactory;\n logger?: Logger;\n /**\n * App-provided storage version, combined with the library's internal storage\n * version. Bump it when deploying a code change that old persisted local DBs\n * cannot survive — on mismatch the local DB is deleted on startup.\n */\n storageVersion?: string;\n /**\n * Dev-time drift check: replays the full migration history on a throwaway\n * database opened by this callback and verifies the result matches the schema\n * declared with `defineSyncSchema`. On mismatch startup throws.\n */\n verifySchemaDriver?: () => SqliteDriver | Promise<SqliteDriver>;\n /**\n * Runs event-log garbage collection on startup. Disabled by default. GC keeps the\n * latest event rows and never removes pending events or local rows that have not\n * been pushed to the remote.\n */\n eventLogGc?: boolean;\n /** Runs once when the instance is disposed, including by `requestReload`. */\n onDispose?: () => void;\n};\n\nexport type LocalSyncedDb<Database> = SyncedDb<Database> & {\n state: SyncedDb<Database>[\"state\"] & {\n /** Drops the remote connection without retrying; call `sync` once the network is back. */\n dropConnection: () => void;\n };\n};\n\nconst STORAGE_VERSION_KEY = \"storage-version\";\n\nconst defaultLogger: Logger = (type, message, level = \"info\") => {\n const logMessage = `[${type}] ${message}`;\n switch (level) {\n case \"info\":\n console.log(logMessage);\n break;\n case \"warning\":\n console.warn(logMessage);\n break;\n case \"error\":\n console.error(logMessage);\n break;\n case \"trace\":\n console.trace(logMessage);\n break;\n }\n};\n\n/**\n * A synced DB backed by a single persisted SQLite connection, for runtimes with one\n * JS context per database (React Native, Electron, Node). Reads and writes go to the\n * persisted database directly; there is no worker, tab coordination or in-memory copy.\n */\nexport async function createLocalSyncedDb<Database>(\n options: LocalSyncedDbOptions<Database>,\n): Promise<LocalSyncedDb<Database>> {\n validateDbId(options.dbId);\n const logger = options.logger ?? defaultLogger;\n\n if (options.verifySchemaDriver) {\n const issues = verifySyncSchemaWithDriver(options.syncDbSchema as SyncDbSchema, await options.verifySchemaDriver());\n if (issues.length > 0) {\n throw new Error(formatSchemaVerificationIssues(issues));\n }\n logger(\"local\", \"Schema verification passed: migrations match the declared schema\", \"info\");\n }\n\n await xxhash.ensureLoaded();\n\n const reactiveDb = await openLocalReactiveDb(options, logger);\n const db = reactiveDb.db as unknown as SQLiteReactiveDb<LocalDbSchema>[\"db\"];\n\n try {\n const { kvStore } = applyLocalDbSchema(db);\n\n const migrator = createMigrator({\n migrations: options.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: localDbConfig.updateLogTable.fullIdentifier,\n });\n migrator.migrateDbToLatest({\n startTransaction: (callback) => {\n db.executeTransaction((tx) =>\n callback({ execute: (sql, parameters, meta) => tx.execute({ sql, parameters }, meta) }),\n );\n },\n });\n db.invalidateDbSchema();\n\n const pullSyncId = kvStore.createNumberStoredValue(\"pull-sync-id\", -1);\n const pushSyncId = kvStore.createNumberStoredValue(\"push-sync-id\", -1);\n const stateHash = createKvStateHashStore(kvStore);\n const storageGeneration = kvStore.createStringStoredValue(\"storage-generation\", \"\");\n if (!storageGeneration.current) {\n storageGeneration.current = generateId();\n }\n\n if (options.eventLogGc === true) {\n runWorkerEventLogGc({ db, dbConfig: localDbConfig, pushSyncId, logger });\n }\n\n for (const table of options.syncDbSchema.tablesConfig) {\n makeCrdtTable({ db, baseTableName: table.baseTableName, crdtTableName: table.crdtTableName, temp: true });\n }\n\n const nodeId = generateId();\n const crdtStorage = createCrdtStorage({\n nodeId,\n initialLocalSyncId: getMaxSyncId(db),\n migrator,\n hlc: new HLCCounter(nodeId, () => Date.now()),\n db,\n dbConfig: localDbConfig,\n stateHash,\n schema: options.syncDbSchema,\n });\n registerCrdtIntentDrainer({ reactiveDb, storage: crdtStorage });\n\n kvStore.set(STORAGE_VERSION_KEY, formatStorageVersion(options.storageVersion));\n\n const eventTarget = createTypedEventTarget<WorkerNotificationEvents>();\n const notify = (notification: WorkerNotificationMessage) => {\n eventTarget.dispatchEvent(notification.notificationType, notification as never);\n };\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n notify({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n stateHash: chunk.stateHash,\n });\n },\n });\n\n const remoteSource = createCrdtSyncRemoteSource({\n bufferSize: PUSH_BATCH_SIZE,\n pullSyncId,\n pushSyncId,\n nodeId,\n storage: crdtStorage,\n migrator,\n remoteFactory: options.createRemoteSource,\n });\n remoteSource.addEventListener(\"state-changed\", () => {\n notify({\n notificationType: \"state-changed\",\n state: remoteSource.getState(),\n storageGeneration: storageGeneration.current,\n });\n });\n remoteSource.addEventListener(\"de-sync-detected\", (event) => {\n notify({ notificationType: \"de-sync-detected\", reason: event.payload.reason });\n });\n remoteSource.addEventListener(\"remote-schema-version-mismatch\", (event) => {\n notify({\n notificationType: \"remote-schema-version-mismatch\",\n remoteSchemaVersion: event.payload.remoteSchemaVersion,\n localSchemaVersion: event.payload.localSchemaVersion,\n });\n });\n remoteSource.goOnline();\n\n let unregisterDevtools: (() => void) | undefined;\n let disposePromise: Promise<void> | null = null;\n const dispose = () => {\n disposePromise ??= (async () => {\n options.onDispose?.();\n unregisterDevtools?.();\n try {\n await remoteSource.dispose();\n await crdtStorage.internal.processEnqueuedEvents();\n } finally {\n reactiveDb.dispose();\n }\n })();\n return disposePromise;\n };\n\n const importData = createImportData({\n migrator,\n tablesConfig: options.syncDbSchema.tablesConfig,\n applyEvents: async (events) => {\n crdtStorage.applyOwnEvents(events);\n await crdtStorage.internal.processEnqueuedEvents();\n },\n });\n\n const sync = () => {\n remoteSource.syncWithRemote().catch((error) => console.warn(\"Failed to start sync\", error));\n };\n\n const syncedDb: LocalSyncedDb<Database> = {\n db: createSyncedDbDatabase(reactiveDb),\n state: {\n getState: () => remoteSource.getState(),\n subscribe: (onChange: () => void) => {\n const { unsubscribe } = eventTarget.addEventListener(\"state-changed\", onChange);\n return unsubscribe;\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n dropConnection: () => remoteSource.dropConnection(\"NETWORK_OFFLINE\"),\n sync,\n },\n /**\n * Disposes this instance and notifies `reload-requested` subscribers, which are\n * expected to create a new instance. With `clean: true` the persisted database is\n * deleted first, so the next instance starts empty and re-syncs from the remote.\n * Pending local events that were not pushed yet are lost with a clean reload.\n */\n requestReload: async ({ clean }: { clean: boolean }) => {\n await dispose();\n if (clean) {\n await options.deleteDatabase();\n }\n notify({ notificationType: \"reload-requested\", reloadEpoch: generateId(), clean });\n },\n subscribe: eventTarget.addEventListener,\n exportData: createExportData({\n reactiveDb,\n tablesConfig: options.syncDbSchema.tablesConfig,\n schemaVersion: migrator.currentSchemaVersion,\n }),\n importData: async (data, opts) => importData(data, opts),\n dispose,\n _internal: {\n executeAsync: async (query) => reactiveDb.db.execute(query),\n getMemoryQueryTables: reactiveDb.getTablesUsed.bind(reactiveDb),\n getSharedLiveQueriesSnapshot: reactiveDb.getSharedLiveQueriesSnapshot.bind(reactiveDb),\n crdtTableNames: options.syncDbSchema.tablesConfig.map((table) => table.crdtTableName),\n crdtTablesConfig: options.syncDbSchema.tablesConfig,\n tables: options.syncDbSchema.tables,\n schemaVersion: migrator.currentSchemaVersion,\n migrationVersions: Object.keys(options.syncDbSchema.migrations)\n .map(Number)\n .sort((a, b) => a - b),\n },\n };\n\n unregisterDevtools = getOrCreateSQLiteSyncDevtoolsRegistry().register({\n instanceId: generateId(),\n dbId: options.dbId,\n createdAt: Date.now(),\n instance: syncedDb,\n });\n\n return syncedDb;\n } catch (error) {\n reactiveDb.dispose();\n throw error;\n }\n}\n\nasync function openLocalReactiveDb<Database>(options: LocalSyncedDbOptions<Database>, logger: Logger) {\n const open = async () => {\n const driver = await options.openDriver();\n const reactiveDb = SQLiteReactiveDb.create<Database>({ driver, logger, loggerPrefix: \"local\" });\n try {\n reactiveDb.db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n reactiveDb.db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n reactiveDb.db.executeKysely((kysely) => createKvStoreTableQuery(kysely.schema, LOCAL_KV_TABLE_NAME), {\n loggerLevel: \"system\",\n });\n } catch (error) {\n reactiveDb.dispose();\n throw error;\n }\n return reactiveDb;\n };\n\n const reactiveDb = await open();\n const currentVersion = formatStorageVersion(options.storageVersion);\n const storedVersion = createSQLiteKvStore({ db: reactiveDb.db, metaTableName: LOCAL_KV_TABLE_NAME }).get(\n STORAGE_VERSION_KEY,\n );\n if (storedVersion === null || storedVersion === currentVersion) {\n return reactiveDb;\n }\n\n logger(\"local\", `Storage version mismatch — resetting local DB to ${currentVersion}`, \"warning\");\n reactiveDb.dispose();\n await options.deleteDatabase();\n return open();\n}\n\nfunction getMaxSyncId(db: SQLiteReactiveDb<LocalDbSchema>[\"db\"]) {\n const [result] = db.executePrepared(\n \"get-max-sync-id\",\n {},\n (db) => db.selectFrom(\"crdt_events\").select((eb) => eb.fn.max(\"sync_id\").as(\"sync_id\")),\n { loggerLevel: \"system\" },\n );\n\n return result?.sync_id ?? 0;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local.js","names":[],"sources":["../src/local/local-synced-db.ts"],"sourcesContent":["import { validateDbId } from \"../db-id\";\nimport { getOrCreateSQLiteSyncDevtoolsRegistry } from \"../devtools-registry\";\nimport { createExportData, createImportData } from \"../export-import\";\nimport { xxhash } from \"../hash\";\nimport { HLCCounter } from \"../hlc\";\nimport type { Logger } from \"../logger\";\nimport { SQLiteReactiveDb } from \"../memory-db/sqlite-reactive-db\";\nimport { createMigrator } from \"../migrations/migrator\";\nimport {\n applyLocalDbSchema,\n LOCAL_KV_TABLE_NAME,\n type LocalDbSchema,\n localDbConfig,\n} from \"../migrations/system-schema\";\nimport { formatSchemaVerificationIssues, verifySyncSchemaWithDriver } from \"../schema/verify-sync-schema\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { createCrdtStorage } from \"../sqlite-crdt/crdt-storage\";\nimport { createCrdtSyncProducer } from \"../sqlite-crdt/crdt-sync-producer\";\nimport {\n type CreateRemoteSourceFactory,\n createCrdtSyncRemoteSource,\n PUSH_BATCH_SIZE,\n} from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport { makeCrdtTable, registerCrdtIntentDrainer } from \"../sqlite-crdt/make-crdt-table\";\nimport type { SqliteDriver } from \"../sqlite-driver\";\nimport { createKvStateHashStore, createKvStoreTableQuery, createSQLiteKvStore } from \"../sqlite-kv-store\";\nimport type { SyncedDb } from \"../sync-db\";\nimport { createSyncedDbDatabase } from \"../synced-db-database\";\nimport { createTypedEventTarget, generateId } from \"../utils\";\nimport { runWorkerEventLogGc } from \"../worker-db/event-log-gc\";\nimport { formatStorageVersion } from \"../worker-db/storage-version\";\nimport type { WorkerNotificationEvents, WorkerNotificationMessage } from \"../worker-db/worker-common\";\n\nexport type LocalSyncedDbOptions<Database> = {\n dbId: string;\n syncDbSchema: SyncDbSchema<Database>;\n /** Opens the persisted database. Called again after a reset deleted it. */\n openDriver: () => SqliteDriver | Promise<SqliteDriver>;\n /** Deletes the persisted database. Only called while no connection to it is open. */\n deleteDatabase: () => void | Promise<void>;\n createRemoteSource?: CreateRemoteSourceFactory;\n logger?: Logger;\n /**\n * App-provided storage version, combined with the library's internal storage\n * version. Bump it when deploying a code change that old persisted local DBs\n * cannot survive — on mismatch the local DB is deleted on startup.\n */\n storageVersion?: string;\n /**\n * Dev-time drift check: replays the full migration history on a throwaway\n * database opened by this callback and verifies the result matches the schema\n * declared with `defineSyncSchema`. On mismatch startup throws.\n */\n verifySchemaDriver?: () => SqliteDriver | Promise<SqliteDriver>;\n /**\n * Runs event-log garbage collection on startup. Disabled by default. GC keeps the\n * latest event rows and never removes pending events or local rows that have not\n * been pushed to the remote.\n */\n eventLogGc?: boolean;\n /** Runs once when the instance is disposed, including by `requestReload`. */\n onDispose?: () => void;\n};\n\nexport type LocalSyncedDb<Database> = SyncedDb<Database> & {\n state: SyncedDb<Database>[\"state\"] & {\n /** Drops the remote connection without retrying; call `sync` once the network is back. */\n dropConnection: () => void;\n };\n};\n\nconst STORAGE_VERSION_KEY = \"storage-version\";\n\nconst defaultLogger: Logger = (type, message, level = \"info\") => {\n const logMessage = `[${type}] ${message}`;\n switch (level) {\n case \"info\":\n console.log(logMessage);\n break;\n case \"warning\":\n console.warn(logMessage);\n break;\n case \"error\":\n console.error(logMessage);\n break;\n case \"trace\":\n console.trace(logMessage);\n break;\n }\n};\n\n/**\n * A synced DB backed by a single persisted SQLite connection, for runtimes with one\n * JS context per database (React Native, Electron, Node). Reads and writes go to the\n * persisted database directly; there is no worker, tab coordination or in-memory copy.\n */\nexport async function createLocalSyncedDb<Database>(\n options: LocalSyncedDbOptions<Database>,\n): Promise<LocalSyncedDb<Database>> {\n validateDbId(options.dbId);\n const logger = options.logger ?? defaultLogger;\n\n if (options.verifySchemaDriver) {\n const issues = verifySyncSchemaWithDriver(options.syncDbSchema as SyncDbSchema, await options.verifySchemaDriver());\n if (issues.length > 0) {\n throw new Error(formatSchemaVerificationIssues(issues));\n }\n logger(\"local\", \"Schema verification passed: migrations match the declared schema\", \"info\");\n }\n\n await xxhash.ensureLoaded();\n\n const reactiveDb = await openLocalReactiveDb(options, logger);\n const db = reactiveDb.db as unknown as SQLiteReactiveDb<LocalDbSchema>[\"db\"];\n\n try {\n const { kvStore } = applyLocalDbSchema(db);\n\n const migrator = createMigrator({\n migrations: options.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: localDbConfig.updateLogTable.fullIdentifier,\n });\n migrator.migrateDbToLatest({\n startTransaction: (callback) => {\n db.executeTransaction((tx) =>\n callback({ execute: (sql, parameters, meta) => tx.execute({ sql, parameters }, meta) }),\n );\n },\n });\n db.invalidateDbSchema();\n\n const pullSyncId = kvStore.createNumberStoredValue(\"pull-sync-id\", -1);\n const pushSyncId = kvStore.createNumberStoredValue(\"push-sync-id\", -1);\n const stateHash = createKvStateHashStore(kvStore);\n const storageGeneration = kvStore.createStringStoredValue(\"storage-generation\", \"\");\n if (!storageGeneration.current) {\n storageGeneration.current = generateId();\n }\n\n if (options.eventLogGc === true) {\n runWorkerEventLogGc({ db, dbConfig: localDbConfig, pushSyncId, logger });\n }\n\n for (const table of options.syncDbSchema.tablesConfig) {\n makeCrdtTable({ db, baseTableName: table.baseTableName, crdtTableName: table.crdtTableName, temp: true });\n }\n\n const nodeId = generateId();\n const crdtStorage = createCrdtStorage({\n nodeId,\n initialLocalSyncId: Math.max(getMaxSyncId(db), pushSyncId.current),\n migrator,\n hlc: new HLCCounter(nodeId, () => Date.now()),\n db,\n dbConfig: localDbConfig,\n stateHash,\n schema: options.syncDbSchema,\n });\n registerCrdtIntentDrainer({ reactiveDb, storage: crdtStorage });\n\n kvStore.set(STORAGE_VERSION_KEY, formatStorageVersion(options.storageVersion));\n\n const eventTarget = createTypedEventTarget<WorkerNotificationEvents>();\n const notify = (notification: WorkerNotificationMessage) => {\n eventTarget.dispatchEvent(notification.notificationType, notification as never);\n };\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n notify({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n stateHash: chunk.stateHash,\n });\n },\n });\n\n const remoteSource = createCrdtSyncRemoteSource({\n bufferSize: PUSH_BATCH_SIZE,\n pullSyncId,\n pushSyncId,\n nodeId,\n storage: crdtStorage,\n migrator,\n remoteFactory: options.createRemoteSource,\n });\n\n let stateSnapshot = remoteSource.getState();\n remoteSource.addEventListener(\"state-changed\", () => {\n stateSnapshot = remoteSource.getState();\n notify({\n notificationType: \"state-changed\",\n state: stateSnapshot,\n storageGeneration: storageGeneration.current,\n });\n });\n remoteSource.addEventListener(\"de-sync-detected\", (event) => {\n notify({ notificationType: \"de-sync-detected\", reason: event.payload.reason });\n });\n remoteSource.addEventListener(\"remote-schema-version-mismatch\", (event) => {\n notify({\n notificationType: \"remote-schema-version-mismatch\",\n remoteSchemaVersion: event.payload.remoteSchemaVersion,\n localSchemaVersion: event.payload.localSchemaVersion,\n });\n });\n remoteSource.goOnline();\n\n let unregisterDevtools: (() => void) | undefined;\n let disposePromise: Promise<void> | null = null;\n const dispose = () => {\n disposePromise ??= (async () => {\n options.onDispose?.();\n unregisterDevtools?.();\n try {\n await remoteSource.dispose();\n await crdtStorage.internal.processEnqueuedEvents();\n } finally {\n reactiveDb.dispose();\n }\n })();\n return disposePromise;\n };\n\n const importData = createImportData({\n migrator,\n tablesConfig: options.syncDbSchema.tablesConfig,\n applyEvents: async (events) => {\n crdtStorage.applyOwnEvents(events);\n await crdtStorage.internal.processEnqueuedEvents();\n },\n });\n\n const sync = () => {\n remoteSource.syncWithRemote().catch((error) => console.warn(\"Failed to start sync\", error));\n };\n\n const syncedDb: LocalSyncedDb<Database> = {\n db: createSyncedDbDatabase(reactiveDb),\n state: {\n getState: () => stateSnapshot,\n subscribe: (onChange: () => void) => {\n const { unsubscribe } = eventTarget.addEventListener(\"state-changed\", onChange);\n return unsubscribe;\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n dropConnection: () => remoteSource.dropConnection(\"NETWORK_OFFLINE\"),\n sync,\n },\n /**\n * Disposes this instance and notifies `reload-requested` subscribers, which are\n * expected to create a new instance. With `clean: true` the persisted database is\n * deleted first, so the next instance starts empty and re-syncs from the remote.\n * Pending local events that were not pushed yet are lost with a clean reload.\n */\n requestReload: async ({ clean }: { clean: boolean }) => {\n await dispose();\n if (clean) {\n await options.deleteDatabase();\n }\n notify({ notificationType: \"reload-requested\", reloadEpoch: generateId(), clean });\n },\n subscribe: eventTarget.addEventListener,\n exportData: createExportData({\n reactiveDb,\n tablesConfig: options.syncDbSchema.tablesConfig,\n schemaVersion: migrator.currentSchemaVersion,\n }),\n importData: async (data, opts) => importData(data, opts),\n dispose,\n _internal: {\n executeAsync: async (query) => reactiveDb.db.execute(query),\n getMemoryQueryTables: reactiveDb.getTablesUsed.bind(reactiveDb),\n getSharedLiveQueriesSnapshot: reactiveDb.getSharedLiveQueriesSnapshot.bind(reactiveDb),\n crdtTableNames: options.syncDbSchema.tablesConfig.map((table) => table.crdtTableName),\n crdtTablesConfig: options.syncDbSchema.tablesConfig,\n tables: options.syncDbSchema.tables,\n schemaVersion: migrator.currentSchemaVersion,\n migrationVersions: Object.keys(options.syncDbSchema.migrations)\n .map(Number)\n .sort((a, b) => a - b),\n },\n };\n\n unregisterDevtools = getOrCreateSQLiteSyncDevtoolsRegistry().register({\n instanceId: generateId(),\n dbId: options.dbId,\n createdAt: Date.now(),\n instance: syncedDb,\n });\n\n return syncedDb;\n } catch (error) {\n reactiveDb.dispose();\n throw error;\n }\n}\n\nasync function openLocalReactiveDb<Database>(options: LocalSyncedDbOptions<Database>, logger: Logger) {\n const open = async () => {\n const driver = await options.openDriver();\n const reactiveDb = SQLiteReactiveDb.create<Database>({ driver, logger, loggerPrefix: \"local\" });\n try {\n reactiveDb.db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n reactiveDb.db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n reactiveDb.db.executeKysely((kysely) => createKvStoreTableQuery(kysely.schema, LOCAL_KV_TABLE_NAME), {\n loggerLevel: \"system\",\n });\n } catch (error) {\n reactiveDb.dispose();\n throw error;\n }\n return reactiveDb;\n };\n\n const reactiveDb = await open();\n const currentVersion = formatStorageVersion(options.storageVersion);\n const storedVersion = createSQLiteKvStore({ db: reactiveDb.db, metaTableName: LOCAL_KV_TABLE_NAME }).get(\n STORAGE_VERSION_KEY,\n );\n if (storedVersion === null || storedVersion === currentVersion) {\n return reactiveDb;\n }\n\n logger(\"local\", `Storage version mismatch — resetting local DB to ${currentVersion}`, \"warning\");\n reactiveDb.dispose();\n await options.deleteDatabase();\n return open();\n}\n\nfunction getMaxSyncId(db: SQLiteReactiveDb<LocalDbSchema>[\"db\"]) {\n const [result] = db.executePrepared(\n \"get-max-sync-id\",\n {},\n (db) => db.selectFrom(\"crdt_events\").select((eb) => eb.fn.max(\"sync_id\").as(\"sync_id\")),\n { loggerLevel: \"system\" },\n );\n\n return result?.sync_id ?? 0;\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as acquireLock, l as generateId, r as TypedBroadcastChannel } from "./crdt-table-schema-D6Yf61Ex.js";
|
|
2
|
-
import { o as verifySyncSchemaWithDriver } from "./crdt-sync-producer-
|
|
2
|
+
import { o as verifySyncSchemaWithDriver } from "./crdt-sync-producer-t2jHxO38.js";
|
|
3
3
|
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
|
4
4
|
import { createStore, del, get, set } from "idb-keyval";
|
|
5
5
|
//#region src/wasm-driver.ts
|
|
@@ -217,4 +217,4 @@ function createReloadRequestHandler({ resetState, broadcast, generateEpoch = gen
|
|
|
217
217
|
//#endregion
|
|
218
218
|
export { createWasmSqliteDriver as _, acquireReplicaLock as a, isWorkerErrorResponseMessage as c, isWorkerRequestMessage as d, isWorkerResponseMessage as f, createWasmMemoryDriver as g, verifySyncSchema as h, createResetStateStore as i, isWorkerInitMessage as l, syncDbWorkerLockName as m, createIdbResetStore as n, runWithNoActiveReplicas as o, syncDbClientLockName as p, createReloadRequestHandler as r, createBroadcastChannels as s, RESET_REQUEST_TTL_MS as t, isWorkerNotificationMessage as u, loadSqliteWasm as v };
|
|
219
219
|
|
|
220
|
-
//# sourceMappingURL=reset-state-
|
|
220
|
+
//# sourceMappingURL=reset-state-Bz1sfbf3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reset-state-CPOi_45B.js","names":[],"sources":["../src/wasm-driver.ts","../src/schema/verify-sync-schema-wasm.ts","../src/worker-db/worker-common.ts","../src/worker-db/replica-lock.ts","../src/worker-db/reset-state.ts"],"sourcesContent":["import sqlite3InitModule, {\n type BindableValue,\n type Database,\n type Sqlite3Static,\n type SqlValue,\n} from \"@sqlite.org/sqlite-wasm\";\nimport type { SqliteDriver, SqliteDriverStatement, SqliteDriverValue } from \"./sqlite-driver\";\n\nlet sqliteModulePromise: Promise<Sqlite3Static> | null = null;\n\nexport function loadSqliteWasm() {\n sqliteModulePromise ??= sqlite3InitModule();\n return sqliteModulePromise;\n}\n\nexport async function createWasmMemoryDriver() {\n const sqlite3 = await loadSqliteWasm();\n return createWasmSqliteDriver(sqlite3, new sqlite3.oo1.DB({ filename: \":memory:\" }));\n}\n\nexport function createWasmSqliteDriver(sqlite3: Sqlite3Static, db: Database): SqliteDriver {\n const { capi } = sqlite3;\n\n const prepare = (sql: string): SqliteDriverStatement => {\n const statement = db.prepare(sql);\n const pointer = statement.pointer;\n if (!pointer) {\n throw new Error(\"Prepared statement is already finalized\");\n }\n\n return {\n readonly: capi.sqlite3_stmt_readonly(pointer) !== 0,\n run: (parameters) => {\n try {\n if (parameters.length > 0) {\n statement.bind(parameters as BindableValue[]);\n }\n\n const rows: SqliteDriverValue[][] = [];\n let columns: string[] | undefined;\n while (statement.step()) {\n columns ??= statement.getColumnNames([]);\n rows.push(statement.get([]) as SqliteDriverValue[]);\n }\n return { columns: columns ?? [], rows };\n } finally {\n statement.reset(true);\n }\n },\n finalize: () => {\n statement.finalize();\n },\n };\n };\n\n return {\n prepare,\n synchronousHooks: true,\n totalChanges: () => capi.sqlite3_total_changes(db),\n setHooks: (hooks) => {\n if (!hooks) {\n capi.sqlite3_update_hook(db, 0 as never, 0);\n capi.sqlite3_rollback_hook(db, 0, 0);\n capi.sqlite3_commit_hook(db, 0, 0);\n return;\n }\n\n capi.sqlite3_update_hook(db, (_ctx, _opId, _db, table) => hooks.onUpdate(table), 0);\n capi.sqlite3_rollback_hook(\n db,\n () => {\n hooks.onRollback();\n return 0;\n },\n 0,\n );\n capi.sqlite3_commit_hook(\n db,\n () => {\n hooks.onCommit();\n return 0;\n },\n 0,\n );\n },\n serialize: () => capi.sqlite3_js_db_export(db),\n deserialize: (snapshot) => {\n const dataPointer = sqlite3.wasm.allocFromTypedArray(snapshot);\n const resultCode = capi.sqlite3_deserialize(\n db,\n \"main\",\n dataPointer,\n snapshot.byteLength,\n snapshot.byteLength,\n capi.SQLITE_DESERIALIZE_FREEONCLOSE | capi.SQLITE_DESERIALIZE_RESIZEABLE,\n );\n db.checkRc(resultCode);\n },\n createScalarFunction: ({ name, callback, arity, deterministic, directOnly, innocuous }) => {\n db.createFunction({\n name,\n xFunc: (_, ...args) => callback(...(args as SqliteDriverValue[])) as SqlValue,\n arity,\n deterministic,\n directOnly,\n innocuous,\n });\n },\n close: () => {\n db.close();\n },\n };\n}\n","import type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { createWasmMemoryDriver } from \"../wasm-driver\";\nimport { type SchemaVerificationIssue, verifySyncSchemaWithDriver } from \"./verify-sync-schema\";\n\n/** {@link verifySyncSchemaWithDriver} on a throwaway in-memory sqlite-wasm database. */\nexport async function verifySyncSchema(schema: SyncDbSchema): Promise<SchemaVerificationIssue[]> {\n return verifySyncSchemaWithDriver(schema, await createWasmMemoryDriver());\n}\n","import type { ImportDataOptions, ImportDataResult, SyncedDbExport } from \"../export-import\";\nimport type {\n DeSyncDetectedReason,\n EventsPullRequest,\n EventsPushRequest,\n EventsPushResponse,\n} from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport type { CrdtEventType } from \"../sqlite-crdt/crdt-table-schema\";\nimport type { ExecuteParams, ExecuteResult } from \"../sqlite-db-wrapper\";\nimport { TypedBroadcastChannel } from \"../utils\";\n\nexport const syncDbWorkerLockName = \"sync-db-worker-lock\";\nexport const syncDbClientLockName = \"sync-db-client-lock\";\n\nexport type WorkerNotificationMessage =\n | {\n notificationType: \"new-event-chunk-applied\";\n newSyncId: number;\n /** Worker's state hash after applying up to `newSyncId`. */\n stateHash: string | null;\n }\n | {\n notificationType: \"state-changed\";\n state: WorkerState;\n /** Id of the worker's persisted database. A wiped database gets a new one. */\n storageGeneration: string;\n }\n | {\n notificationType: \"reload-requested\";\n reloadEpoch: string;\n clean: boolean;\n }\n | {\n notificationType: \"de-sync-detected\";\n reason: DeSyncDetectedReason;\n }\n | {\n notificationType: \"remote-schema-version-mismatch\";\n remoteSchemaVersion: number;\n localSchemaVersion: number;\n };\n\nexport type WorkerNotificationEvents = {\n [K in WorkerNotificationMessage[\"notificationType\"]]: Extract<WorkerNotificationMessage, { notificationType: K }>;\n};\n\nexport type WorkerState = {\n remoteState: \"online\" | \"offline\" | \"connecting\";\n deSynced: boolean;\n schemaVersionMismatched: boolean;\n};\n\nexport type GetSnapshotResponse = {\n file: Uint8Array<ArrayBufferLike>;\n syncId: number;\n schemaVersion: number;\n storageGeneration: string;\n};\n\nexport type EventsPullResponse = {\n events: {\n schema_version: number;\n type: CrdtEventType;\n timestamp: string;\n dataset: string;\n item_id: string;\n payload: string;\n }[];\n hasMore: boolean;\n nextSyncId: number;\n};\n\nexport interface WorkerRpc {\n getSnapshot: () => GetSnapshotResponse;\n importData: (data: SyncedDbExport, opts?: ImportDataOptions) => ImportDataResult | Promise<ImportDataResult>;\n pushTabEvents: (request: EventsPushRequest) => EventsPushResponse;\n execute: (query: ExecuteParams) => ExecuteResult<unknown>;\n pullEvents: (params: EventsPullRequest) => EventsPullResponse;\n postState: () => void;\n goOnline: () => Promise<void>;\n goOffline: () => void;\n dropConnection: () => void;\n sync: () => void;\n requestReload: (options: { clean: boolean }) => Promise<void>;\n}\n\nexport type WorkerRequestMethod = keyof WorkerRpc;\n\nexport type WorkerRequestMessage<TMethod extends WorkerRequestMethod = WorkerRequestMethod> = {\n type: \"request\";\n requestId: string;\n method: TMethod;\n args: Parameters<WorkerRpc[TMethod]>;\n};\n\nexport type WorkerResponseMessage<TMethod extends WorkerRequestMethod = WorkerRequestMethod> = {\n type: \"response\";\n requestId: string;\n data: ReturnType<WorkerRpc[TMethod]>;\n};\n\nexport type WorkerErrorResponseMessage = {\n type: \"error-response\";\n requestId: string;\n error: string;\n};\n\nexport type AsyncRpc<T> = {\n [K in keyof T]: T[K] extends (...args: infer U) => infer V ? (...args: U) => Promise<Awaited<V>> : never;\n};\n\nexport const broadcastChannelNames = {\n requests: \"sync-db-worker-requests\",\n responses: \"sync-db-worker-responses\",\n} as const;\n\nexport type WorkerBroadcastChannels = {\n requests: TypedBroadcastChannel<WorkerRequestMessage>;\n responses: TypedBroadcastChannel<WorkerResponseMessage | WorkerErrorResponseMessage | WorkerNotificationMessage>;\n};\n\nexport const createBroadcastChannels = (prefix: string): WorkerBroadcastChannels => {\n return {\n requests: new TypedBroadcastChannel(`${prefix}-${broadcastChannelNames.requests}`),\n responses: new TypedBroadcastChannel(`${prefix}-${broadcastChannelNames.responses}`),\n };\n};\n\nexport type WorkerConfig<Props = any> = {\n dbId: string;\n clientId: string;\n props: Props;\n};\n\nexport type WorkerInitMessage = {\n type: \"init\";\n config: WorkerConfig;\n};\n\nexport function isWorkerInitMessage(message: unknown): message is WorkerInitMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"init\";\n}\n\nexport function isWorkerRequestMessage(message: unknown): message is WorkerRequestMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"request\";\n}\n\nexport function isWorkerResponseMessage(message: unknown): message is WorkerResponseMessage {\n return (\n typeof message === \"object\" && message !== null && \"type\" in message && \"requestId\" in message && \"data\" in message\n );\n}\n\nexport function isWorkerErrorResponseMessage(message: unknown): message is WorkerErrorResponseMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"error-response\";\n}\n\nexport function isWorkerNotificationMessage(message: unknown): message is WorkerNotificationMessage {\n return typeof message === \"object\" && message !== null && \"notificationType\" in message && !!message.notificationType;\n}\n","import { acquireLock } from \"../utils\";\n\nconst replicaLockName = (dbId: string) => `sync-db-replica-lock-${dbId}`;\n\nexport function acquireReplicaLock({ dbId }: { dbId: string }) {\n return acquireLock(replicaLockName(dbId), { mode: \"shared\" });\n}\n\nexport function runWithNoActiveReplicas({ dbId, run }: { dbId: string; run: () => void | Promise<void> }) {\n return navigator.locks.request(replicaLockName(dbId), { mode: \"exclusive\", ifAvailable: true }, async (lock) => {\n if (!lock) {\n return false;\n }\n await run();\n return true;\n });\n}\n","import { createStore, del, get, set } from \"idb-keyval\";\nimport { generateId } from \"../utils\";\nimport type { WorkerNotificationMessage } from \"./worker-common\";\n\nexport type ResetRequest = {\n epoch: string;\n requestedAt: number;\n};\n\n/** Durable async key-value storage for reset state. Injectable for tests. */\nexport type ResetStore = {\n get: <T>(key: string) => Promise<T | undefined>;\n set: (key: string, value: unknown) => Promise<void>;\n delete: (key: string) => Promise<void>;\n};\n\n/** Default IndexedDB-backed reset store (idb-keyval over a dedicated database). */\nexport function createIdbResetStore(): ResetStore {\n const store = createStore(\"sqlite-sync\", \"kv\");\n return {\n get: (key) => get(key, store),\n set: (key, value) => set(key, value, store),\n delete: (key) => del(key, store),\n };\n}\n\n/**\n * A clean reset is a recovery action for a de-sync detected now. If the reload\n * never happens (broadcast lost, tab crashed mid-reload, browser killed the page),\n * the request must not fire on an arbitrary later cold start and silently wipe\n * local-only writes accumulated since.\n */\nexport const RESET_REQUEST_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\nconst resetRequestKey = (dbId: string) => `sqlite-sync-reset-request-${dbId}`;\nconst resetAppliedKey = (dbId: string) => `sqlite-sync-reset-applied-${dbId}`;\n\ntype ResetStateStoreOptions = {\n store: ResetStore;\n dbId: string;\n now?: () => number;\n};\n\nexport type ResetStateStore = ReturnType<typeof createResetStateStore>;\n\n/**\n * Worker-owned durable reset state. The reset decision must be owned by the\n * elected worker, not by tabs during `createSyncedDb` — otherwise a later\n * worker election could repeat an already-applied wipe.\n */\nexport function createResetStateStore({ store, dbId, now = () => Date.now() }: ResetStateStoreOptions) {\n const requestKey = resetRequestKey(dbId);\n const appliedKey = resetAppliedKey(dbId);\n\n return {\n async writeResetRequest(epoch: string): Promise<ResetRequest> {\n const request: ResetRequest = { epoch, requestedAt: now() };\n await store.set(requestKey, request);\n return request;\n },\n /**\n * Read the pending reset request after winning the worker election.\n * Returns the request only when it has not been applied yet and is within\n * the TTL. Stale requests are deleted so they cannot fire on a later cold start.\n */\n async resolvePendingReset(): Promise<ResetRequest | undefined> {\n const request = await store.get<ResetRequest>(requestKey);\n if (!request) {\n return undefined;\n }\n\n if (now() - request.requestedAt > RESET_REQUEST_TTL_MS) {\n await store.delete(requestKey);\n return undefined;\n }\n\n const appliedEpoch = await store.get<string>(appliedKey);\n if (request.epoch === appliedEpoch) {\n return undefined;\n }\n\n return request;\n },\n /**\n * Record the epoch as applied. Must be called only after the worker has\n * successfully initialized with `clearOnInit: true`, so a failed init can\n * be retried by a later elected worker.\n */\n async markResetApplied(epoch: string): Promise<void> {\n await store.set(appliedKey, epoch);\n },\n };\n}\n\ntype ReloadRequestHandlerOptions = {\n resetState: ResetStateStore;\n broadcast: (message: WorkerNotificationMessage) => void;\n generateEpoch?: () => string;\n};\n\n/**\n * Worker-side `requestReload` RPC implementation. For `clean: true` the reset\n * request is durably stored before broadcasting and before the RPC resolves,\n * so the epoch survives no matter which path triggers the reload.\n */\nexport function createReloadRequestHandler({\n resetState,\n broadcast,\n generateEpoch = generateId,\n}: ReloadRequestHandlerOptions) {\n return async (options: { clean: boolean }): Promise<void> => {\n const reloadEpoch = generateEpoch();\n\n if (options.clean) {\n await resetState.writeResetRequest(reloadEpoch);\n }\n\n broadcast({\n notificationType: \"reload-requested\",\n reloadEpoch,\n clean: options.clean,\n });\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reset-state-Bz1sfbf3.js","names":[],"sources":["../src/wasm-driver.ts","../src/schema/verify-sync-schema-wasm.ts","../src/worker-db/worker-common.ts","../src/worker-db/replica-lock.ts","../src/worker-db/reset-state.ts"],"sourcesContent":["import sqlite3InitModule, {\n type BindableValue,\n type Database,\n type Sqlite3Static,\n type SqlValue,\n} from \"@sqlite.org/sqlite-wasm\";\nimport type { SqliteDriver, SqliteDriverStatement, SqliteDriverValue } from \"./sqlite-driver\";\n\nlet sqliteModulePromise: Promise<Sqlite3Static> | null = null;\n\nexport function loadSqliteWasm() {\n sqliteModulePromise ??= sqlite3InitModule();\n return sqliteModulePromise;\n}\n\nexport async function createWasmMemoryDriver() {\n const sqlite3 = await loadSqliteWasm();\n return createWasmSqliteDriver(sqlite3, new sqlite3.oo1.DB({ filename: \":memory:\" }));\n}\n\nexport function createWasmSqliteDriver(sqlite3: Sqlite3Static, db: Database): SqliteDriver {\n const { capi } = sqlite3;\n\n const prepare = (sql: string): SqliteDriverStatement => {\n const statement = db.prepare(sql);\n const pointer = statement.pointer;\n if (!pointer) {\n throw new Error(\"Prepared statement is already finalized\");\n }\n\n return {\n readonly: capi.sqlite3_stmt_readonly(pointer) !== 0,\n run: (parameters) => {\n try {\n if (parameters.length > 0) {\n statement.bind(parameters as BindableValue[]);\n }\n\n const rows: SqliteDriverValue[][] = [];\n let columns: string[] | undefined;\n while (statement.step()) {\n columns ??= statement.getColumnNames([]);\n rows.push(statement.get([]) as SqliteDriverValue[]);\n }\n return { columns: columns ?? [], rows };\n } finally {\n statement.reset(true);\n }\n },\n finalize: () => {\n statement.finalize();\n },\n };\n };\n\n return {\n prepare,\n synchronousHooks: true,\n totalChanges: () => capi.sqlite3_total_changes(db),\n setHooks: (hooks) => {\n if (!hooks) {\n capi.sqlite3_update_hook(db, 0 as never, 0);\n capi.sqlite3_rollback_hook(db, 0, 0);\n capi.sqlite3_commit_hook(db, 0, 0);\n return;\n }\n\n capi.sqlite3_update_hook(db, (_ctx, _opId, _db, table) => hooks.onUpdate(table), 0);\n capi.sqlite3_rollback_hook(\n db,\n () => {\n hooks.onRollback();\n return 0;\n },\n 0,\n );\n capi.sqlite3_commit_hook(\n db,\n () => {\n hooks.onCommit();\n return 0;\n },\n 0,\n );\n },\n serialize: () => capi.sqlite3_js_db_export(db),\n deserialize: (snapshot) => {\n const dataPointer = sqlite3.wasm.allocFromTypedArray(snapshot);\n const resultCode = capi.sqlite3_deserialize(\n db,\n \"main\",\n dataPointer,\n snapshot.byteLength,\n snapshot.byteLength,\n capi.SQLITE_DESERIALIZE_FREEONCLOSE | capi.SQLITE_DESERIALIZE_RESIZEABLE,\n );\n db.checkRc(resultCode);\n },\n createScalarFunction: ({ name, callback, arity, deterministic, directOnly, innocuous }) => {\n db.createFunction({\n name,\n xFunc: (_, ...args) => callback(...(args as SqliteDriverValue[])) as SqlValue,\n arity,\n deterministic,\n directOnly,\n innocuous,\n });\n },\n close: () => {\n db.close();\n },\n };\n}\n","import type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { createWasmMemoryDriver } from \"../wasm-driver\";\nimport { type SchemaVerificationIssue, verifySyncSchemaWithDriver } from \"./verify-sync-schema\";\n\n/** {@link verifySyncSchemaWithDriver} on a throwaway in-memory sqlite-wasm database. */\nexport async function verifySyncSchema(schema: SyncDbSchema): Promise<SchemaVerificationIssue[]> {\n return verifySyncSchemaWithDriver(schema, await createWasmMemoryDriver());\n}\n","import type { ImportDataOptions, ImportDataResult, SyncedDbExport } from \"../export-import\";\nimport type {\n DeSyncDetectedReason,\n EventsPullRequest,\n EventsPushRequest,\n EventsPushResponse,\n} from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport type { CrdtEventType } from \"../sqlite-crdt/crdt-table-schema\";\nimport type { ExecuteParams, ExecuteResult } from \"../sqlite-db-wrapper\";\nimport { TypedBroadcastChannel } from \"../utils\";\n\nexport const syncDbWorkerLockName = \"sync-db-worker-lock\";\nexport const syncDbClientLockName = \"sync-db-client-lock\";\n\nexport type WorkerNotificationMessage =\n | {\n notificationType: \"new-event-chunk-applied\";\n newSyncId: number;\n /** Worker's state hash after applying up to `newSyncId`. */\n stateHash: string | null;\n }\n | {\n notificationType: \"state-changed\";\n state: WorkerState;\n /** Id of the worker's persisted database. A wiped database gets a new one. */\n storageGeneration: string;\n }\n | {\n notificationType: \"reload-requested\";\n reloadEpoch: string;\n clean: boolean;\n }\n | {\n notificationType: \"de-sync-detected\";\n reason: DeSyncDetectedReason;\n }\n | {\n notificationType: \"remote-schema-version-mismatch\";\n remoteSchemaVersion: number;\n localSchemaVersion: number;\n };\n\nexport type WorkerNotificationEvents = {\n [K in WorkerNotificationMessage[\"notificationType\"]]: Extract<WorkerNotificationMessage, { notificationType: K }>;\n};\n\nexport type WorkerState = {\n remoteState: \"online\" | \"offline\" | \"connecting\";\n deSynced: boolean;\n schemaVersionMismatched: boolean;\n};\n\nexport type GetSnapshotResponse = {\n file: Uint8Array<ArrayBufferLike>;\n syncId: number;\n schemaVersion: number;\n storageGeneration: string;\n};\n\nexport type EventsPullResponse = {\n events: {\n schema_version: number;\n type: CrdtEventType;\n timestamp: string;\n dataset: string;\n item_id: string;\n payload: string;\n }[];\n hasMore: boolean;\n nextSyncId: number;\n};\n\nexport interface WorkerRpc {\n getSnapshot: () => GetSnapshotResponse;\n importData: (data: SyncedDbExport, opts?: ImportDataOptions) => ImportDataResult | Promise<ImportDataResult>;\n pushTabEvents: (request: EventsPushRequest) => EventsPushResponse;\n execute: (query: ExecuteParams) => ExecuteResult<unknown>;\n pullEvents: (params: EventsPullRequest) => EventsPullResponse;\n postState: () => void;\n goOnline: () => Promise<void>;\n goOffline: () => void;\n dropConnection: () => void;\n sync: () => void;\n requestReload: (options: { clean: boolean }) => Promise<void>;\n}\n\nexport type WorkerRequestMethod = keyof WorkerRpc;\n\nexport type WorkerRequestMessage<TMethod extends WorkerRequestMethod = WorkerRequestMethod> = {\n type: \"request\";\n requestId: string;\n method: TMethod;\n args: Parameters<WorkerRpc[TMethod]>;\n};\n\nexport type WorkerResponseMessage<TMethod extends WorkerRequestMethod = WorkerRequestMethod> = {\n type: \"response\";\n requestId: string;\n data: ReturnType<WorkerRpc[TMethod]>;\n};\n\nexport type WorkerErrorResponseMessage = {\n type: \"error-response\";\n requestId: string;\n error: string;\n};\n\nexport type AsyncRpc<T> = {\n [K in keyof T]: T[K] extends (...args: infer U) => infer V ? (...args: U) => Promise<Awaited<V>> : never;\n};\n\nexport const broadcastChannelNames = {\n requests: \"sync-db-worker-requests\",\n responses: \"sync-db-worker-responses\",\n} as const;\n\nexport type WorkerBroadcastChannels = {\n requests: TypedBroadcastChannel<WorkerRequestMessage>;\n responses: TypedBroadcastChannel<WorkerResponseMessage | WorkerErrorResponseMessage | WorkerNotificationMessage>;\n};\n\nexport const createBroadcastChannels = (prefix: string): WorkerBroadcastChannels => {\n return {\n requests: new TypedBroadcastChannel(`${prefix}-${broadcastChannelNames.requests}`),\n responses: new TypedBroadcastChannel(`${prefix}-${broadcastChannelNames.responses}`),\n };\n};\n\nexport type WorkerConfig<Props = any> = {\n dbId: string;\n clientId: string;\n props: Props;\n};\n\nexport type WorkerInitMessage = {\n type: \"init\";\n config: WorkerConfig;\n};\n\nexport function isWorkerInitMessage(message: unknown): message is WorkerInitMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"init\";\n}\n\nexport function isWorkerRequestMessage(message: unknown): message is WorkerRequestMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"request\";\n}\n\nexport function isWorkerResponseMessage(message: unknown): message is WorkerResponseMessage {\n return (\n typeof message === \"object\" && message !== null && \"type\" in message && \"requestId\" in message && \"data\" in message\n );\n}\n\nexport function isWorkerErrorResponseMessage(message: unknown): message is WorkerErrorResponseMessage {\n return typeof message === \"object\" && message !== null && \"type\" in message && message.type === \"error-response\";\n}\n\nexport function isWorkerNotificationMessage(message: unknown): message is WorkerNotificationMessage {\n return typeof message === \"object\" && message !== null && \"notificationType\" in message && !!message.notificationType;\n}\n","import { acquireLock } from \"../utils\";\n\nconst replicaLockName = (dbId: string) => `sync-db-replica-lock-${dbId}`;\n\nexport function acquireReplicaLock({ dbId }: { dbId: string }) {\n return acquireLock(replicaLockName(dbId), { mode: \"shared\" });\n}\n\nexport function runWithNoActiveReplicas({ dbId, run }: { dbId: string; run: () => void | Promise<void> }) {\n return navigator.locks.request(replicaLockName(dbId), { mode: \"exclusive\", ifAvailable: true }, async (lock) => {\n if (!lock) {\n return false;\n }\n await run();\n return true;\n });\n}\n","import { createStore, del, get, set } from \"idb-keyval\";\nimport { generateId } from \"../utils\";\nimport type { WorkerNotificationMessage } from \"./worker-common\";\n\nexport type ResetRequest = {\n epoch: string;\n requestedAt: number;\n};\n\n/** Durable async key-value storage for reset state. Injectable for tests. */\nexport type ResetStore = {\n get: <T>(key: string) => Promise<T | undefined>;\n set: (key: string, value: unknown) => Promise<void>;\n delete: (key: string) => Promise<void>;\n};\n\n/** Default IndexedDB-backed reset store (idb-keyval over a dedicated database). */\nexport function createIdbResetStore(): ResetStore {\n const store = createStore(\"sqlite-sync\", \"kv\");\n return {\n get: (key) => get(key, store),\n set: (key, value) => set(key, value, store),\n delete: (key) => del(key, store),\n };\n}\n\n/**\n * A clean reset is a recovery action for a de-sync detected now. If the reload\n * never happens (broadcast lost, tab crashed mid-reload, browser killed the page),\n * the request must not fire on an arbitrary later cold start and silently wipe\n * local-only writes accumulated since.\n */\nexport const RESET_REQUEST_TTL_MS = 10 * 60 * 1000; // 10 minutes\n\nconst resetRequestKey = (dbId: string) => `sqlite-sync-reset-request-${dbId}`;\nconst resetAppliedKey = (dbId: string) => `sqlite-sync-reset-applied-${dbId}`;\n\ntype ResetStateStoreOptions = {\n store: ResetStore;\n dbId: string;\n now?: () => number;\n};\n\nexport type ResetStateStore = ReturnType<typeof createResetStateStore>;\n\n/**\n * Worker-owned durable reset state. The reset decision must be owned by the\n * elected worker, not by tabs during `createSyncedDb` — otherwise a later\n * worker election could repeat an already-applied wipe.\n */\nexport function createResetStateStore({ store, dbId, now = () => Date.now() }: ResetStateStoreOptions) {\n const requestKey = resetRequestKey(dbId);\n const appliedKey = resetAppliedKey(dbId);\n\n return {\n async writeResetRequest(epoch: string): Promise<ResetRequest> {\n const request: ResetRequest = { epoch, requestedAt: now() };\n await store.set(requestKey, request);\n return request;\n },\n /**\n * Read the pending reset request after winning the worker election.\n * Returns the request only when it has not been applied yet and is within\n * the TTL. Stale requests are deleted so they cannot fire on a later cold start.\n */\n async resolvePendingReset(): Promise<ResetRequest | undefined> {\n const request = await store.get<ResetRequest>(requestKey);\n if (!request) {\n return undefined;\n }\n\n if (now() - request.requestedAt > RESET_REQUEST_TTL_MS) {\n await store.delete(requestKey);\n return undefined;\n }\n\n const appliedEpoch = await store.get<string>(appliedKey);\n if (request.epoch === appliedEpoch) {\n return undefined;\n }\n\n return request;\n },\n /**\n * Record the epoch as applied. Must be called only after the worker has\n * successfully initialized with `clearOnInit: true`, so a failed init can\n * be retried by a later elected worker.\n */\n async markResetApplied(epoch: string): Promise<void> {\n await store.set(appliedKey, epoch);\n },\n };\n}\n\ntype ReloadRequestHandlerOptions = {\n resetState: ResetStateStore;\n broadcast: (message: WorkerNotificationMessage) => void;\n generateEpoch?: () => string;\n};\n\n/**\n * Worker-side `requestReload` RPC implementation. For `clean: true` the reset\n * request is durably stored before broadcasting and before the RPC resolves,\n * so the epoch survives no matter which path triggers the reload.\n */\nexport function createReloadRequestHandler({\n resetState,\n broadcast,\n generateEpoch = generateId,\n}: ReloadRequestHandlerOptions) {\n return async (options: { clean: boolean }): Promise<void> => {\n const reloadEpoch = generateEpoch();\n\n if (options.clean) {\n await resetState.writeResetRequest(reloadEpoch);\n }\n\n broadcast({\n notificationType: \"reload-requested\",\n reloadEpoch,\n clean: options.clean,\n });\n };\n}\n"],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as EventsPullResponse, J as ClientDatasetRejectionReason, On as MigratableEvent, Ot as jsonSafeParse, T as EventsPushResponse, dn as SyncDbSchema, jn as SyncDbMigrator } from "./index-B3s_6aG6.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/server/admit-client-push.d.ts
|
|
4
4
|
type PushedCrdtEvent = MigratableEvent & {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { g as SQLiteDbWrapper, v as BoundMap, y as startPerformanceLogger } from "./system-schema-uHQdQTCk.js";
|
|
2
2
|
import { s as createTypedEventTarget } from "./crdt-table-schema-D6Yf61Ex.js";
|
|
3
3
|
//#region src/memory-db/sqlite-reactive-db.ts
|
|
4
4
|
var SQLiteReactiveDb = class SQLiteReactiveDb {
|
|
@@ -293,4 +293,4 @@ function parametersAreEqual(a, b) {
|
|
|
293
293
|
//#endregion
|
|
294
294
|
export { parametersAreEqual as n, SQLiteReactiveDb as t };
|
|
295
295
|
|
|
296
|
-
//# sourceMappingURL=sqlite-reactive-db-
|
|
296
|
+
//# sourceMappingURL=sqlite-reactive-db-BwY3FoIy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-reactive-db-DLS1xRZp.js","names":[],"sources":["../src/memory-db/sqlite-reactive-db.ts"],"sourcesContent":["import { BoundMap } from \"../bound-map\";\nimport { type Logger, startPerformanceLogger } from \"../logger\";\nimport { type PreparedStatement, SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport type { SqliteDriver } from \"../sqlite-driver\";\nimport { createTypedEventTarget, type TypedEvent } from \"../utils\";\n\ntype TableName<Database> = keyof Database extends string ? keyof Database : never;\n\nexport type SQLiteReactiveDbOptions = {\n driver: SqliteDriver;\n snapshot?: Uint8Array<ArrayBufferLike>;\n logger: Logger;\n loggerPrefix?: string;\n};\n\ntype EventsMap = {\n \"transaction-committed\": undefined;\n \"transaction-rolled-back\": undefined;\n \"any-table-changed\": undefined;\n} & Record<`table:${string}`, void>;\n\ntype LiveQuery<TResult> = {\n getRows: () => TResult[];\n refresh: () => void;\n subscribe: (onchange: () => void) => () => void;\n};\n\nexport type SharedLiveQuery<TResult> = LiveQuery<TResult> & {\n readonly sql: string;\n readonly parameters: readonly unknown[];\n getSubscriberCount: () => number;\n};\n\ntype SharedLiveQueryEntry<TResult> = SharedLiveQuery<TResult> & {\n listeners: Set<() => void>;\n unsubscribeFromLiveQuery: (() => void) | null;\n cleanupTimeout: ReturnType<typeof setTimeout> | null;\n};\n\nexport type SharedLiveQuerySnapshot = {\n sql: string;\n parameters: readonly unknown[];\n subscriberCount: number;\n};\n\nexport class SQLiteReactiveDb<Database> {\n readonly db: SQLiteDbWrapper<Database>;\n private readonly driver: SqliteDriver;\n\n private readonly logger: Logger;\n\n private tablesUsedStatement: PreparedStatement<[string], { name: string; isWrite: boolean }> | null = null;\n\n private eventTarget = createTypedEventTarget<EventsMap>();\n private isDisposed = false;\n private mutationVersion = 0;\n private tableMutationVersions = new Map<string, number>();\n\n private constructor(opts: SQLiteReactiveDbOptions) {\n this.driver = opts.driver;\n this.logger = opts.logger;\n\n this.db = new SQLiteDbWrapper({\n driver: opts.driver,\n logger: this.logger,\n loggerPrefix: opts.loggerPrefix ?? \"memory\",\n });\n }\n\n static create<Database>(opts: SQLiteReactiveDbOptions) {\n const perf = startPerformanceLogger(opts.logger, \"system\");\n\n const db = new SQLiteReactiveDb<Database>(opts);\n\n if (opts.snapshot) {\n db.useSnapshot(opts.snapshot);\n }\n db.registerDbHooks();\n\n perf.logEnd(\"createSQLiteMemoryDb\", \"success\", \"system\");\n\n return db;\n }\n\n /**\n * When false, table mutation versions only advance after the writing statement\n * returns, so they cannot tell whether that statement wrote to a table.\n */\n get hasSynchronousHooks() {\n return this.driver.synchronousHooks;\n }\n\n private liveQueryStatements = new BoundMap<string, PreparedStatement<any[], unknown>>({\n maxSize: 100,\n onRemove(_, value) {\n value.finalize();\n },\n });\n\n private sharedLiveQueries = new Map<string, SharedLiveQueryEntry<unknown>[]>();\n\n createLiveQuery<TResult>(query: { sql: string; parameters: readonly unknown[] }) {\n let rowsVersion = 0;\n\n const fetchRows = (parameters: readonly unknown[]) => {\n let statement = this.liveQueryStatements.get(query.sql);\n if (!statement) {\n statement = this.db.prepare<any[], any>(query.sql);\n this.liveQueryStatements.set(query.sql, statement);\n }\n rowsVersion = this.mutationVersion;\n return statement.execute(parameters as any) as TResult[];\n };\n\n let rows: TResult[] | null = null;\n\n const getRows = () => {\n if (!rows) {\n rows = fetchRows(query.parameters);\n }\n return rows;\n };\n\n let subscriber: (() => void) | null = null;\n\n let lastParameters: readonly unknown[] = query.parameters;\n const refresh = (parameters?: readonly unknown[]) => {\n if (parameters) {\n lastParameters = parameters;\n }\n rows = fetchRows(lastParameters);\n subscriber?.();\n };\n\n const subscribe = (onchange: () => void) => {\n if (subscriber) {\n throw new Error(\"Subscriber already exists\");\n }\n\n subscriber = onchange;\n const subscription = this.subscribeToQueryChanges({\n sql: query.sql,\n onDataChange: refresh,\n });\n\n // Tables may have changed while nothing was subscribed, e.g. between React's render and commit.\n if (rows && this.mutationVersion !== rowsVersion) {\n refresh();\n }\n\n return () => {\n subscription.unsubscribe();\n subscriber = null;\n };\n };\n\n return { getRows, refresh, subscribe };\n }\n\n getSharedLiveQuery<TResult>(query: { sql: string; parameters: readonly unknown[] }) {\n const existingEntry = this.sharedLiveQueries\n .get(query.sql)\n ?.find((entry) => parametersAreEqual(entry.parameters, query.parameters));\n if (existingEntry) {\n if (existingEntry.listeners.size === 0) {\n this.scheduleSharedLiveQueryCleanup(existingEntry);\n }\n return existingEntry as SharedLiveQuery<TResult>;\n }\n\n const liveQuery = this.createLiveQuery<TResult>(query);\n const entry: SharedLiveQueryEntry<TResult> = {\n sql: query.sql,\n parameters: query.parameters,\n listeners: new Set(),\n unsubscribeFromLiveQuery: null,\n cleanupTimeout: null,\n getRows: () => liveQuery.getRows(),\n refresh: () => {\n liveQuery.refresh();\n },\n getSubscriberCount: () => entry.listeners.size,\n subscribe: (onchange) => {\n this.cancelSharedLiveQueryCleanup(entry);\n this.registerSharedLiveQueryEntry(entry as SharedLiveQueryEntry<unknown>);\n entry.listeners.add(onchange);\n\n if (!entry.unsubscribeFromLiveQuery) {\n entry.unsubscribeFromLiveQuery = liveQuery.subscribe(() => {\n for (const listener of entry.listeners) {\n listener();\n }\n });\n }\n\n return () => {\n entry.listeners.delete(onchange);\n\n if (entry.listeners.size === 0) {\n entry.unsubscribeFromLiveQuery?.();\n entry.unsubscribeFromLiveQuery = null;\n this.scheduleSharedLiveQueryCleanup(entry);\n }\n };\n },\n };\n\n this.registerSharedLiveQueryEntry(entry as SharedLiveQueryEntry<unknown>);\n\n // Evict the entry if no subscriber attaches by the next tick.\n this.scheduleSharedLiveQueryCleanup(entry as SharedLiveQueryEntry<unknown>);\n\n return entry;\n }\n\n private registerSharedLiveQueryEntry(entry: SharedLiveQueryEntry<unknown>) {\n const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql) ?? [];\n if (!matchingSqlEntries.includes(entry)) {\n matchingSqlEntries.push(entry);\n this.sharedLiveQueries.set(entry.sql, matchingSqlEntries);\n }\n }\n\n getSharedLiveQueriesSnapshot(): SharedLiveQuerySnapshot[] {\n const snapshot: SharedLiveQuerySnapshot[] = [];\n for (const entries of this.sharedLiveQueries.values()) {\n for (const entry of entries) {\n snapshot.push({\n sql: entry.sql,\n parameters: entry.parameters,\n subscriberCount: entry.listeners.size,\n });\n }\n }\n return snapshot;\n }\n\n private scheduleSharedLiveQueryCleanup(entry: SharedLiveQueryEntry<unknown>) {\n this.cancelSharedLiveQueryCleanup(entry);\n entry.cleanupTimeout = setTimeout(() => {\n entry.cleanupTimeout = null;\n this.releaseSharedLiveQuery(entry);\n }, 0);\n }\n\n private cancelSharedLiveQueryCleanup(entry: SharedLiveQueryEntry<unknown>) {\n if (entry.cleanupTimeout) {\n clearTimeout(entry.cleanupTimeout);\n entry.cleanupTimeout = null;\n }\n }\n\n private releaseSharedLiveQuery(entry: SharedLiveQueryEntry<unknown>) {\n if (entry.listeners.size > 0 || entry.unsubscribeFromLiveQuery) {\n return;\n }\n\n const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql);\n if (!matchingSqlEntries) {\n return;\n }\n\n const nextEntries = matchingSqlEntries.filter((candidate) => candidate !== entry);\n if (nextEntries.length > 0) {\n this.sharedLiveQueries.set(entry.sql, nextEntries);\n } else {\n this.sharedLiveQueries.delete(entry.sql);\n }\n }\n\n subscribeToQueryChanges(params: { sql: string; onDataChange: () => void }) {\n const { sql, onDataChange } = params;\n\n const tables = this.getTablesUsed(sql);\n const readTables = new Set<string>();\n for (const table of tables) {\n if (!readTables.has(table.name)) {\n readTables.add(table.name);\n } else if (table.isWrite) {\n throw new Error(\"This query writes and reads from the same table. This may cause infinite loops.\");\n }\n }\n\n const notifyDataChange = createDebouncedCallback(() => {\n onDataChange();\n }, 30);\n\n for (const table of readTables) {\n this.eventTarget.addEventListener(`table:${table}`, notifyDataChange);\n }\n this.eventTarget.addEventListener(\"any-table-changed\", notifyDataChange);\n\n return {\n unsubscribe: () => {\n for (const table of readTables) {\n this.eventTarget.removeEventListener(`table:${table}`, notifyDataChange);\n }\n this.eventTarget.removeEventListener(\"any-table-changed\", notifyDataChange);\n },\n };\n }\n\n subscribeToTableChanges(table: string, onChanges: () => void) {\n this.eventTarget.addEventListener(`table:${table}`, onChanges);\n this.eventTarget.addEventListener(\"any-table-changed\", onChanges);\n return {\n unsubscribe: () => {\n this.eventTarget.removeEventListener(`table:${table}`, onChanges);\n this.eventTarget.removeEventListener(\"any-table-changed\", onChanges);\n },\n };\n }\n\n getTablesUsed(query: string) {\n if (!this.tablesUsedStatement) {\n this.tablesUsedStatement = this.db.prepare<[string], { name: string; isWrite: boolean }>(\n \"select t.tbl_name as name, u.wr as isWrite from tables_used(?) as u inner join sqlite_master as t on t.name = u.name where u.schema = 'main'\",\n { loggerLevel: \"system\" },\n );\n }\n\n const tables = this.tablesUsedStatement.execute([query]);\n\n if (tables.length === 0 && query.toLowerCase().includes(\"delete\")) {\n // tables_used function does not work with delete queries that clear entire tables\n tables.push(...this.getClearedTables(query));\n }\n\n return tables;\n }\n\n private getClearedTables(query: string) {\n const operations = this.db.execute<{\n opcode: string;\n p1: number;\n p2: number;\n }>(`EXPLAIN ${query.split(\";\")[0]}`, { loggerLevel: \"system\" }).rows;\n\n const clearedTablesRootPages = new Set<number>();\n for (const operation of operations) {\n if (operation.opcode === \"Clear\" && operation.p2 === 0) {\n clearedTablesRootPages.add(operation.p1);\n }\n }\n\n if (clearedTablesRootPages.size === 0) {\n return [];\n }\n\n const tableNames = this.db.execute<{ name: string; isWrite: boolean }>(\n `select t.tbl_name as name, true as isWrite from sqlite_master as t where t.rootpage in (${Array.from(\n clearedTablesRootPages,\n ).join(\",\")})`,\n { loggerLevel: \"system\" },\n ).rows;\n\n return tableNames;\n }\n\n addEventListener<K extends keyof EventsMap>(type: K, listener: (event: TypedEvent<EventsMap[K]>) => void) {\n this.eventTarget.addEventListener(type, listener);\n }\n\n removeEventListener<K extends keyof EventsMap>(type: K, listener: (event: TypedEvent<EventsMap[K]>) => void) {\n this.eventTarget.removeEventListener(type, listener);\n }\n\n getTableMutationVersion(table: string) {\n return this.tableMutationVersions.get(table) ?? 0;\n }\n\n private notifyTableSubscribers(tables: (TableName<Database> | (string & {}))[] | Set<string> | null = null) {\n if (!tables) {\n this.eventTarget.dispatchEvent(\"any-table-changed\", undefined);\n return;\n }\n\n for (const table of tables) {\n this.eventTarget.dispatchEvent(`table:${table}`, undefined);\n }\n }\n\n private registerDbHooks() {\n let updateQueue = new Set<string>();\n\n // Asynchronous drivers can deliver hooks that were queued before dispose.\n this.driver.setHooks({\n onUpdate: (table) => {\n if (this.isDisposed) {\n return;\n }\n updateQueue.add(table);\n this.tableMutationVersions.set(table, ++this.mutationVersion);\n },\n onRollback: () => {\n if (this.isDisposed || updateQueue.size === 0) {\n return;\n }\n\n updateQueue.clear();\n this.eventTarget.dispatchEvent(\"transaction-rolled-back\", undefined);\n },\n onCommit: () => {\n if (this.isDisposed || updateQueue.size === 0) {\n return;\n }\n\n const tables = updateQueue;\n updateQueue = new Set<string>();\n this.eventTarget.dispatchEvent(\"transaction-committed\", undefined);\n\n queueMicrotask(() => {\n if (!this.isDisposed) {\n this.notifyTableSubscribers(tables);\n }\n });\n },\n });\n }\n\n createSnapshot() {\n const perf = startPerformanceLogger(this.logger);\n const snapshot = this.db.createSnapshot();\n perf.logEnd(\"createSnapshot\", `snapshot size: ${snapshot.byteLength}`, \"info\");\n\n return snapshot;\n }\n\n useSnapshot(snapshot: Uint8Array<ArrayBufferLike>) {\n this.db.useSnapshot(snapshot);\n this.mutationVersion++;\n this.notifyTableSubscribers();\n }\n\n dispose() {\n this.isDisposed = true;\n for (const entries of this.sharedLiveQueries.values()) {\n for (const entry of entries) {\n this.cancelSharedLiveQueryCleanup(entry);\n entry.unsubscribeFromLiveQuery?.();\n entry.unsubscribeFromLiveQuery = null;\n entry.listeners.clear();\n }\n }\n this.sharedLiveQueries.clear();\n this.liveQueryStatements.clear();\n if (this.tablesUsedStatement) {\n this.tablesUsedStatement.finalize();\n this.tablesUsedStatement = null;\n }\n this.driver.setHooks(null);\n this.db.close();\n }\n}\n\nfunction createDebouncedCallback<TArgs extends unknown[]>(callback: (...args: TArgs) => void, delay: number) {\n let timeout: unknown | null = null;\n let shouldCallWithoutDelay = true;\n\n return (...args: TArgs) => {\n if (shouldCallWithoutDelay) {\n callback(...args);\n shouldCallWithoutDelay = false;\n return;\n }\n\n const effect = () => {\n timeout = null;\n shouldCallWithoutDelay = true;\n return callback(...args);\n };\n\n if (timeout) {\n clearTimeout(timeout as any);\n }\n\n timeout = setTimeout(effect, delay);\n };\n}\n\nexport function parametersAreEqual(a: readonly unknown[] | undefined, b: readonly unknown[] | undefined): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) {\n return false;\n }\n }\n return true;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sqlite-reactive-db-BwY3FoIy.js","names":[],"sources":["../src/memory-db/sqlite-reactive-db.ts"],"sourcesContent":["import { BoundMap } from \"../bound-map\";\nimport { type Logger, startPerformanceLogger } from \"../logger\";\nimport { type PreparedStatement, SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport type { SqliteDriver } from \"../sqlite-driver\";\nimport { createTypedEventTarget, type TypedEvent } from \"../utils\";\n\ntype TableName<Database> = keyof Database extends string ? keyof Database : never;\n\nexport type SQLiteReactiveDbOptions = {\n driver: SqliteDriver;\n snapshot?: Uint8Array<ArrayBufferLike>;\n logger: Logger;\n loggerPrefix?: string;\n};\n\ntype EventsMap = {\n \"transaction-committed\": undefined;\n \"transaction-rolled-back\": undefined;\n \"any-table-changed\": undefined;\n} & Record<`table:${string}`, void>;\n\ntype LiveQuery<TResult> = {\n getRows: () => TResult[];\n refresh: () => void;\n subscribe: (onchange: () => void) => () => void;\n};\n\nexport type SharedLiveQuery<TResult> = LiveQuery<TResult> & {\n readonly sql: string;\n readonly parameters: readonly unknown[];\n getSubscriberCount: () => number;\n};\n\ntype SharedLiveQueryEntry<TResult> = SharedLiveQuery<TResult> & {\n listeners: Set<() => void>;\n unsubscribeFromLiveQuery: (() => void) | null;\n cleanupTimeout: ReturnType<typeof setTimeout> | null;\n};\n\nexport type SharedLiveQuerySnapshot = {\n sql: string;\n parameters: readonly unknown[];\n subscriberCount: number;\n};\n\nexport class SQLiteReactiveDb<Database> {\n readonly db: SQLiteDbWrapper<Database>;\n private readonly driver: SqliteDriver;\n\n private readonly logger: Logger;\n\n private tablesUsedStatement: PreparedStatement<[string], { name: string; isWrite: boolean }> | null = null;\n\n private eventTarget = createTypedEventTarget<EventsMap>();\n private isDisposed = false;\n private mutationVersion = 0;\n private tableMutationVersions = new Map<string, number>();\n\n private constructor(opts: SQLiteReactiveDbOptions) {\n this.driver = opts.driver;\n this.logger = opts.logger;\n\n this.db = new SQLiteDbWrapper({\n driver: opts.driver,\n logger: this.logger,\n loggerPrefix: opts.loggerPrefix ?? \"memory\",\n });\n }\n\n static create<Database>(opts: SQLiteReactiveDbOptions) {\n const perf = startPerformanceLogger(opts.logger, \"system\");\n\n const db = new SQLiteReactiveDb<Database>(opts);\n\n if (opts.snapshot) {\n db.useSnapshot(opts.snapshot);\n }\n db.registerDbHooks();\n\n perf.logEnd(\"createSQLiteMemoryDb\", \"success\", \"system\");\n\n return db;\n }\n\n /**\n * When false, table mutation versions only advance after the writing statement\n * returns, so they cannot tell whether that statement wrote to a table.\n */\n get hasSynchronousHooks() {\n return this.driver.synchronousHooks;\n }\n\n private liveQueryStatements = new BoundMap<string, PreparedStatement<any[], unknown>>({\n maxSize: 100,\n onRemove(_, value) {\n value.finalize();\n },\n });\n\n private sharedLiveQueries = new Map<string, SharedLiveQueryEntry<unknown>[]>();\n\n createLiveQuery<TResult>(query: { sql: string; parameters: readonly unknown[] }) {\n let rowsVersion = 0;\n\n const fetchRows = (parameters: readonly unknown[]) => {\n let statement = this.liveQueryStatements.get(query.sql);\n if (!statement) {\n statement = this.db.prepare<any[], any>(query.sql);\n this.liveQueryStatements.set(query.sql, statement);\n }\n rowsVersion = this.mutationVersion;\n return statement.execute(parameters as any) as TResult[];\n };\n\n let rows: TResult[] | null = null;\n\n const getRows = () => {\n if (!rows) {\n rows = fetchRows(query.parameters);\n }\n return rows;\n };\n\n let subscriber: (() => void) | null = null;\n\n let lastParameters: readonly unknown[] = query.parameters;\n const refresh = (parameters?: readonly unknown[]) => {\n if (parameters) {\n lastParameters = parameters;\n }\n rows = fetchRows(lastParameters);\n subscriber?.();\n };\n\n const subscribe = (onchange: () => void) => {\n if (subscriber) {\n throw new Error(\"Subscriber already exists\");\n }\n\n subscriber = onchange;\n const subscription = this.subscribeToQueryChanges({\n sql: query.sql,\n onDataChange: refresh,\n });\n\n // Tables may have changed while nothing was subscribed, e.g. between React's render and commit.\n if (rows && this.mutationVersion !== rowsVersion) {\n refresh();\n }\n\n return () => {\n subscription.unsubscribe();\n subscriber = null;\n };\n };\n\n return { getRows, refresh, subscribe };\n }\n\n getSharedLiveQuery<TResult>(query: { sql: string; parameters: readonly unknown[] }) {\n const existingEntry = this.sharedLiveQueries\n .get(query.sql)\n ?.find((entry) => parametersAreEqual(entry.parameters, query.parameters));\n if (existingEntry) {\n if (existingEntry.listeners.size === 0) {\n this.scheduleSharedLiveQueryCleanup(existingEntry);\n }\n return existingEntry as SharedLiveQuery<TResult>;\n }\n\n const liveQuery = this.createLiveQuery<TResult>(query);\n const entry: SharedLiveQueryEntry<TResult> = {\n sql: query.sql,\n parameters: query.parameters,\n listeners: new Set(),\n unsubscribeFromLiveQuery: null,\n cleanupTimeout: null,\n getRows: () => liveQuery.getRows(),\n refresh: () => {\n liveQuery.refresh();\n },\n getSubscriberCount: () => entry.listeners.size,\n subscribe: (onchange) => {\n this.cancelSharedLiveQueryCleanup(entry);\n this.registerSharedLiveQueryEntry(entry as SharedLiveQueryEntry<unknown>);\n entry.listeners.add(onchange);\n\n if (!entry.unsubscribeFromLiveQuery) {\n entry.unsubscribeFromLiveQuery = liveQuery.subscribe(() => {\n for (const listener of entry.listeners) {\n listener();\n }\n });\n }\n\n return () => {\n entry.listeners.delete(onchange);\n\n if (entry.listeners.size === 0) {\n entry.unsubscribeFromLiveQuery?.();\n entry.unsubscribeFromLiveQuery = null;\n this.scheduleSharedLiveQueryCleanup(entry);\n }\n };\n },\n };\n\n this.registerSharedLiveQueryEntry(entry as SharedLiveQueryEntry<unknown>);\n\n // Evict the entry if no subscriber attaches by the next tick.\n this.scheduleSharedLiveQueryCleanup(entry as SharedLiveQueryEntry<unknown>);\n\n return entry;\n }\n\n private registerSharedLiveQueryEntry(entry: SharedLiveQueryEntry<unknown>) {\n const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql) ?? [];\n if (!matchingSqlEntries.includes(entry)) {\n matchingSqlEntries.push(entry);\n this.sharedLiveQueries.set(entry.sql, matchingSqlEntries);\n }\n }\n\n getSharedLiveQueriesSnapshot(): SharedLiveQuerySnapshot[] {\n const snapshot: SharedLiveQuerySnapshot[] = [];\n for (const entries of this.sharedLiveQueries.values()) {\n for (const entry of entries) {\n snapshot.push({\n sql: entry.sql,\n parameters: entry.parameters,\n subscriberCount: entry.listeners.size,\n });\n }\n }\n return snapshot;\n }\n\n private scheduleSharedLiveQueryCleanup(entry: SharedLiveQueryEntry<unknown>) {\n this.cancelSharedLiveQueryCleanup(entry);\n entry.cleanupTimeout = setTimeout(() => {\n entry.cleanupTimeout = null;\n this.releaseSharedLiveQuery(entry);\n }, 0);\n }\n\n private cancelSharedLiveQueryCleanup(entry: SharedLiveQueryEntry<unknown>) {\n if (entry.cleanupTimeout) {\n clearTimeout(entry.cleanupTimeout);\n entry.cleanupTimeout = null;\n }\n }\n\n private releaseSharedLiveQuery(entry: SharedLiveQueryEntry<unknown>) {\n if (entry.listeners.size > 0 || entry.unsubscribeFromLiveQuery) {\n return;\n }\n\n const matchingSqlEntries = this.sharedLiveQueries.get(entry.sql);\n if (!matchingSqlEntries) {\n return;\n }\n\n const nextEntries = matchingSqlEntries.filter((candidate) => candidate !== entry);\n if (nextEntries.length > 0) {\n this.sharedLiveQueries.set(entry.sql, nextEntries);\n } else {\n this.sharedLiveQueries.delete(entry.sql);\n }\n }\n\n subscribeToQueryChanges(params: { sql: string; onDataChange: () => void }) {\n const { sql, onDataChange } = params;\n\n const tables = this.getTablesUsed(sql);\n const readTables = new Set<string>();\n for (const table of tables) {\n if (!readTables.has(table.name)) {\n readTables.add(table.name);\n } else if (table.isWrite) {\n throw new Error(\"This query writes and reads from the same table. This may cause infinite loops.\");\n }\n }\n\n const notifyDataChange = createDebouncedCallback(() => {\n onDataChange();\n }, 30);\n\n for (const table of readTables) {\n this.eventTarget.addEventListener(`table:${table}`, notifyDataChange);\n }\n this.eventTarget.addEventListener(\"any-table-changed\", notifyDataChange);\n\n return {\n unsubscribe: () => {\n for (const table of readTables) {\n this.eventTarget.removeEventListener(`table:${table}`, notifyDataChange);\n }\n this.eventTarget.removeEventListener(\"any-table-changed\", notifyDataChange);\n },\n };\n }\n\n subscribeToTableChanges(table: string, onChanges: () => void) {\n this.eventTarget.addEventListener(`table:${table}`, onChanges);\n this.eventTarget.addEventListener(\"any-table-changed\", onChanges);\n return {\n unsubscribe: () => {\n this.eventTarget.removeEventListener(`table:${table}`, onChanges);\n this.eventTarget.removeEventListener(\"any-table-changed\", onChanges);\n },\n };\n }\n\n getTablesUsed(query: string) {\n if (!this.tablesUsedStatement) {\n this.tablesUsedStatement = this.db.prepare<[string], { name: string; isWrite: boolean }>(\n \"select t.tbl_name as name, u.wr as isWrite from tables_used(?) as u inner join sqlite_master as t on t.name = u.name where u.schema = 'main'\",\n { loggerLevel: \"system\" },\n );\n }\n\n const tables = this.tablesUsedStatement.execute([query]);\n\n if (tables.length === 0 && query.toLowerCase().includes(\"delete\")) {\n // tables_used function does not work with delete queries that clear entire tables\n tables.push(...this.getClearedTables(query));\n }\n\n return tables;\n }\n\n private getClearedTables(query: string) {\n const operations = this.db.execute<{\n opcode: string;\n p1: number;\n p2: number;\n }>(`EXPLAIN ${query.split(\";\")[0]}`, { loggerLevel: \"system\" }).rows;\n\n const clearedTablesRootPages = new Set<number>();\n for (const operation of operations) {\n if (operation.opcode === \"Clear\" && operation.p2 === 0) {\n clearedTablesRootPages.add(operation.p1);\n }\n }\n\n if (clearedTablesRootPages.size === 0) {\n return [];\n }\n\n const tableNames = this.db.execute<{ name: string; isWrite: boolean }>(\n `select t.tbl_name as name, true as isWrite from sqlite_master as t where t.rootpage in (${Array.from(\n clearedTablesRootPages,\n ).join(\",\")})`,\n { loggerLevel: \"system\" },\n ).rows;\n\n return tableNames;\n }\n\n addEventListener<K extends keyof EventsMap>(type: K, listener: (event: TypedEvent<EventsMap[K]>) => void) {\n this.eventTarget.addEventListener(type, listener);\n }\n\n removeEventListener<K extends keyof EventsMap>(type: K, listener: (event: TypedEvent<EventsMap[K]>) => void) {\n this.eventTarget.removeEventListener(type, listener);\n }\n\n getTableMutationVersion(table: string) {\n return this.tableMutationVersions.get(table) ?? 0;\n }\n\n private notifyTableSubscribers(tables: (TableName<Database> | (string & {}))[] | Set<string> | null = null) {\n if (!tables) {\n this.eventTarget.dispatchEvent(\"any-table-changed\", undefined);\n return;\n }\n\n for (const table of tables) {\n this.eventTarget.dispatchEvent(`table:${table}`, undefined);\n }\n }\n\n private registerDbHooks() {\n let updateQueue = new Set<string>();\n\n // Asynchronous drivers can deliver hooks that were queued before dispose.\n this.driver.setHooks({\n onUpdate: (table) => {\n if (this.isDisposed) {\n return;\n }\n updateQueue.add(table);\n this.tableMutationVersions.set(table, ++this.mutationVersion);\n },\n onRollback: () => {\n if (this.isDisposed || updateQueue.size === 0) {\n return;\n }\n\n updateQueue.clear();\n this.eventTarget.dispatchEvent(\"transaction-rolled-back\", undefined);\n },\n onCommit: () => {\n if (this.isDisposed || updateQueue.size === 0) {\n return;\n }\n\n const tables = updateQueue;\n updateQueue = new Set<string>();\n this.eventTarget.dispatchEvent(\"transaction-committed\", undefined);\n\n queueMicrotask(() => {\n if (!this.isDisposed) {\n this.notifyTableSubscribers(tables);\n }\n });\n },\n });\n }\n\n createSnapshot() {\n const perf = startPerformanceLogger(this.logger);\n const snapshot = this.db.createSnapshot();\n perf.logEnd(\"createSnapshot\", `snapshot size: ${snapshot.byteLength}`, \"info\");\n\n return snapshot;\n }\n\n useSnapshot(snapshot: Uint8Array<ArrayBufferLike>) {\n this.db.useSnapshot(snapshot);\n this.mutationVersion++;\n this.notifyTableSubscribers();\n }\n\n dispose() {\n this.isDisposed = true;\n for (const entries of this.sharedLiveQueries.values()) {\n for (const entry of entries) {\n this.cancelSharedLiveQueryCleanup(entry);\n entry.unsubscribeFromLiveQuery?.();\n entry.unsubscribeFromLiveQuery = null;\n entry.listeners.clear();\n }\n }\n this.sharedLiveQueries.clear();\n this.liveQueryStatements.clear();\n if (this.tablesUsedStatement) {\n this.tablesUsedStatement.finalize();\n this.tablesUsedStatement = null;\n }\n this.driver.setHooks(null);\n this.db.close();\n }\n}\n\nfunction createDebouncedCallback<TArgs extends unknown[]>(callback: (...args: TArgs) => void, delay: number) {\n let timeout: unknown | null = null;\n let shouldCallWithoutDelay = true;\n\n return (...args: TArgs) => {\n if (shouldCallWithoutDelay) {\n callback(...args);\n shouldCallWithoutDelay = false;\n return;\n }\n\n const effect = () => {\n timeout = null;\n shouldCallWithoutDelay = true;\n return callback(...args);\n };\n\n if (timeout) {\n clearTimeout(timeout as any);\n }\n\n timeout = setTimeout(effect, delay);\n };\n}\n\nexport function parametersAreEqual(a: readonly unknown[] | undefined, b: readonly unknown[] | undefined): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!Object.is(a[i], b[i])) {\n return false;\n }\n }\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -446,7 +446,7 @@ function createSQLiteKvStore({ db, metaTableName }) {
|
|
|
446
446
|
})
|
|
447
447
|
};
|
|
448
448
|
}
|
|
449
|
-
const STATE_HASH_KV_KEY = "crdt.consistency.state_hash.
|
|
449
|
+
const STATE_HASH_KV_KEY = "crdt.consistency.state_hash.v2";
|
|
450
450
|
function createKvStateHashStore(kvStore) {
|
|
451
451
|
return {
|
|
452
452
|
read: () => kvStore.get(STATE_HASH_KV_KEY),
|
|
@@ -539,6 +539,19 @@ FROM (
|
|
|
539
539
|
WHERE "latest"."dataset" = "log"."dataset" AND "latest"."item_id" = "log"."item_id"
|
|
540
540
|
AND "latest"."timestamp" > coalesce(json_extract("log"."payload", '$.id'), '')`);
|
|
541
541
|
}
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
version: 6,
|
|
545
|
+
up: (ctx) => {
|
|
546
|
+
const updateLog = quoteId(ctx.updateLogTable.fullIdentifier);
|
|
547
|
+
const isStale = `json_extract("payload", '$.tombstone') < json_extract("payload", '$.id')`;
|
|
548
|
+
const { rows } = ctx.execute(`SELECT DISTINCT "log"."dataset" AS "dataset" FROM ${updateLog} AS "log"
|
|
549
|
+
JOIN "sqlite_master" AS "master" ON "master"."type" = 'table' AND "master"."name" = "log"."dataset"
|
|
550
|
+
WHERE ${isStale}`);
|
|
551
|
+
for (const { dataset } of rows) ctx.execute(`UPDATE ${quoteId(dataset)} SET "tombstone" = 0
|
|
552
|
+
WHERE "tombstone" <> 0 AND "id" IN (SELECT "item_id" FROM ${updateLog} WHERE "dataset" = ? AND ${isStale})`, [dataset]);
|
|
553
|
+
ctx.execute(`UPDATE ${updateLog} SET "payload" = json_set("payload", '$.tombstone', json_extract("payload", '$.id')) WHERE ${isStale}`);
|
|
554
|
+
}
|
|
542
555
|
}
|
|
543
556
|
];
|
|
544
557
|
function runSystemMigrations(opts) {
|
|
@@ -573,7 +586,10 @@ function applySystemDbSchema(db, { kvTableName, dbConfig }) {
|
|
|
573
586
|
migrations: baseSystemMigrations,
|
|
574
587
|
version: kvStore.createNumberStoredValue("internal-schema-version", -1),
|
|
575
588
|
dbConfig,
|
|
576
|
-
execute: (sql) => db.execute(
|
|
589
|
+
execute: (sql, parameters = []) => db.execute({
|
|
590
|
+
sql,
|
|
591
|
+
parameters
|
|
592
|
+
}, { loggerLevel: "system" }),
|
|
577
593
|
transaction: (callback) => db.executeTransaction(callback)
|
|
578
594
|
});
|
|
579
595
|
return { kvStore };
|
|
@@ -602,6 +618,6 @@ function applyMemoryDbSchema(db) {
|
|
|
602
618
|
)`, { loggerLevel: "system" });
|
|
603
619
|
}
|
|
604
620
|
//#endregion
|
|
605
|
-
export {
|
|
621
|
+
export { compileKyselyStatement as _, baseSystemMigrations as a, introspectDb as b, memoryDbConfig as c, STATE_HASH_KV_KEY as d, createKvStateHashStore as f, SQLiteDbWrapper as g, createStoredValue as h, applyWorkerDbSchema as i, runSystemMigrations as l, createSQLiteKvStore as m, applyLocalDbSchema as n, createSystemDbConfig as o, createKvStoreTableQuery as p, applyMemoryDbSchema as r, localDbConfig as s, LOCAL_KV_TABLE_NAME as t, workerDbConfig as u, BoundMap as v, dummyKysely as x, startPerformanceLogger as y };
|
|
606
622
|
|
|
607
|
-
//# sourceMappingURL=system-schema-
|
|
623
|
+
//# sourceMappingURL=system-schema-uHQdQTCk.js.map
|