@sqlite-sync/core 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{crdt-sync-remote-source-Q6P6OiL0.js → crdt-sync-remote-source-CPXzmSIS.js} +2 -2
- package/dist/{crdt-sync-remote-source-Q6P6OiL0.js.map → crdt-sync-remote-source-CPXzmSIS.js.map} +1 -1
- package/dist/{crdt-table-schema-2w_AIpsV.js → crdt-table-schema-BRm_WdcW.js} +19 -2
- package/dist/crdt-table-schema-BRm_WdcW.js.map +1 -0
- package/dist/{index-3zVvNWRd.d.ts → index-BHsKLqcK.d.ts} +6 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +212 -186
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +3 -3
- package/dist/{make-crdt-table-D8VQ-ptj.js → make-crdt-table-C6l0F-o_.js} +2 -2
- package/dist/{make-crdt-table-D8VQ-ptj.js.map → make-crdt-table-C6l0F-o_.js.map} +1 -1
- package/dist/{reset-state-BQi96NLn.js → reset-state-t0tTGqoh.js} +23 -342
- package/dist/reset-state-t0tTGqoh.js.map +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +2 -2
- package/dist/system-schema-Dvl_BLId.js +523 -0
- package/dist/system-schema-Dvl_BLId.js.map +1 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +15 -10
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/crdt-table-schema-2w_AIpsV.js.map +0 -1
- package/dist/reset-state-BQi96NLn.js.map +0 -1
- package/dist/system-schema-C2JZDv6L.js +0 -178
- package/dist/system-schema-C2JZDv6L.js.map +0 -1
package/dist/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","names":[],"sources":["../src/web-socket/ws-remote-source.ts","../src/worker-db/event-log-gc.ts","../src/worker-db/storage-version.ts","../src/worker-db/db-worker.ts"],"sourcesContent":["import type { SyncServerMessage, SyncServerRequest } from \"../server\";\nimport { HEARTBEAT_PING, HEARTBEAT_PONG } from \"../server/heartbeat\";\nimport type { CreateRemoteSourceFactory } from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport { createDeferredPromise, type DeferredPromise, type DistributiveOmit, jsonSafeParse } from \"../utils\";\nimport type { EventsPullRequest, EventsPushRequest, EventsPushResponse, GetEventsBatch } from \"../worker\";\n\nconst WS_OPEN = 1;\n\ntype WsRemoteSourceConfig = {\n createWebSocket: () => Pick<WebSocket, \"onmessage\" | \"close\" | \"addEventListener\" | \"removeEventListener\"> & {\n readyState: number;\n send: (data: string) => void;\n };\n};\n\nexport const createWsRemoteSource = ({ createWebSocket }: WsRemoteSourceConfig): CreateRemoteSourceFactory => {\n return ({ onEventsAvailable, onClosed }) => {\n const socket = createWebSocket();\n const opened = createDeferredPromise<void>();\n opened.promise.catch(() => {});\n const requestsMap = new Map<string, DeferredPromise<unknown>>();\n let onPong: (() => void) | undefined;\n let closed = false;\n\n const shutdown = () => {\n if (closed) {\n return false;\n }\n closed = true;\n const error = new Error(\"WebSocket closed\");\n opened.reject(error);\n for (const request of requestsMap.values()) {\n request.reject(error);\n }\n requestsMap.clear();\n // Servers may not answer a close frame without a code, leaving the socket stuck in CLOSING.\n socket.close(1000);\n return true;\n };\n\n if (socket.readyState === WS_OPEN) {\n opened.resolve();\n }\n socket.addEventListener(\"open\", () => {\n opened.resolve();\n });\n // Closing on the first drop stops wrappers like PartySocket from reconnecting behind the library's back.\n socket.addEventListener(\"close\", () => {\n if (shutdown()) {\n onClosed();\n }\n });\n\n const send = async <T>(request: DistributiveOmit<SyncServerRequest, \"requestId\">): Promise<T> => {\n await opened.promise;\n if (closed) {\n throw new Error(\"WebSocket closed\");\n }\n const requestId = crypto.randomUUID();\n const promise = createDeferredPromise<T>();\n requestsMap.set(requestId, promise as DeferredPromise<unknown>);\n socket.send(JSON.stringify({ ...request, requestId }));\n return promise.promise;\n };\n\n const pushEvents = (request: EventsPushRequest) =>\n send<EventsPushResponse>({ type: \"push-events\", nodeId: request.nodeId, events: request.events });\n\n const pullEvents = (request: EventsPullRequest) =>\n send<GetEventsBatch>({\n type: \"pull-events\",\n afterSyncId: request.afterSyncId,\n excludeNodeId: request.excludeNodeId,\n });\n\n const ping = () => {\n socket.send(HEARTBEAT_PING);\n return new Promise<void>((resolve) => {\n onPong = resolve;\n });\n };\n\n socket.onmessage = (event) => {\n if (event.data === HEARTBEAT_PONG) {\n onPong?.();\n return;\n }\n\n const result = jsonSafeParse<SyncServerMessage>(event.data);\n\n if (!result.success || !(\"type\" in result.data) || !result.data.type) {\n return;\n }\n\n const message = result.data;\n\n switch (message.type) {\n case \"events-pull-response\":\n case \"events-push-response\": {\n const promise = requestsMap.get(message.requestId);\n if (!promise) {\n return;\n }\n promise.resolve(message.data);\n requestsMap.delete(message.requestId);\n break;\n }\n case \"events-applied\":\n onEventsAvailable({ newSyncId: message.newSyncId, remoteEventHlcSum: message.eventHlcSum });\n break;\n default:\n message satisfies never;\n return;\n }\n };\n\n return {\n pushEvents,\n pullEvents,\n ping,\n disconnect: () => {\n shutdown();\n },\n };\n };\n};\n","import type { Logger } from \"../logger\";\nimport type { SystemDbConfig } from \"../migrations/system-schema\";\nimport type { StoredValue } from \"../sqlite-crdt/stored-value\";\nimport type { InternalSQLiteWrapper } from \"../sqlite-db-wrapper\";\n\nexport const WORKER_EVENT_LOG_GC_MIN_RETAINED_EVENTS = 100;\n\ntype EventLogGcOptions = {\n db: InternalSQLiteWrapper<any>;\n dbConfig: SystemDbConfig;\n pushSyncId: StoredValue<number>;\n eventHlcAccumulator: StoredValue<string>;\n logger?: Logger;\n};\n\nexport function runWorkerEventLogGc({ db, dbConfig, pushSyncId, eventHlcAccumulator, logger }: EventLogGcOptions) {\n const eventsTable = dbConfig.eventsTable.fullIdentifier;\n\n if (eventHlcAccumulator.current === \"\") {\n const [{ count }] = db.executePreparedRaw<[], { count: number }>({\n key: \"event-log-gc-count-applied-events\",\n sql: `SELECT count(*) AS count FROM ${eventsTable} WHERE \"status\" = 'applied'`,\n meta: { loggerLevel: \"system\" },\n });\n if (count > 0) {\n logger?.(\n \"worker\",\n \"Skipping event log GC until the event HLC checksum has been computed from full history\",\n \"info\",\n );\n }\n return { skipped: true as const };\n }\n\n const [{ deleteBeforeSyncId }] = db.executePreparedRaw<[number], { deleteBeforeSyncId: number | null }>({\n key: \"event-log-gc-delete-before-sync-id\",\n sql: `\n SELECT min(\"sync_id\") AS deleteBeforeSyncId\n FROM (\n SELECT \"sync_id\"\n FROM ${eventsTable}\n WHERE \"status\" IN ('applied', 'deduped')\n ORDER BY \"sync_id\" DESC\n LIMIT ?\n )\n `,\n params: [WORKER_EVENT_LOG_GC_MIN_RETAINED_EVENTS],\n meta: { loggerLevel: \"system\" },\n });\n\n if (deleteBeforeSyncId === null) {\n return { skipped: false as const };\n }\n\n db.executePreparedRaw<[number, number], never>({\n key: \"event-log-gc-delete-events\",\n sql: `\n DELETE FROM ${eventsTable}\n WHERE \"sync_id\" < ?\n AND \"status\" IN ('applied', 'deduped')\n AND (\"origin\" = 'remote' OR \"sync_id\" <= ?)\n `,\n params: [deleteBeforeSyncId, pushSyncId.current],\n meta: { loggerLevel: \"system\" },\n });\n\n logger?.(\"worker\", \"Event log GC completed\", \"info\");\n return { skipped: false as const };\n}\n","import type { ResetStore } from \"./reset-state\";\n\n/**\n * Internal persisted-storage format version. Bump when the library changes\n * the worker DB layout in a way old persisted databases cannot survive —\n * every client resets its local DB on the next worker start.\n */\nexport const LIB_STORAGE_VERSION = 2;\n\nconst storageVersionKey = (dbId: string) => `sqlite-sync-storage-version-${dbId}`;\n\nexport function formatStorageVersion(appStorageVersion: string | undefined): string {\n return appStorageVersion === undefined\n ? `lib-v${LIB_STORAGE_VERSION}`\n : `lib-v${LIB_STORAGE_VERSION}:app-${appStorageVersion}`;\n}\n\ntype StorageVersionStoreOptions = {\n store: ResetStore;\n dbId: string;\n /** Dev-provided app storage version, combined with the internal lib version. */\n appStorageVersion?: string;\n};\n\nexport type StorageVersionStore = ReturnType<typeof createStorageVersionStore>;\n\n/**\n * Worker-owned durable storage version. The current version combines the\n * internal lib version with the dev-provided app version; when the stored\n * version does not match (including a missing record), the elected worker\n * initializes with `clearOnInit: true`. Wiping on a missing record is\n * harmless for fresh installs and correctly resets databases persisted\n * before versioning existed.\n */\nexport function createStorageVersionStore({ store, dbId, appStorageVersion }: StorageVersionStoreOptions) {\n const key = storageVersionKey(dbId);\n const currentVersion = formatStorageVersion(appStorageVersion);\n\n return {\n currentVersion,\n async getVersionStatus(): Promise<\"current\" | \"missing\" | \"outdated\"> {\n const storedVersion = await store.get<string>(key);\n if (storedVersion === undefined) {\n return \"missing\";\n }\n return storedVersion === currentVersion ? \"current\" : \"outdated\";\n },\n /**\n * Record the current version. Must be called only after the worker has\n * successfully initialized with the wiped DB, so a failed init can be\n * retried by a later elected worker.\n */\n async markCurrentVersionApplied(): Promise<void> {\n await store.set(key, currentVersion);\n },\n };\n}\n","import sqlite3InitModule from \"@sqlite.org/sqlite-wasm\";\nimport { createImportData } from \"../export-import\";\nimport { xxhash } from \"../hash\";\nimport { HLCCounter } from \"../hlc\";\nimport type { Logger } from \"../logger\";\nimport { createMigrator, type SyncDbMigrator } from \"../migrations/migrator\";\nimport { applyWorkerDbSchema, type WorkerDbSchema, workerDbConfig } from \"../migrations/system-schema\";\nimport { formatSchemaVerificationIssues, verifySyncSchema } from \"../schema/verify-sync-schema\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { type CrdtStorage, 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 type { CrdtEventStatus } from \"../sqlite-crdt/crdt-table-schema\";\nimport { SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport type { KvStore } from \"../sqlite-kv-store\";\nimport { createDeferredPromise, generateId } from \"../utils\";\nimport { runWorkerEventLogGc } from \"./event-log-gc\";\nimport { createIdbResetStore, createReloadRequestHandler, createResetStateStore, type ResetStore } from \"./reset-state\";\nimport { createStorageVersionStore } from \"./storage-version\";\nimport {\n createBroadcastChannels,\n isWorkerInitMessage,\n isWorkerRequestMessage,\n syncDbClientLockName,\n syncDbWorkerLockName,\n type WorkerConfig,\n type WorkerErrorResponseMessage,\n type WorkerNotificationMessage,\n type WorkerResponseMessage,\n type WorkerRpc,\n} from \"./worker-common\";\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\nasync function createDbWorker(config: WorkerConfig, opts: WorkerOptions) {\n const broadcastChannels = createBroadcastChannels(config.dbId);\n const logger = opts.logger ?? defaultLogger;\n\n if (opts.verifySchema) {\n const issues = await verifySyncSchema(opts.syncDbSchema);\n if (issues.length > 0) {\n throw new Error(formatSchemaVerificationIssues(issues));\n }\n logger(\"worker\", \"Schema verification passed: migrations match the declared schema\", \"info\");\n }\n\n const [sqlite3] = await Promise.all([sqlite3InitModule(), xxhash.ensureLoaded()]);\n\n const resetStore = opts.resetStore ?? createIdbResetStore();\n const resetState = createResetStateStore({\n store: resetStore,\n dbId: config.dbId,\n });\n const storageVersion = createStorageVersionStore({\n store: resetStore,\n dbId: config.dbId,\n appStorageVersion: opts.storageVersion,\n });\n const [pendingReset, versionStatus] = await Promise.all([\n resetState.resolvePendingReset(),\n storageVersion.getVersionStatus(),\n ]);\n const isVersionMismatch = versionStatus !== \"current\";\n\n if (versionStatus === \"missing\") {\n logger(\"worker\", `Initializing local DB storage version ${storageVersion.currentVersion}`, \"info\");\n } else if (versionStatus === \"outdated\") {\n logger(\"worker\", `Storage version mismatch — resetting local DB to ${storageVersion.currentVersion}`, \"warning\");\n }\n\n const pool = await sqlite3.installOpfsSAHPoolVfs({\n name: config.dbId,\n directory: `.${config.dbId}`,\n clearOnInit: !!pendingReset || isVersionMismatch,\n initialCapacity: 8,\n });\n\n const db = new SQLiteDbWrapper<WorkerDbSchema>({\n db: () => new pool.OpfsSAHPoolDb(`/${config.dbId}-main.db`),\n logger: logger,\n loggerPrefix: \"worker\",\n sqlite3,\n });\n\n db.execute(\"PRAGMA locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n db.execute(`ATTACH DATABASE '/${config.dbId}-worker.db' as worker`, { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n const { kvStore } = applyWorkerDbSchema(db);\n\n const migrator = createMigrator({\n migrations: opts.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: workerDbConfig.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 eventHlcAccumulator = kvStore.createStringStoredValue(\"crdt.consistency.event_hlc_sum.v2\", \"\");\n const storageGeneration = kvStore.createStringStoredValue(\"storage-generation\", \"\");\n if (!storageGeneration.current) {\n storageGeneration.current = generateId();\n }\n\n // Record the applied reset epoch / storage version only after the wiped DB\n // initialized successfully, so a failed init can be retried by a later\n // elected worker, while a later election does not wipe again.\n if (pendingReset) {\n await resetState.markResetApplied(pendingReset.epoch);\n }\n if (isVersionMismatch) {\n await storageVersion.markCurrentVersionApplied();\n }\n\n if (opts.eventLogGc === true) {\n runWorkerEventLogGc({\n db,\n dbConfig: workerDbConfig,\n pushSyncId,\n eventHlcAccumulator,\n logger,\n });\n }\n\n const crdtStorage = createCrdtStorage({\n nodeId: config.clientId,\n initialLocalSyncId: getMaxSyncId(db, \"none\"),\n migrator,\n hlc: new HLCCounter(config.clientId, () => Date.now()),\n db,\n dbConfig: workerDbConfig,\n eventHlcAccumulator,\n schema: opts.syncDbSchema,\n });\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n broadcastChannels.responses.postMessage({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n eventHlcSum: chunk.eventHlcSum,\n });\n },\n });\n\n const remoteSource = createRemoteSource({\n crdtStorage,\n migrator,\n clientId: config.clientId,\n pullSyncId,\n pushSyncId,\n remoteFactory: opts.createRemoteSource,\n });\n remoteSource.goOnline();\n\n const broadcastNotification = (notification: WorkerNotificationMessage) => {\n broadcastChannels.responses.postMessage(notification);\n };\n\n const postState = () => {\n broadcastNotification({\n notificationType: \"state-changed\",\n state: remoteSource.getState(),\n storageGeneration: storageGeneration.current,\n });\n };\n const stateChangedSubscription = remoteSource.addEventListener(\"state-changed\", () => {\n postState();\n });\n const deSyncDetectedSubscription = remoteSource.addEventListener(\"de-sync-detected\", (event) => {\n broadcastNotification({\n notificationType: \"de-sync-detected\",\n reason: event.payload.reason,\n });\n });\n const remoteSchemaVersionMismatchSubscription = remoteSource.addEventListener(\n \"remote-schema-version-mismatch\",\n (event) => {\n broadcastNotification({\n notificationType: \"remote-schema-version-mismatch\",\n remoteSchemaVersion: event.payload.remoteSchemaVersion,\n localSchemaVersion: event.payload.localSchemaVersion,\n });\n },\n );\n\n const rpcTarget: WorkerRpc = {\n execute: (query) => db.execute(query),\n getSnapshot: () => {\n const appliedSyncId = getMaxSyncId(db, \"pending\");\n db.execute(\"PRAGMA journal_mode=off\", { loggerLevel: \"system\" });\n const file = db.createSnapshot();\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n return {\n file,\n syncId: appliedSyncId,\n schemaVersion: migrator.currentSchemaVersion,\n storageGeneration: storageGeneration.current,\n };\n },\n importData: createImportData({\n migrator,\n tablesConfig: opts.syncDbSchema.tablesConfig,\n applyEvents: async (events) => {\n crdtStorage.applyOwnEvents(events);\n await crdtStorage.internal.processEnqueuedEvents();\n },\n }),\n postState,\n pushTabEvents: (request) => {\n const { beforeSyncId, afterSyncId } = crdtStorage.enqueueLocalEvents(request.events, request.nodeId);\n return {\n ok: true,\n beforeSyncId,\n afterSyncId,\n };\n },\n pullEvents: (request) => {\n return crdtStorage.getEventsBatch({\n afterSyncId: request.afterSyncId,\n status: \"applied\",\n excludeNodeId: request.excludeNodeId ?? \"\",\n limit: 100,\n });\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n dropConnection: () => remoteSource.dropConnection(\"NETWORK_OFFLINE\"),\n sync: () => {\n remoteSource.syncWithRemote().catch((error) => console.warn(\"Sync failed\", error));\n },\n requestReload: createReloadRequestHandler({\n resetState,\n broadcast: broadcastNotification,\n }),\n };\n\n broadcastChannels.requests.onmessage = (event) => {\n const message = event.data;\n\n if (!isWorkerRequestMessage(message)) {\n return;\n }\n\n const sendError = (error: unknown) => {\n const response: WorkerErrorResponseMessage = {\n type: \"error-response\",\n requestId: message.requestId,\n error: error instanceof Error ? error.message : String(error),\n };\n broadcastChannels.responses.postMessage(response);\n };\n\n try {\n const method = rpcTarget[message.method] as () => ReturnType<WorkerRpc[keyof WorkerRpc]>;\n const data = method.apply(null, message.args as []);\n\n if (data instanceof Promise) {\n data\n .then((result) => {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data: result,\n };\n broadcastChannels.responses.postMessage(response);\n })\n .catch(sendError);\n } else {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data,\n };\n broadcastChannels.responses.postMessage(response);\n }\n } catch (error) {\n sendError(error);\n }\n };\n\n rpcTarget.postState();\n\n return async () => {\n stateChangedSubscription.unsubscribe();\n deSyncDetectedSubscription.unsubscribe();\n remoteSchemaVersionMismatchSubscription.unsubscribe();\n await remoteSource.dispose();\n broadcastChannels.requests.close();\n broadcastChannels.responses.close();\n db.close();\n };\n}\n\ntype InitRemoteOptions = {\n clientId: string;\n crdtStorage: CrdtStorage;\n migrator: SyncDbMigrator;\n pullSyncId: ReturnType<KvStore[\"createNumberStoredValue\"]>;\n pushSyncId: ReturnType<KvStore[\"createNumberStoredValue\"]>;\n remoteFactory?: CreateRemoteSourceFactory;\n};\n\nfunction createRemoteSource({\n clientId,\n crdtStorage,\n migrator,\n pullSyncId,\n pushSyncId,\n remoteFactory,\n}: InitRemoteOptions) {\n return createCrdtSyncRemoteSource({\n bufferSize: PUSH_BATCH_SIZE,\n pullSyncId,\n pushSyncId,\n nodeId: clientId,\n storage: crdtStorage,\n migrator,\n remoteFactory,\n });\n}\n\nexport async function getWorkerConfig<Props = never>(): Promise<WorkerConfig<Props>> {\n let configSet = false;\n const responsePromise = createDeferredPromise<WorkerConfig>();\n\n self.onmessage = (event: MessageEvent<unknown>) => {\n if (configSet) {\n console.error(\"Worker config already set\");\n return;\n }\n\n const message = event.data;\n if (!isWorkerInitMessage(message)) {\n return;\n }\n\n responsePromise.resolve(message.config);\n configSet = true;\n };\n\n return responsePromise.promise;\n}\n\ntype WorkerOptions = {\n syncDbSchema: SyncDbSchema;\n logger?: Logger;\n createRemoteSource?: CreateRemoteSourceFactory;\n workerConfig?: WorkerConfig;\n /** Durable storage for reset state. Defaults to an IndexedDB-backed store. */\n resetStore?: ResetStore;\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 elected worker wipes the local DB on startup.\n */\n storageVersion?: string;\n /**\n * Dev-time drift check: replays the full migration history on a throwaway\n * in-memory database and verifies the result matches the schema declared with\n * `defineSyncSchema`. On mismatch the worker throws and refuses to start.\n * Enable in development only, e.g. `verifySchema: import.meta.env.DEV`.\n */\n verifySchema?: boolean;\n /**\n * Runs local worker event-log garbage collection on startup. Disabled by default;\n * set `eventLogGc: true` to enable. GC keeps the latest event rows and never removes\n * pending events or local rows that have not been pushed to the remote.\n */\n eventLogGc?: boolean;\n};\n\nexport async function startDbWorker(opts: WorkerOptions) {\n const config = opts.workerConfig ?? (await getWorkerConfig());\n\n await navigator.locks.request(`${syncDbWorkerLockName}-${config.dbId}`, { mode: \"exclusive\" }, async (lock) => {\n if (!lock) {\n return;\n }\n\n const cleanup = await createDbWorker(config, opts);\n\n const clientLockName = `${syncDbClientLockName}-${config.dbId}`;\n await new Promise<void>((resolve) => {\n const interval = setInterval(async () => {\n const { held } = await navigator.locks.query();\n const hasClients = held?.some((l) => l.name === clientLockName && l.mode === \"shared\");\n if (!hasClients) {\n clearInterval(interval);\n resolve();\n }\n }, 5_000);\n });\n\n await cleanup();\n });\n\n self.close();\n}\n\nfunction getMaxSyncId(db: SQLiteDbWrapper<WorkerDbSchema>, excludingStatus: \"none\" | \"pending\") {\n const [result] = db.executePrepared(\n \"get-max-sync-id\",\n { excludingStatus: excludingStatus as CrdtEventStatus },\n (db, params) =>\n db\n .selectFrom(\"worker.crdt_events\")\n .where(\"status\", \"!=\", params(\"excludingStatus\"))\n .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":"worker.js","names":[],"sources":["../src/web-socket/ws-remote-source.ts","../src/worker-db/event-log-gc.ts","../src/worker-db/storage-version.ts","../src/worker-db/db-worker.ts"],"sourcesContent":["import type { SyncServerMessage, SyncServerRequest } from \"../server\";\nimport { HEARTBEAT_PING, HEARTBEAT_PONG } from \"../server/heartbeat\";\nimport type { CreateRemoteSourceFactory } from \"../sqlite-crdt/crdt-sync-remote-source\";\nimport { createDeferredPromise, type DeferredPromise, type DistributiveOmit, jsonSafeParse } from \"../utils\";\nimport type { EventsPullRequest, EventsPushRequest, EventsPushResponse, GetEventsBatch } from \"../worker\";\n\nconst WS_OPEN = 1;\n\ntype WsRemoteSourceConfig = {\n createWebSocket: () => Pick<WebSocket, \"onmessage\" | \"close\" | \"addEventListener\" | \"removeEventListener\"> & {\n readyState: number;\n send: (data: string) => void;\n };\n};\n\nexport const createWsRemoteSource = ({ createWebSocket }: WsRemoteSourceConfig): CreateRemoteSourceFactory => {\n return ({ onEventsAvailable, onClosed }) => {\n const socket = createWebSocket();\n const opened = createDeferredPromise<void>();\n opened.promise.catch(() => {});\n const requestsMap = new Map<string, DeferredPromise<unknown>>();\n let onPong: (() => void) | undefined;\n let closed = false;\n\n const shutdown = () => {\n if (closed) {\n return false;\n }\n closed = true;\n const error = new Error(\"WebSocket closed\");\n opened.reject(error);\n for (const request of requestsMap.values()) {\n request.reject(error);\n }\n requestsMap.clear();\n // Servers may not answer a close frame without a code, leaving the socket stuck in CLOSING.\n socket.close(1000);\n return true;\n };\n\n if (socket.readyState === WS_OPEN) {\n opened.resolve();\n }\n socket.addEventListener(\"open\", () => {\n opened.resolve();\n });\n // Closing on the first drop stops wrappers like PartySocket from reconnecting behind the library's back.\n socket.addEventListener(\"close\", () => {\n if (shutdown()) {\n onClosed();\n }\n });\n\n const send = async <T>(request: DistributiveOmit<SyncServerRequest, \"requestId\">): Promise<T> => {\n await opened.promise;\n if (closed) {\n throw new Error(\"WebSocket closed\");\n }\n const requestId = crypto.randomUUID();\n const promise = createDeferredPromise<T>();\n requestsMap.set(requestId, promise as DeferredPromise<unknown>);\n socket.send(JSON.stringify({ ...request, requestId }));\n return promise.promise;\n };\n\n const pushEvents = (request: EventsPushRequest) =>\n send<EventsPushResponse>({ type: \"push-events\", nodeId: request.nodeId, events: request.events });\n\n const pullEvents = (request: EventsPullRequest) =>\n send<GetEventsBatch>({\n type: \"pull-events\",\n afterSyncId: request.afterSyncId,\n excludeNodeId: request.excludeNodeId,\n });\n\n const ping = () => {\n socket.send(HEARTBEAT_PING);\n return new Promise<void>((resolve) => {\n onPong = resolve;\n });\n };\n\n socket.onmessage = (event) => {\n if (event.data === HEARTBEAT_PONG) {\n onPong?.();\n return;\n }\n\n const result = jsonSafeParse<SyncServerMessage>(event.data);\n\n if (!result.success || !(\"type\" in result.data) || !result.data.type) {\n return;\n }\n\n const message = result.data;\n\n switch (message.type) {\n case \"events-pull-response\":\n case \"events-push-response\": {\n const promise = requestsMap.get(message.requestId);\n if (!promise) {\n return;\n }\n promise.resolve(message.data);\n requestsMap.delete(message.requestId);\n break;\n }\n case \"events-applied\":\n onEventsAvailable({ newSyncId: message.newSyncId, remoteEventHlcSum: message.eventHlcSum });\n break;\n default:\n message satisfies never;\n return;\n }\n };\n\n return {\n pushEvents,\n pullEvents,\n ping,\n disconnect: () => {\n shutdown();\n },\n };\n };\n};\n","import type { Logger } from \"../logger\";\nimport type { SystemDbConfig } from \"../migrations/system-schema\";\nimport type { StoredValue } from \"../sqlite-crdt/stored-value\";\nimport type { InternalSQLiteWrapper } from \"../sqlite-db-wrapper\";\n\nexport const WORKER_EVENT_LOG_GC_MIN_RETAINED_EVENTS = 100;\n\ntype EventLogGcOptions = {\n db: InternalSQLiteWrapper<any>;\n dbConfig: SystemDbConfig;\n pushSyncId: StoredValue<number>;\n eventHlcAccumulator: StoredValue<string>;\n logger?: Logger;\n};\n\nexport function runWorkerEventLogGc({ db, dbConfig, pushSyncId, eventHlcAccumulator, logger }: EventLogGcOptions) {\n const eventsTable = dbConfig.eventsTable.fullIdentifier;\n\n if (eventHlcAccumulator.current === \"\") {\n const [{ count }] = db.executePreparedRaw<[], { count: number }>({\n key: \"event-log-gc-count-applied-events\",\n sql: `SELECT count(*) AS count FROM ${eventsTable} WHERE \"status\" = 'applied'`,\n meta: { loggerLevel: \"system\" },\n });\n if (count > 0) {\n logger?.(\n \"worker\",\n \"Skipping event log GC until the event HLC checksum has been computed from full history\",\n \"info\",\n );\n }\n return { skipped: true as const };\n }\n\n const [{ deleteBeforeSyncId }] = db.executePreparedRaw<[number], { deleteBeforeSyncId: number | null }>({\n key: \"event-log-gc-delete-before-sync-id\",\n sql: `\n SELECT min(\"sync_id\") AS deleteBeforeSyncId\n FROM (\n SELECT \"sync_id\"\n FROM ${eventsTable}\n WHERE \"status\" IN ('applied', 'deduped')\n ORDER BY \"sync_id\" DESC\n LIMIT ?\n )\n `,\n params: [WORKER_EVENT_LOG_GC_MIN_RETAINED_EVENTS],\n meta: { loggerLevel: \"system\" },\n });\n\n if (deleteBeforeSyncId === null) {\n return { skipped: false as const };\n }\n\n db.executePreparedRaw<[number, number], never>({\n key: \"event-log-gc-delete-events\",\n sql: `\n DELETE FROM ${eventsTable}\n WHERE \"sync_id\" < ?\n AND \"status\" IN ('applied', 'deduped')\n AND (\"origin\" = 'remote' OR \"sync_id\" <= ?)\n `,\n params: [deleteBeforeSyncId, pushSyncId.current],\n meta: { loggerLevel: \"system\" },\n });\n\n logger?.(\"worker\", \"Event log GC completed\", \"info\");\n return { skipped: false as const };\n}\n","import type { ResetStore } from \"./reset-state\";\n\n/**\n * Internal persisted-storage format version. Bump when the library changes\n * the worker DB layout in a way old persisted databases cannot survive —\n * every client resets its local DB on the next worker start.\n */\nexport const LIB_STORAGE_VERSION = 2;\n\nconst storageVersionKey = (dbId: string) => `sqlite-sync-storage-version-${dbId}`;\n\nexport function formatStorageVersion(appStorageVersion: string | undefined): string {\n return appStorageVersion === undefined\n ? `lib-v${LIB_STORAGE_VERSION}`\n : `lib-v${LIB_STORAGE_VERSION}:app-${appStorageVersion}`;\n}\n\ntype StorageVersionStoreOptions = {\n store: ResetStore;\n dbId: string;\n /** Dev-provided app storage version, combined with the internal lib version. */\n appStorageVersion?: string;\n};\n\nexport type StorageVersionStore = ReturnType<typeof createStorageVersionStore>;\n\n/**\n * Worker-owned durable storage version. The current version combines the\n * internal lib version with the dev-provided app version; when the stored\n * version does not match (including a missing record), the elected worker\n * initializes with `clearOnInit: true`. Wiping on a missing record is\n * harmless for fresh installs and correctly resets databases persisted\n * before versioning existed.\n */\nexport function createStorageVersionStore({ store, dbId, appStorageVersion }: StorageVersionStoreOptions) {\n const key = storageVersionKey(dbId);\n const currentVersion = formatStorageVersion(appStorageVersion);\n\n return {\n currentVersion,\n async getVersionStatus(): Promise<\"current\" | \"missing\" | \"outdated\"> {\n const storedVersion = await store.get<string>(key);\n if (storedVersion === undefined) {\n return \"missing\";\n }\n return storedVersion === currentVersion ? \"current\" : \"outdated\";\n },\n /**\n * Record the current version. Must be called only after the worker has\n * successfully initialized with the wiped DB, so a failed init can be\n * retried by a later elected worker.\n */\n async markCurrentVersionApplied(): Promise<void> {\n await store.set(key, currentVersion);\n },\n };\n}\n","import sqlite3InitModule from \"@sqlite.org/sqlite-wasm\";\nimport { createImportData } from \"../export-import\";\nimport { xxhash } from \"../hash\";\nimport { HLCCounter } from \"../hlc\";\nimport type { Logger } from \"../logger\";\nimport { createMigrator, type SyncDbMigrator } from \"../migrations/migrator\";\nimport { applyWorkerDbSchema, type WorkerDbSchema, workerDbConfig } from \"../migrations/system-schema\";\nimport { formatSchemaVerificationIssues, verifySyncSchema } from \"../schema/verify-sync-schema\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { type CrdtStorage, 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 type { CrdtEventStatus } from \"../sqlite-crdt/crdt-table-schema\";\nimport { SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport type { KvStore } from \"../sqlite-kv-store\";\nimport { createDeferredPromise, generateId } from \"../utils\";\nimport { runWorkerEventLogGc } from \"./event-log-gc\";\nimport { runWithNoActiveReplicas } from \"./replica-lock\";\nimport { createIdbResetStore, createReloadRequestHandler, createResetStateStore, type ResetStore } from \"./reset-state\";\nimport { createStorageVersionStore } from \"./storage-version\";\nimport {\n createBroadcastChannels,\n isWorkerInitMessage,\n isWorkerRequestMessage,\n syncDbClientLockName,\n syncDbWorkerLockName,\n type WorkerConfig,\n type WorkerErrorResponseMessage,\n type WorkerNotificationMessage,\n type WorkerResponseMessage,\n type WorkerRpc,\n} from \"./worker-common\";\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\nasync function createDbWorker(config: WorkerConfig, opts: WorkerOptions) {\n const broadcastChannels = createBroadcastChannels(config.dbId);\n const logger = opts.logger ?? defaultLogger;\n\n if (opts.verifySchema) {\n const issues = await verifySyncSchema(opts.syncDbSchema);\n if (issues.length > 0) {\n throw new Error(formatSchemaVerificationIssues(issues));\n }\n logger(\"worker\", \"Schema verification passed: migrations match the declared schema\", \"info\");\n }\n\n const [sqlite3] = await Promise.all([sqlite3InitModule(), xxhash.ensureLoaded()]);\n\n const resetStore = opts.resetStore ?? createIdbResetStore();\n const resetState = createResetStateStore({\n store: resetStore,\n dbId: config.dbId,\n });\n const storageVersion = createStorageVersionStore({\n store: resetStore,\n dbId: config.dbId,\n appStorageVersion: opts.storageVersion,\n });\n const [pendingReset, versionStatus] = await Promise.all([\n resetState.resolvePendingReset(),\n storageVersion.getVersionStatus(),\n ]);\n const isVersionMismatch = versionStatus !== \"current\";\n\n if (versionStatus === \"missing\") {\n logger(\"worker\", `Initializing local DB storage version ${storageVersion.currentVersion}`, \"info\");\n } else if (versionStatus === \"outdated\") {\n logger(\"worker\", `Storage version mismatch — resetting local DB to ${storageVersion.currentVersion}`, \"warning\");\n }\n\n const pool = await sqlite3.installOpfsSAHPoolVfs({\n name: config.dbId,\n directory: `.${config.dbId}`,\n clearOnInit: !!pendingReset || isVersionMismatch,\n initialCapacity: 8,\n });\n\n const db = new SQLiteDbWrapper<WorkerDbSchema>({\n db: () => new pool.OpfsSAHPoolDb(`/${config.dbId}-main.db`),\n logger: logger,\n loggerPrefix: \"worker\",\n sqlite3,\n });\n\n db.execute(\"PRAGMA locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n db.execute(`ATTACH DATABASE '/${config.dbId}-worker.db' as worker`, { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.locking_mode=exclusive\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.journal_mode=WAL\", { loggerLevel: \"system\" });\n db.execute(\"PRAGMA worker.synchronous=NORMAL\", { loggerLevel: \"system\" });\n\n const { kvStore } = applyWorkerDbSchema(db);\n\n const migrator = createMigrator({\n migrations: opts.syncDbSchema.migrations,\n schemaVersion: kvStore.createNumberStoredValue(\"schema-version\", -1),\n updateLogTableName: workerDbConfig.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 eventHlcAccumulator = kvStore.createStringStoredValue(\"crdt.consistency.event_hlc_sum.v2\", \"\");\n const storageGeneration = kvStore.createStringStoredValue(\"storage-generation\", \"\");\n if (!storageGeneration.current) {\n storageGeneration.current = generateId();\n }\n\n // Record the applied reset epoch / storage version only after the wiped DB\n // initialized successfully, so a failed init can be retried by a later\n // elected worker, while a later election does not wipe again.\n if (pendingReset) {\n await resetState.markResetApplied(pendingReset.epoch);\n }\n if (isVersionMismatch) {\n await storageVersion.markCurrentVersionApplied();\n }\n\n if (opts.eventLogGc === true) {\n await runWithNoActiveReplicas({\n dbId: config.dbId,\n run: () => {\n runWorkerEventLogGc({\n db,\n dbConfig: workerDbConfig,\n pushSyncId,\n eventHlcAccumulator,\n logger,\n });\n },\n });\n }\n\n const crdtStorage = createCrdtStorage({\n nodeId: config.clientId,\n initialLocalSyncId: getMaxSyncId(db, \"none\"),\n migrator,\n hlc: new HLCCounter(config.clientId, () => Date.now()),\n db,\n dbConfig: workerDbConfig,\n eventHlcAccumulator,\n schema: opts.syncDbSchema,\n });\n\n createCrdtSyncProducer({\n storage: crdtStorage,\n broadcastEvents: (chunk) => {\n broadcastChannels.responses.postMessage({\n notificationType: \"new-event-chunk-applied\",\n newSyncId: chunk.newSyncId,\n eventHlcSum: chunk.eventHlcSum,\n });\n },\n });\n\n const remoteSource = createRemoteSource({\n crdtStorage,\n migrator,\n clientId: config.clientId,\n pullSyncId,\n pushSyncId,\n remoteFactory: opts.createRemoteSource,\n });\n remoteSource.goOnline();\n\n const broadcastNotification = (notification: WorkerNotificationMessage) => {\n broadcastChannels.responses.postMessage(notification);\n };\n\n const postState = () => {\n broadcastNotification({\n notificationType: \"state-changed\",\n state: remoteSource.getState(),\n storageGeneration: storageGeneration.current,\n });\n };\n const stateChangedSubscription = remoteSource.addEventListener(\"state-changed\", () => {\n postState();\n });\n const deSyncDetectedSubscription = remoteSource.addEventListener(\"de-sync-detected\", (event) => {\n broadcastNotification({\n notificationType: \"de-sync-detected\",\n reason: event.payload.reason,\n });\n });\n const remoteSchemaVersionMismatchSubscription = remoteSource.addEventListener(\n \"remote-schema-version-mismatch\",\n (event) => {\n broadcastNotification({\n notificationType: \"remote-schema-version-mismatch\",\n remoteSchemaVersion: event.payload.remoteSchemaVersion,\n localSchemaVersion: event.payload.localSchemaVersion,\n });\n },\n );\n\n const rpcTarget: WorkerRpc = {\n execute: (query) => db.execute(query),\n getSnapshot: () => {\n const appliedSyncId = getMaxSyncId(db, \"pending\");\n db.execute(\"PRAGMA journal_mode=off\", { loggerLevel: \"system\" });\n const file = db.createSnapshot();\n db.execute(\"PRAGMA journal_mode=WAL\", { loggerLevel: \"system\" });\n return {\n file,\n syncId: appliedSyncId,\n schemaVersion: migrator.currentSchemaVersion,\n storageGeneration: storageGeneration.current,\n };\n },\n importData: createImportData({\n migrator,\n tablesConfig: opts.syncDbSchema.tablesConfig,\n applyEvents: async (events) => {\n crdtStorage.applyOwnEvents(events);\n await crdtStorage.internal.processEnqueuedEvents();\n },\n }),\n postState,\n pushTabEvents: (request) => {\n const { beforeSyncId, afterSyncId } = crdtStorage.enqueueLocalEvents(request.events, request.nodeId);\n return {\n ok: true,\n beforeSyncId,\n afterSyncId,\n };\n },\n pullEvents: (request) => {\n return crdtStorage.getEventsBatch({\n afterSyncId: request.afterSyncId,\n status: \"applied\",\n excludeNodeId: request.excludeNodeId ?? \"\",\n limit: 100,\n });\n },\n goOnline: () => remoteSource.goOnline(),\n goOffline: () => remoteSource.goOffline(\"DISCONNECTED\"),\n dropConnection: () => remoteSource.dropConnection(\"NETWORK_OFFLINE\"),\n sync: () => {\n remoteSource.syncWithRemote().catch((error) => console.warn(\"Sync failed\", error));\n },\n requestReload: createReloadRequestHandler({\n resetState,\n broadcast: broadcastNotification,\n }),\n };\n\n broadcastChannels.requests.onmessage = (event) => {\n const message = event.data;\n\n if (!isWorkerRequestMessage(message)) {\n return;\n }\n\n const sendError = (error: unknown) => {\n const response: WorkerErrorResponseMessage = {\n type: \"error-response\",\n requestId: message.requestId,\n error: error instanceof Error ? error.message : String(error),\n };\n broadcastChannels.responses.postMessage(response);\n };\n\n try {\n const method = rpcTarget[message.method] as () => ReturnType<WorkerRpc[keyof WorkerRpc]>;\n const data = method.apply(null, message.args as []);\n\n if (data instanceof Promise) {\n data\n .then((result) => {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data: result,\n };\n broadcastChannels.responses.postMessage(response);\n })\n .catch(sendError);\n } else {\n const response: WorkerResponseMessage = {\n type: \"response\",\n requestId: message.requestId,\n data,\n };\n broadcastChannels.responses.postMessage(response);\n }\n } catch (error) {\n sendError(error);\n }\n };\n\n rpcTarget.postState();\n\n return async () => {\n stateChangedSubscription.unsubscribe();\n deSyncDetectedSubscription.unsubscribe();\n remoteSchemaVersionMismatchSubscription.unsubscribe();\n await remoteSource.dispose();\n broadcastChannels.requests.close();\n broadcastChannels.responses.close();\n db.close();\n };\n}\n\ntype InitRemoteOptions = {\n clientId: string;\n crdtStorage: CrdtStorage;\n migrator: SyncDbMigrator;\n pullSyncId: ReturnType<KvStore[\"createNumberStoredValue\"]>;\n pushSyncId: ReturnType<KvStore[\"createNumberStoredValue\"]>;\n remoteFactory?: CreateRemoteSourceFactory;\n};\n\nfunction createRemoteSource({\n clientId,\n crdtStorage,\n migrator,\n pullSyncId,\n pushSyncId,\n remoteFactory,\n}: InitRemoteOptions) {\n return createCrdtSyncRemoteSource({\n bufferSize: PUSH_BATCH_SIZE,\n pullSyncId,\n pushSyncId,\n nodeId: clientId,\n storage: crdtStorage,\n migrator,\n remoteFactory,\n });\n}\n\nexport async function getWorkerConfig<Props = never>(): Promise<WorkerConfig<Props>> {\n let configSet = false;\n const responsePromise = createDeferredPromise<WorkerConfig>();\n\n self.onmessage = (event: MessageEvent<unknown>) => {\n if (configSet) {\n console.error(\"Worker config already set\");\n return;\n }\n\n const message = event.data;\n if (!isWorkerInitMessage(message)) {\n return;\n }\n\n responsePromise.resolve(message.config);\n configSet = true;\n };\n\n return responsePromise.promise;\n}\n\ntype WorkerOptions = {\n syncDbSchema: SyncDbSchema;\n logger?: Logger;\n createRemoteSource?: CreateRemoteSourceFactory;\n workerConfig?: WorkerConfig;\n /** Durable storage for reset state. Defaults to an IndexedDB-backed store. */\n resetStore?: ResetStore;\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 elected worker wipes the local DB on startup.\n */\n storageVersion?: string;\n /**\n * Dev-time drift check: replays the full migration history on a throwaway\n * in-memory database and verifies the result matches the schema declared with\n * `defineSyncSchema`. On mismatch the worker throws and refuses to start.\n * Enable in development only, e.g. `verifySchema: import.meta.env.DEV`.\n */\n verifySchema?: boolean;\n /**\n * Runs local worker event-log garbage collection on startup. Disabled by default;\n * set `eventLogGc: true` to enable. GC keeps the latest event rows and never removes\n * pending events or local rows that have not been pushed to the remote.\n * Skipped while an existing tab holds an in-memory replica of this DB.\n */\n eventLogGc?: boolean;\n};\n\nexport async function startDbWorker(opts: WorkerOptions) {\n const config = opts.workerConfig ?? (await getWorkerConfig());\n\n await navigator.locks.request(`${syncDbWorkerLockName}-${config.dbId}`, { mode: \"exclusive\" }, async (lock) => {\n if (!lock) {\n return;\n }\n\n const cleanup = await createDbWorker(config, opts);\n\n const clientLockName = `${syncDbClientLockName}-${config.dbId}`;\n await new Promise<void>((resolve) => {\n const interval = setInterval(async () => {\n const { held } = await navigator.locks.query();\n const hasClients = held?.some((l) => l.name === clientLockName && l.mode === \"shared\");\n if (!hasClients) {\n clearInterval(interval);\n resolve();\n }\n }, 5_000);\n });\n\n await cleanup();\n });\n\n self.close();\n}\n\nfunction getMaxSyncId(db: SQLiteDbWrapper<WorkerDbSchema>, excludingStatus: \"none\" | \"pending\") {\n const [result] = db.executePrepared(\n \"get-max-sync-id\",\n { excludingStatus: excludingStatus as CrdtEventStatus },\n (db, params) =>\n db\n .selectFrom(\"worker.crdt_events\")\n .where(\"status\", \"!=\", params(\"excludingStatus\"))\n .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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"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"crdt-table-schema-2w_AIpsV.js","names":[],"sources":["../src/utils.ts","../src/sqlite-crdt/crdt-table-schema.ts"],"sourcesContent":["export type DeferredPromise<T> = {\n promise: Promise<T>;\n resolve: (value: T) => void;\n reject: (error: Error) => void;\n};\n\ntype DeferredPromiseOptions = {\n timeout?: number;\n onTimeout?: () => void;\n};\n\nexport function createDeferredPromise<T>(opts?: DeferredPromiseOptions): DeferredPromise<T> {\n let resolve!: (value: T | PromiseLike<T>) => void;\n let reject!: (reason?: unknown) => void;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const promise = new Promise<T>((_resolve, _reject) => {\n resolve = (value) => {\n if (timeoutId) clearTimeout(timeoutId);\n _resolve(value);\n };\n reject = (reason) => {\n if (timeoutId) clearTimeout(timeoutId);\n _reject(reason);\n };\n\n if (opts?.timeout) {\n timeoutId = setTimeout(rejectTimeout, opts.timeout, _reject, opts);\n }\n });\n\n return { promise, resolve, reject };\n}\n\nfunction rejectTimeout(reject: (reason?: unknown) => void, opts: DeferredPromiseOptions) {\n reject(new Error(`Promise timed out after ${opts.timeout}ms`));\n tryCatch(() => opts?.onTimeout?.());\n}\n\nexport const generateId = () => {\n return crypto.randomUUID();\n};\n\nexport type DistributiveOmit<T, K extends keyof T> = T extends any ? Omit<T, K> : never;\n\nexport function ensureSingletonExecution<T, TArgs extends any[]>(\n fn: (...args: TArgs) => Promise<T>,\n opts: { queueReExecution?: boolean } = { queueReExecution: true },\n) {\n let executingPromise: Promise<T> | null = null;\n let shouldReExecute = false;\n\n const wrappedFn = (...args: TArgs) => {\n if (executingPromise) {\n shouldReExecute = true;\n return executingPromise;\n }\n\n executingPromise = fn(...args).finally(() => {\n executingPromise = null;\n\n if (shouldReExecute && opts?.queueReExecution) {\n shouldReExecute = false;\n wrappedFn(...args);\n }\n });\n return executingPromise;\n };\n\n wrappedFn.promise = () => executingPromise;\n wrappedFn.isExecuting = () => !!executingPromise;\n\n return wrappedFn;\n}\n\nexport function createAutoFlushBuffer<T>({ size, flush }: { size: number; flush: (items: T[]) => void }) {\n const buffer: T[] = [];\n\n return {\n add(item: T) {\n buffer.push(item);\n if (buffer.length >= size) {\n flush(buffer);\n buffer.length = 0;\n }\n },\n flush() {\n flush(buffer);\n buffer.length = 0;\n },\n };\n}\n\nexport function createAsyncAutoFlushBuffer<T>({\n size,\n flush,\n}: {\n size: number;\n flush: (items: T[]) => void | Promise<void>;\n}) {\n const buffer: T[] = [];\n\n return {\n async add(item: T) {\n buffer.push(item);\n if (buffer.length >= size) {\n await this.flush();\n }\n },\n async flush() {\n const itemsToFlush = buffer.splice(0);\n if (itemsToFlush.length === 0) {\n return;\n }\n await flush(itemsToFlush);\n },\n };\n}\n\nexport class TypedBroadcastChannel<TMessage> {\n private readonly channel: BroadcastChannel;\n\n constructor(name: string) {\n this.channel = new BroadcastChannel(name);\n }\n\n postMessage(message: TMessage) {\n this.channel.postMessage(message);\n }\n\n set onmessage(callback: ((event: MessageEvent<TMessage>) => void) | null) {\n this.channel.onmessage = callback;\n }\n\n close() {\n this.channel.close();\n }\n}\n\nexport class TypedEvent<T = unknown> extends Event {\n readonly payload: T;\n constructor(type: string, payload: T) {\n super(type);\n this.payload = payload;\n }\n}\n\nexport type TypedEventTarget<T extends Record<string, unknown>> = {\n addEventListener: <K extends keyof T & string>(\n type: K,\n listener: (event: TypedEvent<T[K]>) => void,\n ) => { unsubscribe: () => void };\n removeEventListener: <K extends keyof T & string>(type: K, listener: (event: TypedEvent<T[K]>) => void) => void;\n dispatchEvent: <K extends keyof T & string>(type: K, payload: T[K]) => void;\n};\n\nexport const createTypedEventTarget = <T extends Record<string, unknown>>(): TypedEventTarget<T> => {\n const eventTarget = new EventTarget();\n\n const addEventListener = <K extends keyof T & string>(type: K, listener: (event: TypedEvent<T[K]>) => void) => {\n eventTarget.addEventListener(type, listener as (e: Event) => void);\n\n return {\n unsubscribe: () => {\n eventTarget.removeEventListener(type, listener as (e: Event) => void);\n },\n };\n };\n\n const removeEventListener = <K extends keyof T & string>(type: K, listener: (event: TypedEvent<T[K]>) => void) => {\n eventTarget.removeEventListener(type, listener as (e: Event) => void);\n };\n\n const dispatchEvent = <K extends keyof T & string>(type: K, payload: T[K]) => {\n eventTarget.dispatchEvent(new TypedEvent(type, payload));\n };\n\n return {\n addEventListener,\n removeEventListener,\n dispatchEvent,\n };\n};\n\ntype TryCatchResult<T> =\n | {\n success: true;\n data: T;\n }\n | {\n success: false;\n error: unknown;\n };\n\nexport function tryCatch<T>(fn: () => T): TryCatchResult<T> {\n try {\n return {\n success: true,\n data: fn(),\n };\n } catch (error) {\n return {\n success: false,\n error,\n };\n }\n}\n\nexport async function tryCatchAsync<T>(fn: () => Promise<T>): Promise<TryCatchResult<T>> {\n try {\n return {\n success: true,\n data: await fn(),\n };\n } catch (error) {\n return {\n success: false,\n error,\n };\n }\n}\n\nexport function jsonSafeParse<T>(json: string) {\n return tryCatch(() => JSON.parse(json) as T);\n}\n\n/** Quote a SQLite identifier (table/column name), handling dot-separated schema qualifiers. */\nexport function quoteId(name: string): string {\n return name\n .split(\".\")\n .map((s) => `\"${s.replace(/\"/g, '\"\"')}\"`)\n .join(\".\");\n}\n\nexport function noop(..._args: unknown[]): void {}\n\nexport type ParsedTableName = {\n schema: \"main\" | (string & {});\n table: string;\n fullIdentifier: string;\n};\n\nexport function parseTableName(tableName: string): ParsedTableName {\n if (!tableName?.trim()) {\n throw new Error(\"Parse table: missing table name\");\n }\n const parts = tableName.split(\".\");\n if (parts.length > 2) {\n throw new Error(\"Parse table: too many dot-delimited segments in table name\");\n }\n\n return parts.length === 1\n ? {\n schema: \"main\",\n table: parts[0],\n fullIdentifier: parts[0],\n }\n : {\n schema: parts[0],\n table: parts[1],\n fullIdentifier: `${parts[0]}.${parts[1]}`,\n };\n}\n","import type { SchemaModule } from \"kysely\";\n\nexport type NewCrdtEvent = {\n type: CrdtEventType;\n dataset: string;\n item_id: string;\n payload: Record<string, unknown>;\n};\n\nexport type CrdtEventType = \"item-created\" | \"item-updated\" | \"item-deleted\";\n\nexport type CrdtEventStatus = \"pending\" | \"applied\" | \"failed\" | \"deduped\";\n\n/** Persisted on applied events that were accepted but did not mutate materialized state. */\nexport const CRDT_EVENT_NO_OP_PAYLOAD = \"no-op\";\n\nexport function isNoOpCrdtEventPayload(payload: string) {\n return payload === CRDT_EVENT_NO_OP_PAYLOAD;\n}\n\nexport type CrdtEventOrigin = \"remote\" | \"own\" | \"own-applied\" | \"local\";\n\nexport type PersistedCrdtEvent = {\n schema_version: number;\n sync_id: number;\n status: CrdtEventStatus;\n type: CrdtEventType;\n timestamp: string;\n origin: CrdtEventOrigin;\n source_node_id: string;\n dataset: string;\n item_id: string;\n payload: string;\n};\n\nexport type CrdtUpdateLogItem = {\n dataset: string;\n item_id: string;\n payload: string;\n};\n\nexport type CrdtUpdateLogPayload = Record<string, string>;\n\nexport const crdtSchema = {\n persistedEventsTable: createPersistedEventsTable,\n crdtUpdateLogTable: createCrdtUpdateLogTableQuery,\n};\n\nfunction createPersistedEventsTable(schema: SchemaModule, tableName: string) {\n return schema\n .createTable(tableName)\n .ifNotExists()\n .addColumn(\"sync_id\", \"integer\", (col) => col.notNull().primaryKey())\n .addColumn(\"schema_version\", \"integer\", (col) => col.notNull())\n .addColumn(\"status\", \"text\", (col) => col.notNull())\n .addColumn(\"type\", \"text\", (col) => col.notNull())\n .addColumn(\"timestamp\", \"text\", (col) => col.notNull())\n .addColumn(\"origin\", \"text\", (col) => col.notNull())\n .addColumn(\"dataset\", \"text\", (col) => col.notNull())\n .addColumn(\"item_id\", \"text\", (col) => col.notNull())\n .addColumn(\"payload\", \"text\", (col) => col.notNull());\n}\n\nfunction createCrdtUpdateLogTableQuery(schema: SchemaModule, tableName: string) {\n return schema\n .createTable(tableName)\n .ifNotExists()\n .addColumn(\"dataset\", \"text\", (col) => col.notNull())\n .addColumn(\"item_id\", \"text\", (col) => col.notNull())\n .addColumn(\"payload\", \"text\", (col) => col.notNull())\n .addPrimaryKeyConstraint(`pk_${tableName}`, [\"item_id\", \"dataset\"]);\n}\n"],"mappings":";AAWA,SAAgB,sBAAyB,MAAmD;CAC1F,IAAI;CACJ,IAAI;CACJ,IAAI;CAiBJ,OAAO;EAAE,SAAA,IAfW,SAAY,UAAU,YAAY;GACpD,WAAW,UAAU;IACnB,IAAI,WAAW,aAAa,SAAS;IACrC,SAAS,KAAK;GAChB;GACA,UAAU,WAAW;IACnB,IAAI,WAAW,aAAa,SAAS;IACrC,QAAQ,MAAM;GAChB;GAEA,IAAI,MAAM,SACR,YAAY,WAAW,eAAe,KAAK,SAAS,SAAS,IAAI;EAErE,CAEe;EAAG;EAAS;CAAO;AACpC;AAEA,SAAS,cAAc,QAAoC,MAA8B;CACvF,uBAAO,IAAI,MAAM,2BAA2B,KAAK,QAAQ,GAAG,CAAC;CAC7D,eAAe,MAAM,YAAY,CAAC;AACpC;AAEA,MAAa,mBAAmB;CAC9B,OAAO,OAAO,WAAW;AAC3B;AAIA,SAAgB,yBACd,IACA,OAAuC,EAAE,kBAAkB,KAAK,GAChE;CACA,IAAI,mBAAsC;CAC1C,IAAI,kBAAkB;CAEtB,MAAM,aAAa,GAAG,SAAgB;EACpC,IAAI,kBAAkB;GACpB,kBAAkB;GAClB,OAAO;EACT;EAEA,mBAAmB,GAAG,GAAG,IAAI,CAAC,CAAC,cAAc;GAC3C,mBAAmB;GAEnB,IAAI,mBAAmB,MAAM,kBAAkB;IAC7C,kBAAkB;IAClB,UAAU,GAAG,IAAI;GACnB;EACF,CAAC;EACD,OAAO;CACT;CAEA,UAAU,gBAAgB;CAC1B,UAAU,oBAAoB,CAAC,CAAC;CAEhC,OAAO;AACT;AA8CA,IAAa,wBAAb,MAA6C;CAC3C;CAEA,YAAY,MAAc;EACxB,KAAK,UAAU,IAAI,iBAAiB,IAAI;CAC1C;CAEA,YAAY,SAAmB;EAC7B,KAAK,QAAQ,YAAY,OAAO;CAClC;CAEA,IAAI,UAAU,UAA4D;EACxE,KAAK,QAAQ,YAAY;CAC3B;CAEA,QAAQ;EACN,KAAK,QAAQ,MAAM;CACrB;AACF;AAEA,IAAa,aAAb,cAA6C,MAAM;CACjD;CACA,YAAY,MAAc,SAAY;EACpC,MAAM,IAAI;EACV,KAAK,UAAU;CACjB;AACF;AAWA,MAAa,+BAAuF;CAClG,MAAM,cAAc,IAAI,YAAY;CAEpC,MAAM,oBAAgD,MAAS,aAAgD;EAC7G,YAAY,iBAAiB,MAAM,QAA8B;EAEjE,OAAO,EACL,mBAAmB;GACjB,YAAY,oBAAoB,MAAM,QAA8B;EACtE,EACF;CACF;CAEA,MAAM,uBAAmD,MAAS,aAAgD;EAChH,YAAY,oBAAoB,MAAM,QAA8B;CACtE;CAEA,MAAM,iBAA6C,MAAS,YAAkB;EAC5E,YAAY,cAAc,IAAI,WAAW,MAAM,OAAO,CAAC;CACzD;CAEA,OAAO;EACL;EACA;EACA;CACF;AACF;AAYA,SAAgB,SAAY,IAAgC;CAC1D,IAAI;EACF,OAAO;GACL,SAAS;GACT,MAAM,GAAG;EACX;CACF,SAAS,OAAO;EACd,OAAO;GACL,SAAS;GACT;EACF;CACF;AACF;AAEA,eAAsB,cAAiB,IAAkD;CACvF,IAAI;EACF,OAAO;GACL,SAAS;GACT,MAAM,MAAM,GAAG;EACjB;CACF,SAAS,OAAO;EACd,OAAO;GACL,SAAS;GACT;EACF;CACF;AACF;AAEA,SAAgB,cAAiB,MAAc;CAC7C,OAAO,eAAe,KAAK,MAAM,IAAI,CAAM;AAC7C;;AAGA,SAAgB,QAAQ,MAAsB;CAC5C,OAAO,KACJ,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,IAAI,EAAE,QAAQ,MAAM,MAAI,EAAE,EAAE,CAAC,CACxC,KAAK,GAAG;AACb;AAEA,SAAgB,KAAK,GAAG,OAAwB,CAAC;AAQjD,SAAgB,eAAe,WAAoC;CACjE,IAAI,CAAC,WAAW,KAAK,GACnB,MAAM,IAAI,MAAM,iCAAiC;CAEnD,MAAM,QAAQ,UAAU,MAAM,GAAG;CACjC,IAAI,MAAM,SAAS,GACjB,MAAM,IAAI,MAAM,4DAA4D;CAG9E,OAAO,MAAM,WAAW,IACpB;EACE,QAAQ;EACR,OAAO,MAAM;EACb,gBAAgB,MAAM;CACxB,IACA;EACE,QAAQ,MAAM;EACd,OAAO,MAAM;EACb,gBAAgB,GAAG,MAAM,GAAG,GAAG,MAAM;CACvC;AACN;;;;ACxPA,MAAa,2BAA2B;AAExC,SAAgB,uBAAuB,SAAiB;CACtD,OAAO,YAAY;AACrB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"reset-state-BQi96NLn.js","names":["_exhaustive"],"sources":["../src/dummy-kysely.ts","../src/export-import.ts","../src/hash.ts","../src/introspection.ts","../src/logger.ts","../src/sqlite-db-wrapper.ts","../src/migrations/migrator.ts","../src/schema/verify-sync-schema.ts","../src/sqlite-crdt/apply-crdt-event.ts","../src/sqlite-crdt/event-consistency.ts","../src/sqlite-crdt/crdt-storage.ts","../src/sqlite-crdt/crdt-sync-producer.ts","../src/worker-db/worker-common.ts","../src/worker-db/reset-state.ts"],"sourcesContent":["import { DummyDriver, Kysely, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from \"kysely\";\n\nexport const dummyKysely: Kysely<any> = new Kysely({\n dialect: {\n createAdapter: () => new SqliteAdapter(),\n createDriver: () => new DummyDriver(),\n createQueryCompiler: () => new SqliteQueryCompiler(),\n createIntrospector: (db) => new SqliteIntrospector(db),\n },\n});\n","import type { MigratableEvent, SyncDbMigrator } from \"./migrations/migrator\";\nimport type { CrdtTableConfig } from \"./sqlite-crdt/crdt-schema\";\nimport type { OwnCrdtEvent } from \"./sqlite-crdt/crdt-storage\";\nimport type { ExecuteResult } from \"./sqlite-db-wrapper\";\nimport { quoteId } from \"./utils\";\n\n/**\n * A portable, schema-version-aware dump of every active entity in a synced DB.\n * Unlike a raw SQLite snapshot, it carries no event history — just the current\n * `tombstone = 0` rows, ready to be replayed as fresh create events on import.\n */\nexport type SyncedDbExport = {\n schemaVersion: number;\n exportedAt: string;\n /** base table name -> active rows (each includes `id`, excludes `tombstone`). */\n tables: Record<string, Array<Record<string, unknown>>>;\n};\n\nexport type ImportDataOptions = { validate?: boolean };\nexport type ImportDataResult = { imported: number };\n\ntype ExportImportReactiveDb = {\n db: { execute<T>(sql: string, meta: { loggerLevel: \"system\" }): ExecuteResult<T> };\n};\n\ntype ApplyImportEvents = (events: OwnCrdtEvent[]) => void | Promise<void>;\n\ntype ImportMigrator = Pick<SyncDbMigrator, \"currentSchemaVersion\" | \"migrateEvents\">;\n\ntype CreateExportDataOptions = {\n reactiveDb: ExportImportReactiveDb;\n tablesConfig: CrdtTableConfig[];\n schemaVersion: number;\n};\n\ntype CreateImportDataOptions = {\n migrator: ImportMigrator;\n tablesConfig?: CrdtTableConfig[];\n applyEvents: ApplyImportEvents;\n};\n\nfunction createTableNameNormalizer(tablesConfig: CrdtTableConfig[] | undefined) {\n const tablesByAlias = new Map<string, CrdtTableConfig>();\n for (const table of tablesConfig ?? []) {\n const { baseTableName, crdtTableName } = table;\n tablesByAlias.set(baseTableName, table);\n tablesByAlias.set(crdtTableName, table);\n }\n return (tableName: string) => {\n const table = tablesByAlias.get(tableName);\n if (table?.exportImport === \"ignore\") {\n return null;\n }\n return table?.baseTableName ?? tableName;\n };\n}\n\nexport function createImportData({ migrator, tablesConfig, applyEvents }: CreateImportDataOptions) {\n const normalizeTableName = createTableNameNormalizer(tablesConfig);\n\n /**\n * Replay an export as a sequence of `item-created` CRDT events, seeding the\n * rows through the provided CRDT event applicator.\n *\n * Schema migration: a dump exported at an older schema version is forward-\n * migrated up to the DB's current version (running the same event transformers\n * remote events go through) before being authored — so an old backup can be\n * restored into an upgraded app. Rows in tables removed by a later migration\n * are dropped. The reverse (a dump from a *newer* version than this DB) cannot\n * be down-migrated and is rejected.\n *\n * Overwrite-by-default: because the generated events carry fresh (newest) HLC\n * timestamps, importing a row whose `id` already exists overwrites every field\n * under last-write-wins, while new ids are inserted. Existing rows absent from\n * the dump are never touched. This is a restore/seed, not a CRDT merge —\n * original timestamps are not preserved.\n *\n * @param opts.validate When `false`, skip the too-new guard and author the rows\n * as-is at the current version (footgun — payloads are not down-migrated).\n * Forward migration and per-row payload validation always run regardless.\n */\n return (data: SyncedDbExport, opts?: ImportDataOptions): ImportDataResult | Promise<ImportDataResult> => {\n const validate = opts?.validate ?? true;\n const currentVersion = migrator.currentSchemaVersion;\n\n if (validate && data.schemaVersion > currentVersion) {\n throw new Error(\n `Cannot import data from schema version ${data.schemaVersion} into a database at older schema version ${currentVersion}. Exports migrate forward to newer schema versions, not backward.`,\n );\n }\n\n const sourceEvents: MigratableEvent[] = [];\n for (const [tableName, rows] of Object.entries(data.tables)) {\n const normalizedTableName = normalizeTableName(tableName);\n if (normalizedTableName === null) {\n continue;\n }\n for (const row of rows) {\n sourceEvents.push({\n schema_version: data.schemaVersion,\n type: \"item-created\",\n dataset: normalizedTableName,\n item_id: row.id as string,\n payload: JSON.stringify(row),\n });\n }\n }\n\n // Forward-migrate the historical export up to the current schema version\n // before authoring it: own events are always written at the current version.\n const events: OwnCrdtEvent[] = migrator.migrateEvents(sourceEvents, currentVersion).map((event) => ({\n type: event.type,\n dataset: event.dataset,\n item_id: event.item_id,\n payload: event.payload,\n }));\n\n const applied = applyEvents(events);\n if (applied instanceof Promise) {\n return applied.then(() => ({ imported: events.length }));\n }\n\n return { imported: events.length };\n };\n}\n\nexport function createExportData({ reactiveDb, tablesConfig, schemaVersion }: CreateExportDataOptions) {\n const tableNames = tablesConfig.flatMap((config) => [config.crdtTableName, config.baseTableName]);\n\n const resolveTables = (requested: string[] | undefined): CrdtTableConfig[] => {\n if (!requested) {\n return tablesConfig;\n }\n\n const seenBaseTableNames = new Set<string>();\n const resolved: CrdtTableConfig[] = [];\n for (const name of requested) {\n const config = tablesConfig.find(\n (tableConfig) => tableConfig.crdtTableName === name || tableConfig.baseTableName === name,\n );\n if (!config) {\n throw new Error(`Unknown table \"${name}\". Known synced tables: ${tableNames.join(\", \")}`);\n }\n if (!seenBaseTableNames.has(config.baseTableName)) {\n seenBaseTableNames.add(config.baseTableName);\n resolved.push(config);\n }\n }\n return resolved;\n };\n\n /**\n * Dump every active entity (the `tombstone = 0` rows) from the synced tables.\n *\n * Reads this tab's reactive (in-memory) DB — the state the user currently sees,\n * which may differ slightly from the worker's authoritative DB while local\n * writes are in flight.\n */\n return (opts?: { tables?: string[] }): SyncedDbExport => {\n const tables: Record<string, Array<Record<string, unknown>>> = {};\n\n for (const { baseTableName, crdtTableName, exportImport } of resolveTables(opts?.tables)) {\n if (exportImport === \"ignore\") {\n continue;\n }\n const { rows } = reactiveDb.db.execute<Record<string, unknown>>(`select * from ${quoteId(crdtTableName)}`, {\n loggerLevel: \"system\",\n });\n tables[baseTableName] = rows.map(({ tombstone: _tombstone, ...rest }) => rest);\n }\n\n return {\n schemaVersion,\n exportedAt: new Date().toISOString(),\n tables,\n };\n };\n}\n","import initXxhash, { type XXHashAPI } from \"xxhash-wasm\";\n\nlet loadPromise: Promise<void> | null = null;\nlet api: XXHashAPI | null = null;\n\nfunction ensureLoaded(): Promise<void> {\n if (!loadPromise) {\n loadPromise = initXxhash().then((hasher) => {\n api = hasher;\n });\n }\n return loadPromise;\n}\n\nfunction h64(input: string, seed = 0n): bigint {\n if (!api) {\n throw new Error(\"xxhash is not initialized; call xxhash.ensureLoaded() first\");\n }\n return api.h64(input, seed);\n}\n\nexport const xxhash = {\n ensureLoaded,\n h64,\n};\n","import type { Kysely, QueryCreator } from \"kysely\";\nimport { sql } from \"kysely\";\nimport type { SQLiteDbWrapper } from \"./sqlite-db-wrapper\";\n\ninterface SqliteSystemDatabase {\n // https://www.sqlite.org/schematab.html#alternative_names\n sqlite_master: SQliteMasterTable;\n}\n\n// https://www.sqlite.org/schematab.html#interpretation_of_the_schema_table\ninterface SQliteMasterTable {\n name: string;\n rootpage: number | null;\n sql: string;\n tbl_name: string;\n type: \"index\" | \"table\" | \"trigger\" | \"view\";\n}\n\n// https://www.sqlite.org/pragma.html#pragma_table_info\ninterface PragmaTableInfo {\n cid: number;\n dflt_value: unknown;\n name: string;\n notnull: 0 | 1;\n pk: number;\n type: string;\n}\n\nfunction tablesQuery(qb: QueryCreator<SqliteSystemDatabase> | Kysely<SqliteSystemDatabase>) {\n return qb\n .selectFrom(\"sqlite_master\")\n .where(\"type\", \"in\", [\"table\", \"view\"])\n .where(\"name\", \"not like\", \"sqlite_%\")\n .select([\"name\", \"sql\", \"type\"])\n .orderBy(\"name\");\n}\n\nexport type TableMetadata = {\n name: string;\n isView: boolean;\n columns: ColumnMetadata[];\n};\n\nexport type DatabaseIntrospection = Record<string, TableMetadata>;\n\nexport type ColumnMetadata = {\n name: string;\n dataType: string;\n isNullable: boolean;\n isAutoIncrementing: boolean;\n hasDefaultValue: boolean;\n defaultValueSql: string | null;\n isPrimaryKey: boolean;\n comment: undefined;\n};\n\nexport function introspectDb<BaseDatabase>(_db: SQLiteDbWrapper<BaseDatabase>): DatabaseIntrospection {\n const db = _db as unknown as SQLiteDbWrapper<SqliteSystemDatabase>;\n const tables = db.executeKysely((db) => tablesQuery(db as unknown as Kysely<SqliteSystemDatabase>), {\n loggerLevel: \"system\",\n }).rows;\n\n const tablesMetadata = db.executeKysely(\n (db) =>\n db\n .with(\"table_list\", (qb) => tablesQuery(qb as unknown as Kysely<SqliteSystemDatabase>))\n .selectFrom([\"table_list as tl\", sql<PragmaTableInfo>`pragma_table_info(tl.name)`.as(\"p\")])\n .select([\"tl.name as table\", \"p.cid\", \"p.name\", \"p.type\", \"p.notnull\", \"p.dflt_value\", \"p.pk\"])\n .orderBy(\"tl.name\")\n .orderBy(\"p.cid\"),\n { loggerLevel: \"system\" },\n ).rows;\n\n const columnsByTable: Record<string, typeof tablesMetadata> = {};\n for (const row of tablesMetadata) {\n columnsByTable[row.table] ??= [];\n columnsByTable[row.table].push(row);\n }\n\n return Object.fromEntries(\n tables.map(({ name, sql, type }) => {\n // Try to find the name of the column that has `autoincrement`\n let autoIncrementCol = sql\n ?.split(/[(),]/)\n ?.find((it) => it.toLowerCase().includes(\"autoincrement\"))\n ?.trimStart()\n ?.split(/\\s+/)?.[0]\n ?.replace(/[\"`]/g, \"\");\n\n const columns = columnsByTable[name] ?? [];\n\n // Otherwise, check for an INTEGER PRIMARY KEY\n // https://www.sqlite.org/autoinc.html\n if (!autoIncrementCol) {\n const pkCols = columns.filter((r) => r.pk > 0);\n if (pkCols.length === 1 && pkCols[0].type.toLowerCase() === \"integer\") {\n autoIncrementCol = pkCols[0].name;\n }\n }\n\n return [\n name,\n {\n name: name,\n isView: type === \"view\",\n columns: columns.map((col) => ({\n name: col.name,\n dataType: col.type,\n isNullable: !col.notnull,\n isAutoIncrementing: col.name === autoIncrementCol,\n hasDefaultValue: col.dflt_value != null,\n defaultValueSql: col.dflt_value == null ? null : String(col.dflt_value),\n isPrimaryKey: col.pk > 0,\n comment: undefined,\n })),\n },\n ];\n }),\n );\n}\n","export type LogLevel = \"info\" | \"warning\" | \"error\" | \"trace\" | \"system\";\n\nexport type Logger = (type: string, message: string, level?: LogLevel) => void;\n\nexport type PerformanceLogger = {\n restart: () => void;\n logEnd: (type: string, message: string, level?: LogLevel) => void;\n};\n\nconst noopPerformanceLogger: PerformanceLogger = {\n restart() {},\n logEnd() {},\n};\n\nexport const startPerformanceLogger = (logger: Logger | undefined, level: LogLevel = \"info\"): PerformanceLogger => {\n if (!logger || level === \"system\") {\n return noopPerformanceLogger;\n }\n\n let startTime = performance.now();\n\n return {\n restart: () => {\n startTime = performance.now();\n },\n logEnd: (type: string, message: string, logLevel: LogLevel = level) => {\n const elapsed = performance.now() - startTime;\n\n logger(type, `${elapsed.toFixed(2)}ms - ${message}`, logLevel);\n },\n };\n};\n","import type {\n BindableValue,\n FunctionOptions,\n Database as SQLiteDatabase,\n Sqlite3Static,\n SqlValue,\n} from \"@sqlite.org/sqlite-wasm\";\nimport type { Compilable, CompiledQuery, Kysely } from \"kysely\";\nimport { dummyKysely } from \"./dummy-kysely\";\nimport { type DatabaseIntrospection, introspectDb } from \"./introspection\";\nimport { type Logger, startPerformanceLogger } from \"./logger\";\n\nexport type ExecuteParams = {\n sql: string;\n parameters: readonly unknown[];\n};\n\nexport type ExecuteResult<T> = {\n rows: T[];\n};\n\nexport type PreparedStatement<TParams extends unknown[], TResult> = {\n execute: (parameters: TParams) => TResult[];\n finalize: () => void;\n isFinalized: boolean;\n};\n\ntype ScalarFunctionOptions<TArgs extends readonly SqlValue[], TResult extends SqlValue | undefined> = {\n name: string;\n callback: (...args: TArgs) => TResult;\n} & Pick<FunctionOptions, \"deterministic\" | \"directOnly\" | \"innocuous\">;\n\ntype SqliteWrapperOptions = {\n logger?: Logger;\n loggerPrefix?: string;\n sqlite3: Sqlite3Static;\n db: () => SQLiteDatabase;\n};\n\nexport type SQLiteTransactionWrapper<TDatabase = unknown> = Pick<\n SQLiteDbWrapper<TDatabase>,\n \"sql\" | \"execute\" | \"executePrepared\" | \"executePreparedRaw\" | \"executeKysely\"\n>;\n\nexport type InternalSQLiteTransactionWrapper<TDatabase = unknown> = Pick<\n SQLiteDbWrapper<TDatabase>,\n \"executePrepared\" | \"executePreparedRaw\"\n>;\n\nexport type InternalSQLiteWrapper<TDatabase = unknown> = InternalSQLiteTransactionWrapper<TDatabase> & {\n executeTransaction: (callback: (db: InternalSQLiteTransactionWrapper<TDatabase>) => void) => void;\n};\n\ntype QueryMetaOpts = {\n loggerLevel?: \"info\" | \"system\";\n};\n\n/** Runs after user SQL succeeds, before its transaction or savepoint is committed. */\ntype AfterMutatingStatement<TDatabase> = (db: InternalSQLiteTransactionWrapper<TDatabase>) => void;\n\ntype ExecuteOptions = QueryMetaOpts & {\n /** For internal mutations whose effects must not be processed by afterMutatingStatement. */\n skipAfterMutatingStatement?: boolean;\n};\n\nexport class SQLiteDbWrapper<TDatabase = unknown> {\n private db: SQLiteDatabase | null = null;\n private sqlite3: Sqlite3Static;\n private logger?: Logger;\n private loggerPrefix?: string;\n\n private loadedDbSchema: DatabaseIntrospection | null = null;\n\n private readonly dataPointers = [] as number[];\n\n private preparedStatements: PreparedStatement<SqlValue[], unknown>[] = [];\n private preparedStatementsMap = new Map<string, TypedStatement<Record<string, unknown>, unknown>>();\n private preparedRawStatementsMap = new Map<string, PreparedStatement<SqlValue[], unknown>>();\n private afterMutatingStatement: AfterMutatingStatement<TDatabase> | null = null;\n\n constructor(opts: SqliteWrapperOptions) {\n this.db = opts.db();\n this.sqlite3 = opts.sqlite3;\n this.logger = opts.logger;\n this.loggerPrefix = opts.loggerPrefix;\n }\n\n get ensureDb() {\n if (!this.db) {\n throw new Error(\"Database is already closed\");\n }\n return this.db;\n }\n\n get dbSchema() {\n if (!this.loadedDbSchema) {\n this.loadedDbSchema = introspectDb(this);\n }\n return this.loadedDbSchema;\n }\n\n private executeSql<T>(\n sql: string,\n bind: readonly unknown[] | undefined,\n loggerLevel: QueryMetaOpts[\"loggerLevel\"],\n skipAfterMutatingStatement: boolean,\n ): ExecuteResult<T> {\n const perf = startPerformanceLogger(this.logger, loggerLevel);\n const statement = this.ensureDb.prepare(sql);\n let transaction: { commit: () => void; rollback: () => void } | undefined;\n\n try {\n const statementPointer = statement.pointer;\n if (!statementPointer) {\n throw new Error(\"Prepared statement is already finalized\");\n }\n const afterMutatingStatement =\n !skipAfterMutatingStatement &&\n this.afterMutatingStatement &&\n this.sqlite3.capi.sqlite3_stmt_readonly(statementPointer) === 0\n ? this.afterMutatingStatement\n : null;\n\n let totalChangesBefore = 0;\n if (afterMutatingStatement) {\n transaction = this.beginStatementTransaction();\n totalChangesBefore = this.sqlite3.capi.sqlite3_total_changes(this.ensureDb);\n }\n\n if (bind && bind.length > 0) {\n statement.bind(bind as BindableValue[]);\n }\n\n const rows: T[] = [];\n let columnNames: string[] | undefined;\n while (statement.step()) {\n columnNames ??= statement.getColumnNames([]);\n const values = statement.get([]);\n const row = Object.create(null) as Record<string, unknown>;\n for (let index = 0; index < columnNames.length; index++) {\n row[columnNames[index]] = values[index];\n }\n rows.push(row as T);\n }\n statement.finalize();\n perf.logEnd(`${this.loggerPrefix ?? \"\"}:query`, sql, loggerLevel);\n\n if (transaction) {\n // Nothing was written, so the statement cannot have produced any work for the callback.\n if (this.sqlite3.capi.sqlite3_total_changes(this.ensureDb) !== totalChangesBefore) {\n afterMutatingStatement?.(this);\n }\n transaction.commit();\n }\n\n return { rows };\n } catch (error) {\n statement.finalize();\n transaction?.rollback();\n throw error;\n }\n }\n\n execute<T = unknown>(opts: ExecuteParams | string | CompiledQuery<T>, options?: ExecuteOptions): ExecuteResult<T> {\n const sql = typeof opts === \"string\" ? opts : opts.sql;\n const bind = typeof opts === \"string\" ? undefined : opts.parameters;\n\n return this.executeSql<T>(sql, bind, options?.loggerLevel, options?.skipAfterMutatingStatement ?? false);\n }\n\n setAfterMutatingStatement(callback: AfterMutatingStatement<TDatabase>) {\n if (this.afterMutatingStatement) {\n throw new Error(\"An afterMutatingStatement callback is already registered\");\n }\n this.afterMutatingStatement = callback;\n }\n\n executeTransaction<T>(callback: (db: SQLiteTransactionWrapper<TDatabase>) => T): T {\n const transaction = this.beginTransaction();\n try {\n const result = callback(this);\n transaction.commit();\n return result;\n } catch (error) {\n transaction.rollback();\n throw error;\n }\n }\n\n isInTransaction() {\n return this.sqlite3.capi.sqlite3_get_autocommit(this.ensureDb) === 0;\n }\n\n beginTransaction() {\n this.executePreparedRaw({\n key: \"$begin-transaction\",\n sql: \"begin\",\n meta: {\n loggerLevel: \"system\",\n },\n });\n\n return {\n commit: () => {\n this.executePreparedRaw({\n key: \"$commit-transaction\",\n sql: \"commit\",\n meta: {\n loggerLevel: \"system\",\n },\n });\n },\n rollback: () => {\n this.executePreparedRaw({\n key: \"$rollback-transaction\",\n sql: \"rollback\",\n meta: {\n loggerLevel: \"system\",\n },\n });\n },\n };\n }\n\n private beginStatementTransaction() {\n if (!this.isInTransaction()) {\n return this.beginTransaction();\n }\n\n this.executePreparedRaw({\n key: \"$begin-statement-savepoint\",\n sql: \"savepoint sqlite_sync_statement\",\n meta: { loggerLevel: \"system\" },\n });\n\n return {\n commit: () => {\n this.executePreparedRaw({\n key: \"$commit-statement-savepoint\",\n sql: \"release savepoint sqlite_sync_statement\",\n meta: { loggerLevel: \"system\" },\n });\n },\n rollback: () => {\n this.executePreparedRaw({\n key: \"$rollback-statement-savepoint\",\n sql: \"rollback to savepoint sqlite_sync_statement\",\n meta: { loggerLevel: \"system\" },\n });\n this.executePreparedRaw({\n key: \"$release-rolled-back-statement-savepoint\",\n sql: \"release savepoint sqlite_sync_statement\",\n meta: { loggerLevel: \"system\" },\n });\n },\n };\n }\n\n prepare<TParams extends unknown[], TResult>(sql: string, opts?: QueryMetaOpts) {\n const perf = startPerformanceLogger(this.logger, opts?.loggerLevel);\n const stmt = this.ensureDb.prepare(sql);\n perf.logEnd(`${this.loggerPrefix ?? \"\"}:prepare`, sql, opts?.loggerLevel);\n\n let isFinalized = false;\n\n const execute = (params: TParams) => {\n if (isFinalized) {\n throw new Error(\"Statement is finalized\");\n }\n\n const perf = startPerformanceLogger(this.logger, opts?.loggerLevel);\n try {\n if (params.length > 0) {\n stmt.bind(params as SqlValue[]);\n }\n\n const results = [] as TResult[];\n // Reading rows as arrays and naming them here keeps sqlite from looking up\n // every column name again for every row.\n let columnNames: string[] | undefined;\n while (stmt.step()) {\n columnNames ??= stmt.getColumnNames([]);\n const values = stmt.get([]);\n const row = {} as Record<string, unknown>;\n for (let index = 0; index < columnNames.length; index++) {\n row[columnNames[index]] = values[index];\n }\n results.push(row as TResult);\n }\n\n return results;\n } finally {\n stmt.reset(true);\n perf.logEnd(`${this.loggerPrefix ?? \"\"}:prepare-execute`, sql, opts?.loggerLevel);\n }\n };\n\n const finalize = () => {\n isFinalized = true;\n stmt.finalize();\n };\n\n const preparedStatement: PreparedStatement<TParams, TResult> = {\n execute,\n finalize,\n get isFinalized() {\n return isFinalized;\n },\n };\n\n this.preparedStatements.push(preparedStatement as PreparedStatement<SqlValue[], unknown>);\n\n return preparedStatement;\n }\n\n prepareKysely<TParams extends Record<string, unknown>>(opts?: QueryMetaOpts) {\n return <TQuery extends Compilable<TResult>, TResult = QueryBuilderOutput<TQuery>>(\n factory: KyselyStatementFactory<TParams, TDatabase, TQuery, TResult>,\n ): TypedStatement<TParams, TResult> => {\n const query = factory(dummyKysely, (key) => key as any).compile();\n const statement = this.prepare<SqlValue[], TResult>(query.sql, opts);\n\n return {\n execute: (parameters) => {\n const params = query.parameters.map((param) => parameters[param as keyof TParams]);\n const result = statement.execute(params as SqlValue[]);\n return result;\n },\n };\n };\n }\n\n executeKysely<TQuery extends Compilable<TResult>, TResult = QueryBuilderOutput<TQuery>>(\n factory: KyselyQueryFactory<TDatabase, TQuery, TResult>,\n options?: ExecuteOptions,\n ) {\n const query = factory(dummyKysely).compile();\n return this.execute(query, options);\n }\n\n executePrepared<\n TParams extends Record<string, unknown>,\n TQuery extends Compilable<TResult>,\n TResult = QueryBuilderOutput<TQuery>,\n >(\n key: string,\n params: TParams,\n factory: KyselyStatementFactory<TParams, TDatabase, TQuery, TResult>,\n meta?: QueryMetaOpts,\n ) {\n let statement = this.preparedStatementsMap.get(key) as TypedStatement<TParams, TResult> | undefined;\n if (!statement) {\n statement = this.prepareKysely<TParams>(meta)(factory);\n this.preparedStatementsMap.set(key, statement as TypedStatement<Record<string, unknown>, unknown>);\n }\n\n return statement.execute(params);\n }\n\n executePreparedRaw<TParams extends unknown[], TResult>({\n key,\n sql,\n params,\n meta,\n }: {\n key: string;\n sql: string;\n params?: TParams;\n meta?: QueryMetaOpts;\n }) {\n let statement = this.preparedRawStatementsMap.get(key) as PreparedStatement<TParams, TResult> | undefined;\n if (!statement) {\n statement = this.prepare(sql, meta);\n this.preparedRawStatementsMap.set(key, statement as PreparedStatement<any[], unknown>);\n }\n\n return statement.execute((params ?? []) as TParams);\n }\n\n sql<T = unknown>(templateOrString: TemplateStringsArray | string, ...parameters: unknown[]) {\n if (typeof templateOrString === \"string\") {\n return this.execute<T>({\n sql: templateOrString,\n parameters,\n });\n }\n return this.execute<T>({\n sql: templateOrString.join(\"?\"),\n parameters,\n });\n }\n\n createScalarFunction<TArgs extends SqlValue[], TResult extends SqlValue | undefined>({\n name,\n callback,\n deterministic,\n directOnly,\n innocuous,\n }: ScalarFunctionOptions<TArgs, TResult>) {\n return this.ensureDb.createFunction({\n name,\n xFunc: (_, ...args) => {\n const result = callback(...(args as TArgs)) as SqlValue;\n return result;\n },\n arity: callback.length,\n deterministic,\n directOnly,\n innocuous,\n });\n }\n\n useSnapshot(snapshot: Uint8Array<ArrayBufferLike>) {\n const perf = startPerformanceLogger(this.logger, \"system\");\n const dataPointer = this.sqlite3.wasm.allocFromTypedArray(snapshot);\n this.dataPointers.push(dataPointer);\n\n const resultCode = this.sqlite3.capi.sqlite3_deserialize(\n this.ensureDb,\n \"main\",\n dataPointer,\n snapshot.byteLength,\n snapshot.byteLength,\n this.sqlite3.capi.SQLITE_DESERIALIZE_FREEONCLOSE | this.sqlite3.capi.SQLITE_DESERIALIZE_RESIZEABLE,\n );\n\n this.ensureDb.checkRc(resultCode);\n\n this.invalidateDbSchema();\n\n perf.logEnd(\"useSnapshot\", \"success\", \"system\");\n }\n\n createSnapshot() {\n return this.sqlite3.capi.sqlite3_js_db_export(this.ensureDb);\n }\n\n invalidateDbSchema() {\n this.loadedDbSchema = null;\n }\n\n cleanup() {\n this.preparedStatements.forEach((stmt) => {\n stmt.finalize();\n });\n this.preparedStatements.splice(0);\n this.preparedStatementsMap.clear();\n this.preparedRawStatementsMap.clear();\n }\n\n close() {\n this.cleanup();\n\n this.db?.close();\n this.db = null;\n }\n}\n\nexport type QueryBuilderOutput<QB> = QB extends Compilable<infer O> ? O : never;\ntype ParamsGetter<TParams> = <TKey extends keyof TParams>(key: TKey) => TParams[TKey];\n\ntype TypedStatement<TParams extends Record<string, unknown>, TResult> = {\n execute: (parameters: TParams) => TResult[];\n};\nexport type KyselyStatementFactory<\n TParams extends Record<string, unknown>,\n TDatabase,\n TQuery extends Compilable<TResult>,\n TResult = QueryBuilderOutput<TQuery>,\n> = (kysely: Kysely<TDatabase>, params: ParamsGetter<TParams>) => TQuery;\nexport type KyselyQueryFactory<TDatabase, TQuery extends Compilable<TResult>, TResult = QueryBuilderOutput<TQuery>> = (\n kysely: Kysely<TDatabase>,\n) => TQuery;\n","import type {\n ColumnDataType,\n ColumnDefinitionBuilderCallback,\n Compilable,\n CreateIndexBuilder,\n CreateTableBuilder,\n Expression,\n Kysely,\n} from \"kysely\";\nimport { dummyKysely } from \"../dummy-kysely\";\nimport { type CrdtEventType, isNoOpCrdtEventPayload, type NewCrdtEvent } from \"../sqlite-crdt/crdt-table-schema\";\nimport type { StoredValue } from \"../sqlite-crdt/stored-value\";\n\nexport type MigratableEvent = {\n schema_version: number;\n type: CrdtEventType;\n dataset: string;\n item_id: string;\n payload: string;\n};\n\ntype CrdtEventTransformer = (event: NewCrdtEvent) => NewCrdtEvent | null;\n\ntype MigrationStepSql =\n | {\n sql: string;\n parameters?: readonly unknown[];\n }\n | Compilable\n | ((db: Kysely<unknown>) => Compilable);\n\ntype UpdateLogMutation =\n | { type: \"rename-table\"; oldTable: string; newTable: string }\n | { type: \"drop-table\"; table: string }\n | { type: \"rename-column\"; table: string; oldColumn: string; newColumn: string }\n | { type: \"add-column\"; table: string; column: string }\n | { type: \"drop-column\"; table: string; column: string };\n\ntype MigrationStep = {\n sql: MigrationStepSql | MigrationStepSql[];\n eventTransformer?: MigrationEventTransformers;\n updateLogMutations?: UpdateLogMutation[];\n};\n\ntype RawMigrationStep = {\n sql: MigrationSql[];\n eventTransformer?: CompiledMigrationEventTransformer;\n updateLogMutations?: UpdateLogMutation[];\n};\n\ntype MigrationSql = { sql: string; parameters: readonly unknown[] };\n\ntype DataTypeExpression = ColumnDataType | Expression<any>;\n\nconst protectedColumns = [\"id\", \"tombstone\"];\n\nfunction assertColumnNotProtected(column: string, operation: string) {\n if (protectedColumns.includes(column)) {\n throw new Error(`Cannot ${operation} protected column \"${column}\"`);\n }\n}\n\nconst migrationSteps = {\n createTable: (table: string, build: (table: CreateTableBuilder<string, never>) => Compilable): MigrationStep => ({\n sql: (db) => build(db.schema.createTable(table)),\n }),\n\n dropTable: (table: string): MigrationStep => ({\n sql: (db) => db.schema.dropTable(table),\n eventTransformer: {\n [table]: () => null,\n },\n updateLogMutations: [{ type: \"drop-table\", table }],\n }),\n\n createIndex: (indexName: string, build: (index: CreateIndexBuilder) => Compilable): MigrationStep => ({\n sql: (db) => build(db.schema.createIndex(indexName)),\n }),\n\n dropIndex: (indexName: string): MigrationStep => ({\n sql: (db) => db.schema.dropIndex(indexName),\n }),\n\n renameTable: ({ oldTable, newTable }: { oldTable: string; newTable: string }): MigrationStep => ({\n sql: (db) => db.schema.alterTable(oldTable).renameTo(newTable),\n eventTransformer: {\n [oldTable]: (event) => {\n event.dataset = newTable;\n return event;\n },\n },\n updateLogMutations: [{ type: \"rename-table\", oldTable, newTable }],\n }),\n\n renameColumn: ({\n table,\n oldColumn,\n newColumn,\n }: {\n table: string;\n oldColumn: string;\n newColumn: string;\n }): MigrationStep => {\n assertColumnNotProtected(oldColumn, \"rename\");\n assertColumnNotProtected(newColumn, \"rename to\");\n return {\n sql: (db) => db.schema.alterTable(table).renameColumn(oldColumn, newColumn),\n eventTransformer: {\n [table]: (event) => {\n if ((event.type !== \"item-updated\" && event.type !== \"item-created\") || !(oldColumn in event.payload)) {\n return event;\n }\n\n const oldVal = event.payload[oldColumn];\n delete event.payload[oldColumn];\n event.payload[newColumn] = oldVal;\n\n return event;\n },\n },\n updateLogMutations: [{ type: \"rename-column\", table, oldColumn, newColumn }],\n };\n },\n\n addColumn: ({\n table,\n column,\n type,\n defaultValue,\n build = (e) => e,\n }: {\n table: string;\n column: string;\n type: DataTypeExpression;\n defaultValue: number | boolean | string | null;\n build?: ColumnDefinitionBuilderCallback;\n }): MigrationStep => ({\n sql: (db) => db.schema.alterTable(table).addColumn(column, type, (x) => build(x).defaultTo(defaultValue)),\n eventTransformer: {\n [table]: (event) => {\n if (event.type !== \"item-created\") {\n return event;\n }\n\n event.payload[column] = defaultValue;\n\n return event;\n },\n },\n updateLogMutations: [{ type: \"add-column\", table, column }],\n }),\n\n dropColumn: ({ table, column }: { table: string; column: string }): MigrationStep => {\n assertColumnNotProtected(column, \"drop\");\n return {\n sql: (db) => db.schema.alterTable(table).dropColumn(column),\n eventTransformer: {\n [table]: (event) => {\n if (!(column in event.payload)) {\n return event;\n }\n\n delete event.payload[column];\n\n if (event.type === \"item-updated\" && Object.keys(event.payload).length === 0) {\n return null;\n }\n\n return event;\n },\n },\n updateLogMutations: [{ type: \"drop-column\", table, column }],\n };\n },\n};\n\ntype MigrationEventTransformers = Record<string, CrdtEventTransformer>;\ntype CompiledMigrationEventTransformer = (event: NewCrdtEvent) => NewCrdtEvent | null;\n\nfunction buildMigrationSql(steps: MigrationStep[]): MigrationSql[] {\n return steps\n .flatMap((step) => (Array.isArray(step.sql) ? step.sql : [step.sql]))\n .map((sql): MigrationSql => {\n if (typeof sql === \"string\") {\n return { sql, parameters: [] };\n }\n\n if (typeof sql === \"function\") {\n const query = sql(dummyKysely).compile();\n return { sql: query.sql, parameters: query.parameters };\n }\n\n if (\"compile\" in sql) {\n const query = sql.compile();\n return { sql: query.sql, parameters: query.parameters };\n }\n\n return {\n sql: sql.sql,\n parameters: sql.parameters ?? [],\n };\n });\n}\n\nfunction buildMigrationEventTransformer(steps: MigrationStep[]): CompiledMigrationEventTransformer | undefined {\n const transformers: Array<[string, CrdtEventTransformer]> = [];\n\n for (const step of steps) {\n if (step.eventTransformer) {\n transformers.push(...Object.entries(step.eventTransformer));\n }\n }\n\n if (transformers.length === 0) {\n return undefined;\n }\n\n return (event: NewCrdtEvent) => {\n let transformedEvent: NewCrdtEvent | null = event;\n\n for (const [table, transformer] of transformers) {\n if (transformedEvent === null) {\n return null;\n }\n if (transformedEvent.dataset !== table) {\n continue;\n }\n transformedEvent = transformer(transformedEvent);\n if (transformedEvent === null) {\n return null;\n }\n }\n\n return transformedEvent;\n };\n}\n\nexport function createMigrations(buildMigrations: (builder: typeof migrationSteps) => Record<number, MigrationStep[]>) {\n const migrations: Record<number, RawMigrationStep> = Object.fromEntries(\n Object.entries(buildMigrations(migrationSteps)).map(([version, steps]) => {\n const versionNumber = Number(version);\n\n if (Number.isNaN(versionNumber)) {\n throw new Error(`Invalid migration version: ${version}`);\n }\n\n if (versionNumber < 0) {\n throw new Error(`Migration version cannot be negative: ${version}`);\n }\n\n const updateLogMutations = steps.flatMap((step) => step.updateLogMutations ?? []);\n\n return [\n version,\n {\n sql: buildMigrationSql(steps),\n eventTransformer: buildMigrationEventTransformer(steps),\n ...(updateLogMutations.length > 0 && { updateLogMutations }),\n },\n ];\n }),\n );\n\n return migrations;\n}\n\nexport type Migrations = ReturnType<typeof createMigrations>;\n\nexport type MigrationsDb = {\n startTransaction: (callback: (tx: MigrationsTransaction) => void) => void;\n};\n\ntype MigrationsTransaction = {\n execute: (sql: string, parameters: readonly unknown[], meta?: { loggerLevel?: \"system\" }) => void;\n};\n\nexport function createMigrator({\n migrations,\n schemaVersion,\n updateLogTableName,\n}: {\n migrations: Migrations;\n schemaVersion: StoredValue<number>;\n updateLogTableName?: string;\n}) {\n const latestSchemaVersion = Math.max(...Object.keys(migrations).map(Number));\n\n // Pre-sort migrations once for efficient range lookups\n const sortedMigrations = Object.entries(migrations)\n .map(([v, m]) => [Number(v), m] as const)\n .sort((a, b) => a[0] - b[0]);\n\n const applyMigration = (db: MigrationsDb, version: number, migration: RawMigrationStep) => {\n if (version <= schemaVersion.current) {\n throw new Error(`Cannot apply migration ${version} to schema version ${schemaVersion.current}`);\n }\n\n db.startTransaction((tx) => {\n for (const statement of migration.sql) {\n tx.execute(statement.sql, statement.parameters);\n }\n if (updateLogTableName && migration.updateLogMutations) {\n for (const mutation of migration.updateLogMutations) {\n switch (mutation.type) {\n case \"rename-table\": {\n tx.execute(`DELETE FROM ${updateLogTableName} WHERE \"dataset\" = ?`, [mutation.newTable], {\n loggerLevel: \"system\",\n });\n tx.execute(\n `UPDATE ${updateLogTableName} SET \"dataset\" = ? WHERE \"dataset\" = ?`,\n [mutation.newTable, mutation.oldTable],\n { loggerLevel: \"system\" },\n );\n break;\n }\n case \"drop-table\": {\n tx.execute(`DELETE FROM ${updateLogTableName} WHERE \"dataset\" = ?`, [mutation.table], {\n loggerLevel: \"system\",\n });\n break;\n }\n case \"rename-column\": {\n tx.execute(\n `UPDATE ${updateLogTableName}\nSET \"payload\" = COALESCE(\n (\n SELECT json_group_object(CASE WHEN \"key\" = ? THEN ? ELSE \"key\" END, \"value\")\n FROM json_each(\"payload\")\n WHERE \"key\" != ?\n ),\n '{}'\n)\nWHERE \"dataset\" = ?\n AND EXISTS (SELECT 1 FROM json_each(\"payload\") WHERE \"key\" = ? OR \"key\" = ?)`,\n [\n mutation.oldColumn,\n mutation.newColumn,\n mutation.newColumn,\n mutation.table,\n mutation.oldColumn,\n mutation.newColumn,\n ],\n { loggerLevel: \"system\" },\n );\n break;\n }\n case \"add-column\":\n case \"drop-column\": {\n tx.execute(\n `UPDATE ${updateLogTableName}\nSET \"payload\" = COALESCE(\n (SELECT json_group_object(\"key\", \"value\") FROM json_each(\"payload\") WHERE \"key\" != ?),\n '{}'\n)\nWHERE \"dataset\" = ?\n AND EXISTS (SELECT 1 FROM json_each(\"payload\") WHERE \"key\" = ?)`,\n [mutation.column, mutation.table, mutation.column],\n { loggerLevel: \"system\" },\n );\n break;\n }\n default: {\n const _exhaustive: never = mutation;\n throw new Error(`Unsupported update-log mutation: ${JSON.stringify(_exhaustive)}`);\n }\n }\n }\n }\n schemaVersion.current = version;\n });\n };\n\n const migrateEvent = <Event extends MigratableEvent>(event: Event, targetVersion?: number): Event | null => {\n targetVersion ??= latestSchemaVersion;\n if (targetVersion > schemaVersion.current) {\n throw new Error(\n `Target schema version ${targetVersion} is greater than current schema version ${schemaVersion.current}`,\n );\n }\n\n if (isNoOpCrdtEventPayload(event.payload)) {\n if (event.schema_version < targetVersion) {\n event.schema_version = targetVersion;\n }\n return event;\n }\n\n if (event.schema_version >= targetVersion) {\n return event;\n }\n\n const fromVersion = event.schema_version;\n\n let crdtEvent: NewCrdtEvent | null = {\n dataset: event.dataset,\n item_id: event.item_id,\n type: event.type,\n payload: JSON.parse(event.payload),\n };\n\n for (let i = 0; i < sortedMigrations.length; i++) {\n const [version, migration] = sortedMigrations[i];\n if (version <= fromVersion) continue;\n if (version > targetVersion) break;\n\n const transformer = migration.eventTransformer;\n if (transformer) {\n crdtEvent = transformer(crdtEvent);\n if (crdtEvent === null) return null;\n }\n }\n\n if (crdtEvent === null) {\n return null;\n }\n\n event.schema_version = targetVersion;\n event.dataset = crdtEvent.dataset;\n event.item_id = crdtEvent.item_id;\n event.type = crdtEvent.type;\n event.payload = JSON.stringify(crdtEvent.payload);\n\n return event;\n };\n\n const migrateEvents = <Event extends MigratableEvent>(events: Event[], targetVersion?: number): Event[] => {\n return events\n .map((event) => migrateEvent(event, targetVersion ?? latestSchemaVersion))\n .filter((event): event is NonNullable<typeof event> => event !== null);\n };\n\n return {\n latestSchemaVersion,\n get currentSchemaVersion() {\n return schemaVersion.current;\n },\n migrateDbToLatest: (db: MigrationsDb) => {\n const currentSchemaVersion = schemaVersion.current;\n\n if (currentSchemaVersion >= latestSchemaVersion) {\n return;\n }\n\n for (let i = 0; i < sortedMigrations.length; i++) {\n const [version, migration] = sortedMigrations[i];\n if (version <= currentSchemaVersion) continue;\n applyMigration(db, version, migration);\n }\n },\n migrateEvent,\n migrateEvents,\n };\n}\n\nexport type SyncDbMigrator = ReturnType<typeof createMigrator>;\n","import sqlite3InitModule, { type Sqlite3Static } from \"@sqlite.org/sqlite-wasm\";\nimport { introspectDb, type TableMetadata } from \"../introspection\";\nimport { createMigrator } from \"../migrations/migrator\";\nimport type { SyncDbSchema } from \"../sqlite-crdt/crdt-schema\";\nimport { createStoredValue } from \"../sqlite-crdt/stored-value\";\nimport { SQLiteDbWrapper } from \"../sqlite-db-wrapper\";\nimport type { ColumnKind, ColumnMeta } from \"./table-builder\";\n\nexport type SchemaVerificationIssue = {\n table: string;\n column?: string;\n message: string;\n};\n\nconst acceptedDataTypes: Record<ColumnKind, string[]> = {\n text: [\"TEXT\"],\n integer: [\"INTEGER\", \"INT\"],\n real: [\"REAL\", \"DOUBLE\", \"FLOAT\"],\n boolean: [\"BOOLEAN\", \"INTEGER\", \"INT\"],\n enum: [\"TEXT\"],\n};\n\nlet sqliteModule: Sqlite3Static | null = null;\n\n/**\n * Verify that replaying the full migration history on a fresh database produces\n * exactly the declared table schema. Creates a throwaway in-memory SQLite db,\n * applies all migrations, introspects the result, and diffs it against the\n * `t.table()` declarations.\n *\n * Returns an empty array when migrations and schema agree. Intended for dev-time\n * checks (`startDbWorker({ verifySchema: true })`) and tests:\n * `expect(await verifySyncSchema(syncDbSchema)).toEqual([])`.\n */\nexport async function verifySyncSchema(schema: SyncDbSchema): Promise<SchemaVerificationIssue[]> {\n if (!sqliteModule) {\n sqliteModule = await sqlite3InitModule();\n }\n const sqlite3 = sqliteModule;\n\n const db = new SQLiteDbWrapper({\n db: () => new sqlite3.oo1.DB({ filename: \":memory:\" }),\n logger: () => {},\n loggerPrefix: \"schema-verify\",\n sqlite3,\n });\n\n try {\n const migrator = createMigrator({\n migrations: schema.migrations,\n schemaVersion: createStoredValue({ initialValue: -1 }),\n });\n\n try {\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 } catch (error) {\n return [\n {\n table: \"(migrations)\",\n message: `migration replay failed on an empty database: ${error instanceof Error ? error.message : String(error)}`,\n },\n ];\n }\n\n const introspection = introspectDb(db);\n\n const issues: SchemaVerificationIssue[] = [];\n for (const { crdtTableName, baseTableName } of schema.tablesConfig) {\n const declared = schema.tables[crdtTableName];\n if (!declared) continue;\n verifyTable(db, baseTableName, declared.columns, introspection[baseTableName], issues);\n }\n return issues;\n } finally {\n db.close();\n }\n}\n\nexport function formatSchemaVerificationIssues(issues: SchemaVerificationIssue[]): string {\n const lines = issues.map(\n (issue) => ` - table \"${issue.table}\"${issue.column ? ` column \"${issue.column}\"` : \"\"}: ${issue.message}`,\n );\n return `sync schema verification failed (${issues.length} issue${issues.length === 1 ? \"\" : \"s\"}):\\n${lines.join(\"\\n\")}`;\n}\n\nfunction verifyTable(\n db: SQLiteDbWrapper,\n table: string,\n declaredColumns: Record<string, ColumnMeta>,\n actual: TableMetadata | undefined,\n issues: SchemaVerificationIssue[],\n) {\n if (!actual) {\n issues.push({\n table,\n message: \"declared in the schema but missing after migrations — add a createTable step in a new version\",\n });\n return;\n }\n if (actual.isView) {\n issues.push({ table, message: \"expected a base table, but migrations created a view with this name\" });\n return;\n }\n\n const actualColumns = new Map(actual.columns.map((column) => [column.name, column]));\n\n for (const [name, meta] of Object.entries(declaredColumns)) {\n const column = actualColumns.get(name);\n if (!column) {\n issues.push({\n table,\n column: name,\n message: \"declared in the schema but missing after migrations — add an addColumn step in a new version\",\n });\n continue;\n }\n\n const accepted = acceptedDataTypes[meta.kind];\n if (!accepted.includes(column.dataType.toUpperCase())) {\n issues.push({\n table,\n column: name,\n message: `type mismatch — schema declares ${meta.kind} (${accepted.join(\"/\")}), migrated column is ${column.dataType.toUpperCase()}`,\n });\n }\n\n if (column.isNullable !== meta.nullable) {\n issues.push({\n table,\n column: name,\n message: meta.nullable\n ? \"schema declares the column nullable, but the migrated column is NOT NULL\"\n : \"schema declares the column NOT NULL, but the migrated column is nullable\",\n });\n }\n\n if (name === \"id\" && !column.isPrimaryKey) {\n issues.push({ table, column: name, message: \"the id column must be the PRIMARY KEY\" });\n }\n\n verifyDefault(db, table, name, meta, column.defaultValueSql, issues);\n }\n\n for (const column of actual.columns) {\n if (!(column.name in declaredColumns)) {\n issues.push({\n table,\n column: column.name,\n message:\n \"created by migrations but not declared in the schema — sync event payloads include every column, so declare it (or drop it in a new version)\",\n });\n }\n }\n}\n\nfunction verifyDefault(\n db: SQLiteDbWrapper,\n table: string,\n column: string,\n meta: ColumnMeta,\n defaultValueSql: string | null,\n issues: SchemaVerificationIssue[],\n) {\n // A declared `null` default and an absent DEFAULT clause are equivalent in SQLite\n // (a column with no DEFAULT defaults to NULL), so don't flag that pairing.\n if (meta.hasDefault && meta.defaultValue !== null && defaultValueSql === null) {\n issues.push({\n table,\n column,\n message: `schema declares default ${formatExpected(meta)}, but the migrated column has no default`,\n });\n return;\n }\n if (!meta.hasDefault && defaultValueSql !== null) {\n issues.push({\n table,\n column,\n message: `migrated column has default ${defaultValueSql}, but the schema declares none`,\n });\n return;\n }\n if (!meta.hasDefault || defaultValueSql === null) {\n return;\n }\n\n // Declared defaults are always constants (ColumnBuilder.default takes a JS value),\n // so a non-constant migrated default (e.g. strftime(...)) is always a real mismatch.\n let actual: unknown;\n try {\n actual = db.execute<{ value: unknown }>({\n sql: `SELECT (${defaultValueSql}) AS value`,\n parameters: [],\n }).rows[0]?.value;\n } catch {\n actual = undefined;\n }\n\n if (actual !== expectedDefaultValue(meta)) {\n issues.push({\n table,\n column,\n message: `default mismatch — schema declares ${formatExpected(meta)}, migrated column default is ${defaultValueSql}`,\n });\n }\n}\n\n/** The declared default normalized to how SQLite stores it (booleans as 0/1). */\nfunction expectedDefaultValue(meta: ColumnMeta): string | number | null {\n if (meta.defaultValue === null) return null;\n switch (meta.kind) {\n case \"boolean\":\n return meta.defaultValue ? 1 : 0;\n case \"integer\":\n case \"real\":\n return Number(meta.defaultValue);\n case \"text\":\n case \"enum\":\n return String(meta.defaultValue);\n }\n}\n\nfunction formatExpected(meta: ColumnMeta): string {\n const expected = expectedDefaultValue(meta);\n return typeof expected === \"string\" ? `'${expected}'` : String(expected);\n}\n","import type { SqlValue } from \"@sqlite.org/sqlite-wasm\";\nimport type { Kysely } from \"kysely\";\nimport type { SystemDbConfig } from \"../migrations/system-schema\";\nimport type { InternalSQLiteWrapper } from \"../sqlite-db-wrapper\";\nimport { quoteId } from \"../utils\";\nimport {\n type CrdtEventType,\n type CrdtUpdateLogItem,\n type CrdtUpdateLogPayload,\n isNoOpCrdtEventPayload,\n} from \"./crdt-table-schema\";\n\nexport type PendingCrdtEvent = {\n type: CrdtEventType;\n dataset: string;\n item_id: string;\n timestamp: string;\n payload: string;\n};\n\nexport const createSQLiteCrdtApplyFunction = ({\n db,\n dbConfig,\n}: {\n db: InternalSQLiteWrapper<any>;\n dbConfig: SystemDbConfig;\n}) => {\n const applyCrdtEvent = createCrdtApplyFunction({\n getCrdtUpdateLog(opts) {\n const [metaRow] = db.executePrepared(\n \"get-item-crdt-meta\",\n {\n item_id: opts.itemId,\n dataset: opts.dataset,\n },\n (db, params) => {\n return (db as unknown as Kysely<{ table: CrdtUpdateLogItem }>)\n .selectFrom(dbConfig.updateLogTable.fullIdentifier as \"table\")\n .select(\"payload\")\n .where(\"item_id\", \"=\", params(\"item_id\"))\n .where(\"dataset\", \"=\", params(\"dataset\"));\n },\n { loggerLevel: \"system\" },\n );\n const meta = metaRow ? (JSON.parse(metaRow.payload) as CrdtUpdateLogPayload) : null;\n return meta;\n },\n insertCrdtUpdateLog(opts) {\n db.executePrepared(\n \"insert-crdt-update-log\",\n {\n item_id: opts.itemId,\n dataset: opts.dataset,\n payload: opts.payload,\n },\n (db, params) =>\n (db as unknown as Kysely<{ table: CrdtUpdateLogItem }>)\n .insertInto(dbConfig.updateLogTable.fullIdentifier as \"table\")\n .values({\n item_id: params(\"item_id\"),\n dataset: params(\"dataset\"),\n payload: params(\"payload\"),\n }),\n { loggerLevel: \"system\" },\n );\n },\n updateCrdtUpdateLog(opts) {\n db.executePrepared(\n \"update-crdt-update-log\",\n {\n item_id: opts.itemId,\n dataset: opts.dataset,\n payload: opts.payload,\n },\n (db, params) =>\n (db as unknown as Kysely<{ table: CrdtUpdateLogItem }>)\n .updateTable(dbConfig.updateLogTable.fullIdentifier as \"table\")\n .set({\n payload: params(\"payload\"),\n })\n .where(\"item_id\", \"=\", params(\"item_id\"))\n .where(\"dataset\", \"=\", params(\"dataset\")),\n { loggerLevel: \"system\" },\n );\n },\n insertItem(opts) {\n // Key by the sorted column set, not just the dataset: executePrepared caches\n // the compiled INSERT forever, freezing its column list. A later create event\n // omitting a column would otherwise bind undefined (-> NULL) against the stale\n // statement, bypassing the table DEFAULT. Sorting makes the key canonical so\n // events with the same columns in any order share one statement.\n const keys = Object.keys(opts.payload).sort();\n const insertPayload = {} as Record<string, unknown>;\n for (const key of keys) {\n insertPayload[key] = key;\n }\n db.executePrepared(\n `crdt-insert-item-${opts.dataset}-${keys.join(\"-\")}`,\n opts.payload,\n (db) => db.insertInto(opts.dataset).values(insertPayload),\n { loggerLevel: \"system\" },\n );\n },\n updateItem(opts) {\n const keys = Array.from(Object.keys(opts.payload));\n keys.sort();\n db.executePreparedRaw({\n key: `update-item-${opts.dataset}-${keys.join(\"-\")}`,\n sql: `update ${quoteId(opts.dataset)} set ${keys.map((key) => `${quoteId(key)} = ?`).join(\",\")} where id = ?`,\n params: [...keys.map((key) => opts.payload[key]), opts.itemId] as SqlValue[],\n meta: { loggerLevel: \"system\" },\n });\n },\n });\n\n return applyCrdtEvent;\n};\n\ntype CreateCrdtApplyOpts = {\n getCrdtUpdateLog: (opts: { itemId: string; dataset: string }) => CrdtUpdateLogPayload | null;\n insertItem: (opts: { dataset: string; payload: Record<string, unknown> }) => void;\n insertCrdtUpdateLog: (opts: { dataset: string; itemId: string; payload: string }) => void;\n updateItem: (opts: { dataset: string; itemId: string; payload: Record<string, unknown> }) => void;\n updateCrdtUpdateLog: (opts: { dataset: string; itemId: string; payload: string }) => void;\n};\n\nexport function createCrdtApplyFunction({\n getCrdtUpdateLog,\n insertItem,\n insertCrdtUpdateLog,\n updateItem,\n updateCrdtUpdateLog,\n}: CreateCrdtApplyOpts) {\n type ItemCreatedOpts = {\n event: PendingCrdtEvent;\n };\n const applyItemCreated = ({ event }: ItemCreatedOpts) => {\n const eventPayload = JSON.parse(event.payload);\n\n eventPayload.tombstone = 0;\n insertItem({ dataset: event.dataset, payload: eventPayload });\n\n const newUpdateLog = {} as Record<string, string>;\n for (const key of Object.keys(eventPayload)) {\n newUpdateLog[key] = event.timestamp;\n }\n\n insertCrdtUpdateLog({\n dataset: event.dataset,\n itemId: event.item_id,\n payload: JSON.stringify(newUpdateLog),\n });\n };\n\n type ItemUpdatedOpts = {\n event: PendingCrdtEvent;\n meta: CrdtUpdateLogPayload;\n };\n const applyItemUpdated = ({ event, meta }: ItemUpdatedOpts) => {\n if (!meta) {\n throw new Error(`Item ${event.item_id} in dataset ${event.dataset} not found`);\n }\n // A delete carries no field data on the wire — the tombstone is a local\n // materialization of the soft-delete, stamped here with the event's HLC so\n // it competes with concurrent field edits under the same last-write-wins rule.\n const eventPayload = event.type === \"item-deleted\" ? { tombstone: 1 } : JSON.parse(event.payload);\n if (event.type === \"item-created\") {\n eventPayload.tombstone = 0;\n }\n\n const updatePayload = {} as Record<string, unknown>;\n let hasUpdates = false;\n\n for (const [key, value] of Object.entries(eventPayload)) {\n if (key === \"id\") {\n continue;\n }\n\n const lastUpdateTimestamp = meta[key];\n const currentUpdateTimestamp = event.timestamp;\n\n if (!lastUpdateTimestamp || !currentUpdateTimestamp || currentUpdateTimestamp > lastUpdateTimestamp) {\n updatePayload[key] = value;\n meta[key] = currentUpdateTimestamp;\n hasUpdates = true;\n }\n }\n\n if (!hasUpdates) {\n return;\n }\n\n updateItem({\n dataset: event.dataset,\n itemId: event.item_id,\n payload: updatePayload,\n });\n updateCrdtUpdateLog({\n dataset: event.dataset,\n itemId: event.item_id,\n payload: JSON.stringify(meta),\n });\n };\n\n return (event: PendingCrdtEvent) => {\n if (isNoOpCrdtEventPayload(event.payload)) {\n return;\n }\n\n const meta = getCrdtUpdateLog({\n itemId: event.item_id,\n dataset: event.dataset,\n });\n\n // TODO Check primary key / unique constraints\n\n if (event.type !== \"item-created\" && event.type !== \"item-updated\" && event.type !== \"item-deleted\") {\n throw new Error(`Unknown event type: ${event.type}`);\n }\n\n if (meta) {\n applyItemUpdated({ event, meta });\n return;\n }\n\n if (event.type === \"item-created\") {\n applyItemCreated({ event });\n return;\n }\n\n throw new Error(`Item ${event.item_id} in dataset ${event.dataset} not found`);\n };\n}\n","import { xxhash } from \"../hash\";\n\nconst MASK_128 = (1n << 128n) - 1n;\nconst HEX_128_PATTERN = /^[0-9a-f]{32}$/;\n\nexport function createEventHlcAccumulator(initialValue: string) {\n let current = parseHex128(initialValue);\n\n return {\n add(timestamp: string) {\n current = (current + hash128BigInt(timestamp)) & MASK_128;\n },\n get current() {\n return toHex128(current);\n },\n };\n}\n\nfunction parseHex128(value: string) {\n if (value === \"\") {\n return 0n;\n }\n const normalized = value.toLowerCase();\n if (!HEX_128_PATTERN.test(normalized)) {\n throw new Error(`Invalid event HLC accumulator value: ${value}`);\n }\n return BigInt(`0x${normalized}`);\n}\n\nfunction toHex128(value: bigint) {\n return value.toString(16).padStart(32, \"0\");\n}\n\nfunction hash128BigInt(value: string) {\n return xxhash.h64(value, 0n) | (xxhash.h64(value, 1n) << 64n);\n}\n","import { sql } from \"kysely\";\nimport { deserializeHLC, type HLCCounter, serializeHLC } from \"../hlc\";\nimport type { SyncDbMigrator } from \"../migrations/migrator\";\nimport type { SystemDbConfig } from \"../migrations/system-schema\";\nimport { CrdtEventValidationError, validateNewCrdtEvent } from \"../schema/validate-crdt-event\";\nimport type { InternalSQLiteTransactionWrapper, InternalSQLiteWrapper } from \"../sqlite-db-wrapper\";\nimport { createTypedEventTarget, ensureSingletonExecution, quoteId } from \"../utils\";\nimport { createSQLiteCrdtApplyFunction } from \"./apply-crdt-event\";\nimport type { SyncDbSchema } from \"./crdt-schema\";\nimport {\n CRDT_EVENT_NO_OP_PAYLOAD,\n type CrdtEventOrigin,\n type CrdtEventStatus,\n type CrdtEventType,\n type CrdtUpdateLogItem,\n isNoOpCrdtEventPayload,\n type NewCrdtEvent,\n type PersistedCrdtEvent,\n} from \"./crdt-table-schema\";\nimport { createEventHlcAccumulator } from \"./event-consistency\";\nimport type { StoredValue } from \"./stored-value\";\n\ntype LocalCrdtEvent = {\n type: CrdtEventType;\n dataset: string;\n item_id: string;\n payload: string;\n timestamp: string;\n schema_version: number;\n};\n\nexport type OwnCrdtEvent = {\n type: CrdtEventType;\n dataset: string;\n item_id: string;\n payload: string;\n timestamp?: undefined;\n schema_version?: undefined;\n};\n\nexport type OwnCrdtSnapshot = {\n dataset: string;\n item_id: string;\n patch: Record<string, unknown>;\n};\n\nexport type CrdtChangeIntent = {\n seq: number;\n dataset: string;\n type: CrdtEventType;\n item_id: string;\n new_item_id: string | null;\n payload_json: string;\n};\n\ntype RemoteCrdtEvent = {\n type: CrdtEventType;\n dataset: string;\n item_id: string;\n payload: string;\n timestamp: string;\n schema_version: number;\n};\n\ntype EnqueuedCrdtEvent = LocalCrdtEvent | OwnCrdtEvent | RemoteCrdtEvent;\n\nexport type GetEventsOptions = {\n afterSyncId?: number;\n status?: CrdtEventStatus;\n excludeOrigin?: string;\n excludeNodeId?: string;\n limit?: number;\n};\n\nexport type GetEventsBatchQuery = {\n afterSyncId: number;\n status: CrdtEventStatus;\n limit: number;\n} & (\n | { excludeOrigin: CrdtEventOrigin; excludeNodeId?: undefined }\n | { excludeOrigin?: undefined; excludeNodeId: string }\n);\n\nexport type GetEventsBatch = {\n events: PersistedCrdtEvent[];\n hasMore: boolean;\n nextSyncId: number;\n};\n\nexport type EnqueueEventsResult = {\n beforeSyncId: number;\n afterSyncId: number;\n /** Resolves when the enqueued events have been processed (applied/deduped/failed). */\n processed: Promise<void>;\n};\n\nexport type EventUpdate = {\n status: CrdtEventStatus;\n schema_version: number;\n type: CrdtEventType;\n dataset: string;\n item_id: string;\n payload: string;\n};\n\ntype StorageHLC = Pick<HLCCounter, \"getNextHLC\" | \"mergeHLC\" | \"restoreHLC\">;\n\ntype DbSyncerStorage = {\n nodeId: string;\n initialLocalSyncId: number;\n migrator: SyncDbMigrator;\n db: InternalSQLiteWrapper<any>;\n dbConfig: SystemDbConfig;\n hlc: StorageHLC;\n eventHlcAccumulator?: StoredValue<string>;\n onEventApplied?: (event: PersistedCrdtEvent) => void;\n schema: Pick<SyncDbSchema, \"tables\" | \"tablesConfig\">;\n};\n\nexport type CrdtStorage = Omit<ReturnType<typeof createCrdtStorage>, \"internal\">;\n\nexport type InternalCrdtStorage = ReturnType<typeof createCrdtStorage>;\n\ntype EventsAppliedPayload = {\n syncId: number;\n eventHlcSum: string | null;\n};\n\ntype InternalDbSchema = {\n _crdt_events: PersistedCrdtEvent;\n _crdt_update_log: CrdtUpdateLogItem;\n};\n\ntype FirstAcceptedSyncIdRow = { timestamp: string; first_sync_id: number };\n\nexport function createCrdtStorage(storage: DbSyncerStorage) {\n let localSyncId = storage.initialLocalSyncId;\n\n const db = storage.db as InternalSQLiteWrapper<InternalDbSchema>;\n\n const crdtEventsTable = storage.dbConfig.eventsTable.fullIdentifier as \"_crdt_events\";\n const quotedEventsTable = quoteId(storage.dbConfig.eventsTable.fullIdentifier);\n const noOpPayloadSqlLiteral = `'${CRDT_EVENT_NO_OP_PAYLOAD}'`;\n\n const [persistedClock] = db.executePreparedRaw<[], { timestamp: string | null }>({\n key: \"get-persisted-hlc\",\n sql: `select max(\"timestamp\") as \"timestamp\" from (\n select max(\"timestamp\") as \"timestamp\" from ${quotedEventsTable} where \"status\" = 'applied'\n union all\n select max(fields.value) as \"timestamp\"\n from ${quoteId(storage.dbConfig.updateLogTable.fullIdentifier)} as updates, json_each(updates.payload) as fields\n )`,\n meta: { loggerLevel: \"system\" },\n });\n if (persistedClock?.timestamp) {\n storage.hlc.restoreHLC(deserializeHLC(persistedClock.timestamp));\n }\n\n const getInitialSequentialSyncId = () => {\n const [firstPendingEvent] = db.executePrepared(\n \"get-first-pending-event\",\n { status: \"pending\" as const },\n (db, params) =>\n db\n .selectFrom(crdtEventsTable)\n .select(\"sync_id\")\n .where(\"status\", \"=\", params(\"status\"))\n .orderBy(\"sync_id\", \"asc\")\n .limit(sql.lit(1)),\n { loggerLevel: \"system\" },\n );\n\n return firstPendingEvent ? firstPendingEvent.sync_id - 1 : localSyncId;\n };\n\n let sequentialSyncId = getInitialSequentialSyncId();\n\n const eventTarget = createTypedEventTarget<{\n \"events-applied\": EventsAppliedPayload;\n \"remote-event-apply-failed\": { syncId: number };\n }>();\n\n // Column order is the JSON array order for persist/read.\n const EVENT_COLUMNS = [\n \"sync_id\",\n \"schema_version\",\n \"status\",\n \"type\",\n \"timestamp\",\n \"origin\",\n \"source_node_id\",\n \"dataset\",\n \"item_id\",\n \"payload\",\n ] as const;\n\n const quotedEventColumns = EVENT_COLUMNS.map((column) => quoteId(column)).join(\", \");\n\n const persistEventsSql = `insert into ${quotedEventsTable} (${quotedEventColumns}) select ${EVENT_COLUMNS.map(\n (_, index) => `\"value\"->>${index}`,\n ).join(\", \")} from json_each(?)`;\n\n const pendingEventsSql = `select json_group_array(json_array(${quotedEventColumns})) as \"batch\" from (select ${quotedEventColumns} from ${quotedEventsTable} where \"status\" = ? order by \"sync_id\" asc limit ?)`;\n\n const PERSIST_EVENTS_CHUNK_SIZE = 100;\n\n const eventToRow = (event: PersistedCrdtEvent) => [\n event.sync_id,\n event.schema_version,\n event.status,\n event.type,\n event.timestamp,\n event.origin,\n event.source_node_id,\n event.dataset,\n event.item_id,\n event.payload,\n ];\n\n type EventRow = [\n number,\n number,\n CrdtEventStatus,\n CrdtEventType,\n string,\n CrdtEventOrigin,\n string,\n string,\n string,\n string,\n ];\n\n const rowToEvent = (row: EventRow): PersistedCrdtEvent => ({\n sync_id: row[0],\n schema_version: row[1],\n status: row[2],\n type: row[3],\n timestamp: row[4],\n origin: row[5],\n source_node_id: row[6],\n dataset: row[7],\n item_id: row[8],\n payload: row[9],\n });\n\n const persistEvents = (tx: InternalSQLiteTransactionWrapper<InternalDbSchema>, events: PersistedCrdtEvent[]) => {\n for (let offset = 0; offset < events.length; offset += PERSIST_EVENTS_CHUNK_SIZE) {\n const chunk = events.slice(offset, offset + PERSIST_EVENTS_CHUNK_SIZE);\n tx.executePreparedRaw<[string], never>({\n key: \"persist-crdt-events\",\n sql: persistEventsSql,\n params: [JSON.stringify(chunk.map(eventToRow))],\n meta: { loggerLevel: \"system\" },\n });\n }\n };\n\n const readPendingEvents = (limit: number): PersistedCrdtEvent[] => {\n const [row] = db.executePreparedRaw<[string, number], { batch: string }>({\n key: \"get-enqueued-pending-events\",\n sql: pendingEventsSql,\n params: [\"pending\", limit],\n meta: { loggerLevel: \"system\" },\n });\n\n return (JSON.parse(row.batch) as EventRow[]).map(rowToEvent);\n };\n\n const enqueueEvents = (\n origin: CrdtEventOrigin,\n sourceNodeId: string,\n events: EnqueuedCrdtEvent[],\n ): EnqueueEventsResult => {\n const beforeSyncId = localSyncId;\n if (events.length === 0) {\n return { beforeSyncId, afterSyncId: beforeSyncId, processed: Promise.resolve() };\n }\n\n db.executeTransaction((tx) => {\n const persistedEvents = events.map(\n (event): PersistedCrdtEvent => ({\n schema_version: event.schema_version ?? storage.migrator.currentSchemaVersion,\n timestamp: event.timestamp ?? serializeHLC(storage.hlc.getNextHLC()),\n type: event.type,\n dataset: event.dataset,\n item_id: event.item_id,\n origin: origin,\n source_node_id: sourceNodeId,\n payload: event.payload,\n sync_id: ++localSyncId,\n status: \"pending\",\n }),\n );\n persistEvents(tx, persistedEvents);\n });\n\n return { beforeSyncId, afterSyncId: localSyncId, processed: processEnqueuedEvents() };\n };\n\n const enqueueLocalEvents = (events: LocalCrdtEvent[], sourceNodeId: string): EnqueueEventsResult => {\n const known = getFirstAcceptedSyncIdsByTimestamp(db, events);\n const newEvents = events.filter((event) => {\n if (known.has(event.timestamp)) {\n return false;\n }\n known.set(event.timestamp, 0);\n return true;\n });\n return enqueueEvents(\"local\", sourceNodeId, newEvents);\n };\n\n const enqueueRemoteEvents = (events: RemoteCrdtEvent[]): EnqueueEventsResult => {\n return enqueueEvents(\"remote\", \"\", events);\n };\n\n const notifyEventsApplied = (events: PersistedCrdtEvent[]) => {\n const onEventApplied = storage.onEventApplied;\n if (!onEventApplied || events.length === 0) {\n return;\n }\n\n queueMicrotask(() => {\n let callbackFailed = false;\n let firstError: unknown;\n for (const event of events) {\n try {\n onEventApplied(event);\n } catch (error) {\n callbackFailed = true;\n firstError ??= error;\n }\n }\n if (callbackFailed) {\n throw firstError;\n }\n });\n };\n\n const applyOwnEvents = (events: OwnCrdtEvent[]) => {\n const schema = storage.schema;\n if (!schema) {\n throw new Error(\"applyOwnEvents requires a sync schema; none was provided to createCrdtStorage\");\n }\n\n const validatedEvents: NewCrdtEvent[] = [];\n const errors: string[] = [];\n\n for (const [index, event] of events.entries()) {\n const result = validateNewCrdtEvent(schema, {\n type: event.type,\n dataset: event.dataset,\n item_id: event.item_id,\n payload: event.payload,\n });\n\n if (!result.success) {\n errors.push(...result.errors.map((error) => `[${index}] ${error}`));\n continue;\n }\n\n validatedEvents.push(result.event);\n }\n\n if (errors.length > 0) {\n throw new CrdtEventValidationError(errors);\n }\n\n db.executeTransaction((tx) => {\n const persistedEvents = validatedEvents.map(\n (event): PersistedCrdtEvent => ({\n schema_version: storage.migrator.currentSchemaVersion,\n timestamp: serializeHLC(storage.hlc.getNextHLC()),\n type: event.type,\n dataset: event.dataset,\n item_id: event.item_id,\n origin: \"own-applied\",\n source_node_id: storage.nodeId,\n payload: JSON.stringify(event.payload),\n sync_id: ++localSyncId,\n status: \"pending\",\n }),\n );\n persistEvents(tx, persistedEvents);\n for (const persistedEvent of persistedEvents) {\n applyCrdtEvent(persistedEvent);\n }\n });\n\n void processEnqueuedEvents();\n };\n\n const applyOwnSnapshot = (snapshot: OwnCrdtSnapshot) => {\n const syncId = localSyncId + 1;\n db.executeTransaction((tx) => {\n const [currentRow] = tx.executePreparedRaw<[string], Record<string, unknown>>({\n key: `get-crdt-snapshot-row-${snapshot.dataset}`,\n sql: `select * from ${quoteId(snapshot.dataset)} where \"id\" = ? limit 1`,\n params: [snapshot.item_id],\n meta: { loggerLevel: \"system\" },\n });\n\n const event: PersistedCrdtEvent = {\n schema_version: storage.migrator.currentSchemaVersion,\n timestamp: serializeHLC(storage.hlc.getNextHLC()),\n type: \"item-created\",\n dataset: snapshot.dataset,\n item_id: snapshot.item_id,\n origin: \"own-applied\",\n source_node_id: storage.nodeId,\n payload: JSON.stringify({\n ...(currentRow ?? {}),\n ...snapshot.patch,\n id: snapshot.item_id,\n tombstone: false,\n }),\n sync_id: syncId,\n status: \"pending\",\n };\n\n // An event newer than the snapshot (e.g. a pending remote event stamped by a clock ahead of\n // ours) still wins LWW, and fresh replicas can only replay it on top of the row's original\n // history, so the history is kept intact until a later snapshot outranks every event.\n const [newerEvent] = tx.executePreparedRaw<[string, string, string], { sync_id: number }>({\n key: \"get-crdt-row-event-newer-than-snapshot\",\n sql: `select \"sync_id\" from ${quotedEventsTable}\nwhere \"dataset\" = ? and \"item_id\" = ? and \"timestamp\" > ? and \"payload\" <> ${noOpPayloadSqlLiteral}\nlimit 1`,\n params: [event.dataset, event.item_id, event.timestamp],\n meta: { loggerLevel: \"system\" },\n });\n\n if (!newerEvent) {\n tx.executePreparedRaw<[string, string, string], never>({\n key: \"replace-crdt-row-history-with-no-ops\",\n // Keep the no-op sentinel literal in this predicate so SQLite can use the\n // matching partial index. A bound parameter does not imply its predicate.\n sql: `update ${quotedEventsTable}\nset \"payload\" = ?\nwhere \"dataset\" = ? and \"item_id\" = ? and \"payload\" <> ${noOpPayloadSqlLiteral}`,\n params: [CRDT_EVENT_NO_OP_PAYLOAD, event.dataset, event.item_id],\n meta: { loggerLevel: \"system\" },\n });\n }\n persistEvents(tx, [event]);\n applyCrdtEvent(event);\n });\n localSyncId = syncId;\n\n void processEnqueuedEvents();\n };\n\n const applyOwnEventFromTransaction = (\n tx: InternalSQLiteTransactionWrapper<InternalDbSchema>,\n event: OwnCrdtEvent,\n ) => {\n const persistedEvent: PersistedCrdtEvent = {\n schema_version: storage.migrator.currentSchemaVersion,\n timestamp: serializeHLC(storage.hlc.getNextHLC()),\n type: event.type,\n dataset: event.dataset,\n item_id: event.item_id,\n origin: \"own-applied\",\n source_node_id: storage.nodeId,\n payload: event.payload,\n sync_id: ++localSyncId,\n status: \"pending\",\n };\n\n persistEvents(tx, [persistedEvent]);\n applyCrdtEvent(persistedEvent);\n };\n\n const applyOwnIntentsFromTransaction = (\n tx: InternalSQLiteTransactionWrapper<InternalDbSchema>,\n intents: CrdtChangeIntent[],\n ) => {\n let appliedEvents = 0;\n\n for (const intent of intents) {\n if (intent.type === \"item-updated\") {\n if (intent.item_id !== intent.new_item_id) {\n throw new Error(\n `Cannot update the \"id\" column of an item. It is used to identify the item and must be immutable.`,\n );\n }\n if (intent.payload_json === \"{}\") {\n continue;\n }\n }\n\n applyOwnEventFromTransaction(tx, {\n type: intent.type,\n dataset: intent.dataset,\n item_id: intent.item_id,\n payload: intent.payload_json,\n });\n appliedEvents++;\n }\n\n return { appliedEvents };\n };\n\n const dispatchEventsApplied = (previousSequentialSyncId: number) => {\n if (sequentialSyncId <= previousSequentialSyncId) {\n return;\n }\n eventTarget.dispatchEvent(\"events-applied\", {\n syncId: sequentialSyncId,\n eventHlcSum: sequentialSyncId === localSyncId ? (eventHlcAccumulator?.current ?? null) : null,\n });\n };\n\n const publishProcessedBatch = ({\n previousSequentialSyncId,\n nextSequentialSyncId,\n appliedEvents,\n }: {\n previousSequentialSyncId: number;\n nextSequentialSyncId: number;\n appliedEvents: PersistedCrdtEvent[];\n }) => {\n sequentialSyncId = nextSequentialSyncId;\n\n // Schedule per-event observers before dispatching cursor progress, matching\n // the previous ordering while keeping both signals behind one post-commit API.\n notifyEventsApplied(appliedEvents);\n dispatchEventsApplied(previousSequentialSyncId);\n };\n\n const hasPendingEvents = (): boolean => {\n const events = db.executePrepared(\n \"has-pending-events\",\n { status: \"pending\" as const },\n (db, params) =>\n db.selectFrom(crdtEventsTable).select(\"sync_id\").where(\"status\", \"=\", params(\"status\")).limit(sql.lit(1)),\n { loggerLevel: \"system\" },\n );\n return events.length > 0;\n };\n\n const getEventsBatch = (options: GetEventsBatchQuery): GetEventsBatch => {\n const limit = options.limit ?? 50;\n\n const queryParams = {\n limit: limit + 1,\n status: options.status ?? null,\n afterSyncId: options.afterSyncId ?? null,\n excludeOrigin: options.excludeOrigin ?? null,\n excludeNodeId: options.excludeNodeId ?? null,\n maxSyncId: options.status === \"applied\" ? sequentialSyncId : localSyncId,\n };\n\n const filterKeys = [\n queryParams.excludeNodeId ? \"nodeid\" : \"no-nodeid\",\n queryParams.excludeOrigin ? \"origin\" : \"no-origin\",\n ];\n\n const events = db.executePrepared(\n `get-events-batch-${filterKeys.join(\"-\")}`,\n queryParams,\n (db, params) =>\n db\n .selectFrom(crdtEventsTable)\n .where(\"sync_id\", \">\", params(\"afterSyncId\"))\n .where(\"sync_id\", \"<=\", params(\"maxSyncId\"))\n .where(\"status\", \"=\", params(\"status\"))\n .$if(!!queryParams.excludeNodeId, (qb) => qb.where(\"source_node_id\", \"!=\", params(\"excludeNodeId\")))\n .$if(!!queryParams.excludeOrigin, (qb) => qb.where(\"origin\", \"!=\", params(\"excludeOrigin\")))\n .selectAll()\n .limit(params(\"limit\"))\n .orderBy(\"sync_id\", \"asc\"),\n { loggerLevel: \"system\" },\n );\n\n const hasMore = events.length > limit;\n if (hasMore) {\n events.pop();\n }\n\n const lastReturnedSyncId = events[events.length - 1]?.sync_id ?? options.afterSyncId ?? 0;\n let nextSyncId = lastReturnedSyncId;\n if (!hasMore && options.status === \"applied\") {\n nextSyncId = Math.max(lastReturnedSyncId, queryParams.maxSyncId);\n }\n\n return {\n events,\n hasMore,\n nextSyncId,\n };\n };\n\n // Storage is quiescent when there is nothing left to converge: no events waiting\n // to be applied, and no local applied events past `pushedSyncId` still waiting to\n // be pushed to the remote. When quiescent and caught up, the local and remote node\n // share the exact same set of applied events, so their HLC checksums must match.\n const checkIsQuiescent = (pushedSyncId: number): boolean => {\n if (hasPendingEvents()) {\n return false;\n }\n\n const unpushed = getEventsBatch({\n status: \"applied\",\n afterSyncId: pushedSyncId,\n excludeOrigin: \"remote\",\n limit: 1,\n });\n\n return unpushed.events.length === 0;\n };\n\n const applyCrdtEvent = createSQLiteCrdtApplyFunction({\n db,\n dbConfig: storage.dbConfig,\n });\n const eventHlcAccumulator = storage.eventHlcAccumulator\n ? createEventHlcAccumulator(storage.eventHlcAccumulator.current)\n : null;\n\n const persistEventHlcAccumulator = () => {\n if (eventHlcAccumulator && storage.eventHlcAccumulator) {\n storage.eventHlcAccumulator.current = eventHlcAccumulator.current;\n }\n };\n\n // Rebuild the accumulator from the full applied-event history when it has\n // never been computed for this storage. An empty stored value is the \"never\n // computed\" sentinel: once any event is applied the accumulator is persisted\n // as padded hex, never \"\". This recovers storages created before the\n // accumulator existed and lets us force a recompute by bumping the stored\n // value's key version. The accumulator is a commutative sum, so scan order\n // does not matter.\n const recomputeEventHlcAccumulatorIfNeeded = () => {\n if (!eventHlcAccumulator || !storage.eventHlcAccumulator) {\n return;\n }\n if (storage.eventHlcAccumulator.current !== \"\") {\n return;\n }\n\n const batchSize = 1000;\n let afterSyncId = 0;\n for (;;) {\n const rows = db.executePrepared(\n \"get-applied-event-timestamps\",\n { status: \"applied\" as const, afterSyncId, limit: batchSize },\n (db, params) =>\n db\n .selectFrom(crdtEventsTable)\n .select([\"sync_id\", \"timestamp\"])\n .where(\"status\", \"=\", params(\"status\"))\n .where(\"sync_id\", \">\", params(\"afterSyncId\"))\n .orderBy(\"sync_id\", \"asc\")\n .limit(params(\"limit\")),\n { loggerLevel: \"system\" },\n );\n\n if (rows.length === 0) {\n break;\n }\n\n for (const row of rows) {\n eventHlcAccumulator.add(row.timestamp);\n afterSyncId = row.sync_id;\n }\n\n if (rows.length < batchSize) {\n break;\n }\n }\n\n persistEventHlcAccumulator();\n };\n\n const getFirstAcceptedSyncIdsByTimestamp = (\n tx: InternalSQLiteTransactionWrapper<InternalDbSchema>,\n events: { timestamp: string }[],\n ) => {\n const timestamps = Array.from(new Set(events.map((event) => event.timestamp)));\n if (timestamps.length === 0) {\n return new Map<string, number>();\n }\n\n const rows =\n timestamps.length === 1\n ? tx.executePreparedRaw<string[], FirstAcceptedSyncIdRow>({\n key: \"get-first-accepted-sync-id-by-timestamp\",\n sql: `select \"timestamp\", min(\"sync_id\") as \"first_sync_id\" from ${quotedEventsTable} where \"status\" = 'applied' and \"timestamp\" = ? group by \"timestamp\"`,\n params: timestamps,\n meta: { loggerLevel: \"system\" },\n })\n : tx.executePreparedRaw<string[], FirstAcceptedSyncIdRow>({\n key: \"get-first-accepted-sync-ids-by-timestamp\",\n sql: `select \"timestamp\", min(\"sync_id\") as \"first_sync_id\" from ${quotedEventsTable} where \"status\" = 'applied' and \"timestamp\" in (select \"value\" from json_each(?)) group by \"timestamp\"`,\n params: [JSON.stringify(timestamps)],\n meta: { loggerLevel: \"system\" },\n });\n\n return new Map(rows.map((row) => [row.timestamp, row.first_sync_id]));\n };\n\n const updateEventStatuses = (\n tx: InternalSQLiteTransactionWrapper<InternalDbSchema>,\n events: PersistedCrdtEvent[],\n ) => {\n const syncIdsByStatus = new Map<Exclude<CrdtEventStatus, \"pending\">, number[]>();\n for (const event of events) {\n if (event.status === \"pending\") {\n throw new Error(`Event ${event.sync_id} is still pending after processing`);\n }\n\n const syncIds = syncIdsByStatus.get(event.status) ?? [];\n syncIds.push(event.sync_id);\n syncIdsByStatus.set(event.status, syncIds);\n }\n\n for (const [status, syncIds] of syncIdsByStatus) {\n if (syncIds.length === 1) {\n tx.executePreparedRaw<[string, number], never>({\n key: \"update-crdt-event-status\",\n sql: `update ${quotedEventsTable} set \"status\" = ? where \"sync_id\" = ?`,\n params: [status, syncIds[0]],\n meta: { loggerLevel: \"system\" },\n });\n continue;\n }\n\n tx.executePreparedRaw<[string, string], never>({\n key: \"update-crdt-event-status-batch\",\n sql: `update ${quotedEventsTable} set \"status\" = ? where \"sync_id\" in (select \"value\" from json_each(?))`,\n params: [status, JSON.stringify(syncIds)],\n meta: { loggerLevel: \"system\" },\n });\n }\n };\n\n const processPersistedEvent = (\n tx: InternalSQLiteTransactionWrapper<InternalDbSchema>,\n event: PersistedCrdtEvent,\n firstAcceptedSyncIdByTimestamp: Map<string, number>,\n statusOnlyEvents: PersistedCrdtEvent[],\n ) => {\n if (event.status !== \"pending\") {\n throw new Error(`Event ${event.sync_id} is not pending`);\n }\n\n const persistedEventData = {\n schema_version: event.schema_version,\n type: event.type,\n dataset: event.dataset,\n item_id: event.item_id,\n payload: event.payload,\n };\n\n try {\n if (event.origin === \"own-applied\") {\n event.status = \"applied\";\n return event;\n }\n\n const firstAcceptedSyncId = firstAcceptedSyncIdByTimestamp.get(event.timestamp);\n if (firstAcceptedSyncId !== undefined && firstAcceptedSyncId < event.sync_id) {\n event.status = \"deduped\";\n return event;\n }\n\n // Always advance HLC, even for no-op events, to maintain monotonic ordering\n if (event.origin === \"local\" || event.origin === \"remote\") {\n storage.hlc.mergeHLC(deserializeHLC(event.timestamp));\n }\n\n if (isNoOpCrdtEventPayload(event.payload)) {\n applyCrdtEvent(event);\n event.status = \"applied\";\n return event;\n }\n\n const migratedEvent = storage.migrator.migrateEvent(event, storage.migrator.latestSchemaVersion);\n\n if (migratedEvent === null) {\n // Event was dropped during migration (e.g., table was deleted)\n event.schema_version = storage.migrator.latestSchemaVersion;\n event.payload = CRDT_EVENT_NO_OP_PAYLOAD;\n\n applyCrdtEvent(event);\n event.status = \"applied\";\n return event;\n }\n\n event.schema_version = migratedEvent.schema_version;\n event.type = migratedEvent.type;\n event.dataset = migratedEvent.dataset;\n event.item_id = migratedEvent.item_id;\n event.payload = migratedEvent.payload;\n\n applyCrdtEvent(event);\n event.status = \"applied\";\n } catch (error) {\n console.error(\"Error applying enqueued CRDT event\", error);\n event.status = \"failed\";\n } finally {\n const eventDataChanged =\n event.schema_version !== persistedEventData.schema_version ||\n event.type !== persistedEventData.type ||\n event.dataset !== persistedEventData.dataset ||\n event.item_id !== persistedEventData.item_id ||\n event.payload !== persistedEventData.payload;\n\n if (eventDataChanged) {\n tx.executePrepared(\n \"update-crdt-event-full\",\n event,\n (db, params) =>\n db\n .updateTable(crdtEventsTable)\n .set({\n status: params(\"status\"),\n schema_version: params(\"schema_version\"),\n type: params(\"type\"),\n dataset: params(\"dataset\"),\n item_id: params(\"item_id\"),\n payload: params(\"payload\"),\n })\n .where(\"sync_id\", \"=\", params(\"sync_id\")),\n { loggerLevel: \"system\" },\n );\n } else {\n statusOnlyEvents.push(event);\n }\n }\n };\n\n const processEnqueuedEvents = ensureSingletonExecution(async () => {\n let hasMore = true;\n while (hasMore) {\n await Promise.resolve();\n\n const batchSize = 100;\n\n const events = readPendingEvents(batchSize + 1);\n hasMore = events.length > batchSize;\n if (hasMore) {\n events.pop();\n }\n\n const previousSequentialSyncId = sequentialSyncId;\n if (events.length === 0) {\n publishProcessedBatch({\n previousSequentialSyncId,\n nextSequentialSyncId: localSyncId,\n appliedEvents: [],\n });\n break;\n }\n\n const failedRemoteSyncIds: number[] = [];\n const appliedEventsToNotify: PersistedCrdtEvent[] = [];\n\n db.executeTransaction((tx) => {\n const firstAcceptedSyncIdByTimestamp = getFirstAcceptedSyncIdsByTimestamp(\n tx,\n events.filter((event) => event.origin !== \"own-applied\"),\n );\n const statusOnlyEvents: PersistedCrdtEvent[] = [];\n\n for (const event of events) {\n processPersistedEvent(tx, event, firstAcceptedSyncIdByTimestamp, statusOnlyEvents);\n if (event.status === \"applied\") {\n const firstAcceptedSyncId = firstAcceptedSyncIdByTimestamp.get(event.timestamp);\n if (firstAcceptedSyncId === undefined || event.sync_id < firstAcceptedSyncId) {\n firstAcceptedSyncIdByTimestamp.set(event.timestamp, event.sync_id);\n }\n eventHlcAccumulator?.add(event.timestamp);\n if (storage.onEventApplied) {\n appliedEventsToNotify.push(event);\n }\n }\n if (event.status === \"failed\" && event.origin === \"remote\") {\n failedRemoteSyncIds.push(event.sync_id);\n }\n }\n updateEventStatuses(tx, statusOnlyEvents);\n persistEventHlcAccumulator();\n });\n publishProcessedBatch({\n previousSequentialSyncId,\n nextSequentialSyncId: hasMore ? (events[events.length - 1]?.sync_id ?? sequentialSyncId) : localSyncId,\n appliedEvents: appliedEventsToNotify,\n });\n\n // A remote event was accepted by the server but could not be applied\n // locally, which means our local state has diverged from the server.\n for (const syncId of failedRemoteSyncIds) {\n eventTarget.dispatchEvent(\"remote-event-apply-failed\", { syncId });\n }\n }\n });\n\n recomputeEventHlcAccumulatorIfNeeded();\n\n void processEnqueuedEvents();\n\n return {\n getEventsBatch,\n enqueueLocalEvents,\n enqueueRemoteEvents,\n applyOwnEvents,\n applyOwnSnapshot,\n checkIsQuiescent,\n getEventHlcAccumulator: () => eventHlcAccumulator?.current ?? null,\n\n addEventListener: eventTarget.addEventListener,\n removeEventListener: eventTarget.removeEventListener,\n\n internal: {\n applyOwnEventFromTransaction,\n applyOwnIntentsFromTransaction,\n processEnqueuedEvents,\n },\n };\n}\n","import type { CrdtStorage } from \"./crdt-storage\";\n\ntype CrdtSyncProducer = {\n storage: CrdtStorage;\n broadcastEvents: (request: { newSyncId: number; eventHlcSum: string | null }) => void;\n};\n\nexport const createCrdtSyncProducer = ({ storage, broadcastEvents }: CrdtSyncProducer) => {\n storage.addEventListener(\"events-applied\", (event) => {\n broadcastEvents({\n newSyncId: event.payload.syncId,\n eventHlcSum: event.payload.eventHlcSum,\n });\n });\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 HLC checksum after applying up to `newSyncId`. */\n eventHlcSum: 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 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 { 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":";;;;;;;;AAEA,MAAa,cAA2B,IAAI,OAAO,EACjD,SAAS;CACP,qBAAqB,IAAI,cAAc;CACvC,oBAAoB,IAAI,YAAY;CACpC,2BAA2B,IAAI,oBAAoB;CACnD,qBAAqB,OAAO,IAAI,mBAAmB,EAAE;AACvD,EACF,CAAC;;;ACgCD,SAAS,0BAA0B,cAA6C;CAC9E,MAAM,gCAAgB,IAAI,IAA6B;CACvD,KAAK,MAAM,SAAS,gBAAgB,CAAC,GAAG;EACtC,MAAM,EAAE,eAAe,kBAAkB;EACzC,cAAc,IAAI,eAAe,KAAK;EACtC,cAAc,IAAI,eAAe,KAAK;CACxC;CACA,QAAQ,cAAsB;EAC5B,MAAM,QAAQ,cAAc,IAAI,SAAS;EACzC,IAAI,OAAO,iBAAiB,UAC1B,OAAO;EAET,OAAO,OAAO,iBAAiB;CACjC;AACF;AAEA,SAAgB,iBAAiB,EAAE,UAAU,cAAc,eAAwC;CACjG,MAAM,qBAAqB,0BAA0B,YAAY;;;;;;;;;;;;;;;;;;;;;;CAuBjE,QAAQ,MAAsB,SAA2E;EACvG,MAAM,WAAW,MAAM,YAAY;EACnC,MAAM,iBAAiB,SAAS;EAEhC,IAAI,YAAY,KAAK,gBAAgB,gBACnC,MAAM,IAAI,MACR,0CAA0C,KAAK,cAAc,2CAA2C,eAAe,kEACzH;EAGF,MAAM,eAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,WAAW,SAAS,OAAO,QAAQ,KAAK,MAAM,GAAG;GAC3D,MAAM,sBAAsB,mBAAmB,SAAS;GACxD,IAAI,wBAAwB,MAC1B;GAEF,KAAK,MAAM,OAAO,MAChB,aAAa,KAAK;IAChB,gBAAgB,KAAK;IACrB,MAAM;IACN,SAAS;IACT,SAAS,IAAI;IACb,SAAS,KAAK,UAAU,GAAG;GAC7B,CAAC;EAEL;EAIA,MAAM,SAAyB,SAAS,cAAc,cAAc,cAAc,CAAC,CAAC,KAAK,WAAW;GAClG,MAAM,MAAM;GACZ,SAAS,MAAM;GACf,SAAS,MAAM;GACf,SAAS,MAAM;EACjB,EAAE;EAEF,MAAM,UAAU,YAAY,MAAM;EAClC,IAAI,mBAAmB,SACrB,OAAO,QAAQ,YAAY,EAAE,UAAU,OAAO,OAAO,EAAE;EAGzD,OAAO,EAAE,UAAU,OAAO,OAAO;CACnC;AACF;AAEA,SAAgB,iBAAiB,EAAE,YAAY,cAAc,iBAA0C;CACrG,MAAM,aAAa,aAAa,SAAS,WAAW,CAAC,OAAO,eAAe,OAAO,aAAa,CAAC;CAEhG,MAAM,iBAAiB,cAAuD;EAC5E,IAAI,CAAC,WACH,OAAO;EAGT,MAAM,qCAAqB,IAAI,IAAY;EAC3C,MAAM,WAA8B,CAAC;EACrC,KAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,SAAS,aAAa,MACzB,gBAAgB,YAAY,kBAAkB,QAAQ,YAAY,kBAAkB,IACvF;GACA,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,kBAAkB,KAAK,0BAA0B,WAAW,KAAK,IAAI,GAAG;GAE1F,IAAI,CAAC,mBAAmB,IAAI,OAAO,aAAa,GAAG;IACjD,mBAAmB,IAAI,OAAO,aAAa;IAC3C,SAAS,KAAK,MAAM;GACtB;EACF;EACA,OAAO;CACT;;;;;;;;CASA,QAAQ,SAAiD;EACvD,MAAM,SAAyD,CAAC;EAEhE,KAAK,MAAM,EAAE,eAAe,eAAe,kBAAkB,cAAc,MAAM,MAAM,GAAG;GACxF,IAAI,iBAAiB,UACnB;GAEF,MAAM,EAAE,SAAS,WAAW,GAAG,QAAiC,iBAAiB,QAAQ,aAAa,KAAK,EACzG,aAAa,SACf,CAAC;GACD,OAAO,iBAAiB,KAAK,KAAK,EAAE,WAAW,YAAY,GAAG,WAAW,IAAI;EAC/E;EAEA,OAAO;GACL;GACA,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;GACnC;EACF;CACF;AACF;;;AC/KA,IAAI,cAAoC;AACxC,IAAI,MAAwB;AAE5B,SAAS,eAA8B;CACrC,IAAI,CAAC,aACH,cAAc,WAAW,CAAC,CAAC,MAAM,WAAW;EAC1C,MAAM;CACR,CAAC;CAEH,OAAO;AACT;AAEA,SAAS,IAAI,OAAe,OAAO,IAAY;CAC7C,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,6DAA6D;CAE/E,OAAO,IAAI,IAAI,OAAO,IAAI;AAC5B;AAEA,MAAa,SAAS;CACpB;CACA;AACF;;;ACIA,SAAS,YAAY,IAAuE;CAC1F,OAAO,GACJ,WAAW,eAAe,CAAC,CAC3B,MAAM,QAAQ,MAAM,CAAC,SAAS,MAAM,CAAC,CAAC,CACtC,MAAM,QAAQ,YAAY,UAAU,CAAC,CACrC,OAAO;EAAC;EAAQ;EAAO;CAAM,CAAC,CAAC,CAC/B,QAAQ,MAAM;AACnB;AAqBA,SAAgB,aAA2B,KAA2D;CACpG,MAAM,KAAK;CACX,MAAM,SAAS,GAAG,eAAe,OAAO,YAAY,EAA6C,GAAG,EAClG,aAAa,SACf,CAAC,CAAC,CAAC;CAEH,MAAM,iBAAiB,GAAG,eACvB,OACC,GACG,KAAK,eAAe,OAAO,YAAY,EAA6C,CAAC,CAAC,CACtF,WAAW,CAAC,oBAAoB,GAAoB,6BAA6B,GAAG,GAAG,CAAC,CAAC,CAAC,CAC1F,OAAO;EAAC;EAAoB;EAAS;EAAU;EAAU;EAAa;EAAgB;CAAM,CAAC,CAAC,CAC9F,QAAQ,SAAS,CAAC,CAClB,QAAQ,OAAO,GACpB,EAAE,aAAa,SAAS,CAC1B,CAAC,CAAC;CAEF,MAAM,iBAAwD,CAAC;CAC/D,KAAK,MAAM,OAAO,gBAAgB;EAChC,eAAe,IAAI,WAAW,CAAC;EAC/B,eAAe,IAAI,MAAM,CAAC,KAAK,GAAG;CACpC;CAEA,OAAO,OAAO,YACZ,OAAO,KAAK,EAAE,MAAM,KAAK,WAAW;EAElC,IAAI,mBAAmB,KACnB,MAAM,OAAO,CAAC,EACd,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,SAAS,eAAe,CAAC,CAAC,EACxD,UAAU,CAAC,EACX,MAAM,KAAK,CAAC,GAAG,EAAE,EACjB,QAAQ,SAAS,EAAE;EAEvB,MAAM,UAAU,eAAe,SAAS,CAAC;EAIzC,IAAI,CAAC,kBAAkB;GACrB,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,KAAK,CAAC;GAC7C,IAAI,OAAO,WAAW,KAAK,OAAO,EAAE,CAAC,KAAK,YAAY,MAAM,WAC1D,mBAAmB,OAAO,EAAE,CAAC;EAEjC;EAEA,OAAO,CACL,MACA;GACQ;GACN,QAAQ,SAAS;GACjB,SAAS,QAAQ,KAAK,SAAS;IAC7B,MAAM,IAAI;IACV,UAAU,IAAI;IACd,YAAY,CAAC,IAAI;IACjB,oBAAoB,IAAI,SAAS;IACjC,iBAAiB,IAAI,cAAc;IACnC,iBAAiB,IAAI,cAAc,OAAO,OAAO,OAAO,IAAI,UAAU;IACtE,cAAc,IAAI,KAAK;IACvB,SAAS,KAAA;GACX,EAAE;EACJ,CACF;CACF,CAAC,CACH;AACF;;;AC9GA,MAAM,wBAA2C;CAC/C,UAAU,CAAC;CACX,SAAS,CAAC;AACZ;AAEA,MAAa,0BAA0B,QAA4B,QAAkB,WAA8B;CACjH,IAAI,CAAC,UAAU,UAAU,UACvB,OAAO;CAGT,IAAI,YAAY,YAAY,IAAI;CAEhC,OAAO;EACL,eAAe;GACb,YAAY,YAAY,IAAI;EAC9B;EACA,SAAS,MAAc,SAAiB,WAAqB,UAAU;GAGrE,OAAO,MAAM,IAFG,YAAY,IAAI,IAAI,UAAA,CAEZ,QAAQ,CAAC,EAAE,OAAO,WAAW,QAAQ;EAC/D;CACF;AACF;;;ACkCA,IAAa,kBAAb,MAAkD;CAChD,KAAoC;CACpC;CACA;CACA;CAEA,iBAAuD;CAEvD,eAAgC,CAAC;CAEjC,qBAAuE,CAAC;CACxE,wCAAgC,IAAI,IAA8D;CAClG,2CAAmC,IAAI,IAAoD;CAC3F,yBAA2E;CAE3E,YAAY,MAA4B;EACtC,KAAK,KAAK,KAAK,GAAG;EAClB,KAAK,UAAU,KAAK;EACpB,KAAK,SAAS,KAAK;EACnB,KAAK,eAAe,KAAK;CAC3B;CAEA,IAAI,WAAW;EACb,IAAI,CAAC,KAAK,IACR,MAAM,IAAI,MAAM,4BAA4B;EAE9C,OAAO,KAAK;CACd;CAEA,IAAI,WAAW;EACb,IAAI,CAAC,KAAK,gBACR,KAAK,iBAAiB,aAAa,IAAI;EAEzC,OAAO,KAAK;CACd;CAEA,WACE,KACA,MACA,aACA,4BACkB;EAClB,MAAM,OAAO,uBAAuB,KAAK,QAAQ,WAAW;EAC5D,MAAM,YAAY,KAAK,SAAS,QAAQ,GAAG;EAC3C,IAAI;EAEJ,IAAI;GACF,MAAM,mBAAmB,UAAU;GACnC,IAAI,CAAC,kBACH,MAAM,IAAI,MAAM,yCAAyC;GAE3D,MAAM,yBACJ,CAAC,8BACD,KAAK,0BACL,KAAK,QAAQ,KAAK,sBAAsB,gBAAgB,MAAM,IAC1D,KAAK,yBACL;GAEN,IAAI,qBAAqB;GACzB,IAAI,wBAAwB;IAC1B,cAAc,KAAK,0BAA0B;IAC7C,qBAAqB,KAAK,QAAQ,KAAK,sBAAsB,KAAK,QAAQ;GAC5E;GAEA,IAAI,QAAQ,KAAK,SAAS,GACxB,UAAU,KAAK,IAAuB;GAGxC,MAAM,OAAY,CAAC;GACnB,IAAI;GACJ,OAAO,UAAU,KAAK,GAAG;IACvB,gBAAgB,UAAU,eAAe,CAAC,CAAC;IAC3C,MAAM,SAAS,UAAU,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,OAAO,OAAO,IAAI;IAC9B,KAAK,IAAI,QAAQ,GAAG,QAAQ,YAAY,QAAQ,SAC9C,IAAI,YAAY,UAAU,OAAO;IAEnC,KAAK,KAAK,GAAQ;GACpB;GACA,UAAU,SAAS;GACnB,KAAK,OAAO,GAAG,KAAK,gBAAgB,GAAG,SAAS,KAAK,WAAW;GAEhE,IAAI,aAAa;IAEf,IAAI,KAAK,QAAQ,KAAK,sBAAsB,KAAK,QAAQ,MAAM,oBAC7D,yBAAyB,IAAI;IAE/B,YAAY,OAAO;GACrB;GAEA,OAAO,EAAE,KAAK;EAChB,SAAS,OAAO;GACd,UAAU,SAAS;GACnB,aAAa,SAAS;GACtB,MAAM;EACR;CACF;CAEA,QAAqB,MAAiD,SAA4C;EAChH,MAAM,MAAM,OAAO,SAAS,WAAW,OAAO,KAAK;EACnD,MAAM,OAAO,OAAO,SAAS,WAAW,KAAA,IAAY,KAAK;EAEzD,OAAO,KAAK,WAAc,KAAK,MAAM,SAAS,aAAa,SAAS,8BAA8B,KAAK;CACzG;CAEA,0BAA0B,UAA6C;EACrE,IAAI,KAAK,wBACP,MAAM,IAAI,MAAM,0DAA0D;EAE5E,KAAK,yBAAyB;CAChC;CAEA,mBAAsB,UAA6D;EACjF,MAAM,cAAc,KAAK,iBAAiB;EAC1C,IAAI;GACF,MAAM,SAAS,SAAS,IAAI;GAC5B,YAAY,OAAO;GACnB,OAAO;EACT,SAAS,OAAO;GACd,YAAY,SAAS;GACrB,MAAM;EACR;CACF;CAEA,kBAAkB;EAChB,OAAO,KAAK,QAAQ,KAAK,uBAAuB,KAAK,QAAQ,MAAM;CACrE;CAEA,mBAAmB;EACjB,KAAK,mBAAmB;GACtB,KAAK;GACL,KAAK;GACL,MAAM,EACJ,aAAa,SACf;EACF,CAAC;EAED,OAAO;GACL,cAAc;IACZ,KAAK,mBAAmB;KACtB,KAAK;KACL,KAAK;KACL,MAAM,EACJ,aAAa,SACf;IACF,CAAC;GACH;GACA,gBAAgB;IACd,KAAK,mBAAmB;KACtB,KAAK;KACL,KAAK;KACL,MAAM,EACJ,aAAa,SACf;IACF,CAAC;GACH;EACF;CACF;CAEA,4BAAoC;EAClC,IAAI,CAAC,KAAK,gBAAgB,GACxB,OAAO,KAAK,iBAAiB;EAG/B,KAAK,mBAAmB;GACtB,KAAK;GACL,KAAK;GACL,MAAM,EAAE,aAAa,SAAS;EAChC,CAAC;EAED,OAAO;GACL,cAAc;IACZ,KAAK,mBAAmB;KACtB,KAAK;KACL,KAAK;KACL,MAAM,EAAE,aAAa,SAAS;IAChC,CAAC;GACH;GACA,gBAAgB;IACd,KAAK,mBAAmB;KACtB,KAAK;KACL,KAAK;KACL,MAAM,EAAE,aAAa,SAAS;IAChC,CAAC;IACD,KAAK,mBAAmB;KACtB,KAAK;KACL,KAAK;KACL,MAAM,EAAE,aAAa,SAAS;IAChC,CAAC;GACH;EACF;CACF;CAEA,QAA4C,KAAa,MAAsB;EAC7E,MAAM,OAAO,uBAAuB,KAAK,QAAQ,MAAM,WAAW;EAClE,MAAM,OAAO,KAAK,SAAS,QAAQ,GAAG;EACtC,KAAK,OAAO,GAAG,KAAK,gBAAgB,GAAG,WAAW,KAAK,MAAM,WAAW;EAExE,IAAI,cAAc;EAElB,MAAM,WAAW,WAAoB;GACnC,IAAI,aACF,MAAM,IAAI,MAAM,wBAAwB;GAG1C,MAAM,OAAO,uBAAuB,KAAK,QAAQ,MAAM,WAAW;GAClE,IAAI;IACF,IAAI,OAAO,SAAS,GAClB,KAAK,KAAK,MAAoB;IAGhC,MAAM,UAAU,CAAC;IAGjB,IAAI;IACJ,OAAO,KAAK,KAAK,GAAG;KAClB,gBAAgB,KAAK,eAAe,CAAC,CAAC;KACtC,MAAM,SAAS,KAAK,IAAI,CAAC,CAAC;KAC1B,MAAM,MAAM,CAAC;KACb,KAAK,IAAI,QAAQ,GAAG,QAAQ,YAAY,QAAQ,SAC9C,IAAI,YAAY,UAAU,OAAO;KAEnC,QAAQ,KAAK,GAAc;IAC7B;IAEA,OAAO;GACT,UAAU;IACR,KAAK,MAAM,IAAI;IACf,KAAK,OAAO,GAAG,KAAK,gBAAgB,GAAG,mBAAmB,KAAK,MAAM,WAAW;GAClF;EACF;EAEA,MAAM,iBAAiB;GACrB,cAAc;GACd,KAAK,SAAS;EAChB;EAEA,MAAM,oBAAyD;GAC7D;GACA;GACA,IAAI,cAAc;IAChB,OAAO;GACT;EACF;EAEA,KAAK,mBAAmB,KAAK,iBAA2D;EAExF,OAAO;CACT;CAEA,cAAuD,MAAsB;EAC3E,QACE,YACqC;GACrC,MAAM,QAAQ,QAAQ,cAAc,QAAQ,GAAU,CAAC,CAAC,QAAQ;GAChE,MAAM,YAAY,KAAK,QAA6B,MAAM,KAAK,IAAI;GAEnE,OAAO,EACL,UAAU,eAAe;IACvB,MAAM,SAAS,MAAM,WAAW,KAAK,UAAU,WAAW,MAAuB;IAEjF,OADe,UAAU,QAAQ,MACrB;GACd,EACF;EACF;CACF;CAEA,cACE,SACA,SACA;EACA,MAAM,QAAQ,QAAQ,WAAW,CAAC,CAAC,QAAQ;EAC3C,OAAO,KAAK,QAAQ,OAAO,OAAO;CACpC;CAEA,gBAKE,KACA,QACA,SACA,MACA;EACA,IAAI,YAAY,KAAK,sBAAsB,IAAI,GAAG;EAClD,IAAI,CAAC,WAAW;GACd,YAAY,KAAK,cAAuB,IAAI,CAAC,CAAC,OAAO;GACrD,KAAK,sBAAsB,IAAI,KAAK,SAA6D;EACnG;EAEA,OAAO,UAAU,QAAQ,MAAM;CACjC;CAEA,mBAAuD,EACrD,KACA,KACA,QACA,QAMC;EACD,IAAI,YAAY,KAAK,yBAAyB,IAAI,GAAG;EACrD,IAAI,CAAC,WAAW;GACd,YAAY,KAAK,QAAQ,KAAK,IAAI;GAClC,KAAK,yBAAyB,IAAI,KAAK,SAA8C;EACvF;EAEA,OAAO,UAAU,QAAS,UAAU,CAAC,CAAa;CACpD;CAEA,IAAiB,kBAAiD,GAAG,YAAuB;EAC1F,IAAI,OAAO,qBAAqB,UAC9B,OAAO,KAAK,QAAW;GACrB,KAAK;GACL;EACF,CAAC;EAEH,OAAO,KAAK,QAAW;GACrB,KAAK,iBAAiB,KAAK,GAAG;GAC9B;EACF,CAAC;CACH;CAEA,qBAAqF,EACnF,MACA,UACA,eACA,YACA,aACwC;EACxC,OAAO,KAAK,SAAS,eAAe;GAClC;GACA,QAAQ,GAAG,GAAG,SAAS;IAErB,OADe,SAAS,GAAI,IAChB;GACd;GACA,OAAO,SAAS;GAChB;GACA;GACA;EACF,CAAC;CACH;CAEA,YAAY,UAAuC;EACjD,MAAM,OAAO,uBAAuB,KAAK,QAAQ,QAAQ;EACzD,MAAM,cAAc,KAAK,QAAQ,KAAK,oBAAoB,QAAQ;EAClE,KAAK,aAAa,KAAK,WAAW;EAElC,MAAM,aAAa,KAAK,QAAQ,KAAK,oBACnC,KAAK,UACL,QACA,aACA,SAAS,YACT,SAAS,YACT,KAAK,QAAQ,KAAK,iCAAiC,KAAK,QAAQ,KAAK,6BACvE;EAEA,KAAK,SAAS,QAAQ,UAAU;EAEhC,KAAK,mBAAmB;EAExB,KAAK,OAAO,eAAe,WAAW,QAAQ;CAChD;CAEA,iBAAiB;EACf,OAAO,KAAK,QAAQ,KAAK,qBAAqB,KAAK,QAAQ;CAC7D;CAEA,qBAAqB;EACnB,KAAK,iBAAiB;CACxB;CAEA,UAAU;EACR,KAAK,mBAAmB,SAAS,SAAS;GACxC,KAAK,SAAS;EAChB,CAAC;EACD,KAAK,mBAAmB,OAAO,CAAC;EAChC,KAAK,sBAAsB,MAAM;EACjC,KAAK,yBAAyB,MAAM;CACtC;CAEA,QAAQ;EACN,KAAK,QAAQ;EAEb,KAAK,IAAI,MAAM;EACf,KAAK,KAAK;CACZ;AACF;;;AClZA,MAAM,mBAAmB,CAAC,MAAM,WAAW;AAE3C,SAAS,yBAAyB,QAAgB,WAAmB;CACnE,IAAI,iBAAiB,SAAS,MAAM,GAClC,MAAM,IAAI,MAAM,UAAU,UAAU,qBAAqB,OAAO,EAAE;AAEtE;AAEA,MAAM,iBAAiB;CACrB,cAAc,OAAe,WAAoF,EAC/G,MAAM,OAAO,MAAM,GAAG,OAAO,YAAY,KAAK,CAAC,EACjD;CAEA,YAAY,WAAkC;EAC5C,MAAM,OAAO,GAAG,OAAO,UAAU,KAAK;EACtC,kBAAkB,GACf,cAAc,KACjB;EACA,oBAAoB,CAAC;GAAE,MAAM;GAAc;EAAM,CAAC;CACpD;CAEA,cAAc,WAAmB,WAAqE,EACpG,MAAM,OAAO,MAAM,GAAG,OAAO,YAAY,SAAS,CAAC,EACrD;CAEA,YAAY,eAAsC,EAChD,MAAM,OAAO,GAAG,OAAO,UAAU,SAAS,EAC5C;CAEA,cAAc,EAAE,UAAU,gBAAuE;EAC/F,MAAM,OAAO,GAAG,OAAO,WAAW,QAAQ,CAAC,CAAC,SAAS,QAAQ;EAC7D,kBAAkB,GACf,YAAY,UAAU;GACrB,MAAM,UAAU;GAChB,OAAO;EACT,EACF;EACA,oBAAoB,CAAC;GAAE,MAAM;GAAgB;GAAU;EAAS,CAAC;CACnE;CAEA,eAAe,EACb,OACA,WACA,gBAKmB;EACnB,yBAAyB,WAAW,QAAQ;EAC5C,yBAAyB,WAAW,WAAW;EAC/C,OAAO;GACL,MAAM,OAAO,GAAG,OAAO,WAAW,KAAK,CAAC,CAAC,aAAa,WAAW,SAAS;GAC1E,kBAAkB,GACf,SAAS,UAAU;IAClB,IAAK,MAAM,SAAS,kBAAkB,MAAM,SAAS,kBAAmB,EAAE,aAAa,MAAM,UAC3F,OAAO;IAGT,MAAM,SAAS,MAAM,QAAQ;IAC7B,OAAO,MAAM,QAAQ;IACrB,MAAM,QAAQ,aAAa;IAE3B,OAAO;GACT,EACF;GACA,oBAAoB,CAAC;IAAE,MAAM;IAAiB;IAAO;IAAW;GAAU,CAAC;EAC7E;CACF;CAEA,YAAY,EACV,OACA,QACA,MACA,cACA,SAAS,MAAM,SAOK;EACpB,MAAM,OAAO,GAAG,OAAO,WAAW,KAAK,CAAC,CAAC,UAAU,QAAQ,OAAO,MAAM,MAAM,CAAC,CAAC,CAAC,UAAU,YAAY,CAAC;EACxG,kBAAkB,GACf,SAAS,UAAU;GAClB,IAAI,MAAM,SAAS,gBACjB,OAAO;GAGT,MAAM,QAAQ,UAAU;GAExB,OAAO;EACT,EACF;EACA,oBAAoB,CAAC;GAAE,MAAM;GAAc;GAAO;EAAO,CAAC;CAC5D;CAEA,aAAa,EAAE,OAAO,aAA+D;EACnF,yBAAyB,QAAQ,MAAM;EACvC,OAAO;GACL,MAAM,OAAO,GAAG,OAAO,WAAW,KAAK,CAAC,CAAC,WAAW,MAAM;GAC1D,kBAAkB,GACf,SAAS,UAAU;IAClB,IAAI,EAAE,UAAU,MAAM,UACpB,OAAO;IAGT,OAAO,MAAM,QAAQ;IAErB,IAAI,MAAM,SAAS,kBAAkB,OAAO,KAAK,MAAM,OAAO,CAAC,CAAC,WAAW,GACzE,OAAO;IAGT,OAAO;GACT,EACF;GACA,oBAAoB,CAAC;IAAE,MAAM;IAAe;IAAO;GAAO,CAAC;EAC7D;CACF;AACF;AAKA,SAAS,kBAAkB,OAAwC;CACjE,OAAO,MACJ,SAAS,SAAU,MAAM,QAAQ,KAAK,GAAG,IAAI,KAAK,MAAM,CAAC,KAAK,GAAG,CAAE,CAAC,CACpE,KAAK,QAAsB;EAC1B,IAAI,OAAO,QAAQ,UACjB,OAAO;GAAE;GAAK,YAAY,CAAC;EAAE;EAG/B,IAAI,OAAO,QAAQ,YAAY;GAC7B,MAAM,QAAQ,IAAI,WAAW,CAAC,CAAC,QAAQ;GACvC,OAAO;IAAE,KAAK,MAAM;IAAK,YAAY,MAAM;GAAW;EACxD;EAEA,IAAI,aAAa,KAAK;GACpB,MAAM,QAAQ,IAAI,QAAQ;GAC1B,OAAO;IAAE,KAAK,MAAM;IAAK,YAAY,MAAM;GAAW;EACxD;EAEA,OAAO;GACL,KAAK,IAAI;GACT,YAAY,IAAI,cAAc,CAAC;EACjC;CACF,CAAC;AACL;AAEA,SAAS,+BAA+B,OAAuE;CAC7G,MAAM,eAAsD,CAAC;CAE7D,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,kBACP,aAAa,KAAK,GAAG,OAAO,QAAQ,KAAK,gBAAgB,CAAC;CAI9D,IAAI,aAAa,WAAW,GAC1B;CAGF,QAAQ,UAAwB;EAC9B,IAAI,mBAAwC;EAE5C,KAAK,MAAM,CAAC,OAAO,gBAAgB,cAAc;GAC/C,IAAI,qBAAqB,MACvB,OAAO;GAET,IAAI,iBAAiB,YAAY,OAC/B;GAEF,mBAAmB,YAAY,gBAAgB;GAC/C,IAAI,qBAAqB,MACvB,OAAO;EAEX;EAEA,OAAO;CACT;AACF;AAEA,SAAgB,iBAAiB,iBAAsF;CA0BrH,OAzBqD,OAAO,YAC1D,OAAO,QAAQ,gBAAgB,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,WAAW;EACxE,MAAM,gBAAgB,OAAO,OAAO;EAEpC,IAAI,OAAO,MAAM,aAAa,GAC5B,MAAM,IAAI,MAAM,8BAA8B,SAAS;EAGzD,IAAI,gBAAgB,GAClB,MAAM,IAAI,MAAM,yCAAyC,SAAS;EAGpE,MAAM,qBAAqB,MAAM,SAAS,SAAS,KAAK,sBAAsB,CAAC,CAAC;EAEhF,OAAO,CACL,SACA;GACE,KAAK,kBAAkB,KAAK;GAC5B,kBAAkB,+BAA+B,KAAK;GACtD,GAAI,mBAAmB,SAAS,KAAK,EAAE,mBAAmB;EAC5D,CACF;CACF,CAAC,CAGa;AAClB;AAYA,SAAgB,eAAe,EAC7B,YACA,eACA,sBAKC;CACD,MAAM,sBAAsB,KAAK,IAAI,GAAG,OAAO,KAAK,UAAU,CAAC,CAAC,IAAI,MAAM,CAAC;CAG3E,MAAM,mBAAmB,OAAO,QAAQ,UAAU,CAAC,CAChD,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAU,CAAC,CACxC,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;CAE7B,MAAM,kBAAkB,IAAkB,SAAiB,cAAgC;EACzF,IAAI,WAAW,cAAc,SAC3B,MAAM,IAAI,MAAM,0BAA0B,QAAQ,qBAAqB,cAAc,SAAS;EAGhG,GAAG,kBAAkB,OAAO;GAC1B,KAAK,MAAM,aAAa,UAAU,KAChC,GAAG,QAAQ,UAAU,KAAK,UAAU,UAAU;GAEhD,IAAI,sBAAsB,UAAU,oBAClC,KAAK,MAAM,YAAY,UAAU,oBAC/B,QAAQ,SAAS,MAAjB;IACE,KAAK;KACH,GAAG,QAAQ,eAAe,mBAAmB,uBAAuB,CAAC,SAAS,QAAQ,GAAG,EACvF,aAAa,SACf,CAAC;KACD,GAAG,QACD,UAAU,mBAAmB,yCAC7B,CAAC,SAAS,UAAU,SAAS,QAAQ,GACrC,EAAE,aAAa,SAAS,CAC1B;KACA;IAEF,KAAK;KACH,GAAG,QAAQ,eAAe,mBAAmB,uBAAuB,CAAC,SAAS,KAAK,GAAG,EACpF,aAAa,SACf,CAAC;KACD;IAEF,KAAK;KACH,GAAG,QACD,UAAU,mBAAmB;;;;;;;;;;iFAW7B;MACE,SAAS;MACT,SAAS;MACT,SAAS;MACT,SAAS;MACT,SAAS;MACT,SAAS;KACX,GACA,EAAE,aAAa,SAAS,CAC1B;KACA;IAEF,KAAK;IACL,KAAK;KACH,GAAG,QACD,UAAU,mBAAmB;;;;;;oEAO7B;MAAC,SAAS;MAAQ,SAAS;MAAO,SAAS;KAAM,GACjD,EAAE,aAAa,SAAS,CAC1B;KACA;IAEF,SAEE,MAAM,IAAI,MAAM,oCAAoC,KAAK,UAAUA,QAAW,GAAG;GAErF;GAGJ,cAAc,UAAU;EAC1B,CAAC;CACH;CAEA,MAAM,gBAA+C,OAAc,kBAAyC;EAC1G,kBAAkB;EAClB,IAAI,gBAAgB,cAAc,SAChC,MAAM,IAAI,MACR,yBAAyB,cAAc,0CAA0C,cAAc,SACjG;EAGF,IAAI,uBAAuB,MAAM,OAAO,GAAG;GACzC,IAAI,MAAM,iBAAiB,eACzB,MAAM,iBAAiB;GAEzB,OAAO;EACT;EAEA,IAAI,MAAM,kBAAkB,eAC1B,OAAO;EAGT,MAAM,cAAc,MAAM;EAE1B,IAAI,YAAiC;GACnC,SAAS,MAAM;GACf,SAAS,MAAM;GACf,MAAM,MAAM;GACZ,SAAS,KAAK,MAAM,MAAM,OAAO;EACnC;EAEA,KAAK,IAAI,IAAI,GAAG,IAAI,iBAAiB,QAAQ,KAAK;GAChD,MAAM,CAAC,SAAS,aAAa,iBAAiB;GAC9C,IAAI,WAAW,aAAa;GAC5B,IAAI,UAAU,eAAe;GAE7B,MAAM,cAAc,UAAU;GAC9B,IAAI,aAAa;IACf,YAAY,YAAY,SAAS;IACjC,IAAI,cAAc,MAAM,OAAO;GACjC;EACF;EAEA,IAAI,cAAc,MAChB,OAAO;EAGT,MAAM,iBAAiB;EACvB,MAAM,UAAU,UAAU;EAC1B,MAAM,UAAU,UAAU;EAC1B,MAAM,OAAO,UAAU;EACvB,MAAM,UAAU,KAAK,UAAU,UAAU,OAAO;EAEhD,OAAO;CACT;CAEA,MAAM,iBAAgD,QAAiB,kBAAoC;EACzG,OAAO,OACJ,KAAK,UAAU,aAAa,OAAO,iBAAiB,mBAAmB,CAAC,CAAC,CACzE,QAAQ,UAA8C,UAAU,IAAI;CACzE;CAEA,OAAO;EACL;EACA,IAAI,uBAAuB;GACzB,OAAO,cAAc;EACvB;EACA,oBAAoB,OAAqB;GACvC,MAAM,uBAAuB,cAAc;GAE3C,IAAI,wBAAwB,qBAC1B;GAGF,KAAK,IAAI,IAAI,GAAG,IAAI,iBAAiB,QAAQ,KAAK;IAChD,MAAM,CAAC,SAAS,aAAa,iBAAiB;IAC9C,IAAI,WAAW,sBAAsB;IACrC,eAAe,IAAI,SAAS,SAAS;GACvC;EACF;EACA;EACA;CACF;AACF;;;ACtbA,MAAM,oBAAkD;CACtD,MAAM,CAAC,MAAM;CACb,SAAS,CAAC,WAAW,KAAK;CAC1B,MAAM;EAAC;EAAQ;EAAU;CAAO;CAChC,SAAS;EAAC;EAAW;EAAW;CAAK;CACrC,MAAM,CAAC,MAAM;AACf;AAEA,IAAI,eAAqC;;;;;;;;;;;AAYzC,eAAsB,iBAAiB,QAA0D;CAC/F,IAAI,CAAC,cACH,eAAe,MAAM,kBAAkB;CAEzC,MAAM,UAAU;CAEhB,MAAM,KAAK,IAAI,gBAAgB;EAC7B,UAAU,IAAI,QAAQ,IAAI,GAAG,EAAE,UAAU,WAAW,CAAC;EACrD,cAAc,CAAC;EACf,cAAc;EACd;CACF,CAAC;CAED,IAAI;EACF,MAAM,WAAW,eAAe;GAC9B,YAAY,OAAO;GACnB,eAAe,kBAAkB,EAAE,cAAc,GAAG,CAAC;EACvD,CAAC;EAED,IAAI;GACF,SAAS,kBAAkB,EACzB,mBAAmB,aAAa;IAC9B,GAAG,oBAAoB,OACrB,SAAS,EAAE,UAAU,KAAK,YAAY,SAAS,GAAG,QAAQ;KAAE;KAAK;IAAW,GAAG,IAAI,EAAE,CAAC,CACxF;GACF,EACF,CAAC;EACH,SAAS,OAAO;GACd,OAAO,CACL;IACE,OAAO;IACP,SAAS,iDAAiD,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACjH,CACF;EACF;EAEA,MAAM,gBAAgB,aAAa,EAAE;EAErC,MAAM,SAAoC,CAAC;EAC3C,KAAK,MAAM,EAAE,eAAe,mBAAmB,OAAO,cAAc;GAClE,MAAM,WAAW,OAAO,OAAO;GAC/B,IAAI,CAAC,UAAU;GACf,YAAY,IAAI,eAAe,SAAS,SAAS,cAAc,gBAAgB,MAAM;EACvF;EACA,OAAO;CACT,UAAU;EACR,GAAG,MAAM;CACX;AACF;AAEA,SAAgB,+BAA+B,QAA2C;CACxF,MAAM,QAAQ,OAAO,KAClB,UAAU,aAAa,MAAM,MAAM,GAAG,MAAM,SAAS,YAAY,MAAM,OAAO,KAAK,GAAG,IAAI,MAAM,SACnG;CACA,OAAO,oCAAoC,OAAO,OAAO,QAAQ,OAAO,WAAW,IAAI,KAAK,IAAI,MAAM,MAAM,KAAK,IAAI;AACvH;AAEA,SAAS,YACP,IACA,OACA,iBACA,QACA,QACA;CACA,IAAI,CAAC,QAAQ;EACX,OAAO,KAAK;GACV;GACA,SAAS;EACX,CAAC;EACD;CACF;CACA,IAAI,OAAO,QAAQ;EACjB,OAAO,KAAK;GAAE;GAAO,SAAS;EAAsE,CAAC;EACrG;CACF;CAEA,MAAM,gBAAgB,IAAI,IAAI,OAAO,QAAQ,KAAK,WAAW,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC;CAEnF,KAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,eAAe,GAAG;EAC1D,MAAM,SAAS,cAAc,IAAI,IAAI;EACrC,IAAI,CAAC,QAAQ;GACX,OAAO,KAAK;IACV;IACA,QAAQ;IACR,SAAS;GACX,CAAC;GACD;EACF;EAEA,MAAM,WAAW,kBAAkB,KAAK;EACxC,IAAI,CAAC,SAAS,SAAS,OAAO,SAAS,YAAY,CAAC,GAClD,OAAO,KAAK;GACV;GACA,QAAQ;GACR,SAAS,mCAAmC,KAAK,KAAK,IAAI,SAAS,KAAK,GAAG,EAAE,wBAAwB,OAAO,SAAS,YAAY;EACnI,CAAC;EAGH,IAAI,OAAO,eAAe,KAAK,UAC7B,OAAO,KAAK;GACV;GACA,QAAQ;GACR,SAAS,KAAK,WACV,6EACA;EACN,CAAC;EAGH,IAAI,SAAS,QAAQ,CAAC,OAAO,cAC3B,OAAO,KAAK;GAAE;GAAO,QAAQ;GAAM,SAAS;EAAwC,CAAC;EAGvF,cAAc,IAAI,OAAO,MAAM,MAAM,OAAO,iBAAiB,MAAM;CACrE;CAEA,KAAK,MAAM,UAAU,OAAO,SAC1B,IAAI,EAAE,OAAO,QAAQ,kBACnB,OAAO,KAAK;EACV;EACA,QAAQ,OAAO;EACf,SACE;CACJ,CAAC;AAGP;AAEA,SAAS,cACP,IACA,OACA,QACA,MACA,iBACA,QACA;CAGA,IAAI,KAAK,cAAc,KAAK,iBAAiB,QAAQ,oBAAoB,MAAM;EAC7E,OAAO,KAAK;GACV;GACA;GACA,SAAS,2BAA2B,eAAe,IAAI,EAAE;EAC3D,CAAC;EACD;CACF;CACA,IAAI,CAAC,KAAK,cAAc,oBAAoB,MAAM;EAChD,OAAO,KAAK;GACV;GACA;GACA,SAAS,+BAA+B,gBAAgB;EAC1D,CAAC;EACD;CACF;CACA,IAAI,CAAC,KAAK,cAAc,oBAAoB,MAC1C;CAKF,IAAI;CACJ,IAAI;EACF,SAAS,GAAG,QAA4B;GACtC,KAAK,WAAW,gBAAgB;GAChC,YAAY,CAAC;EACf,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;CACd,QAAQ;EACN,SAAS,KAAA;CACX;CAEA,IAAI,WAAW,qBAAqB,IAAI,GACtC,OAAO,KAAK;EACV;EACA;EACA,SAAS,sCAAsC,eAAe,IAAI,EAAE,+BAA+B;CACrG,CAAC;AAEL;;AAGA,SAAS,qBAAqB,MAA0C;CACtE,IAAI,KAAK,iBAAiB,MAAM,OAAO;CACvC,QAAQ,KAAK,MAAb;EACE,KAAK,WACH,OAAO,KAAK,eAAe,IAAI;EACjC,KAAK;EACL,KAAK,QACH,OAAO,OAAO,KAAK,YAAY;EACjC,KAAK;EACL,KAAK,QACH,OAAO,OAAO,KAAK,YAAY;CACnC;AACF;AAEA,SAAS,eAAe,MAA0B;CAChD,MAAM,WAAW,qBAAqB,IAAI;CAC1C,OAAO,OAAO,aAAa,WAAW,IAAI,SAAS,KAAK,OAAO,QAAQ;AACzE;;;AClNA,MAAa,iCAAiC,EAC5C,IACA,eAII;CAyFJ,OAxFuB,wBAAwB;EAC7C,iBAAiB,MAAM;GACrB,MAAM,CAAC,WAAW,GAAG,gBACnB,sBACA;IACE,SAAS,KAAK;IACd,SAAS,KAAK;GAChB,IACC,IAAI,WAAW;IACd,OAAQ,GACL,WAAW,SAAS,eAAe,cAAyB,CAAC,CAC7D,OAAO,SAAS,CAAC,CACjB,MAAM,WAAW,KAAK,OAAO,SAAS,CAAC,CAAC,CACxC,MAAM,WAAW,KAAK,OAAO,SAAS,CAAC;GAC5C,GACA,EAAE,aAAa,SAAS,CAC1B;GAEA,OADa,UAAW,KAAK,MAAM,QAAQ,OAAO,IAA6B;EAEjF;EACA,oBAAoB,MAAM;GACxB,GAAG,gBACD,0BACA;IACE,SAAS,KAAK;IACd,SAAS,KAAK;IACd,SAAS,KAAK;GAChB,IACC,IAAI,WACF,GACE,WAAW,SAAS,eAAe,cAAyB,CAAC,CAC7D,OAAO;IACN,SAAS,OAAO,SAAS;IACzB,SAAS,OAAO,SAAS;IACzB,SAAS,OAAO,SAAS;GAC3B,CAAC,GACL,EAAE,aAAa,SAAS,CAC1B;EACF;EACA,oBAAoB,MAAM;GACxB,GAAG,gBACD,0BACA;IACE,SAAS,KAAK;IACd,SAAS,KAAK;IACd,SAAS,KAAK;GAChB,IACC,IAAI,WACF,GACE,YAAY,SAAS,eAAe,cAAyB,CAAC,CAC9D,IAAI,EACH,SAAS,OAAO,SAAS,EAC3B,CAAC,CAAC,CACD,MAAM,WAAW,KAAK,OAAO,SAAS,CAAC,CAAC,CACxC,MAAM,WAAW,KAAK,OAAO,SAAS,CAAC,GAC5C,EAAE,aAAa,SAAS,CAC1B;EACF;EACA,WAAW,MAAM;GAMf,MAAM,OAAO,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,KAAK;GAC5C,MAAM,gBAAgB,CAAC;GACvB,KAAK,MAAM,OAAO,MAChB,cAAc,OAAO;GAEvB,GAAG,gBACD,oBAAoB,KAAK,QAAQ,GAAG,KAAK,KAAK,GAAG,KACjD,KAAK,UACJ,OAAO,GAAG,WAAW,KAAK,OAAO,CAAC,CAAC,OAAO,aAAa,GACxD,EAAE,aAAa,SAAS,CAC1B;EACF;EACA,WAAW,MAAM;GACf,MAAM,OAAO,MAAM,KAAK,OAAO,KAAK,KAAK,OAAO,CAAC;GACjD,KAAK,KAAK;GACV,GAAG,mBAAmB;IACpB,KAAK,eAAe,KAAK,QAAQ,GAAG,KAAK,KAAK,GAAG;IACjD,KAAK,UAAU,QAAQ,KAAK,OAAO,EAAE,OAAO,KAAK,KAAK,QAAQ,GAAG,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC,KAAK,GAAG,EAAE;IAC/F,QAAQ,CAAC,GAAG,KAAK,KAAK,QAAQ,KAAK,QAAQ,IAAI,GAAG,KAAK,MAAM;IAC7D,MAAM,EAAE,aAAa,SAAS;GAChC,CAAC;EACH;CACF,CAEoB;AACtB;AAUA,SAAgB,wBAAwB,EACtC,kBACA,YACA,qBACA,YACA,uBACsB;CAItB,MAAM,oBAAoB,EAAE,YAA6B;EACvD,MAAM,eAAe,KAAK,MAAM,MAAM,OAAO;EAE7C,aAAa,YAAY;EACzB,WAAW;GAAE,SAAS,MAAM;GAAS,SAAS;EAAa,CAAC;EAE5D,MAAM,eAAe,CAAC;EACtB,KAAK,MAAM,OAAO,OAAO,KAAK,YAAY,GACxC,aAAa,OAAO,MAAM;EAG5B,oBAAoB;GAClB,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,SAAS,KAAK,UAAU,YAAY;EACtC,CAAC;CACH;CAMA,MAAM,oBAAoB,EAAE,OAAO,WAA4B;EAC7D,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,QAAQ,MAAM,QAAQ,cAAc,MAAM,QAAQ,WAAW;EAK/E,MAAM,eAAe,MAAM,SAAS,iBAAiB,EAAE,WAAW,EAAE,IAAI,KAAK,MAAM,MAAM,OAAO;EAChG,IAAI,MAAM,SAAS,gBACjB,aAAa,YAAY;EAG3B,MAAM,gBAAgB,CAAC;EACvB,IAAI,aAAa;EAEjB,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,YAAY,GAAG;GACvD,IAAI,QAAQ,MACV;GAGF,MAAM,sBAAsB,KAAK;GACjC,MAAM,yBAAyB,MAAM;GAErC,IAAI,CAAC,uBAAuB,CAAC,0BAA0B,yBAAyB,qBAAqB;IACnG,cAAc,OAAO;IACrB,KAAK,OAAO;IACZ,aAAa;GACf;EACF;EAEA,IAAI,CAAC,YACH;EAGF,WAAW;GACT,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,SAAS;EACX,CAAC;EACD,oBAAoB;GAClB,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,SAAS,KAAK,UAAU,IAAI;EAC9B,CAAC;CACH;CAEA,QAAQ,UAA4B;EAClC,IAAI,uBAAuB,MAAM,OAAO,GACtC;EAGF,MAAM,OAAO,iBAAiB;GAC5B,QAAQ,MAAM;GACd,SAAS,MAAM;EACjB,CAAC;EAID,IAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,kBAAkB,MAAM,SAAS,gBACnF,MAAM,IAAI,MAAM,uBAAuB,MAAM,MAAM;EAGrD,IAAI,MAAM;GACR,iBAAiB;IAAE;IAAO;GAAK,CAAC;GAChC;EACF;EAEA,IAAI,MAAM,SAAS,gBAAgB;GACjC,iBAAiB,EAAE,MAAM,CAAC;GAC1B;EACF;EAEA,MAAM,IAAI,MAAM,QAAQ,MAAM,QAAQ,cAAc,MAAM,QAAQ,WAAW;CAC/E;AACF;;;ACtOA,MAAM,YAAY,MAAM,QAAQ;AAChC,MAAM,kBAAkB;AAExB,SAAgB,0BAA0B,cAAsB;CAC9D,IAAI,UAAU,YAAY,YAAY;CAEtC,OAAO;EACL,IAAI,WAAmB;GACrB,UAAW,UAAU,cAAc,SAAS,IAAK;EACnD;EACA,IAAI,UAAU;GACZ,OAAO,SAAS,OAAO;EACzB;CACF;AACF;AAEA,SAAS,YAAY,OAAe;CAClC,IAAI,UAAU,IACZ,OAAO;CAET,MAAM,aAAa,MAAM,YAAY;CACrC,IAAI,CAAC,gBAAgB,KAAK,UAAU,GAClC,MAAM,IAAI,MAAM,wCAAwC,OAAO;CAEjE,OAAO,OAAO,KAAK,YAAY;AACjC;AAEA,SAAS,SAAS,OAAe;CAC/B,OAAO,MAAM,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,GAAG;AAC5C;AAEA,SAAS,cAAc,OAAe;CACpC,OAAO,OAAO,IAAI,OAAO,EAAE,IAAK,OAAO,IAAI,OAAO,EAAE,KAAK;AAC3D;;;ACoGA,SAAgB,kBAAkB,SAA0B;CAC1D,IAAI,cAAc,QAAQ;CAE1B,MAAM,KAAK,QAAQ;CAEnB,MAAM,kBAAkB,QAAQ,SAAS,YAAY;CACrD,MAAM,oBAAoB,QAAQ,QAAQ,SAAS,YAAY,cAAc;CAC7E,MAAM,wBAAwB,IAAI,yBAAyB;CAE3D,MAAM,CAAC,kBAAkB,GAAG,mBAAqD;EAC/E,KAAK;EACL,KAAK;oDAC2C,kBAAkB;;;aAGzD,QAAQ,QAAQ,SAAS,eAAe,cAAc,EAAE;;EAEjE,MAAM,EAAE,aAAa,SAAS;CAChC,CAAC;CACD,IAAI,gBAAgB,WAClB,QAAQ,IAAI,WAAW,eAAe,eAAe,SAAS,CAAC;CAGjE,MAAM,mCAAmC;EACvC,MAAM,CAAC,qBAAqB,GAAG,gBAC7B,2BACA,EAAE,QAAQ,UAAmB,IAC5B,IAAI,WACH,GACG,WAAW,eAAe,CAAC,CAC3B,OAAO,SAAS,CAAC,CACjB,MAAM,UAAU,KAAK,OAAO,QAAQ,CAAC,CAAC,CACtC,QAAQ,WAAW,KAAK,CAAC,CACzB,MAAM,IAAI,IAAI,CAAC,CAAC,GACrB,EAAE,aAAa,SAAS,CAC1B;EAEA,OAAO,oBAAoB,kBAAkB,UAAU,IAAI;CAC7D;CAEA,IAAI,mBAAmB,2BAA2B;CAElD,MAAM,cAAc,uBAGjB;CAGH,MAAM,gBAAgB;EACpB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAEA,MAAM,qBAAqB,cAAc,KAAK,WAAW,QAAQ,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;CAEnF,MAAM,mBAAmB,eAAe,kBAAkB,IAAI,mBAAmB,WAAW,cAAc,KACvG,GAAG,UAAU,aAAa,OAC7B,CAAC,CAAC,KAAK,IAAI,EAAE;CAEb,MAAM,mBAAmB,sCAAsC,mBAAmB,6BAA6B,mBAAmB,QAAQ,kBAAkB;CAE5J,MAAM,4BAA4B;CAElC,MAAM,cAAc,UAA8B;EAChD,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;CACR;CAeA,MAAM,cAAc,SAAuC;EACzD,SAAS,IAAI;EACb,gBAAgB,IAAI;EACpB,QAAQ,IAAI;EACZ,MAAM,IAAI;EACV,WAAW,IAAI;EACf,QAAQ,IAAI;EACZ,gBAAgB,IAAI;EACpB,SAAS,IAAI;EACb,SAAS,IAAI;EACb,SAAS,IAAI;CACf;CAEA,MAAM,iBAAiB,IAAwD,WAAiC;EAC9G,KAAK,IAAI,SAAS,GAAG,SAAS,OAAO,QAAQ,UAAU,2BAA2B;GAChF,MAAM,QAAQ,OAAO,MAAM,QAAQ,SAAS,yBAAyB;GACrE,GAAG,mBAAoC;IACrC,KAAK;IACL,KAAK;IACL,QAAQ,CAAC,KAAK,UAAU,MAAM,IAAI,UAAU,CAAC,CAAC;IAC9C,MAAM,EAAE,aAAa,SAAS;GAChC,CAAC;EACH;CACF;CAEA,MAAM,qBAAqB,UAAwC;EACjE,MAAM,CAAC,OAAO,GAAG,mBAAwD;GACvE,KAAK;GACL,KAAK;GACL,QAAQ,CAAC,WAAW,KAAK;GACzB,MAAM,EAAE,aAAa,SAAS;EAChC,CAAC;EAED,OAAQ,KAAK,MAAM,IAAI,KAAK,CAAC,CAAgB,IAAI,UAAU;CAC7D;CAEA,MAAM,iBACJ,QACA,cACA,WACwB;EACxB,MAAM,eAAe;EACrB,IAAI,OAAO,WAAW,GACpB,OAAO;GAAE;GAAc,aAAa;GAAc,WAAW,QAAQ,QAAQ;EAAE;EAGjF,GAAG,oBAAoB,OAAO;GAC5B,MAAM,kBAAkB,OAAO,KAC5B,WAA+B;IAC9B,gBAAgB,MAAM,kBAAkB,QAAQ,SAAS;IACzD,WAAW,MAAM,aAAa,aAAa,QAAQ,IAAI,WAAW,CAAC;IACnE,MAAM,MAAM;IACZ,SAAS,MAAM;IACf,SAAS,MAAM;IACP;IACR,gBAAgB;IAChB,SAAS,MAAM;IACf,SAAS,EAAE;IACX,QAAQ;GACV,EACF;GACA,cAAc,IAAI,eAAe;EACnC,CAAC;EAED,OAAO;GAAE;GAAc,aAAa;GAAa,WAAW,sBAAsB;EAAE;CACtF;CAEA,MAAM,sBAAsB,QAA0B,iBAA8C;EAClG,MAAM,QAAQ,mCAAmC,IAAI,MAAM;EAC3D,MAAM,YAAY,OAAO,QAAQ,UAAU;GACzC,IAAI,MAAM,IAAI,MAAM,SAAS,GAC3B,OAAO;GAET,MAAM,IAAI,MAAM,WAAW,CAAC;GAC5B,OAAO;EACT,CAAC;EACD,OAAO,cAAc,SAAS,cAAc,SAAS;CACvD;CAEA,MAAM,uBAAuB,WAAmD;EAC9E,OAAO,cAAc,UAAU,IAAI,MAAM;CAC3C;CAEA,MAAM,uBAAuB,WAAiC;EAC5D,MAAM,iBAAiB,QAAQ;EAC/B,IAAI,CAAC,kBAAkB,OAAO,WAAW,GACvC;EAGF,qBAAqB;GACnB,IAAI,iBAAiB;GACrB,IAAI;GACJ,KAAK,MAAM,SAAS,QAClB,IAAI;IACF,eAAe,KAAK;GACtB,SAAS,OAAO;IACd,iBAAiB;IACjB,eAAe;GACjB;GAEF,IAAI,gBACF,MAAM;EAEV,CAAC;CACH;CAEA,MAAM,kBAAkB,WAA2B;EACjD,MAAM,SAAS,QAAQ;EACvB,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,+EAA+E;EAGjG,MAAM,kBAAkC,CAAC;EACzC,MAAM,SAAmB,CAAC;EAE1B,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,GAAG;GAC7C,MAAM,SAAS,qBAAqB,QAAQ;IAC1C,MAAM,MAAM;IACZ,SAAS,MAAM;IACf,SAAS,MAAM;IACf,SAAS,MAAM;GACjB,CAAC;GAED,IAAI,CAAC,OAAO,SAAS;IACnB,OAAO,KAAK,GAAG,OAAO,OAAO,KAAK,UAAU,IAAI,MAAM,IAAI,OAAO,CAAC;IAClE;GACF;GAEA,gBAAgB,KAAK,OAAO,KAAK;EACnC;EAEA,IAAI,OAAO,SAAS,GAClB,MAAM,IAAI,yBAAyB,MAAM;EAG3C,GAAG,oBAAoB,OAAO;GAC5B,MAAM,kBAAkB,gBAAgB,KACrC,WAA+B;IAC9B,gBAAgB,QAAQ,SAAS;IACjC,WAAW,aAAa,QAAQ,IAAI,WAAW,CAAC;IAChD,MAAM,MAAM;IACZ,SAAS,MAAM;IACf,SAAS,MAAM;IACf,QAAQ;IACR,gBAAgB,QAAQ;IACxB,SAAS,KAAK,UAAU,MAAM,OAAO;IACrC,SAAS,EAAE;IACX,QAAQ;GACV,EACF;GACA,cAAc,IAAI,eAAe;GACjC,KAAK,MAAM,kBAAkB,iBAC3B,eAAe,cAAc;EAEjC,CAAC;EAED,sBAA2B;CAC7B;CAEA,MAAM,oBAAoB,aAA8B;EACtD,MAAM,SAAS,cAAc;EAC7B,GAAG,oBAAoB,OAAO;GAC5B,MAAM,CAAC,cAAc,GAAG,mBAAsD;IAC5E,KAAK,yBAAyB,SAAS;IACvC,KAAK,iBAAiB,QAAQ,SAAS,OAAO,EAAE;IAChD,QAAQ,CAAC,SAAS,OAAO;IACzB,MAAM,EAAE,aAAa,SAAS;GAChC,CAAC;GAED,MAAM,QAA4B;IAChC,gBAAgB,QAAQ,SAAS;IACjC,WAAW,aAAa,QAAQ,IAAI,WAAW,CAAC;IAChD,MAAM;IACN,SAAS,SAAS;IAClB,SAAS,SAAS;IAClB,QAAQ;IACR,gBAAgB,QAAQ;IACxB,SAAS,KAAK,UAAU;KACtB,GAAI,cAAc,CAAC;KACnB,GAAG,SAAS;KACZ,IAAI,SAAS;KACb,WAAW;IACb,CAAC;IACD,SAAS;IACT,QAAQ;GACV;GAKA,MAAM,CAAC,cAAc,GAAG,mBAAkE;IACxF,KAAK;IACL,KAAK,yBAAyB,kBAAkB;6EACqB,sBAAsB;;IAE3F,QAAQ;KAAC,MAAM;KAAS,MAAM;KAAS,MAAM;IAAS;IACtD,MAAM,EAAE,aAAa,SAAS;GAChC,CAAC;GAED,IAAI,CAAC,YACH,GAAG,mBAAoD;IACrD,KAAK;IAGL,KAAK,UAAU,kBAAkB;;yDAEc;IAC/C,QAAQ;KAAC;KAA0B,MAAM;KAAS,MAAM;IAAO;IAC/D,MAAM,EAAE,aAAa,SAAS;GAChC,CAAC;GAEH,cAAc,IAAI,CAAC,KAAK,CAAC;GACzB,eAAe,KAAK;EACtB,CAAC;EACD,cAAc;EAEd,sBAA2B;CAC7B;CAEA,MAAM,gCACJ,IACA,UACG;EACH,MAAM,iBAAqC;GACzC,gBAAgB,QAAQ,SAAS;GACjC,WAAW,aAAa,QAAQ,IAAI,WAAW,CAAC;GAChD,MAAM,MAAM;GACZ,SAAS,MAAM;GACf,SAAS,MAAM;GACf,QAAQ;GACR,gBAAgB,QAAQ;GACxB,SAAS,MAAM;GACf,SAAS,EAAE;GACX,QAAQ;EACV;EAEA,cAAc,IAAI,CAAC,cAAc,CAAC;EAClC,eAAe,cAAc;CAC/B;CAEA,MAAM,kCACJ,IACA,YACG;EACH,IAAI,gBAAgB;EAEpB,KAAK,MAAM,UAAU,SAAS;GAC5B,IAAI,OAAO,SAAS,gBAAgB;IAClC,IAAI,OAAO,YAAY,OAAO,aAC5B,MAAM,IAAI,MACR,kGACF;IAEF,IAAI,OAAO,iBAAiB,MAC1B;GAEJ;GAEA,6BAA6B,IAAI;IAC/B,MAAM,OAAO;IACb,SAAS,OAAO;IAChB,SAAS,OAAO;IAChB,SAAS,OAAO;GAClB,CAAC;GACD;EACF;EAEA,OAAO,EAAE,cAAc;CACzB;CAEA,MAAM,yBAAyB,6BAAqC;EAClE,IAAI,oBAAoB,0BACtB;EAEF,YAAY,cAAc,kBAAkB;GAC1C,QAAQ;GACR,aAAa,qBAAqB,cAAe,qBAAqB,WAAW,OAAQ;EAC3F,CAAC;CACH;CAEA,MAAM,yBAAyB,EAC7B,0BACA,sBACA,oBAKI;EACJ,mBAAmB;EAInB,oBAAoB,aAAa;EACjC,sBAAsB,wBAAwB;CAChD;CAEA,MAAM,yBAAkC;EAQtC,OAPe,GAAG,gBAChB,sBACA,EAAE,QAAQ,UAAmB,IAC5B,IAAI,WACH,GAAG,WAAW,eAAe,CAAC,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,GAC1G,EAAE,aAAa,SAAS,CAEd,CAAC,CAAC,SAAS;CACzB;CAEA,MAAM,kBAAkB,YAAiD;EACvE,MAAM,QAAQ,QAAQ,SAAS;EAE/B,MAAM,cAAc;GAClB,OAAO,QAAQ;GACf,QAAQ,QAAQ,UAAU;GAC1B,aAAa,QAAQ,eAAe;GACpC,eAAe,QAAQ,iBAAiB;GACxC,eAAe,QAAQ,iBAAiB;GACxC,WAAW,QAAQ,WAAW,YAAY,mBAAmB;EAC/D;EAEA,MAAM,aAAa,CACjB,YAAY,gBAAgB,WAAW,aACvC,YAAY,gBAAgB,WAAW,WACzC;EAEA,MAAM,SAAS,GAAG,gBAChB,oBAAoB,WAAW,KAAK,GAAG,KACvC,cACC,IAAI,WACH,GACG,WAAW,eAAe,CAAC,CAC3B,MAAM,WAAW,KAAK,OAAO,aAAa,CAAC,CAAC,CAC5C,MAAM,WAAW,MAAM,OAAO,WAAW,CAAC,CAAC,CAC3C,MAAM,UAAU,KAAK,OAAO,QAAQ,CAAC,CAAC,CACtC,IAAI,CAAC,CAAC,YAAY,gBAAgB,OAAO,GAAG,MAAM,kBAAkB,MAAM,OAAO,eAAe,CAAC,CAAC,CAAC,CACnG,IAAI,CAAC,CAAC,YAAY,gBAAgB,OAAO,GAAG,MAAM,UAAU,MAAM,OAAO,eAAe,CAAC,CAAC,CAAC,CAC3F,UAAU,CAAC,CACX,MAAM,OAAO,OAAO,CAAC,CAAC,CACtB,QAAQ,WAAW,KAAK,GAC7B,EAAE,aAAa,SAAS,CAC1B;EAEA,MAAM,UAAU,OAAO,SAAS;EAChC,IAAI,SACF,OAAO,IAAI;EAGb,MAAM,qBAAqB,OAAO,OAAO,SAAS,EAAE,EAAE,WAAW,QAAQ,eAAe;EACxF,IAAI,aAAa;EACjB,IAAI,CAAC,WAAW,QAAQ,WAAW,WACjC,aAAa,KAAK,IAAI,oBAAoB,YAAY,SAAS;EAGjE,OAAO;GACL;GACA;GACA;EACF;CACF;CAMA,MAAM,oBAAoB,iBAAkC;EAC1D,IAAI,iBAAiB,GACnB,OAAO;EAUT,OAPiB,eAAe;GAC9B,QAAQ;GACR,aAAa;GACb,eAAe;GACf,OAAO;EACT,CAEc,CAAC,CAAC,OAAO,WAAW;CACpC;CAEA,MAAM,iBAAiB,8BAA8B;EACnD;EACA,UAAU,QAAQ;CACpB,CAAC;CACD,MAAM,sBAAsB,QAAQ,sBAChC,0BAA0B,QAAQ,oBAAoB,OAAO,IAC7D;CAEJ,MAAM,mCAAmC;EACvC,IAAI,uBAAuB,QAAQ,qBACjC,QAAQ,oBAAoB,UAAU,oBAAoB;CAE9D;CASA,MAAM,6CAA6C;EACjD,IAAI,CAAC,uBAAuB,CAAC,QAAQ,qBACnC;EAEF,IAAI,QAAQ,oBAAoB,YAAY,IAC1C;EAGF,MAAM,YAAY;EAClB,IAAI,cAAc;EAClB,SAAS;GACP,MAAM,OAAO,GAAG,gBACd,gCACA;IAAE,QAAQ;IAAoB;IAAa,OAAO;GAAU,IAC3D,IAAI,WACH,GACG,WAAW,eAAe,CAAC,CAC3B,OAAO,CAAC,WAAW,WAAW,CAAC,CAAC,CAChC,MAAM,UAAU,KAAK,OAAO,QAAQ,CAAC,CAAC,CACtC,MAAM,WAAW,KAAK,OAAO,aAAa,CAAC,CAAC,CAC5C,QAAQ,WAAW,KAAK,CAAC,CACzB,MAAM,OAAO,OAAO,CAAC,GAC1B,EAAE,aAAa,SAAS,CAC1B;GAEA,IAAI,KAAK,WAAW,GAClB;GAGF,KAAK,MAAM,OAAO,MAAM;IACtB,oBAAoB,IAAI,IAAI,SAAS;IACrC,cAAc,IAAI;GACpB;GAEA,IAAI,KAAK,SAAS,WAChB;EAEJ;EAEA,2BAA2B;CAC7B;CAEA,MAAM,sCACJ,IACA,WACG;EACH,MAAM,aAAa,MAAM,KAAK,IAAI,IAAI,OAAO,KAAK,UAAU,MAAM,SAAS,CAAC,CAAC;EAC7E,IAAI,WAAW,WAAW,GACxB,uBAAO,IAAI,IAAoB;EAGjC,MAAM,OACJ,WAAW,WAAW,IAClB,GAAG,mBAAqD;GACtD,KAAK;GACL,KAAK,8DAA8D,kBAAkB;GACrF,QAAQ;GACR,MAAM,EAAE,aAAa,SAAS;EAChC,CAAC,IACD,GAAG,mBAAqD;GACtD,KAAK;GACL,KAAK,8DAA8D,kBAAkB;GACrF,QAAQ,CAAC,KAAK,UAAU,UAAU,CAAC;GACnC,MAAM,EAAE,aAAa,SAAS;EAChC,CAAC;EAEP,OAAO,IAAI,IAAI,KAAK,KAAK,QAAQ,CAAC,IAAI,WAAW,IAAI,aAAa,CAAC,CAAC;CACtE;CAEA,MAAM,uBACJ,IACA,WACG;EACH,MAAM,kCAAkB,IAAI,IAAmD;EAC/E,KAAK,MAAM,SAAS,QAAQ;GAC1B,IAAI,MAAM,WAAW,WACnB,MAAM,IAAI,MAAM,SAAS,MAAM,QAAQ,mCAAmC;GAG5E,MAAM,UAAU,gBAAgB,IAAI,MAAM,MAAM,KAAK,CAAC;GACtD,QAAQ,KAAK,MAAM,OAAO;GAC1B,gBAAgB,IAAI,MAAM,QAAQ,OAAO;EAC3C;EAEA,KAAK,MAAM,CAAC,QAAQ,YAAY,iBAAiB;GAC/C,IAAI,QAAQ,WAAW,GAAG;IACxB,GAAG,mBAA4C;KAC7C,KAAK;KACL,KAAK,UAAU,kBAAkB;KACjC,QAAQ,CAAC,QAAQ,QAAQ,EAAE;KAC3B,MAAM,EAAE,aAAa,SAAS;IAChC,CAAC;IACD;GACF;GAEA,GAAG,mBAA4C;IAC7C,KAAK;IACL,KAAK,UAAU,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,KAAK,UAAU,OAAO,CAAC;IACxC,MAAM,EAAE,aAAa,SAAS;GAChC,CAAC;EACH;CACF;CAEA,MAAM,yBACJ,IACA,OACA,gCACA,qBACG;EACH,IAAI,MAAM,WAAW,WACnB,MAAM,IAAI,MAAM,SAAS,MAAM,QAAQ,gBAAgB;EAGzD,MAAM,qBAAqB;GACzB,gBAAgB,MAAM;GACtB,MAAM,MAAM;GACZ,SAAS,MAAM;GACf,SAAS,MAAM;GACf,SAAS,MAAM;EACjB;EAEA,IAAI;GACF,IAAI,MAAM,WAAW,eAAe;IAClC,MAAM,SAAS;IACf,OAAO;GACT;GAEA,MAAM,sBAAsB,+BAA+B,IAAI,MAAM,SAAS;GAC9E,IAAI,wBAAwB,KAAA,KAAa,sBAAsB,MAAM,SAAS;IAC5E,MAAM,SAAS;IACf,OAAO;GACT;GAGA,IAAI,MAAM,WAAW,WAAW,MAAM,WAAW,UAC/C,QAAQ,IAAI,SAAS,eAAe,MAAM,SAAS,CAAC;GAGtD,IAAI,uBAAuB,MAAM,OAAO,GAAG;IACzC,eAAe,KAAK;IACpB,MAAM,SAAS;IACf,OAAO;GACT;GAEA,MAAM,gBAAgB,QAAQ,SAAS,aAAa,OAAO,QAAQ,SAAS,mBAAmB;GAE/F,IAAI,kBAAkB,MAAM;IAE1B,MAAM,iBAAiB,QAAQ,SAAS;IACxC,MAAM,UAAU;IAEhB,eAAe,KAAK;IACpB,MAAM,SAAS;IACf,OAAO;GACT;GAEA,MAAM,iBAAiB,cAAc;GACrC,MAAM,OAAO,cAAc;GAC3B,MAAM,UAAU,cAAc;GAC9B,MAAM,UAAU,cAAc;GAC9B,MAAM,UAAU,cAAc;GAE9B,eAAe,KAAK;GACpB,MAAM,SAAS;EACjB,SAAS,OAAO;GACd,QAAQ,MAAM,sCAAsC,KAAK;GACzD,MAAM,SAAS;EACjB,UAAU;GAQR,IANE,MAAM,mBAAmB,mBAAmB,kBAC5C,MAAM,SAAS,mBAAmB,QAClC,MAAM,YAAY,mBAAmB,WACrC,MAAM,YAAY,mBAAmB,WACrC,MAAM,YAAY,mBAAmB,SAGrC,GAAG,gBACD,0BACA,QACC,IAAI,WACH,GACG,YAAY,eAAe,CAAC,CAC5B,IAAI;IACH,QAAQ,OAAO,QAAQ;IACvB,gBAAgB,OAAO,gBAAgB;IACvC,MAAM,OAAO,MAAM;IACnB,SAAS,OAAO,SAAS;IACzB,SAAS,OAAO,SAAS;IACzB,SAAS,OAAO,SAAS;GAC3B,CAAC,CAAC,CACD,MAAM,WAAW,KAAK,OAAO,SAAS,CAAC,GAC5C,EAAE,aAAa,SAAS,CAC1B;QAEA,iBAAiB,KAAK,KAAK;EAE/B;CACF;CAEA,MAAM,wBAAwB,yBAAyB,YAAY;EACjE,IAAI,UAAU;EACd,OAAO,SAAS;GACd,MAAM,QAAQ,QAAQ;GAEtB,MAAM,YAAY;GAElB,MAAM,SAAS,kBAAkB,GAAa;GAC9C,UAAU,OAAO,SAAS;GAC1B,IAAI,SACF,OAAO,IAAI;GAGb,MAAM,2BAA2B;GACjC,IAAI,OAAO,WAAW,GAAG;IACvB,sBAAsB;KACpB;KACA,sBAAsB;KACtB,eAAe,CAAC;IAClB,CAAC;IACD;GACF;GAEA,MAAM,sBAAgC,CAAC;GACvC,MAAM,wBAA8C,CAAC;GAErD,GAAG,oBAAoB,OAAO;IAC5B,MAAM,iCAAiC,mCACrC,IACA,OAAO,QAAQ,UAAU,MAAM,WAAW,aAAa,CACzD;IACA,MAAM,mBAAyC,CAAC;IAEhD,KAAK,MAAM,SAAS,QAAQ;KAC1B,sBAAsB,IAAI,OAAO,gCAAgC,gBAAgB;KACjF,IAAI,MAAM,WAAW,WAAW;MAC9B,MAAM,sBAAsB,+BAA+B,IAAI,MAAM,SAAS;MAC9E,IAAI,wBAAwB,KAAA,KAAa,MAAM,UAAU,qBACvD,+BAA+B,IAAI,MAAM,WAAW,MAAM,OAAO;MAEnE,qBAAqB,IAAI,MAAM,SAAS;MACxC,IAAI,QAAQ,gBACV,sBAAsB,KAAK,KAAK;KAEpC;KACA,IAAI,MAAM,WAAW,YAAY,MAAM,WAAW,UAChD,oBAAoB,KAAK,MAAM,OAAO;IAE1C;IACA,oBAAoB,IAAI,gBAAgB;IACxC,2BAA2B;GAC7B,CAAC;GACD,sBAAsB;IACpB;IACA,sBAAsB,UAAW,OAAO,OAAO,SAAS,EAAE,EAAE,WAAW,mBAAoB;IAC3F,eAAe;GACjB,CAAC;GAID,KAAK,MAAM,UAAU,qBACnB,YAAY,cAAc,6BAA6B,EAAE,OAAO,CAAC;EAErE;CACF,CAAC;CAED,qCAAqC;CAErC,sBAA2B;CAE3B,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA,8BAA8B,qBAAqB,WAAW;EAE9D,kBAAkB,YAAY;EAC9B,qBAAqB,YAAY;EAEjC,UAAU;GACR;GACA;GACA;EACF;CACF;AACF;;;ACj5BA,MAAa,0BAA0B,EAAE,SAAS,sBAAwC;CACxF,QAAQ,iBAAiB,mBAAmB,UAAU;EACpD,gBAAgB;GACd,WAAW,MAAM,QAAQ;GACzB,aAAa,MAAM,QAAQ;EAC7B,CAAC;CACH,CAAC;AACH;;;ACHA,MAAa,uBAAuB;AACpC,MAAa,uBAAuB;AA+FpC,MAAa,wBAAwB;CACnC,UAAU;CACV,WAAW;AACb;AAOA,MAAa,2BAA2B,WAA4C;CAClF,OAAO;EACL,UAAU,IAAI,sBAAsB,GAAG,OAAO,GAAG,sBAAsB,UAAU;EACjF,WAAW,IAAI,sBAAsB,GAAG,OAAO,GAAG,sBAAsB,WAAW;CACrF;AACF;AAaA,SAAgB,oBAAoB,SAAgD;CAClF,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,QAAQ,SAAS;AAClG;AAEA,SAAgB,uBAAuB,SAAmD;CACxF,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,QAAQ,SAAS;AAClG;AAEA,SAAgB,wBAAwB,SAAoD;CAC1F,OACE,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,eAAe,WAAW,UAAU;AAEhH;AAEA,SAAgB,6BAA6B,SAAyD;CACpG,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,UAAU,WAAW,QAAQ,SAAS;AAClG;AAEA,SAAgB,4BAA4B,SAAwD;CAClG,OAAO,OAAO,YAAY,YAAY,YAAY,QAAQ,sBAAsB,WAAW,CAAC,CAAC,QAAQ;AACvG;;;;AC1IA,SAAgB,sBAAkC;CAChD,MAAM,QAAQ,YAAY,eAAe,IAAI;CAC7C,OAAO;EACL,MAAM,QAAQ,IAAI,KAAK,KAAK;EAC5B,MAAM,KAAK,UAAU,IAAI,KAAK,OAAO,KAAK;EAC1C,SAAS,QAAQ,IAAI,KAAK,KAAK;CACjC;AACF;;;;;;;AAQA,MAAa,uBAAuB;AAEpC,MAAM,mBAAmB,SAAiB,6BAA6B;AACvE,MAAM,mBAAmB,SAAiB,6BAA6B;;;;;;AAevE,SAAgB,sBAAsB,EAAE,OAAO,MAAM,YAAY,KAAK,IAAI,KAA6B;CACrG,MAAM,aAAa,gBAAgB,IAAI;CACvC,MAAM,aAAa,gBAAgB,IAAI;CAEvC,OAAO;EACL,MAAM,kBAAkB,OAAsC;GAC5D,MAAM,UAAwB;IAAE;IAAO,aAAa,IAAI;GAAE;GAC1D,MAAM,MAAM,IAAI,YAAY,OAAO;GACnC,OAAO;EACT;;;;;;EAMA,MAAM,sBAAyD;GAC7D,MAAM,UAAU,MAAM,MAAM,IAAkB,UAAU;GACxD,IAAI,CAAC,SACH;GAGF,IAAI,IAAI,IAAI,QAAQ,cAAA,KAAoC;IACtD,MAAM,MAAM,OAAO,UAAU;IAC7B;GACF;GAEA,MAAM,eAAe,MAAM,MAAM,IAAY,UAAU;GACvD,IAAI,QAAQ,UAAU,cACpB;GAGF,OAAO;EACT;;;;;;EAMA,MAAM,iBAAiB,OAA8B;GACnD,MAAM,MAAM,IAAI,YAAY,KAAK;EACnC;CACF;AACF;;;;;;AAaA,SAAgB,2BAA2B,EACzC,YACA,WACA,gBAAgB,cACc;CAC9B,OAAO,OAAO,YAA+C;EAC3D,MAAM,cAAc,cAAc;EAElC,IAAI,QAAQ,OACV,MAAM,WAAW,kBAAkB,WAAW;EAGhD,UAAU;GACR,kBAAkB;GAClB;GACA,OAAO,QAAQ;EACjB,CAAC;CACH;AACF"}
|