syncorejs 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/_dashboard/assets/ConfirmActionDialog-Db4VzVp6.js +1 -0
  2. package/dist/_dashboard/assets/circle-x-VsB4Z8W4.js +1 -0
  3. package/dist/_dashboard/assets/data.lazy-DjdU9CzX.js +18 -0
  4. package/dist/_dashboard/assets/file-code-BrOKjG4n.js +1 -0
  5. package/dist/_dashboard/assets/functions.lazy-DvDwAGHq.js +1 -0
  6. package/dist/_dashboard/assets/funnel-BH8EMMJI.js +1 -0
  7. package/dist/_dashboard/assets/index-DT9ZEELb.css +1 -0
  8. package/dist/_dashboard/assets/index-DrSG4qZZ.js +54 -0
  9. package/dist/_dashboard/assets/loader-circle-CmJFSYga.js +1 -0
  10. package/dist/_dashboard/assets/logs.lazy-50KTk5yd.js +1 -0
  11. package/dist/_dashboard/assets/play-DS52VsLN.js +1 -0
  12. package/dist/_dashboard/assets/queries.lazy-CfysRWkz.js +1 -0
  13. package/dist/_dashboard/assets/scheduler.lazy-BB88mZk-.js +1 -0
  14. package/dist/_dashboard/assets/select-THYcR8Wt.js +1 -0
  15. package/dist/_dashboard/assets/separator-BU7xg615.js +1 -0
  16. package/dist/_dashboard/assets/shared-Bh0wwC2k.js +1 -0
  17. package/dist/_dashboard/assets/sql.lazy-CHtU9Qnt.js +13 -0
  18. package/dist/_dashboard/assets/storage.lazy-CneN7wVU.js +1 -0
  19. package/dist/_dashboard/assets/table-2-CH8JoMXf.js +1 -0
  20. package/dist/_dashboard/index.html +18 -0
  21. package/dist/_vendor/cli/app.d.mts.map +1 -1
  22. package/dist/_vendor/cli/app.mjs +16 -5
  23. package/dist/_vendor/cli/app.mjs.map +1 -1
  24. package/dist/_vendor/core/cli.d.mts.map +1 -1
  25. package/dist/_vendor/core/cli.mjs +358 -16
  26. package/dist/_vendor/core/cli.mjs.map +1 -1
  27. package/dist/_vendor/core/index.d.mts +3 -3
  28. package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
  29. package/dist/_vendor/core/runtime/devtools.mjs +131 -0
  30. package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
  31. package/dist/_vendor/core/runtime/functions.d.mts +3 -3
  32. package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
  33. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +1 -1
  34. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -1
  35. package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs +4 -1
  36. package/dist/_vendor/core/runtime/internal/engines/executionEngine.mjs.map +1 -1
  37. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +6 -3
  38. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -1
  39. package/dist/_vendor/core/runtime/internal/engines/shared.mjs +5 -1
  40. package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -1
  41. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +99 -13
  42. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -1
  43. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +38 -4
  44. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -1
  45. package/dist/_vendor/core/runtime/runtime.d.mts +65 -8
  46. package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
  47. package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
  48. package/dist/_vendor/core/transport.d.mts.map +1 -1
  49. package/dist/_vendor/core/transport.mjs +30 -5
  50. package/dist/_vendor/core/transport.mjs.map +1 -1
  51. package/dist/_vendor/devtools-protocol/index.d.ts +75 -1
  52. package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
  53. package/dist/_vendor/devtools-protocol/index.js.map +1 -1
  54. package/dist/_vendor/next/index.js +9 -1
  55. package/dist/_vendor/next/index.js.map +1 -1
  56. package/dist/_vendor/platform-expo/index.d.ts +1 -1
  57. package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
  58. package/dist/_vendor/platform-expo/index.js +6 -1
  59. package/dist/_vendor/platform-expo/index.js.map +1 -1
  60. package/dist/_vendor/platform-node/index.d.mts +2 -1
  61. package/dist/_vendor/platform-node/index.d.mts.map +1 -1
  62. package/dist/_vendor/platform-node/index.mjs +27 -2
  63. package/dist/_vendor/platform-node/index.mjs.map +1 -1
  64. package/dist/_vendor/platform-node/ipc-react.mjs +4 -0
  65. package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
  66. package/dist/_vendor/platform-web/external-change.d.ts +2 -2
  67. package/dist/_vendor/platform-web/external-change.js +2 -2
  68. package/dist/_vendor/platform-web/external-change.js.map +1 -1
  69. package/dist/_vendor/platform-web/index.d.ts +13 -10
  70. package/dist/_vendor/platform-web/index.d.ts.map +1 -1
  71. package/dist/_vendor/platform-web/index.js +66 -10
  72. package/dist/_vendor/platform-web/index.js.map +1 -1
  73. package/dist/_vendor/platform-web/indexeddb.d.ts +3 -3
  74. package/dist/_vendor/platform-web/indexeddb.js +3 -3
  75. package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
  76. package/dist/_vendor/platform-web/opfs.d.ts +3 -1
  77. package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
  78. package/dist/_vendor/platform-web/opfs.js +29 -3
  79. package/dist/_vendor/platform-web/opfs.js.map +1 -1
  80. package/dist/_vendor/platform-web/persistence.d.ts +31 -1
  81. package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
  82. package/dist/_vendor/platform-web/persistence.js.map +1 -1
  83. package/dist/_vendor/platform-web/react.d.ts.map +1 -1
  84. package/dist/_vendor/platform-web/react.js +9 -1
  85. package/dist/_vendor/platform-web/react.js.map +1 -1
  86. package/dist/_vendor/react/index.d.ts +6 -5
  87. package/dist/_vendor/react/index.d.ts.map +1 -1
  88. package/dist/_vendor/react/index.js +6 -5
  89. package/dist/_vendor/react/index.js.map +1 -1
  90. package/dist/_vendor/svelte/index.d.ts +8 -6
  91. package/dist/_vendor/svelte/index.d.ts.map +1 -1
  92. package/dist/_vendor/svelte/index.js +7 -5
  93. package/dist/_vendor/svelte/index.js.map +1 -1
  94. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/// <reference path=\"./assets.d.ts\" />\n\nimport { Directory, File, Paths } from \"expo-file-system\";\nimport {\n defaultDatabaseDirectory,\n openDatabaseSync,\n type SQLiteDatabase\n} from \"expo-sqlite\";\nimport {\n type ImpactScope,\n type DevtoolsSink,\n type SyncoreExternalChangeApplier,\n type SyncoreExternalChangeSignal,\n SyncoreRuntime,\n type SchedulerOptions,\n type SyncoreCapabilities,\n type SyncoreDataModel,\n type SyncoreRuntimeOptions,\n type SyncoreSqlDriver,\n type SyncoreStorageAdapter,\n type StorageObject,\n type StorageWriteInput\n} from \"@syncore/core\";\nimport {\n BroadcastChannelExternalChangeSignal,\n createDefaultSyncChannelName,\n createWebSyncoreRuntime\n} from \"@syncore/platform-web\";\n\nexport type ExpoSyncoreSchema<\n TSchema extends SyncoreDataModel = SyncoreDataModel\n> = TSchema;\n\n/**\n * Options for {@link createExpoSyncoreRuntime}.\n *\n * On native (iOS/Android) Syncore uses `expo-sqlite` and `expo-file-system`\n * automatically. When the same app is served on web (via `expo-router` or\n * Metro’s web bundler) it falls back to the SQL.js + OPFS web stack instead.\n *\n * At minimum supply `schema` and `functions`. Everything else has platform\n * defaults.\n *\n * ```ts\n * const runtime = createExpoSyncoreRuntime({\n * schema,\n * functions,\n * databaseName: \"app.db\",\n * storageDirectoryName: \"app-storage\",\n * });\n * await runtime.start();\n * ```\n */\nexport interface CreateExpoRuntimeOptions<\n TSchema extends ExpoSyncoreSchema = ExpoSyncoreSchema\n> {\n /** The data model that defines the available tables and indexes. */\n schema: TSchema;\n\n /**\n * The registered function map. Use the `functions` export from\n * `syncore/_generated/functions.ts`.\n */\n functions: SyncoreRuntimeOptions<TSchema>[\"functions\"];\n\n /**\n * Resolved Syncore component instances. Only required when your app\n * installs Syncore component packages.\n */\n components?: SyncoreRuntimeOptions<TSchema>[\"components\"];\n\n /**\n * Platform capabilities injected into `ctx.capabilities` inside function\n * handlers.\n */\n capabilities?: SyncoreCapabilities;\n\n /**\n * Custom SQL driver. Defaults to an `ExpoSqliteDriver` backed by\n * `expo-sqlite`.\n */\n driver?: SyncoreSqlDriver;\n\n /** Custom file/blob storage adapter. Defaults to `ExpoFileStorageAdapter`. */\n storage?: SyncoreStorageAdapter;\n\n /**\n * SQLite database filename (e.g. `\"app.db\"`). Defaults to `\"syncore.db\"`.\n * The file is created inside the app-local documents directory on device.\n */\n databaseName?: string;\n\n /**\n * Absolute path to the directory where the SQLite file is stored.\n * Defaults to `expo-sqlite`’s `defaultDatabaseDirectory`.\n */\n databaseDirectory?: string;\n\n /**\n * Name of the sub-directory inside the app’s documents folder used for\n * blob/file storage. Defaults to `\"syncore-storage\"`.\n */\n storageDirectoryName?: string;\n\n /**\n * Platform label reported to devtools. Defaults to `\"expo\"`. Override to\n * `\"expo-web\"` when running on web.\n */\n platform?: string;\n\n /**\n * Devtools event sink. Omit to disable devtools. On-device devtools\n * connections require pointing to your development machine’s IP address.\n */\n devtools?: DevtoolsSink;\n\n /** Scheduler configuration for background and recurring jobs. */\n scheduler?: SchedulerOptions;\n\n /**\n * External change signal used to keep multiple in-process instances in sync.\n * On web, defaults to a `BroadcastChannel`-based signal automatically.\n */\n externalChangeSignal?: SyncoreExternalChangeSignal;\n\n /**\n * External change applier used when change events arrive from other tabs or\n * processes. On web with `ExpoSqliteDriver`, defaults to\n * `ExpoWebExternalChangeApplier` automatically.\n */\n externalChangeApplier?: SyncoreExternalChangeApplier;\n\n /**\n * Direct URL to the SQL.js `.wasm` binary. Only needed when the app runs on\n * web and the default CDN URL is unreachable.\n */\n wasmUrl?: string;\n\n /**\n * Resolver for SQL.js support files (`.wasm`, `.worker.js`). Equivalent to\n * the `locateFile` option in `initSqlJs()`. Only used on web.\n */\n locateFile?: (fileName: string) => string;\n}\n\n/**\n * A reusable, lazily-started Expo Syncore runtime handle.\n *\n * Created by {@link createExpoSyncoreBootstrap}. The bootstrap defers actual\n * runtime startup until the first call to `getClient()` and keeps a single\n * instance alive across React Navigation reloads.\n *\n * ```ts\n * const bootstrap = createExpoSyncoreBootstrap({ schema, functions });\n *\n * // In your root component:\n * const client = await bootstrap.getClient();\n * ```\n */\nexport interface ExpoSyncoreBootstrap<\n TSchema extends ExpoSyncoreSchema = ExpoSyncoreSchema\n> {\n /** @deprecated Access the runtime via `getClient()` instead. */\n getRuntime(): never;\n\n /**\n * Start the runtime on first call, then return the same client on subsequent\n * calls. Safe to call from multiple places concurrently.\n */\n getClient(): Promise<\n ReturnType<SyncoreRuntime<TSchema>[\"createClient\"]>\n >;\n\n /** Stop the running runtime instance if one is active. */\n stop(): Promise<void>;\n\n /**\n * Stop and discard the current runtime so the next `getClient()` call\n * creates a fresh one. Useful after a full app reset or database migration.\n */\n reset(): Promise<void>;\n}\n\n/**\n * Create a Syncore runtime for Expo (React Native and Expo web) backed by\n * `expo-sqlite` on native platforms and SQL.js on web.\n *\n * Returns an unstarted {@link SyncoreRuntime}. Call `await runtime.start()`\n * before using the client:\n *\n * ```ts\n * import { createExpoSyncoreRuntime } from \"syncorejs/expo\";\n * import schema from \"./syncore/schema\";\n * import { functions } from \"./syncore/_generated/functions\";\n *\n * const runtime = createExpoSyncoreRuntime({ schema, functions });\n * await runtime.start();\n * const client = runtime.createClient();\n * ```\n *\n * For managed lifecycle in React components, prefer\n * {@link createExpoSyncoreBootstrap} instead.\n */\nexport function createExpoSyncoreRuntime<\n TSchema extends ExpoSyncoreSchema\n>(\n options: CreateExpoRuntimeOptions<TSchema>\n): SyncoreRuntime<TSchema> {\n const databaseDirectory =\n options.databaseDirectory ??\n (typeof defaultDatabaseDirectory === \"string\"\n ? defaultDatabaseDirectory\n : undefined);\n const driver =\n options.driver ??\n new ExpoSqliteDriver(\n openDatabaseSync(\n options.databaseName ?? \"syncore.db\",\n undefined,\n databaseDirectory\n ),\n {\n databaseName: options.databaseName ?? \"syncore.db\",\n ...(databaseDirectory ? { databaseDirectory } : {})\n }\n );\n const storage =\n options.storage ??\n new ExpoFileStorageAdapter(\n options.storageDirectoryName ?? \"syncore-storage\"\n );\n const isWebEnvironment =\n typeof window !== \"undefined\" && typeof document !== \"undefined\";\n const webExternalChangeSignal =\n isWebEnvironment && !options.externalChangeSignal\n ? new BroadcastChannelExternalChangeSignal({\n channelName: createDefaultSyncChannelName(\n options.databaseName ?? \"syncore.db\"\n )\n })\n : undefined;\n const webExternalChangeApplier =\n isWebEnvironment &&\n !options.externalChangeApplier &&\n driver instanceof ExpoSqliteDriver\n ? new ExpoWebExternalChangeApplier(driver)\n : undefined;\n\n return new SyncoreRuntime({\n schema: options.schema,\n functions: options.functions,\n ...(options.components ? { components: options.components } : {}),\n driver,\n storage,\n ...(isWebEnvironment && options.externalChangeSignal\n ? { externalChangeSignal: options.externalChangeSignal }\n : isWebEnvironment && webExternalChangeSignal\n ? { externalChangeSignal: webExternalChangeSignal }\n : {}),\n ...(isWebEnvironment && options.externalChangeApplier\n ? { externalChangeApplier: options.externalChangeApplier }\n : isWebEnvironment && webExternalChangeApplier\n ? { externalChangeApplier: webExternalChangeApplier }\n : {}),\n platform: options.platform ?? \"expo\",\n ...(options.capabilities ? { capabilities: options.capabilities } : {}),\n ...(options.devtools ? { devtools: options.devtools } : {}),\n ...(options.scheduler ? { scheduler: options.scheduler } : {})\n });\n}\n\n/**\n * Create a same-process Syncore client directly from a started Expo runtime.\n *\n * Prefer this in scripts or non-component code. For React component trees,\n * use {@link createExpoSyncoreBootstrap} instead to get automatic lifecycle\n * management.\n *\n * ```ts\n * const client = createExpoSyncoreClient(runtime);\n * await client.mutation(api.todos.create, { text: \"Buy milk\" });\n * ```\n */\nexport function createExpoSyncoreClient<\n TSchema extends ExpoSyncoreSchema\n>(runtime: SyncoreRuntime<TSchema>) {\n return runtime.createClient();\n}\n\n/**\n * Create a reusable Expo bootstrap that lazily starts the local runtime the\n * first time a client is requested.\n *\n * The bootstrap keeps a single runtime instance alive across component\n * remounts and safely handles concurrent `getClient()` calls.\n *\n * ```ts\n * // app/_layout.tsx\n * import { createExpoSyncoreBootstrap } from \"syncorejs/expo\";\n * import schema from \"../syncore/schema\";\n * import { functions } from \"../syncore/_generated/functions\";\n *\n * export const syncoreBootstrap = createExpoSyncoreBootstrap({\n * schema,\n * functions,\n * });\n *\n * // Later in SyncoreProvider:\n * const client = await syncoreBootstrap.getClient();\n * ```\n */\nexport function createExpoSyncoreBootstrap<\n TSchema extends ExpoSyncoreSchema\n>(\n options: CreateExpoRuntimeOptions<TSchema>\n): ExpoSyncoreBootstrap<TSchema> {\n let runtime: SyncoreRuntime<TSchema> | null = null;\n let started: Promise<\n ReturnType<SyncoreRuntime<TSchema>[\"createClient\"]>\n > | null = null;\n\n const ensureRuntime = async () => {\n runtime ??= isWebEnvironment()\n ? await createExpoWebSyncoreRuntime(options)\n : createExpoSyncoreRuntime(options);\n return runtime;\n };\n\n return {\n getRuntime() {\n throw new Error(\n \"createExpoSyncoreBootstrap().getRuntime() is not available synchronously. Use getClient() instead.\"\n );\n },\n async getClient() {\n if (!started) {\n started = ensureRuntime().then((activeRuntime) =>\n activeRuntime.start().then(() => activeRuntime.createClient())\n );\n }\n return started;\n },\n async stop() {\n if (!runtime) {\n return;\n }\n await runtime.stop();\n runtime = null;\n started = null;\n },\n async reset() {\n if (runtime) {\n await runtime.stop();\n }\n runtime = null;\n started = null;\n }\n };\n}\n\n/**\n * Syncore SQL driver implementation backed by `expo-sqlite`.\n */\nexport class ExpoSqliteDriver implements SyncoreSqlDriver {\n private transactionDepth = 0;\n private closed = false;\n private readonly databaseName: string;\n private readonly databaseDirectory: string | undefined;\n\n constructor(\n private database: SQLiteDatabase,\n options?: {\n databaseName?: string;\n databaseDirectory?: string;\n }\n ) {\n this.databaseName = options?.databaseName ?? \"syncore.db\";\n this.databaseDirectory = options?.databaseDirectory;\n }\n\n async exec(sql: string): Promise<void> {\n this.ensureOpen();\n await this.database.execAsync(sql);\n }\n\n async run(\n sql: string,\n params: unknown[] = []\n ): Promise<{ changes: number; lastInsertRowid?: number | string }> {\n this.ensureOpen();\n const result = await this.database.runAsync(sql, normalizeParams(params));\n return {\n changes: result.changes,\n lastInsertRowid: result.lastInsertRowId\n };\n }\n\n async get<T>(sql: string, params: unknown[] = []): Promise<T | undefined> {\n this.ensureOpen();\n const row = await this.database.getFirstAsync<T>(\n sql,\n normalizeParams(params)\n );\n return row ?? undefined;\n }\n\n async all<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n this.ensureOpen();\n return this.database.getAllAsync<T>(sql, normalizeParams(params));\n }\n\n async withTransaction<T>(callback: () => Promise<T>): Promise<T> {\n this.ensureOpen();\n if (this.transactionDepth > 0) {\n return this.withSavepoint(`nested_${this.transactionDepth}`, callback);\n }\n\n this.transactionDepth += 1;\n await this.database.execAsync(\"BEGIN IMMEDIATE\");\n try {\n const result = await callback();\n await this.database.execAsync(\"COMMIT\");\n return result;\n } catch (error) {\n await this.database.execAsync(\"ROLLBACK\");\n throw error;\n } finally {\n this.transactionDepth -= 1;\n }\n }\n\n async withSavepoint<T>(name: string, callback: () => Promise<T>): Promise<T> {\n this.ensureOpen();\n const safeName = name.replaceAll(/[^a-zA-Z0-9_]/g, \"_\");\n this.transactionDepth += 1;\n await this.database.execAsync(`SAVEPOINT ${safeName}`);\n try {\n const result = await callback();\n await this.database.execAsync(`RELEASE SAVEPOINT ${safeName}`);\n return result;\n } catch (error) {\n await this.database.execAsync(`ROLLBACK TO SAVEPOINT ${safeName}`);\n await this.database.execAsync(`RELEASE SAVEPOINT ${safeName}`);\n throw error;\n } finally {\n this.transactionDepth -= 1;\n }\n }\n\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n await this.database.closeAsync();\n this.closed = true;\n }\n\n private ensureOpen(): void {\n if (this.closed) {\n throw new Error(\"The Expo SQLite driver is already closed.\");\n }\n }\n\n async reopen(): Promise<void> {\n this.ensureOpen();\n await this.database.closeAsync();\n this.database = openDatabaseSync(\n this.databaseName,\n undefined,\n this.databaseDirectory\n );\n }\n}\n\nclass ExpoWebExternalChangeApplier implements SyncoreExternalChangeApplier {\n constructor(private readonly driver: ExpoSqliteDriver) {}\n\n async applyExternalChange(event: {\n scope: \"database\" | \"storage\" | \"all\";\n changedScopes?: ImpactScope[];\n changedTables?: string[];\n storageIds?: string[];\n }) {\n if (event.scope === \"database\" || event.scope === \"all\") {\n await this.driver.reopen();\n }\n return {\n databaseChanged: event.scope === \"database\" || event.scope === \"all\",\n storageChanged: event.scope === \"storage\" || event.scope === \"all\",\n changedScopes:\n event.changedScopes ??\n ([\n ...(event.changedTables ?? []).map((tableName) => `table:${tableName}`),\n ...(event.storageIds ?? []).map((storageId) => `storage:${storageId}`)\n ] as ImpactScope[])\n };\n }\n}\n\n/**\n * Syncore file/blob storage backed by the Expo file system.\n */\nexport class ExpoFileStorageAdapter implements SyncoreStorageAdapter {\n private readonly rootDirectory: Directory;\n\n constructor(storageDirectoryName: string) {\n this.rootDirectory = new Directory(Paths.document, storageDirectoryName);\n if (!this.rootDirectory.exists) {\n this.rootDirectory.create({ idempotent: true, intermediates: true });\n }\n }\n\n async put(id: string, input: StorageWriteInput): Promise<StorageObject> {\n const file = new File(this.rootDirectory, id);\n if (!file.exists) {\n file.create({ intermediates: true, overwrite: true });\n }\n const bytes = normalizeBinary(input.data);\n file.write(bytes);\n return {\n id,\n path: file.uri,\n size: bytes.byteLength,\n contentType: input.contentType ?? null\n };\n }\n\n async get(id: string): Promise<StorageObject | null> {\n const file = new File(this.rootDirectory, id);\n if (!file.exists) {\n return null;\n }\n return {\n id,\n path: file.uri,\n size: file.size,\n contentType: file.type || null\n };\n }\n\n async read(id: string): Promise<Uint8Array | null> {\n const file = new File(this.rootDirectory, id);\n if (!file.exists) {\n return null;\n }\n return file.bytes();\n }\n\n async delete(id: string): Promise<void> {\n const file = new File(this.rootDirectory, id);\n if (file.exists) {\n file.delete();\n }\n }\n}\n\nfunction normalizeParams(\n values: unknown[]\n): Array<string | number | Uint8Array | null> {\n return values.map((value) => {\n if (typeof value === \"boolean\") {\n return value ? 1 : 0;\n }\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n value instanceof Uint8Array\n ) {\n return value;\n }\n if (value instanceof ArrayBuffer) {\n return new Uint8Array(value);\n }\n return JSON.stringify(value);\n });\n}\n\nfunction normalizeBinary(data: StorageWriteInput[\"data\"]): Uint8Array {\n if (typeof data === \"string\") {\n return new TextEncoder().encode(data);\n }\n if (data instanceof Uint8Array) {\n return data;\n }\n return new Uint8Array(data);\n}\n\nasync function createExpoWebSyncoreRuntime<TSchema extends ExpoSyncoreSchema>(\n options: CreateExpoRuntimeOptions<TSchema>\n): Promise<SyncoreRuntime<TSchema>> {\n const wasmUrl =\n options.wasmUrl ??\n (options.locateFile\n ? undefined\n : await resolveDefaultExpoWebSqlJsWasmUrl());\n\n return createWebSyncoreRuntime({\n schema: options.schema,\n functions: options.functions,\n ...(options.components ? { components: options.components } : {}),\n ...(options.capabilities ? { capabilities: options.capabilities } : {}),\n databaseName: options.databaseName ?? \"syncore.db\",\n storageNamespace: options.storageDirectoryName ?? \"syncore-storage\",\n ...(wasmUrl ? { wasmUrl } : {}),\n ...(options.locateFile ? { locateFile: options.locateFile } : {}),\n platform: options.platform ?? \"expo-web\",\n ...(options.devtools !== undefined ? { devtools: options.devtools } : {}),\n ...(options.scheduler ? { scheduler: options.scheduler } : {})\n });\n}\n\nasync function resolveDefaultExpoWebSqlJsWasmUrl(): Promise<\n string | undefined\n> {\n const module = await import(\"./web-sqljs-wasm.js\");\n return module.resolveDefaultExpoWebSqlJsWasmUrl();\n}\n\nfunction isWebEnvironment(): boolean {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/// <reference path=\"./assets.d.ts\" />\n\nimport { Directory, File, Paths } from \"expo-file-system\";\nimport {\n defaultDatabaseDirectory,\n openDatabaseSync,\n type SQLiteDatabase\n} from \"expo-sqlite\";\nimport {\n type ImpactScope,\n type DevtoolsSink,\n type SyncoreExternalChangeApplier,\n type SyncoreExternalChangeSignal,\n SyncoreRuntime,\n type SchedulerOptions,\n type SyncoreCapabilities,\n type SyncoreDataModel,\n type SyncoreRuntimeOptions,\n type SyncoreSqlDriver,\n type SyncoreStorageAdapter,\n type StorageObject,\n type StorageWriteInput\n} from \"@syncore/core\";\nimport {\n BroadcastChannelExternalChangeSignal,\n createDefaultSyncChannelName,\n createWebSyncoreRuntime\n} from \"@syncore/platform-web\";\n\nexport type ExpoSyncoreSchema<\n TSchema extends SyncoreDataModel = SyncoreDataModel\n> = TSchema;\n\n/**\n * Options for {@link createExpoSyncoreRuntime}.\n *\n * On native (iOS/Android) Syncore uses `expo-sqlite` and `expo-file-system`\n * automatically. When the same app is served on web (via `expo-router` or\n * Metro’s web bundler) it falls back to the SQL.js + OPFS web stack instead.\n *\n * At minimum supply `schema` and `functions`. Everything else has platform\n * defaults.\n *\n * ```ts\n * const runtime = createExpoSyncoreRuntime({\n * schema,\n * functions,\n * databaseName: \"app.db\",\n * storageDirectoryName: \"app-storage\",\n * });\n * await runtime.start();\n * ```\n */\nexport interface CreateExpoRuntimeOptions<\n TSchema extends ExpoSyncoreSchema = ExpoSyncoreSchema\n> {\n /** The data model that defines the available tables and indexes. */\n schema: TSchema;\n\n /**\n * The registered function map. Use the `functions` export from\n * `syncore/_generated/functions.ts`.\n */\n functions: SyncoreRuntimeOptions<TSchema>[\"functions\"];\n\n /**\n * Resolved Syncore component instances. Only required when your app\n * installs Syncore component packages.\n */\n components?: SyncoreRuntimeOptions<TSchema>[\"components\"];\n\n /**\n * Platform capabilities injected into `ctx.capabilities` inside function\n * handlers.\n */\n capabilities?: SyncoreCapabilities;\n\n /**\n * Custom SQL driver. Defaults to an `ExpoSqliteDriver` backed by\n * `expo-sqlite`.\n */\n driver?: SyncoreSqlDriver;\n\n /** Custom file/blob storage adapter. Defaults to `ExpoFileStorageAdapter`. */\n storage?: SyncoreStorageAdapter;\n\n /**\n * SQLite database filename (e.g. `\"app.db\"`). Defaults to `\"syncore.db\"`.\n * The file is created inside the app-local documents directory on device.\n */\n databaseName?: string;\n\n /**\n * Absolute path to the directory where the SQLite file is stored.\n * Defaults to `expo-sqlite`’s `defaultDatabaseDirectory`.\n */\n databaseDirectory?: string;\n\n /**\n * Name of the sub-directory inside the app’s documents folder used for\n * blob/file storage. Defaults to `\"syncore-storage\"`.\n */\n storageDirectoryName?: string;\n\n /**\n * Platform label reported to devtools. Defaults to `\"expo\"`. Override to\n * `\"expo-web\"` when running on web.\n */\n platform?: string;\n\n /**\n * Devtools event sink. Omit to disable devtools. On-device devtools\n * connections require pointing to your development machine’s IP address.\n */\n devtools?: DevtoolsSink;\n\n /** Scheduler configuration for background and recurring jobs. */\n scheduler?: SchedulerOptions;\n\n /**\n * External change signal used to keep multiple in-process instances in sync.\n * On web, defaults to a `BroadcastChannel`-based signal automatically.\n */\n externalChangeSignal?: SyncoreExternalChangeSignal;\n\n /**\n * External change applier used when change events arrive from other tabs or\n * processes. On web with `ExpoSqliteDriver`, defaults to\n * `ExpoWebExternalChangeApplier` automatically.\n */\n externalChangeApplier?: SyncoreExternalChangeApplier;\n\n /**\n * Direct URL to the SQL.js `.wasm` binary. Only needed when the app runs on\n * web and the default CDN URL is unreachable.\n */\n wasmUrl?: string;\n\n /**\n * Resolver for SQL.js support files (`.wasm`, `.worker.js`). Equivalent to\n * the `locateFile` option in `initSqlJs()`. Only used on web.\n */\n locateFile?: (fileName: string) => string;\n}\n\n/**\n * A reusable, lazily-started Expo Syncore runtime handle.\n *\n * Created by {@link createExpoSyncoreBootstrap}. The bootstrap defers actual\n * runtime startup until the first call to `getClient()` and keeps a single\n * instance alive across React Navigation reloads.\n *\n * ```ts\n * const bootstrap = createExpoSyncoreBootstrap({ schema, functions });\n *\n * // In your root component:\n * const client = await bootstrap.getClient();\n * ```\n */\nexport interface ExpoSyncoreBootstrap<\n TSchema extends ExpoSyncoreSchema = ExpoSyncoreSchema\n> {\n /** @deprecated Access the runtime via `getClient()` instead. */\n getRuntime(): never;\n\n /**\n * Start the runtime on first call, then return the same client on subsequent\n * calls. Safe to call from multiple places concurrently.\n */\n getClient(): Promise<\n ReturnType<SyncoreRuntime<TSchema>[\"createClient\"]>\n >;\n\n /** Stop the running runtime instance if one is active. */\n stop(): Promise<void>;\n\n /**\n * Stop and discard the current runtime so the next `getClient()` call\n * creates a fresh one. Useful after a full app reset or database migration.\n */\n reset(): Promise<void>;\n}\n\n/**\n * Create a Syncore runtime for Expo (React Native and Expo web) backed by\n * `expo-sqlite` on native platforms and SQL.js on web.\n *\n * Returns an unstarted SyncoreRuntime. Call `await runtime.start()`\n * before using the client:\n *\n * ```ts\n * import { createExpoSyncoreRuntime } from \"syncorejs/expo\";\n * import schema from \"./syncore/schema\";\n * import { functions } from \"./syncore/_generated/functions\";\n *\n * const runtime = createExpoSyncoreRuntime({ schema, functions });\n * await runtime.start();\n * const client = runtime.createClient();\n * ```\n *\n * For managed lifecycle in React components, prefer\n * {@link createExpoSyncoreBootstrap} instead.\n */\nexport function createExpoSyncoreRuntime<\n TSchema extends ExpoSyncoreSchema\n>(\n options: CreateExpoRuntimeOptions<TSchema>\n): SyncoreRuntime<TSchema> {\n const databaseDirectory =\n options.databaseDirectory ??\n (typeof defaultDatabaseDirectory === \"string\"\n ? defaultDatabaseDirectory\n : undefined);\n const driver =\n options.driver ??\n new ExpoSqliteDriver(\n openDatabaseSync(\n options.databaseName ?? \"syncore.db\",\n undefined,\n databaseDirectory\n ),\n {\n databaseName: options.databaseName ?? \"syncore.db\",\n ...(databaseDirectory ? { databaseDirectory } : {})\n }\n );\n const storage =\n options.storage ??\n new ExpoFileStorageAdapter(\n options.storageDirectoryName ?? \"syncore-storage\"\n );\n const isWebEnvironment =\n typeof window !== \"undefined\" && typeof document !== \"undefined\";\n const webExternalChangeSignal =\n isWebEnvironment && !options.externalChangeSignal\n ? new BroadcastChannelExternalChangeSignal({\n channelName: createDefaultSyncChannelName(\n options.databaseName ?? \"syncore.db\"\n )\n })\n : undefined;\n const webExternalChangeApplier =\n isWebEnvironment &&\n !options.externalChangeApplier &&\n driver instanceof ExpoSqliteDriver\n ? new ExpoWebExternalChangeApplier(driver)\n : undefined;\n\n return new SyncoreRuntime({\n schema: options.schema,\n functions: options.functions,\n ...(options.components ? { components: options.components } : {}),\n driver,\n storage,\n ...(isWebEnvironment && options.externalChangeSignal\n ? { externalChangeSignal: options.externalChangeSignal }\n : isWebEnvironment && webExternalChangeSignal\n ? { externalChangeSignal: webExternalChangeSignal }\n : {}),\n ...(isWebEnvironment && options.externalChangeApplier\n ? { externalChangeApplier: options.externalChangeApplier }\n : isWebEnvironment && webExternalChangeApplier\n ? { externalChangeApplier: webExternalChangeApplier }\n : {}),\n platform: options.platform ?? \"expo\",\n ...(options.capabilities ? { capabilities: options.capabilities } : {}),\n runtimeCapabilities: {\n storage: {\n available: true,\n protocol: \"file\",\n supportsRange: false\n }\n },\n ...(options.devtools ? { devtools: options.devtools } : {}),\n ...(options.scheduler ? { scheduler: options.scheduler } : {})\n });\n}\n\n/**\n * Create a same-process Syncore client directly from a started Expo runtime.\n *\n * Prefer this in scripts or non-component code. For React component trees,\n * use {@link createExpoSyncoreBootstrap} instead to get automatic lifecycle\n * management.\n *\n * ```ts\n * const client = createExpoSyncoreClient(runtime);\n * await client.mutation(api.todos.create, { text: \"Buy milk\" });\n * ```\n */\nexport function createExpoSyncoreClient<\n TSchema extends ExpoSyncoreSchema\n>(runtime: SyncoreRuntime<TSchema>) {\n return runtime.createClient();\n}\n\n/**\n * Create a reusable Expo bootstrap that lazily starts the local runtime the\n * first time a client is requested.\n *\n * The bootstrap keeps a single runtime instance alive across component\n * remounts and safely handles concurrent `getClient()` calls.\n *\n * ```ts\n * // app/_layout.tsx\n * import { createExpoSyncoreBootstrap } from \"syncorejs/expo\";\n * import schema from \"../syncore/schema\";\n * import { functions } from \"../syncore/_generated/functions\";\n *\n * export const syncoreBootstrap = createExpoSyncoreBootstrap({\n * schema,\n * functions,\n * });\n *\n * // Later in SyncoreProvider:\n * const client = await syncoreBootstrap.getClient();\n * ```\n */\nexport function createExpoSyncoreBootstrap<\n TSchema extends ExpoSyncoreSchema\n>(\n options: CreateExpoRuntimeOptions<TSchema>\n): ExpoSyncoreBootstrap<TSchema> {\n let runtime: SyncoreRuntime<TSchema> | null = null;\n let started: Promise<\n ReturnType<SyncoreRuntime<TSchema>[\"createClient\"]>\n > | null = null;\n\n const ensureRuntime = async () => {\n runtime ??= isWebEnvironment()\n ? await createExpoWebSyncoreRuntime(options)\n : createExpoSyncoreRuntime(options);\n return runtime;\n };\n\n return {\n getRuntime() {\n throw new Error(\n \"createExpoSyncoreBootstrap().getRuntime() is not available synchronously. Use getClient() instead.\"\n );\n },\n async getClient() {\n if (!started) {\n started = ensureRuntime().then((activeRuntime) =>\n activeRuntime.start().then(() => activeRuntime.createClient())\n );\n }\n return started;\n },\n async stop() {\n if (!runtime) {\n return;\n }\n await runtime.stop();\n runtime = null;\n started = null;\n },\n async reset() {\n if (runtime) {\n await runtime.stop();\n }\n runtime = null;\n started = null;\n }\n };\n}\n\n/**\n * Syncore SQL driver implementation backed by `expo-sqlite`.\n */\nexport class ExpoSqliteDriver implements SyncoreSqlDriver {\n private transactionDepth = 0;\n private closed = false;\n private readonly databaseName: string;\n private readonly databaseDirectory: string | undefined;\n\n constructor(\n private database: SQLiteDatabase,\n options?: {\n databaseName?: string;\n databaseDirectory?: string;\n }\n ) {\n this.databaseName = options?.databaseName ?? \"syncore.db\";\n this.databaseDirectory = options?.databaseDirectory;\n }\n\n async exec(sql: string): Promise<void> {\n this.ensureOpen();\n await this.database.execAsync(sql);\n }\n\n async run(\n sql: string,\n params: unknown[] = []\n ): Promise<{ changes: number; lastInsertRowid?: number | string }> {\n this.ensureOpen();\n const result = await this.database.runAsync(sql, normalizeParams(params));\n return {\n changes: result.changes,\n lastInsertRowid: result.lastInsertRowId\n };\n }\n\n async get<T>(sql: string, params: unknown[] = []): Promise<T | undefined> {\n this.ensureOpen();\n const row = await this.database.getFirstAsync<T>(\n sql,\n normalizeParams(params)\n );\n return row ?? undefined;\n }\n\n async all<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n this.ensureOpen();\n return this.database.getAllAsync<T>(sql, normalizeParams(params));\n }\n\n async withTransaction<T>(callback: () => Promise<T>): Promise<T> {\n this.ensureOpen();\n if (this.transactionDepth > 0) {\n return this.withSavepoint(`nested_${this.transactionDepth}`, callback);\n }\n\n this.transactionDepth += 1;\n await this.database.execAsync(\"BEGIN IMMEDIATE\");\n try {\n const result = await callback();\n await this.database.execAsync(\"COMMIT\");\n return result;\n } catch (error) {\n await this.database.execAsync(\"ROLLBACK\");\n throw error;\n } finally {\n this.transactionDepth -= 1;\n }\n }\n\n async withSavepoint<T>(name: string, callback: () => Promise<T>): Promise<T> {\n this.ensureOpen();\n const safeName = name.replaceAll(/[^a-zA-Z0-9_]/g, \"_\");\n this.transactionDepth += 1;\n await this.database.execAsync(`SAVEPOINT ${safeName}`);\n try {\n const result = await callback();\n await this.database.execAsync(`RELEASE SAVEPOINT ${safeName}`);\n return result;\n } catch (error) {\n await this.database.execAsync(`ROLLBACK TO SAVEPOINT ${safeName}`);\n await this.database.execAsync(`RELEASE SAVEPOINT ${safeName}`);\n throw error;\n } finally {\n this.transactionDepth -= 1;\n }\n }\n\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n await this.database.closeAsync();\n this.closed = true;\n }\n\n private ensureOpen(): void {\n if (this.closed) {\n throw new Error(\"The Expo SQLite driver is already closed.\");\n }\n }\n\n async reopen(): Promise<void> {\n this.ensureOpen();\n await this.database.closeAsync();\n this.database = openDatabaseSync(\n this.databaseName,\n undefined,\n this.databaseDirectory\n );\n }\n}\n\nclass ExpoWebExternalChangeApplier implements SyncoreExternalChangeApplier {\n constructor(private readonly driver: ExpoSqliteDriver) {}\n\n async applyExternalChange(event: {\n scope: \"database\" | \"storage\" | \"all\";\n changedScopes?: ImpactScope[];\n changedTables?: string[];\n storageIds?: string[];\n }) {\n if (event.scope === \"database\" || event.scope === \"all\") {\n await this.driver.reopen();\n }\n return {\n databaseChanged: event.scope === \"database\" || event.scope === \"all\",\n storageChanged: event.scope === \"storage\" || event.scope === \"all\",\n changedScopes:\n event.changedScopes ??\n ([\n ...(event.changedTables ?? []).map((tableName) => `table:${tableName}`),\n ...(event.storageIds ?? []).map((storageId) => `storage:${storageId}`)\n ] as ImpactScope[])\n };\n }\n}\n\n/**\n * Syncore file/blob storage backed by the Expo file system.\n */\nexport class ExpoFileStorageAdapter implements SyncoreStorageAdapter {\n private readonly rootDirectory: Directory;\n\n constructor(storageDirectoryName: string) {\n this.rootDirectory = new Directory(Paths.document, storageDirectoryName);\n if (!this.rootDirectory.exists) {\n this.rootDirectory.create({ idempotent: true, intermediates: true });\n }\n }\n\n async put(id: string, input: StorageWriteInput): Promise<StorageObject> {\n const file = new File(this.rootDirectory, id);\n if (!file.exists) {\n file.create({ intermediates: true, overwrite: true });\n }\n const bytes = normalizeBinary(input.data);\n file.write(bytes);\n return {\n id,\n path: file.uri,\n size: bytes.byteLength,\n contentType: input.contentType ?? null\n };\n }\n\n async get(id: string): Promise<StorageObject | null> {\n const file = new File(this.rootDirectory, id);\n if (!file.exists) {\n return null;\n }\n return {\n id,\n path: file.uri,\n size: file.size,\n contentType: file.type || null\n };\n }\n\n async read(id: string): Promise<Uint8Array | null> {\n const file = new File(this.rootDirectory, id);\n if (!file.exists) {\n return null;\n }\n return file.bytes();\n }\n\n async delete(id: string): Promise<void> {\n const file = new File(this.rootDirectory, id);\n if (file.exists) {\n file.delete();\n }\n }\n}\n\nfunction normalizeParams(\n values: unknown[]\n): Array<string | number | Uint8Array | null> {\n return values.map((value) => {\n if (typeof value === \"boolean\") {\n return value ? 1 : 0;\n }\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n value instanceof Uint8Array\n ) {\n return value;\n }\n if (value instanceof ArrayBuffer) {\n return new Uint8Array(value);\n }\n return JSON.stringify(value);\n });\n}\n\nfunction normalizeBinary(data: StorageWriteInput[\"data\"]): Uint8Array {\n if (typeof data === \"string\") {\n return new TextEncoder().encode(data);\n }\n if (data instanceof Uint8Array) {\n return data;\n }\n return new Uint8Array(data);\n}\n\nasync function createExpoWebSyncoreRuntime<TSchema extends ExpoSyncoreSchema>(\n options: CreateExpoRuntimeOptions<TSchema>\n): Promise<SyncoreRuntime<TSchema>> {\n const wasmUrl =\n options.wasmUrl ??\n (options.locateFile\n ? undefined\n : await resolveDefaultExpoWebSqlJsWasmUrl());\n\n return createWebSyncoreRuntime({\n schema: options.schema,\n functions: options.functions,\n ...(options.components ? { components: options.components } : {}),\n ...(options.capabilities ? { capabilities: options.capabilities } : {}),\n databaseName: options.databaseName ?? \"syncore.db\",\n storageNamespace: options.storageDirectoryName ?? \"syncore-storage\",\n ...(wasmUrl ? { wasmUrl } : {}),\n ...(options.locateFile ? { locateFile: options.locateFile } : {}),\n platform: options.platform ?? \"expo-web\",\n ...(options.devtools !== undefined ? { devtools: options.devtools } : {}),\n ...(options.scheduler ? { scheduler: options.scheduler } : {})\n });\n}\n\nasync function resolveDefaultExpoWebSqlJsWasmUrl(): Promise<\n string | undefined\n> {\n const module = await import(\"./web-sqljs-wasm.js\");\n return module.resolveDefaultExpoWebSqlJsWasmUrl();\n}\n\nfunction isWebEnvironment(): boolean {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n"],"mappings":"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"}
@@ -53,6 +53,7 @@ declare class NodeFileStorageAdapter implements SyncoreStorageAdapter {
53
53
  put(id: string, input: StorageWriteInput): Promise<StorageObject>;
54
54
  get(id: string): Promise<StorageObject | null>;
55
55
  read(id: string): Promise<Uint8Array | null>;
56
+ readRange(id: string, offset: number, length: number): Promise<Uint8Array | null>;
56
57
  delete(id: string): Promise<void>;
57
58
  list(): Promise<StorageObject[]>;
58
59
  }
@@ -246,7 +247,7 @@ interface CreateSyncoreRendererWindowClientOptions {
246
247
  * ```
247
248
  *
248
249
  * @param options - Configuration object. See {@link CreateNodeRuntimeOptions}.
249
- * @returns A configured (but not yet started) {@link SyncoreRuntime}. Call
250
+ * @returns A configured (but not yet started) SyncoreRuntime. Call
250
251
  * `await runtime.start()` before using the client.
251
252
  */
252
253
  declare function createNodeSyncoreRuntime<TSchema extends NodeSyncoreSchema>(options: CreateNodeRuntimeOptions<TSchema>): SyncoreRuntime<TSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { SyncoreRendererClient, attachNodeIpcRuntime, createNodeIpcMessageEndpoint, createRendererSyncoreBridgeClient, createRendererSyncoreClient, createRendererSyncoreWindowClient, installSyncoreWindowBridge } from "./ipc.mjs";
2
- import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
2
+ import { mkdir, open, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
3
3
  import { createHash } from "node:crypto";
4
4
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import path from "node:path";
@@ -142,6 +142,19 @@ var NodeFileStorageAdapter = class {
142
142
  return null;
143
143
  }
144
144
  }
145
+ async readRange(id, offset, length) {
146
+ let handle;
147
+ try {
148
+ handle = await open(this.filePath(id), "r");
149
+ const buffer = Buffer.alloc(Math.max(length, 0));
150
+ const result = await handle.read(buffer, 0, buffer.byteLength, Math.max(offset, 0));
151
+ return buffer.subarray(0, result.bytesRead);
152
+ } catch {
153
+ return null;
154
+ } finally {
155
+ await handle?.close();
156
+ }
157
+ }
145
158
  async delete(id) {
146
159
  await rm(this.filePath(id), { force: true });
147
160
  }
@@ -288,7 +301,7 @@ function resolvePersistedDataSourceAlias(storageDirectory, storageIdentity) {
288
301
  * ```
289
302
  *
290
303
  * @param options - Configuration object. See {@link CreateNodeRuntimeOptions}.
291
- * @returns A configured (but not yet started) {@link SyncoreRuntime}. Call
304
+ * @returns A configured (but not yet started) SyncoreRuntime. Call
292
305
  * `await runtime.start()` before using the client.
293
306
  */
294
307
  function createNodeSyncoreRuntime(options) {
@@ -313,6 +326,11 @@ function createNodeSyncoreRuntime(options) {
313
326
  ...options.components ? { components: options.components } : {},
314
327
  driver: new NodeSqliteDriver(options.databasePath),
315
328
  storage: new NodeFileStorageAdapter(options.storageDirectory),
329
+ runtimeCapabilities: { storage: {
330
+ available: true,
331
+ protocol: "file",
332
+ supportsRange: true
333
+ } },
316
334
  platform: options.platform ?? "node"
317
335
  };
318
336
  if (options.capabilities) runtimeOptions.capabilities = options.capabilities;
@@ -635,6 +653,13 @@ function createNodeDevtoolsCapabilities() {
635
653
  mutate: true,
636
654
  importExport: true
637
655
  },
656
+ storage: {
657
+ browse: true,
658
+ download: true,
659
+ readRange: true,
660
+ delete: true,
661
+ maxPreviewBytes: 8e4
662
+ },
638
663
  scheduler: {
639
664
  read: true,
640
665
  edit: true
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n mkdir,\n readdir,\n readFile,\n rm,\n stat,\n writeFile\n} from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { DatabaseSync, type SQLInputValue } from \"node:sqlite\";\nimport WebSocket from \"ws\";\nimport {\n SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,\n SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,\n SYNCORE_DEVTOOLS_PROTOCOL_VERSION,\n} from \"@syncore/devtools-protocol\";\nimport type {\n SyncoreDevtoolsClientMessage,\n SyncoreDevtoolsCapabilities,\n SyncoreDevtoolsExternalChangeEvent,\n SyncoreDevtoolsMessage,\n SyncoreRuntimeSummary\n} from \"@syncore/devtools-protocol\";\nimport {\n createDevtoolsCommandHandler,\n createDevtoolsSubscriptionHost,\n type DevtoolsCommandHandler,\n type DevtoolsSink,\n type DevtoolsSubscriptionHost,\n type SchedulerOptions,\n type StorageObject,\n type StorageWriteInput,\n type SyncoreCapabilities,\n type SyncoreDataModel,\n type SyncoreExternalChangeEvent,\n type SyncoreExternalChangeSignal,\n SyncoreRuntime,\n type SyncoreRuntimeOptions,\n type SyncoreSqlDriver,\n type SyncoreStorageAdapter\n} from \"@syncore/core\";\nimport { attachNodeIpcRuntime, createNodeIpcMessageEndpoint } from \"./ipc.js\";\nexport * from \"./ipc.js\";\nexport type {\n SyncoreActiveQueryInfo,\n SyncoreDevtoolsEvent,\n SyncoreRuntimeSummary\n} from \"@syncore/devtools-protocol\";\n\nexport type NodeSyncoreSchema<\n TSchema extends SyncoreDataModel = SyncoreDataModel\n> = TSchema;\n\nconst DEVTOOLS_META_DIRECTORY = \".syncore-devtools\";\nconst DATA_SOURCE_ALIAS_PREFIX = \"data-source-alias\";\n\nfunction normalizeData(input: StorageWriteInput[\"data\"]): Uint8Array {\n if (typeof input === \"string\") {\n return Buffer.from(input);\n }\n if (input instanceof Uint8Array) {\n return input;\n }\n return new Uint8Array(input);\n}\n\nfunction toSqlParameters(params: unknown[]): SQLInputValue[] {\n return params as SQLInputValue[];\n}\n\n/**\n * SQLite driver backed by Node.js’s built-in `node:sqlite` module (Node 22+).\n *\n * Opens the database at `databasePath` with `WAL` journal mode and foreign-key\n * enforcement enabled. The file is created if it does not exist.\n *\n * ```ts\n * const driver = new NodeSqliteDriver(\"./data/app.db\");\n * ```\n *\n * In most cases you should use {@link createNodeSyncoreRuntime} which\n * instantiates this driver automatically from your `databasePath` option.\n */\nexport class NodeSqliteDriver implements SyncoreSqlDriver {\n private readonly database: DatabaseSync;\n private transactionDepth = 0;\n\n constructor(readonly databasePath: string) {\n this.database = new DatabaseSync(databasePath);\n this.database.exec(\"PRAGMA foreign_keys = ON;\");\n this.database.exec(\"PRAGMA journal_mode = WAL;\");\n }\n\n async exec(sql: string): Promise<void> {\n this.database.exec(sql);\n }\n\n async run(\n sql: string,\n params: unknown[] = []\n ): Promise<{ changes: number; lastInsertRowid?: number | string }> {\n const statement = this.database.prepare(sql);\n const result = statement.run(...toSqlParameters(params));\n return {\n changes: Number(result.changes ?? 0),\n lastInsertRowid:\n typeof result.lastInsertRowid === \"bigint\"\n ? Number(result.lastInsertRowid)\n : result.lastInsertRowid\n };\n }\n\n async get<T>(sql: string, params: unknown[] = []): Promise<T | undefined> {\n const statement = this.database.prepare(sql);\n return statement.get(...toSqlParameters(params)) as T | undefined;\n }\n\n async all<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const statement = this.database.prepare(sql);\n return statement.all(...toSqlParameters(params)) as T[];\n }\n\n async withTransaction<T>(callback: () => Promise<T>): Promise<T> {\n if (this.transactionDepth > 0) {\n return this.withSavepoint(`nested_${this.transactionDepth}`, callback);\n }\n\n this.transactionDepth += 1;\n this.database.exec(\"BEGIN\");\n try {\n const result = await callback();\n this.database.exec(\"COMMIT\");\n return result;\n } catch (error) {\n this.database.exec(\"ROLLBACK\");\n throw error;\n } finally {\n this.transactionDepth -= 1;\n }\n }\n\n async withSavepoint<T>(name: string, callback: () => Promise<T>): Promise<T> {\n const safeName = name.replaceAll(/[^a-zA-Z0-9_]/g, \"_\");\n this.database.exec(`SAVEPOINT ${safeName}`);\n try {\n const result = await callback();\n this.database.exec(`RELEASE SAVEPOINT ${safeName}`);\n return result;\n } catch (error) {\n this.database.exec(`ROLLBACK TO SAVEPOINT ${safeName}`);\n this.database.exec(`RELEASE SAVEPOINT ${safeName}`);\n throw error;\n }\n }\n\n async close(): Promise<void> {\n this.database.close();\n }\n}\n\n/**\n * Blob storage adapter that reads and writes files in a local directory.\n *\n * Each stored object is saved as a flat file named by its ID inside\n * `directory`. The directory is created automatically on first write.\n *\n * ```ts\n * const storage = new NodeFileStorageAdapter(\"./data/storage\");\n * ```\n *\n * In most cases you should use {@link createNodeSyncoreRuntime} which\n * instantiates this adapter automatically from your `storageDirectory` option.\n */\nexport class NodeFileStorageAdapter implements SyncoreStorageAdapter {\n constructor(private readonly directory: string) {}\n\n private filePath(id: string): string {\n return path.join(this.directory, id);\n }\n\n async put(id: string, input: StorageWriteInput): Promise<StorageObject> {\n await mkdir(this.directory, { recursive: true });\n const filePath = this.filePath(id);\n const bytes = normalizeData(input.data);\n await writeFile(filePath, bytes);\n return {\n id,\n path: filePath,\n size: bytes.byteLength,\n contentType: input.contentType ?? null\n };\n }\n\n async get(id: string): Promise<StorageObject | null> {\n const filePath = this.filePath(id);\n try {\n const info = await stat(filePath);\n return {\n id,\n path: filePath,\n size: info.size,\n contentType: null\n };\n } catch {\n return null;\n }\n }\n\n async read(id: string): Promise<Uint8Array | null> {\n try {\n return await readFile(this.filePath(id));\n } catch {\n return null;\n }\n }\n\n async delete(id: string): Promise<void> {\n await rm(this.filePath(id), { force: true });\n }\n\n async list(): Promise<StorageObject[]> {\n try {\n const entries = await readdir(this.directory, { withFileTypes: true });\n const objects = await Promise.all(\n entries\n .filter((entry) => entry.isFile())\n .map(async (entry) => {\n const filePath = this.filePath(entry.name);\n const info = await stat(filePath);\n return {\n id: entry.name,\n path: filePath,\n size: info.size,\n contentType: null\n } satisfies StorageObject;\n })\n );\n return objects;\n } catch {\n return [];\n }\n }\n}\n\nconst SESSION_ADJECTIVES = [\n \"Acrobatic\",\n \"Bold\",\n \"Cosmic\",\n \"Daring\",\n \"Electric\",\n \"Fierce\",\n \"Golden\",\n \"Hidden\",\n \"Iron\",\n \"Jade\",\n \"Keen\",\n \"Lunar\",\n \"Mystic\",\n \"Noble\",\n \"Orbital\",\n \"Primal\",\n \"Quick\",\n \"Radiant\",\n \"Shadow\",\n \"Turbo\",\n \"Ultra\",\n \"Vivid\",\n \"Wicked\",\n \"Xenon\",\n \"Zen\",\n \"Arctic\",\n \"Binary\",\n \"Cyber\",\n \"Digital\",\n \"Ember\",\n \"Frozen\",\n \"Galactic\",\n \"Hyper\",\n \"Infra\",\n \"Jumbo\",\n \"Kinetic\",\n \"Liquid\",\n \"Magnetic\",\n \"Neon\",\n \"Onyx\",\n \"Phantom\",\n \"Quantum\",\n \"Rapid\",\n \"Sonic\",\n \"Titan\",\n \"Velvet\",\n \"Wild\",\n \"Blazing\",\n \"Crystal\",\n \"Dynamic\"\n] as const;\n\nconst SESSION_NOUNS = [\n \"Phoenix\",\n \"Dragon\",\n \"Developer\",\n \"Hacker\",\n \"Wizard\",\n \"Runner\",\n \"Ranger\",\n \"Maverick\",\n \"Spartan\",\n \"Viking\",\n \"Sentinel\",\n \"Guardian\",\n \"Nomad\",\n \"Cipher\",\n \"Vector\",\n \"Matrix\",\n \"Prism\",\n \"Nebula\",\n \"Comet\",\n \"Pulse\",\n \"Vertex\",\n \"Flux\",\n \"Storm\",\n \"Blaze\",\n \"Frost\",\n \"Thunder\",\n \"Drift\"\n] as const;\n\nfunction generateUniqueSessionName(): string {\n const adj =\n SESSION_ADJECTIVES[Math.floor(Math.random() * SESSION_ADJECTIVES.length)]!;\n const noun = SESSION_NOUNS[Math.floor(Math.random() * SESSION_NOUNS.length)]!;\n return `${adj} ${noun}`;\n}\n\nfunction resolvePersistedDataSourceAlias(\n storageDirectory: string,\n storageIdentity: string\n): string {\n const metaDirectory = path.join(storageDirectory, DEVTOOLS_META_DIRECTORY);\n const aliasId = createHash(\"sha256\")\n .update(storageIdentity)\n .digest(\"hex\")\n .slice(0, 16);\n const aliasPath = path.join(\n metaDirectory,\n `${DATA_SOURCE_ALIAS_PREFIX}-${aliasId}.txt`\n );\n\n try {\n const existing = readFileSync(aliasPath, \"utf8\").trim();\n if (existing.length > 0) {\n return existing;\n }\n } catch {\n // Missing metadata is expected for a new data source.\n }\n\n const nextValue = generateUniqueSessionName();\n try {\n mkdirSync(metaDirectory, { recursive: true });\n writeFileSync(aliasPath, nextValue, \"utf8\");\n } catch {\n // The alias is a dashboard convenience; runtime startup must not depend on it.\n }\n return nextValue;\n}\n\n/**\n * Options for {@link createNodeSyncoreRuntime}.\n *\n * At minimum supply `databasePath`, `storageDirectory`, `schema`, and\n * `functions`. Everything else has sensible defaults (auto-devtools connect in\n * development, Node SQLite driver, local file storage).\n *\n * ```ts\n * createNodeSyncoreRuntime({\n * databasePath: path.join(dataDir, \"app.db\"),\n * storageDirectory: path.join(dataDir, \"storage\"),\n * schema,\n * functions,\n * });\n * ```\n */\nexport interface CreateNodeRuntimeOptions<\n TSchema extends NodeSyncoreSchema = NodeSyncoreSchema\n> {\n /**\n * Absolute or relative path to the SQLite database file.\n *\n * The file is created if it does not exist. Use an absolute path in\n * production to avoid ambiguity about the current working directory.\n */\n databasePath: string;\n /**\n * Directory where blob storage objects (images, files, etc.) are persisted.\n *\n * The directory is created automatically if it does not exist.\n */\n storageDirectory: string;\n /** The data model that defines the available tables and indexes. */\n schema: TSchema;\n /**\n * The registered function map. Use the `functions` export from\n * `syncore/_generated/functions.ts`.\n */\n functions: SyncoreRuntimeOptions<TSchema>[\"functions\"];\n /**\n * Resolved Syncore component instances. Only required when your app\n * installs Syncore component packages.\n */\n components?: SyncoreRuntimeOptions<TSchema>[\"components\"];\n /**\n * Platform capabilities injected into `ctx.capabilities` inside function\n * handlers.\n */\n capabilities?: SyncoreCapabilities;\n /** Human-readable app name shown in the devtools dashboard. */\n appName?: string;\n /** Origin label (e.g. process name) shown in devtools. */\n origin?: string;\n /** Devtools session label. Auto-generated when omitted. */\n sessionLabel?: string;\n /**\n * Platform label reported to devtools. Defaults to `\"node\"`, or\n * `\"electron-main\"` when the runtime is used inside Electron's main process.\n */\n platform?: string;\n /**\n * Devtools event sink. Pass `false` to disable devtools entirely (recommended\n * for production). Omit to auto-connect to the local devtools server when\n * running in development.\n */\n devtools?: DevtoolsSink | false;\n /**\n * Explicit devtools WebSocket server URL. Defaults to\n * `ws://localhost:3099` (the Syncore devtools default port).\n */\n devtoolsUrl?: string;\n /** Scheduler configuration for background and recurring jobs. */\n scheduler?: SchedulerOptions;\n}\n\n/**\n * Alias of {@link CreateNodeRuntimeOptions} exposed for the managed-client\n * helper.\n * @see CreateNodeRuntimeOptions\n */\nexport type WithNodeSyncoreClientOptions<\n TSchema extends NodeSyncoreSchema = NodeSyncoreSchema\n> = CreateNodeRuntimeOptions<TSchema>;\n\n/**\n * A started local Node runtime paired with its client and a dispose helper.\n *\n * Returned by `withNodeSyncoreClient()`. Call `dispose()` when you are\n * finished (e.g. in tests or short-lived scripts) to stop the runtime and\n * close the database.\n */\nexport interface ManagedNodeSyncoreClient<\n TSchema extends NodeSyncoreSchema = NodeSyncoreSchema\n> {\n /** The underlying runtime instance. */\n runtime: SyncoreRuntime<TSchema>;\n /** A ready-to-use client for calling Syncore functions. */\n client: ReturnType<SyncoreRuntime<TSchema>[\"createClient\"]>;\n /** Stop the runtime, flush pending jobs, and close the database. */\n dispose(): Promise<void>;\n}\n\n/**\n * Opaque handle returned by Syncore’s Electron IPC bridge setup.\n *\n * - `ready`: resolves when the bridge is connected and the renderer is ready to\n * receive messages.\n * - `dispose()`: tears down the bridge and removes IPC listeners.\n */\nexport interface SyncoreElectronIpcBinding {\n ready: Promise<void>;\n dispose(): Promise<void>;\n}\n\n/**\n * Minimal interface that Syncore requires from an Electron `BrowserWindow`\n * instance.\n *\n * Scoped to avoid importing Electron at the type level.\n */\nexport interface SyncoreElectronBridgeWindow {\n isDestroyed(): boolean;\n webContents: {\n send(channel: string, message: unknown): void;\n };\n}\n\n/**\n * Options for setting up Syncore’s Electron main-process IPC bridge.\n *\n * The bridge forwards database change events from the main-process runtime to\n * the renderer window over a named IPC channel.\n *\n * ```ts\n * createElectronSyncoreBridge(runtime, {\n * window: mainWindow,\n * onRendererMessage: (listener) => {\n * ipcMain.on(\"syncore\", (_e, msg) => listener(msg));\n * return () => ipcMain.removeAllListeners(\"syncore\");\n * },\n * });\n * ```\n */\nexport interface CreateElectronSyncoreBridgeOptions {\n /** The renderer window that will receive push messages. */\n window: SyncoreElectronBridgeWindow;\n /**\n * Register a listener for messages sent from the renderer. Must return an\n * unsubscribe function.\n */\n onRendererMessage(listener: (message: unknown) => void): () => void;\n /** IPC channel name. Defaults to `\"syncore\"`. */\n channel?: string;\n}\n\n/**\n * The subset of Electron’s `ipcMain` used by Syncore’s main-process helper.\n *\n * Using this narrowed interface avoids a hard runtime dependency on Electron.\n */\nexport interface SyncoreElectronIpcMain {\n on(\n channel: string,\n listener: (event: { sender: unknown }, message: unknown) => void\n ): void;\n off(\n channel: string,\n listener: (event: { sender: unknown }, message: unknown) => void\n ): void;\n}\n\n/**\n * Options for creating a client inside an Electron renderer process via the\n * preload IPC bridge.\n */\nexport interface CreateSyncoreRendererWindowClientOptions {\n /** Name of the bridge registered in the preload script. Defaults to `\"syncore\"`. */\n bridgeName?: string;\n}\n\n/**\n * Create a Syncore runtime for Node.js (or Electron’s main process) backed by\n * the built-in `node:sqlite` driver and local file storage.\n *\n * This is the recommended entry point for Node and Electron apps. It wires up\n * the SQL driver, storage adapter, devtools WebSocket connection, and\n * cross-process change signals automatically.\n *\n * ```ts\n * import path from \"node:path\";\n * import { createNodeSyncoreRuntime } from \"syncorejs/node\";\n * import schema from \"./syncore/schema\";\n * import { functions } from \"./syncore/_generated/functions\";\n *\n * const runtime = createNodeSyncoreRuntime({\n * databasePath: path.join(app.getPath(\"userData\"), \"db.sqlite\"),\n * storageDirectory: path.join(app.getPath(\"userData\"), \"storage\"),\n * schema,\n * functions,\n * });\n *\n * await runtime.start();\n * const client = runtime.createClient();\n * ```\n *\n * @param options - Configuration object. See {@link CreateNodeRuntimeOptions}.\n * @returns A configured (but not yet started) {@link SyncoreRuntime}. Call\n * `await runtime.start()` before using the client.\n */\nexport function createNodeSyncoreRuntime<\n TSchema extends NodeSyncoreSchema\n>(\n options: CreateNodeRuntimeOptions<TSchema>\n): SyncoreRuntime<TSchema> {\n const resolvedDevtoolsUrl =\n options.devtoolsUrl ?? resolveDefaultNodeDevtoolsUrl();\n const storageIdentity = `file::${path.resolve(options.databasePath)}`;\n const websocketDevtools =\n options.devtools === undefined &&\n resolvedDevtoolsUrl &&\n shouldAutoConnectNodeDevtools()\n ? createNodeWebSocketDevtoolsSink({\n url: resolvedDevtoolsUrl,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel\n ? { sessionLabel: options.sessionLabel }\n : {}),\n targetKind: \"client\",\n storageProtocol: \"file\",\n databaseLabel: path.basename(options.databasePath),\n dataSourceAlias: resolvePersistedDataSourceAlias(\n options.storageDirectory,\n storageIdentity\n ),\n storageIdentity,\n runtimeRole: \"app\",\n capabilities: createNodeDevtoolsCapabilities()\n })\n : undefined;\n const runtimeOptions: SyncoreRuntimeOptions<TSchema> = {\n schema: options.schema,\n functions: options.functions,\n ...(options.components ? { components: options.components } : {}),\n driver: new NodeSqliteDriver(options.databasePath),\n storage: new NodeFileStorageAdapter(options.storageDirectory),\n platform: options.platform ?? \"node\"\n };\n if (options.capabilities) {\n runtimeOptions.capabilities = options.capabilities;\n }\n const resolvedDevtools =\n options.devtools === false\n ? undefined\n : (options.devtools ?? websocketDevtools);\n if (resolvedDevtools) {\n runtimeOptions.devtools = resolvedDevtools;\n }\n if (websocketDevtools?.externalChangeSignal) {\n runtimeOptions.externalChangeSignal = websocketDevtools.externalChangeSignal;\n }\n if (options.scheduler) {\n runtimeOptions.scheduler = options.scheduler;\n }\n const runtime = new SyncoreRuntime(runtimeOptions);\n websocketDevtools?.attachRuntime(runtime);\n if (websocketDevtools) {\n websocketDevtools.attachCommandHandler(\n createDevtoolsCommandHandler({\n driver: runtimeOptions.driver,\n schema: options.schema,\n functions: options.functions,\n admin: runtime.getAdmin()\n })\n );\n websocketDevtools.attachSubscriptionHost(\n createDevtoolsSubscriptionHost({\n driver: runtimeOptions.driver,\n schema: options.schema,\n functions: options.functions,\n admin: runtime.getAdmin()\n })\n );\n const stop = runtime.stop.bind(runtime);\n runtime.stop = async () => {\n websocketDevtools.dispose();\n await stop();\n };\n }\n return runtime;\n}\n\n/**\n * Create a same-process Syncore client from a started Node runtime.\n */\nexport function createNodeSyncoreClient<\n TSchema extends NodeSyncoreSchema\n>(runtime: SyncoreRuntime<TSchema>) {\n return runtime.createClient();\n}\n\n/**\n * Start a Node Syncore runtime and return its client together with a dispose helper.\n */\nexport async function createManagedNodeSyncoreClient<\n TSchema extends NodeSyncoreSchema\n>(\n options: WithNodeSyncoreClientOptions<TSchema>\n): Promise<ManagedNodeSyncoreClient<TSchema>> {\n const runtime = createNodeSyncoreRuntime(options);\n await runtime.start();\n return {\n runtime,\n client: runtime.createClient(),\n async dispose() {\n await runtime.stop();\n }\n };\n}\n\n/**\n * Run a callback with a started local Node Syncore client and always stop the runtime.\n *\n * @example\n * ```ts\n * await withNodeSyncoreClient(options, async (client) => {\n * console.log(await client.query(api.tasks.list));\n * });\n * ```\n */\nexport async function withNodeSyncoreClient<\n TSchema extends NodeSyncoreSchema,\n TResult\n>(\n options: WithNodeSyncoreClientOptions<TSchema>,\n callback: (\n client: ReturnType<SyncoreRuntime<TSchema>[\"createClient\"]>,\n runtime: SyncoreRuntime<TSchema>\n ) => Promise<TResult> | TResult\n): Promise<TResult> {\n const managed = await createManagedNodeSyncoreClient(options);\n try {\n return await callback(managed.client, managed.runtime);\n } finally {\n await managed.dispose();\n }\n}\n\n/**\n * Create the default Electron main-process bridge used to connect a BrowserWindow\n * to a Syncore runtime.\n */\nexport function createElectronSyncoreBridge(\n options: CreateElectronSyncoreBridgeOptions\n) {\n const channel = options.channel ?? \"syncore:message\";\n return createNodeIpcMessageEndpoint({\n postMessage(message: unknown) {\n if (!options.window.isDestroyed()) {\n options.window.webContents.send(channel, message);\n }\n },\n onMessage(listener: (message: unknown) => void) {\n return options.onRendererMessage(listener);\n }\n });\n}\n\n/**\n * Bind a BrowserWindow to a Syncore runtime with the default Electron IPC transport.\n */\nexport function bindElectronWindowToSyncoreRuntime(options: {\n runtime: SyncoreRuntime<NodeSyncoreSchema>;\n window: SyncoreElectronBridgeWindow;\n onRendererMessage(listener: (message: unknown) => void): () => void;\n channel?: string;\n}): SyncoreElectronIpcBinding;\nexport function bindElectronWindowToSyncoreRuntime(options: {\n runtime: SyncoreRuntime<NodeSyncoreSchema>;\n window: SyncoreElectronBridgeWindow;\n ipcMain: SyncoreElectronIpcMain;\n channel?: string;\n}): SyncoreElectronIpcBinding;\nexport function bindElectronWindowToSyncoreRuntime(options: {\n runtime: SyncoreRuntime<NodeSyncoreSchema>;\n window: SyncoreElectronBridgeWindow;\n onRendererMessage?(listener: (message: unknown) => void): () => void;\n ipcMain?: SyncoreElectronIpcMain;\n channel?: string;\n}): SyncoreElectronIpcBinding {\n const cleanupCallbacks: Array<() => void> = [];\n const channel = options.channel ?? \"syncore:message\";\n let onRendererMessage:\n | ((listener: (message: unknown) => void) => () => void)\n | undefined;\n\n if (!options.onRendererMessage) {\n if (!options.ipcMain) {\n throw new Error(\n \"bindElectronWindowToSyncoreRuntime requires either onRendererMessage() or ipcMain.\"\n );\n }\n const listeners = new Set<(message: unknown) => void>();\n const handleRendererMessage = (\n event: { sender: unknown },\n message: unknown\n ) => {\n if (event.sender !== options.window.webContents) {\n return;\n }\n for (const listener of listeners) {\n listener(message);\n }\n };\n options.ipcMain.on(channel, handleRendererMessage);\n cleanupCallbacks.push(() => {\n options.ipcMain?.off(channel, handleRendererMessage);\n listeners.clear();\n });\n onRendererMessage = (listener) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n };\n } else {\n onRendererMessage = (listener) => options.onRendererMessage!(listener);\n }\n\n const endpoint = createElectronSyncoreBridge({\n window: options.window,\n onRendererMessage,\n channel\n });\n const attachedRuntime = attachNodeIpcRuntime({\n endpoint,\n createRuntime: () => options.runtime\n });\n\n return {\n ready: attachedRuntime.ready,\n async dispose() {\n await attachedRuntime.dispose();\n endpoint.dispose();\n for (const cleanup of cleanupCallbacks) {\n cleanup();\n }\n }\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Devtools request handler */\n/* ------------------------------------------------------------------ */\n\nexport interface NodeWebSocketDevtoolsSinkOptions {\n url: string;\n reconnectDelayMs?: number;\n appName?: string;\n origin?: string;\n sessionLabel?: string;\n targetKind?: \"client\" | \"project\";\n runtimeRole?: \"app\" | \"project-target\";\n storageProtocol?: string;\n databaseLabel?: string;\n dataSourceAlias?: string;\n storageIdentity?: string;\n capabilities?: SyncoreDevtoolsCapabilities;\n}\n\nexport interface NodeWebSocketDevtoolsSink extends DevtoolsSink {\n attachRuntime(runtime: SyncoreRuntime<NodeSyncoreSchema>): void;\n attachCommandHandler(handler: DevtoolsCommandHandler): void;\n attachSubscriptionHost(host: DevtoolsSubscriptionHost): void;\n externalChangeSignal?: SyncoreExternalChangeSignal;\n dispose(): void;\n}\n\nexport function createNodeWebSocketDevtoolsSink(\n options: NodeWebSocketDevtoolsSinkOptions\n): NodeWebSocketDevtoolsSink {\n let socket: WebSocket | undefined;\n let disposed = false;\n let connectTimer: ReturnType<typeof setTimeout> | undefined;\n let getSummary: (() => SyncoreRuntimeSummary) | undefined;\n let onCommand: DevtoolsCommandHandler | undefined;\n let subscriptionHost: DevtoolsSubscriptionHost | undefined;\n const externalChangeListeners = new Set<\n (event: SyncoreExternalChangeEvent) => void\n >();\n const pendingMessages: SyncoreDevtoolsMessage[] = [];\n let latestHello:\n | {\n runtimeId: string;\n platform: string;\n }\n | undefined;\n\n const connect = () => {\n if (disposed) {\n return;\n }\n socket = new WebSocket(options.url);\n socket.on(\"open\", () => {\n if (latestHello) {\n sendNow({\n type: \"hello\",\n protocolVersion: SYNCORE_DEVTOOLS_PROTOCOL_VERSION,\n minSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,\n maxSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,\n runtimeId: latestHello.runtimeId,\n platform: latestHello.platform,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel\n ? { sessionLabel: options.sessionLabel }\n : {}),\n ...(options.targetKind ? { targetKind: options.targetKind } : {}),\n ...(options.runtimeRole ? { runtimeRole: options.runtimeRole } : {}),\n ...(options.storageProtocol\n ? { storageProtocol: options.storageProtocol }\n : {}),\n ...(options.databaseLabel ? { databaseLabel: options.databaseLabel } : {}),\n ...(options.dataSourceAlias\n ? { dataSourceAlias: options.dataSourceAlias }\n : {}),\n ...(options.storageIdentity\n ? { storageIdentity: options.storageIdentity }\n : {}),\n capabilities: options.capabilities ?? createNodeDevtoolsCapabilities()\n });\n }\n flushPendingMessages();\n });\n socket.on(\"message\", (payload) => {\n const rawPayload =\n typeof payload === \"string\"\n ? payload\n : payload instanceof Buffer\n ? payload.toString(\"utf8\")\n : Array.isArray(payload)\n ? Buffer.concat(payload).toString(\"utf8\")\n : payload instanceof ArrayBuffer\n ? Buffer.from(payload).toString(\"utf8\")\n : Buffer.from(\n payload.buffer,\n payload.byteOffset,\n payload.byteLength\n ).toString(\"utf8\");\n if (rawPayload.length === 0) {\n return;\n }\n const message = JSON.parse(rawPayload) as\n | SyncoreDevtoolsMessage\n | SyncoreDevtoolsClientMessage;\n if (message.type === \"ping\") {\n send({ type: \"pong\" });\n } else if (message.type === \"external.change\") {\n for (const listener of externalChangeListeners) {\n listener(message.event as SyncoreExternalChangeEvent);\n }\n } else if (message.type === \"command\" && onCommand) {\n onCommand(message.payload)\n .then((responsePayload) => {\n const runtimeId =\n latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"command.result\",\n commandId: message.commandId,\n runtimeId,\n payload: responsePayload\n });\n })\n .catch((err) => {\n const runtimeId =\n latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"command.result\",\n commandId: message.commandId,\n runtimeId,\n payload: {\n kind: \"error\",\n message: err instanceof Error ? err.message : \"Unknown error\"\n }\n });\n });\n } else if (message.type === \"subscribe\" && subscriptionHost) {\n void subscriptionHost.subscribe(\n message.subscriptionId,\n message.payload,\n (payload) => {\n const runtimeId =\n latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"subscription.data\",\n subscriptionId: message.subscriptionId,\n runtimeId,\n payload\n });\n }\n );\n } else if (message.type === \"unsubscribe\") {\n subscriptionHost?.unsubscribe(message.subscriptionId);\n }\n });\n socket.on(\"close\", scheduleReconnect);\n socket.on(\"error\", scheduleReconnect);\n };\n\n const scheduleReconnect = () => {\n if (disposed || connectTimer) {\n return;\n }\n connectTimer = setTimeout(() => {\n connectTimer = undefined;\n connect();\n }, options.reconnectDelayMs ?? 1200);\n };\n\n const sendNow = (message: SyncoreDevtoolsMessage) => {\n if (socket?.readyState === WebSocket.OPEN) {\n socket.send(JSON.stringify(message));\n }\n };\n\n const flushPendingMessages = () => {\n while (pendingMessages.length > 0) {\n const nextMessage = pendingMessages.shift();\n if (!nextMessage) {\n continue;\n }\n sendNow(nextMessage);\n }\n };\n\n const send = (message: SyncoreDevtoolsMessage) => {\n if (socket?.readyState === WebSocket.OPEN) {\n sendNow(message);\n return;\n }\n pendingMessages.push(message);\n };\n\n connect();\n\n const sink: NodeWebSocketDevtoolsSink = {\n emit(event) {\n if (event.type === \"runtime.connected\") {\n latestHello = {\n runtimeId: event.runtimeId,\n platform: event.platform\n };\n send({\n type: \"hello\",\n protocolVersion: SYNCORE_DEVTOOLS_PROTOCOL_VERSION,\n minSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,\n maxSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,\n runtimeId: event.runtimeId,\n platform: event.platform,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel\n ? { sessionLabel: options.sessionLabel }\n : {}),\n ...(options.targetKind ? { targetKind: options.targetKind } : {}),\n ...(options.runtimeRole ? { runtimeRole: options.runtimeRole } : {}),\n ...(options.storageProtocol\n ? { storageProtocol: options.storageProtocol }\n : {}),\n ...(options.databaseLabel ? { databaseLabel: options.databaseLabel } : {}),\n ...(options.dataSourceAlias\n ? { dataSourceAlias: options.dataSourceAlias }\n : {}),\n ...(options.storageIdentity\n ? { storageIdentity: options.storageIdentity }\n : {}),\n capabilities: options.capabilities ?? createNodeDevtoolsCapabilities()\n });\n }\n send({\n type: \"event\",\n event\n });\n },\n attachRuntime(runtime) {\n getSummary = () =>\n withRuntimeSummaryMeta(runtime.getAdmin().getRuntimeSummary(), options);\n },\n attachCommandHandler(handler) {\n onCommand = handler;\n },\n attachSubscriptionHost(host) {\n subscriptionHost = host;\n },\n dispose() {\n disposed = true;\n if (connectTimer) {\n clearTimeout(connectTimer);\n }\n subscriptionHost?.dispose();\n socket?.close();\n }\n };\n if (options.storageIdentity) {\n sink.externalChangeSignal = {\n subscribe(listener) {\n externalChangeListeners.add(listener);\n return () => {\n externalChangeListeners.delete(listener);\n };\n },\n publish(event) {\n const runtimeId = latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"external.change\",\n runtimeId,\n storageIdentity: options.storageIdentity!,\n event: event as SyncoreDevtoolsExternalChangeEvent\n });\n },\n close() {\n externalChangeListeners.clear();\n }\n };\n }\n return sink;\n}\n\nfunction withRuntimeSummaryMeta(\n summary: SyncoreRuntimeSummary,\n options: NodeWebSocketDevtoolsSinkOptions\n): SyncoreRuntimeSummary {\n return {\n ...summary,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel ? { sessionLabel: options.sessionLabel } : {}),\n ...(options.targetKind ? { targetKind: options.targetKind } : {}),\n ...(options.runtimeRole ? { runtimeRole: options.runtimeRole } : {}),\n ...(options.storageProtocol\n ? { storageProtocol: options.storageProtocol }\n : {}),\n ...(options.databaseLabel ? { databaseLabel: options.databaseLabel } : {}),\n ...(options.dataSourceAlias ? { dataSourceAlias: options.dataSourceAlias } : {}),\n ...(options.storageIdentity\n ? { storageIdentity: options.storageIdentity }\n : {}),\n capabilities: options.capabilities ?? createNodeDevtoolsCapabilities()\n };\n}\n\nfunction createNodeDevtoolsCapabilities(): SyncoreDevtoolsCapabilities {\n return {\n sql: {\n read: false,\n write: false,\n live: false,\n reason: \"SQL Console is provided by the Project Target for this data source.\"\n },\n data: {\n browse: true,\n mutate: true,\n importExport: true\n },\n scheduler: {\n read: true,\n edit: true\n }\n };\n}\n\nfunction shouldAutoConnectNodeDevtools(): boolean {\n return process.env.NODE_ENV !== \"production\";\n}\n\nfunction resolveDefaultNodeDevtoolsUrl(): string | undefined {\n if (process.env.SYNCORE_DISABLE_DEVTOOLS === \"1\") {\n return undefined;\n }\n return process.env.SYNCORE_DEVTOOLS_URL ?? \"ws://127.0.0.1:4311\";\n}\n"],"mappings":";;;;;;;;;;AAuDA,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;AAEjC,SAAS,cAAc,OAA8C;CACnE,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,KAAK,KAAK;CAE1B,IAAI,iBAAiB,YACnB,OAAO;CAET,OAAO,IAAI,WAAW,KAAK;AAC7B;AAEA,SAAS,gBAAgB,QAAoC;CAC3D,OAAO;AACT;;;;;;;;;;;;;;AAeA,IAAa,mBAAb,MAA0D;CAInC;CAHrB;CACA,mBAA2B;CAE3B,YAAY,cAA+B;EAAtB,KAAA,eAAA;EACnB,KAAK,WAAW,IAAI,aAAa,YAAY;EAC7C,KAAK,SAAS,KAAK,2BAA2B;EAC9C,KAAK,SAAS,KAAK,4BAA4B;CACjD;CAEA,MAAM,KAAK,KAA4B;EACrC,KAAK,SAAS,KAAK,GAAG;CACxB;CAEA,MAAM,IACJ,KACA,SAAoB,CAAC,GAC4C;EAEjE,MAAM,SADY,KAAK,SAAS,QAAQ,GACjB,EAAE,IAAI,GAAG,gBAAgB,MAAM,CAAC;EACvD,OAAO;GACL,SAAS,OAAO,OAAO,WAAW,CAAC;GACnC,iBACE,OAAO,OAAO,oBAAoB,WAC9B,OAAO,OAAO,eAAe,IAC7B,OAAO;EACf;CACF;CAEA,MAAM,IAAO,KAAa,SAAoB,CAAC,GAA2B;EAExE,OADkB,KAAK,SAAS,QAAQ,GACzB,EAAE,IAAI,GAAG,gBAAgB,MAAM,CAAC;CACjD;CAEA,MAAM,IAAO,KAAa,SAAoB,CAAC,GAAiB;EAE9D,OADkB,KAAK,SAAS,QAAQ,GACzB,EAAE,IAAI,GAAG,gBAAgB,MAAM,CAAC;CACjD;CAEA,MAAM,gBAAmB,UAAwC;EAC/D,IAAI,KAAK,mBAAmB,GAC1B,OAAO,KAAK,cAAc,UAAU,KAAK,oBAAoB,QAAQ;EAGvE,KAAK,oBAAoB;EACzB,KAAK,SAAS,KAAK,OAAO;EAC1B,IAAI;GACF,MAAM,SAAS,MAAM,SAAS;GAC9B,KAAK,SAAS,KAAK,QAAQ;GAC3B,OAAO;EACT,SAAS,OAAO;GACd,KAAK,SAAS,KAAK,UAAU;GAC7B,MAAM;EACR,UAAU;GACR,KAAK,oBAAoB;EAC3B;CACF;CAEA,MAAM,cAAiB,MAAc,UAAwC;EAC3E,MAAM,WAAW,KAAK,WAAW,kBAAkB,GAAG;EACtD,KAAK,SAAS,KAAK,aAAa,UAAU;EAC1C,IAAI;GACF,MAAM,SAAS,MAAM,SAAS;GAC9B,KAAK,SAAS,KAAK,qBAAqB,UAAU;GAClD,OAAO;EACT,SAAS,OAAO;GACd,KAAK,SAAS,KAAK,yBAAyB,UAAU;GACtD,KAAK,SAAS,KAAK,qBAAqB,UAAU;GAClD,MAAM;EACR;CACF;CAEA,MAAM,QAAuB;EAC3B,KAAK,SAAS,MAAM;CACtB;AACF;;;;;;;;;;;;;;AAeA,IAAa,yBAAb,MAAqE;CACtC;CAA7B,YAAY,WAAoC;EAAnB,KAAA,YAAA;CAAoB;CAEjD,SAAiB,IAAoB;EACnC,OAAO,KAAK,KAAK,KAAK,WAAW,EAAE;CACrC;CAEA,MAAM,IAAI,IAAY,OAAkD;EACtE,MAAM,MAAM,KAAK,WAAW,EAAE,WAAW,KAAK,CAAC;EAC/C,MAAM,WAAW,KAAK,SAAS,EAAE;EACjC,MAAM,QAAQ,cAAc,MAAM,IAAI;EACtC,MAAM,UAAU,UAAU,KAAK;EAC/B,OAAO;GACL;GACA,MAAM;GACN,MAAM,MAAM;GACZ,aAAa,MAAM,eAAe;EACpC;CACF;CAEA,MAAM,IAAI,IAA2C;EACnD,MAAM,WAAW,KAAK,SAAS,EAAE;EACjC,IAAI;GAEF,OAAO;IACL;IACA,MAAM;IACN,OAAM,MAJW,KAAK,QAAQ,GAInB;IACX,aAAa;GACf;EACF,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,KAAK,IAAwC;EACjD,IAAI;GACF,OAAO,MAAM,SAAS,KAAK,SAAS,EAAE,CAAC;EACzC,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,OAAO,IAA2B;EACtC,MAAM,GAAG,KAAK,SAAS,EAAE,GAAG,EAAE,OAAO,KAAK,CAAC;CAC7C;CAEA,MAAM,OAAiC;EACrC,IAAI;GACF,MAAM,UAAU,MAAM,QAAQ,KAAK,WAAW,EAAE,eAAe,KAAK,CAAC;GAerE,OAAO,MAde,QAAQ,IAC5B,QACG,QAAQ,UAAU,MAAM,OAAO,CAAC,EAChC,IAAI,OAAO,UAAU;IACpB,MAAM,WAAW,KAAK,SAAS,MAAM,IAAI;IACzC,MAAM,OAAO,MAAM,KAAK,QAAQ;IAChC,OAAO;KACL,IAAI,MAAM;KACV,MAAM;KACN,MAAM,KAAK;KACX,aAAa;IACf;GACF,CAAC,CACL;EAEF,QAAQ;GACN,OAAO,CAAC;EACV;CACF;AACF;AAEA,MAAM,qBAAqB;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,4BAAoC;CAI3C,OAAO,GAFL,mBAAmB,KAAK,MAAM,KAAK,OAAO,IAAI,mBAAmB,MAAM,GAE3D,GADD,cAAc,KAAK,MAAM,KAAK,OAAO,IAAI,cAAc,MAAM;AAE5E;AAEA,SAAS,gCACP,kBACA,iBACQ;CACR,MAAM,gBAAgB,KAAK,KAAK,kBAAkB,uBAAuB;CACzE,MAAM,UAAU,WAAW,QAAQ,EAChC,OAAO,eAAe,EACtB,OAAO,KAAK,EACZ,MAAM,GAAG,EAAE;CACd,MAAM,YAAY,KAAK,KACrB,eACA,GAAG,yBAAyB,GAAG,QAAQ,KACzC;CAEA,IAAI;EACF,MAAM,WAAW,aAAa,WAAW,MAAM,EAAE,KAAK;EACtD,IAAI,SAAS,SAAS,GACpB,OAAO;CAEX,QAAQ,CAER;CAEA,MAAM,YAAY,0BAA0B;CAC5C,IAAI;EACF,UAAU,eAAe,EAAE,WAAW,KAAK,CAAC;EAC5C,cAAc,WAAW,WAAW,MAAM;CAC5C,QAAQ,CAER;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmNA,SAAgB,yBAGd,SACyB;CACzB,MAAM,sBACJ,QAAQ,eAAe,8BAA8B;CACvD,MAAM,kBAAkB,SAAS,KAAK,QAAQ,QAAQ,YAAY;CAClE,MAAM,oBACJ,QAAQ,aAAa,KAAA,KACrB,uBACA,8BAA8B,IAC1B,gCAAgC;EAC9B,KAAK;EACL,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;EACtD,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;EACnD,GAAI,QAAQ,eACR,EAAE,cAAc,QAAQ,aAAa,IACrC,CAAC;EACL,YAAY;EACZ,iBAAiB;EACjB,eAAe,KAAK,SAAS,QAAQ,YAAY;EACjD,iBAAiB,gCACf,QAAQ,kBACR,eACF;EACA;EACA,aAAa;EACb,cAAc,+BAA+B;CAC/C,CAAC,IACD,KAAA;CACN,MAAM,iBAAiD;EACrD,QAAQ,QAAQ;EAChB,WAAW,QAAQ;EACnB,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;EAC/D,QAAQ,IAAI,iBAAiB,QAAQ,YAAY;EACjD,SAAS,IAAI,uBAAuB,QAAQ,gBAAgB;EAC5D,UAAU,QAAQ,YAAY;CAChC;CACA,IAAI,QAAQ,cACV,eAAe,eAAe,QAAQ;CAExC,MAAM,mBACJ,QAAQ,aAAa,QACjB,KAAA,IACC,QAAQ,YAAY;CAC3B,IAAI,kBACF,eAAe,WAAW;CAE5B,IAAI,mBAAmB,sBACrB,eAAe,uBAAuB,kBAAkB;CAE1D,IAAI,QAAQ,WACV,eAAe,YAAY,QAAQ;CAErC,MAAM,UAAU,IAAI,eAAe,cAAc;CACjD,mBAAmB,cAAc,OAAO;CACxC,IAAI,mBAAmB;EACrB,kBAAkB,qBAChB,6BAA6B;GAC3B,QAAQ,eAAe;GACvB,QAAQ,QAAQ;GAChB,WAAW,QAAQ;GACnB,OAAO,QAAQ,SAAS;EAC1B,CAAC,CACH;EACA,kBAAkB,uBAChB,+BAA+B;GAC7B,QAAQ,eAAe;GACvB,QAAQ,QAAQ;GAChB,WAAW,QAAQ;GACnB,OAAO,QAAQ,SAAS;EAC1B,CAAC,CACH;EACA,MAAM,OAAO,QAAQ,KAAK,KAAK,OAAO;EACtC,QAAQ,OAAO,YAAY;GACzB,kBAAkB,QAAQ;GAC1B,MAAM,KAAK;EACb;CACF;CACA,OAAO;AACT;;;;AAKA,SAAgB,wBAEd,SAAkC;CAClC,OAAO,QAAQ,aAAa;AAC9B;;;;AAKA,eAAsB,+BAGpB,SAC4C;CAC5C,MAAM,UAAU,yBAAyB,OAAO;CAChD,MAAM,QAAQ,MAAM;CACpB,OAAO;EACL;EACA,QAAQ,QAAQ,aAAa;EAC7B,MAAM,UAAU;GACd,MAAM,QAAQ,KAAK;EACrB;CACF;AACF;;;;;;;;;;;AAYA,eAAsB,sBAIpB,SACA,UAIkB;CAClB,MAAM,UAAU,MAAM,+BAA+B,OAAO;CAC5D,IAAI;EACF,OAAO,MAAM,SAAS,QAAQ,QAAQ,QAAQ,OAAO;CACvD,UAAU;EACR,MAAM,QAAQ,QAAQ;CACxB;AACF;;;;;AAMA,SAAgB,4BACd,SACA;CACA,MAAM,UAAU,QAAQ,WAAW;CACnC,OAAO,6BAA6B;EAClC,YAAY,SAAkB;GAC5B,IAAI,CAAC,QAAQ,OAAO,YAAY,GAC9B,QAAQ,OAAO,YAAY,KAAK,SAAS,OAAO;EAEpD;EACA,UAAU,UAAsC;GAC9C,OAAO,QAAQ,kBAAkB,QAAQ;EAC3C;CACF,CAAC;AACH;AAiBA,SAAgB,mCAAmC,SAMrB;CAC5B,MAAM,mBAAsC,CAAC;CAC7C,MAAM,UAAU,QAAQ,WAAW;CACnC,IAAI;CAIJ,IAAI,CAAC,QAAQ,mBAAmB;EAC9B,IAAI,CAAC,QAAQ,SACX,MAAM,IAAI,MACR,oFACF;EAEF,MAAM,4BAAY,IAAI,IAAgC;EACtD,MAAM,yBACJ,OACA,YACG;GACH,IAAI,MAAM,WAAW,QAAQ,OAAO,aAClC;GAEF,KAAK,MAAM,YAAY,WACrB,SAAS,OAAO;EAEpB;EACA,QAAQ,QAAQ,GAAG,SAAS,qBAAqB;EACjD,iBAAiB,WAAW;GAC1B,QAAQ,SAAS,IAAI,SAAS,qBAAqB;GACnD,UAAU,MAAM;EAClB,CAAC;EACD,qBAAqB,aAAa;GAChC,UAAU,IAAI,QAAQ;GACtB,aAAa,UAAU,OAAO,QAAQ;EACxC;CACF,OACE,qBAAqB,aAAa,QAAQ,kBAAmB,QAAQ;CAGvE,MAAM,WAAW,4BAA4B;EAC3C,QAAQ,QAAQ;EAChB;EACA;CACF,CAAC;CACD,MAAM,kBAAkB,qBAAqB;EAC3C;EACA,qBAAqB,QAAQ;CAC/B,CAAC;CAED,OAAO;EACL,OAAO,gBAAgB;EACvB,MAAM,UAAU;GACd,MAAM,gBAAgB,QAAQ;GAC9B,SAAS,QAAQ;GACjB,KAAK,MAAM,WAAW,kBACpB,QAAQ;EAEZ;CACF;AACF;AA6BA,SAAgB,gCACd,SAC2B;CAC3B,IAAI;CACJ,IAAI,WAAW;CACf,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM,0CAA0B,IAAI,IAElC;CACF,MAAM,kBAA4C,CAAC;CACnD,IAAI;CAOJ,MAAM,gBAAgB;EACpB,IAAI,UACF;EAEF,SAAS,IAAI,UAAU,QAAQ,GAAG;EAClC,OAAO,GAAG,cAAc;GACtB,IAAI,aACF,QAAQ;IACN,MAAM;IACN,iBAAiB;IACjB,6BACE;IACF,6BACE;IACF,WAAW,YAAY;IACvB,UAAU,YAAY;IACtB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;IACtD,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;IACnD,GAAI,QAAQ,eACR,EAAE,cAAc,QAAQ,aAAa,IACrC,CAAC;IACL,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;IAC/D,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;IAClE,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;IACL,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;IACxE,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;IACL,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;IACL,cAAc,QAAQ,gBAAgB,+BAA+B;GACvE,CAAC;GAEH,qBAAqB;EACvB,CAAC;EACD,OAAO,GAAG,YAAY,YAAY;GAChC,MAAM,aACJ,OAAO,YAAY,WACf,UACA,mBAAmB,SACjB,QAAQ,SAAS,MAAM,IACvB,MAAM,QAAQ,OAAO,IACnB,OAAO,OAAO,OAAO,EAAE,SAAS,MAAM,IACtC,mBAAmB,cACjB,OAAO,KAAK,OAAO,EAAE,SAAS,MAAM,IACpC,OAAO,KACL,QAAQ,QACR,QAAQ,YACR,QAAQ,UACV,EAAE,SAAS,MAAM;GAC7B,IAAI,WAAW,WAAW,GACxB;GAEF,MAAM,UAAU,KAAK,MAAM,UAAU;GAGrC,IAAI,QAAQ,SAAS,QACnB,KAAK,EAAE,MAAM,OAAO,CAAC;QAChB,IAAI,QAAQ,SAAS,mBAC1B,KAAK,MAAM,YAAY,yBACrB,SAAS,QAAQ,KAAmC;QAEjD,IAAI,QAAQ,SAAS,aAAa,WACvC,UAAU,QAAQ,OAAO,EACtB,MAAM,oBAAoB;IACzB,MAAM,YACJ,aAAa,aAAa,aAAa,EAAE;IAC3C,IAAI,CAAC,WACH;IAEF,KAAK;KACH,MAAM;KACN,WAAW,QAAQ;KACnB;KACA,SAAS;IACX,CAAC;GACH,CAAC,EACA,OAAO,QAAQ;IACd,MAAM,YACJ,aAAa,aAAa,aAAa,EAAE;IAC3C,IAAI,CAAC,WACH;IAEF,KAAK;KACH,MAAM;KACN,WAAW,QAAQ;KACnB;KACA,SAAS;MACP,MAAM;MACN,SAAS,eAAe,QAAQ,IAAI,UAAU;KAChD;IACF,CAAC;GACH,CAAC;QACE,IAAI,QAAQ,SAAS,eAAe,kBACzC,iBAAsB,UACpB,QAAQ,gBACR,QAAQ,UACP,YAAY;IACX,MAAM,YACJ,aAAa,aAAa,aAAa,EAAE;IAC3C,IAAI,CAAC,WACH;IAEF,KAAK;KACH,MAAM;KACN,gBAAgB,QAAQ;KACxB;KACA;IACF,CAAC;GACH,CACF;QACK,IAAI,QAAQ,SAAS,eAC1B,kBAAkB,YAAY,QAAQ,cAAc;EAExD,CAAC;EACD,OAAO,GAAG,SAAS,iBAAiB;EACpC,OAAO,GAAG,SAAS,iBAAiB;CACtC;CAEA,MAAM,0BAA0B;EAC9B,IAAI,YAAY,cACd;EAEF,eAAe,iBAAiB;GAC9B,eAAe,KAAA;GACf,QAAQ;EACV,GAAG,QAAQ,oBAAoB,IAAI;CACrC;CAEA,MAAM,WAAW,YAAoC;EACnD,IAAI,QAAQ,eAAe,UAAU,MACnC,OAAO,KAAK,KAAK,UAAU,OAAO,CAAC;CAEvC;CAEA,MAAM,6BAA6B;EACjC,OAAO,gBAAgB,SAAS,GAAG;GACjC,MAAM,cAAc,gBAAgB,MAAM;GAC1C,IAAI,CAAC,aACH;GAEF,QAAQ,WAAW;EACrB;CACF;CAEA,MAAM,QAAQ,YAAoC;EAChD,IAAI,QAAQ,eAAe,UAAU,MAAM;GACzC,QAAQ,OAAO;GACf;EACF;EACA,gBAAgB,KAAK,OAAO;CAC9B;CAEA,QAAQ;CAER,MAAM,OAAkC;EACtC,KAAK,OAAO;GACV,IAAI,MAAM,SAAS,qBAAqB;IACtC,cAAc;KACZ,WAAW,MAAM;KACjB,UAAU,MAAM;IAClB;IACA,KAAK;KACH,MAAM;KACN,iBAAiB;KACjB,6BACE;KACF,6BACE;KACF,WAAW,MAAM;KACjB,UAAU,MAAM;KAChB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;KACtD,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;KACnD,GAAI,QAAQ,eACR,EAAE,cAAc,QAAQ,aAAa,IACrC,CAAC;KACL,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;KAC/D,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;KAClE,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;KACL,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;KACxE,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;KACL,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;KACL,cAAc,QAAQ,gBAAgB,+BAA+B;IACvE,CAAC;GACH;GACA,KAAK;IACH,MAAM;IACN;GACF,CAAC;EACH;EACA,cAAc,SAAS;GACrB,mBACE,uBAAuB,QAAQ,SAAS,EAAE,kBAAkB,GAAG,OAAO;EAC1E;EACA,qBAAqB,SAAS;GAC5B,YAAY;EACd;EACA,uBAAuB,MAAM;GAC3B,mBAAmB;EACrB;EACA,UAAU;GACR,WAAW;GACX,IAAI,cACF,aAAa,YAAY;GAE3B,kBAAkB,QAAQ;GAC1B,QAAQ,MAAM;EAChB;CACF;CACA,IAAI,QAAQ,iBACV,KAAK,uBAAuB;EAC1B,UAAU,UAAU;GAClB,wBAAwB,IAAI,QAAQ;GACpC,aAAa;IACX,wBAAwB,OAAO,QAAQ;GACzC;EACF;EACA,QAAQ,OAAO;GACb,MAAM,YAAY,aAAa,aAAa,aAAa,EAAE;GAC3D,IAAI,CAAC,WACH;GAEF,KAAK;IACH,MAAM;IACN;IACA,iBAAiB,QAAQ;IAClB;GACT,CAAC;EACH;EACA,QAAQ;GACN,wBAAwB,MAAM;EAChC;CACF;CAEF,OAAO;AACT;AAEA,SAAS,uBACP,SACA,SACuB;CACvB,OAAO;EACL,GAAG;EACH,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;EACtD,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;EACnD,GAAI,QAAQ,eAAe,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;EACrE,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;EAC/D,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;EAClE,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;EACL,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;EACxE,GAAI,QAAQ,kBAAkB,EAAE,iBAAiB,QAAQ,gBAAgB,IAAI,CAAC;EAC9E,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;EACL,cAAc,QAAQ,gBAAgB,+BAA+B;CACvE;AACF;AAEA,SAAS,iCAA8D;CACrE,OAAO;EACL,KAAK;GACH,MAAM;GACN,OAAO;GACP,MAAM;GACN,QAAQ;EACV;EACA,MAAM;GACJ,QAAQ;GACR,QAAQ;GACR,cAAc;EAChB;EACA,WAAW;GACT,MAAM;GACN,MAAM;EACR;CACF;AACF;AAEA,SAAS,gCAAyC;CAChD,OAAO,QAAQ,IAAI,aAAa;AAClC;AAEA,SAAS,gCAAoD;CAC3D,IAAI,QAAQ,IAAI,6BAA6B,KAC3C;CAEF,OAAO,QAAQ,IAAI,wBAAwB;AAC7C"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n mkdir,\n open,\n readdir,\n readFile,\n rm,\n stat,\n writeFile\n} from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { DatabaseSync, type SQLInputValue } from \"node:sqlite\";\nimport WebSocket from \"ws\";\nimport {\n SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,\n SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,\n SYNCORE_DEVTOOLS_PROTOCOL_VERSION\n} from \"@syncore/devtools-protocol\";\nimport type {\n SyncoreDevtoolsClientMessage,\n SyncoreDevtoolsCapabilities,\n SyncoreDevtoolsExternalChangeEvent,\n SyncoreDevtoolsMessage,\n SyncoreRuntimeSummary\n} from \"@syncore/devtools-protocol\";\nimport {\n createDevtoolsCommandHandler,\n createDevtoolsSubscriptionHost,\n type DevtoolsCommandHandler,\n type DevtoolsSink,\n type DevtoolsSubscriptionHost,\n type SchedulerOptions,\n type StorageObject,\n type StorageWriteInput,\n type SyncoreCapabilities,\n type SyncoreDataModel,\n type SyncoreExternalChangeEvent,\n type SyncoreExternalChangeSignal,\n SyncoreRuntime,\n type SyncoreRuntimeOptions,\n type SyncoreSqlDriver,\n type SyncoreStorageAdapter\n} from \"@syncore/core\";\nimport { attachNodeIpcRuntime, createNodeIpcMessageEndpoint } from \"./ipc.js\";\nexport * from \"./ipc.js\";\nexport type {\n SyncoreActiveQueryInfo,\n SyncoreDevtoolsEvent,\n SyncoreRuntimeSummary\n} from \"@syncore/devtools-protocol\";\n\nexport type NodeSyncoreSchema<\n TSchema extends SyncoreDataModel = SyncoreDataModel\n> = TSchema;\n\nconst DEVTOOLS_META_DIRECTORY = \".syncore-devtools\";\nconst DATA_SOURCE_ALIAS_PREFIX = \"data-source-alias\";\n\nfunction normalizeData(input: StorageWriteInput[\"data\"]): Uint8Array {\n if (typeof input === \"string\") {\n return Buffer.from(input);\n }\n if (input instanceof Uint8Array) {\n return input;\n }\n return new Uint8Array(input);\n}\n\nfunction toSqlParameters(params: unknown[]): SQLInputValue[] {\n return params as SQLInputValue[];\n}\n\n/**\n * SQLite driver backed by Node.js’s built-in `node:sqlite` module (Node 22+).\n *\n * Opens the database at `databasePath` with `WAL` journal mode and foreign-key\n * enforcement enabled. The file is created if it does not exist.\n *\n * ```ts\n * const driver = new NodeSqliteDriver(\"./data/app.db\");\n * ```\n *\n * In most cases you should use {@link createNodeSyncoreRuntime} which\n * instantiates this driver automatically from your `databasePath` option.\n */\nexport class NodeSqliteDriver implements SyncoreSqlDriver {\n private readonly database: DatabaseSync;\n private transactionDepth = 0;\n\n constructor(readonly databasePath: string) {\n this.database = new DatabaseSync(databasePath);\n this.database.exec(\"PRAGMA foreign_keys = ON;\");\n this.database.exec(\"PRAGMA journal_mode = WAL;\");\n }\n\n async exec(sql: string): Promise<void> {\n this.database.exec(sql);\n }\n\n async run(\n sql: string,\n params: unknown[] = []\n ): Promise<{ changes: number; lastInsertRowid?: number | string }> {\n const statement = this.database.prepare(sql);\n const result = statement.run(...toSqlParameters(params));\n return {\n changes: Number(result.changes ?? 0),\n lastInsertRowid:\n typeof result.lastInsertRowid === \"bigint\"\n ? Number(result.lastInsertRowid)\n : result.lastInsertRowid\n };\n }\n\n async get<T>(sql: string, params: unknown[] = []): Promise<T | undefined> {\n const statement = this.database.prepare(sql);\n return statement.get(...toSqlParameters(params)) as T | undefined;\n }\n\n async all<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const statement = this.database.prepare(sql);\n return statement.all(...toSqlParameters(params)) as T[];\n }\n\n async withTransaction<T>(callback: () => Promise<T>): Promise<T> {\n if (this.transactionDepth > 0) {\n return this.withSavepoint(`nested_${this.transactionDepth}`, callback);\n }\n\n this.transactionDepth += 1;\n this.database.exec(\"BEGIN\");\n try {\n const result = await callback();\n this.database.exec(\"COMMIT\");\n return result;\n } catch (error) {\n this.database.exec(\"ROLLBACK\");\n throw error;\n } finally {\n this.transactionDepth -= 1;\n }\n }\n\n async withSavepoint<T>(name: string, callback: () => Promise<T>): Promise<T> {\n const safeName = name.replaceAll(/[^a-zA-Z0-9_]/g, \"_\");\n this.database.exec(`SAVEPOINT ${safeName}`);\n try {\n const result = await callback();\n this.database.exec(`RELEASE SAVEPOINT ${safeName}`);\n return result;\n } catch (error) {\n this.database.exec(`ROLLBACK TO SAVEPOINT ${safeName}`);\n this.database.exec(`RELEASE SAVEPOINT ${safeName}`);\n throw error;\n }\n }\n\n async close(): Promise<void> {\n this.database.close();\n }\n}\n\n/**\n * Blob storage adapter that reads and writes files in a local directory.\n *\n * Each stored object is saved as a flat file named by its ID inside\n * `directory`. The directory is created automatically on first write.\n *\n * ```ts\n * const storage = new NodeFileStorageAdapter(\"./data/storage\");\n * ```\n *\n * In most cases you should use {@link createNodeSyncoreRuntime} which\n * instantiates this adapter automatically from your `storageDirectory` option.\n */\nexport class NodeFileStorageAdapter implements SyncoreStorageAdapter {\n constructor(private readonly directory: string) {}\n\n private filePath(id: string): string {\n return path.join(this.directory, id);\n }\n\n async put(id: string, input: StorageWriteInput): Promise<StorageObject> {\n await mkdir(this.directory, { recursive: true });\n const filePath = this.filePath(id);\n const bytes = normalizeData(input.data);\n await writeFile(filePath, bytes);\n return {\n id,\n path: filePath,\n size: bytes.byteLength,\n contentType: input.contentType ?? null\n };\n }\n\n async get(id: string): Promise<StorageObject | null> {\n const filePath = this.filePath(id);\n try {\n const info = await stat(filePath);\n return {\n id,\n path: filePath,\n size: info.size,\n contentType: null\n };\n } catch {\n return null;\n }\n }\n\n async read(id: string): Promise<Uint8Array | null> {\n try {\n return await readFile(this.filePath(id));\n } catch {\n return null;\n }\n }\n\n async readRange(\n id: string,\n offset: number,\n length: number\n ): Promise<Uint8Array | null> {\n let handle;\n try {\n handle = await open(this.filePath(id), \"r\");\n const buffer = Buffer.alloc(Math.max(length, 0));\n const result = await handle.read(\n buffer,\n 0,\n buffer.byteLength,\n Math.max(offset, 0)\n );\n return buffer.subarray(0, result.bytesRead);\n } catch {\n return null;\n } finally {\n await handle?.close();\n }\n }\n\n async delete(id: string): Promise<void> {\n await rm(this.filePath(id), { force: true });\n }\n\n async list(): Promise<StorageObject[]> {\n try {\n const entries = await readdir(this.directory, { withFileTypes: true });\n const objects = await Promise.all(\n entries\n .filter((entry) => entry.isFile())\n .map(async (entry) => {\n const filePath = this.filePath(entry.name);\n const info = await stat(filePath);\n return {\n id: entry.name,\n path: filePath,\n size: info.size,\n contentType: null\n } satisfies StorageObject;\n })\n );\n return objects;\n } catch {\n return [];\n }\n }\n}\n\nconst SESSION_ADJECTIVES = [\n \"Acrobatic\",\n \"Bold\",\n \"Cosmic\",\n \"Daring\",\n \"Electric\",\n \"Fierce\",\n \"Golden\",\n \"Hidden\",\n \"Iron\",\n \"Jade\",\n \"Keen\",\n \"Lunar\",\n \"Mystic\",\n \"Noble\",\n \"Orbital\",\n \"Primal\",\n \"Quick\",\n \"Radiant\",\n \"Shadow\",\n \"Turbo\",\n \"Ultra\",\n \"Vivid\",\n \"Wicked\",\n \"Xenon\",\n \"Zen\",\n \"Arctic\",\n \"Binary\",\n \"Cyber\",\n \"Digital\",\n \"Ember\",\n \"Frozen\",\n \"Galactic\",\n \"Hyper\",\n \"Infra\",\n \"Jumbo\",\n \"Kinetic\",\n \"Liquid\",\n \"Magnetic\",\n \"Neon\",\n \"Onyx\",\n \"Phantom\",\n \"Quantum\",\n \"Rapid\",\n \"Sonic\",\n \"Titan\",\n \"Velvet\",\n \"Wild\",\n \"Blazing\",\n \"Crystal\",\n \"Dynamic\"\n] as const;\n\nconst SESSION_NOUNS = [\n \"Phoenix\",\n \"Dragon\",\n \"Developer\",\n \"Hacker\",\n \"Wizard\",\n \"Runner\",\n \"Ranger\",\n \"Maverick\",\n \"Spartan\",\n \"Viking\",\n \"Sentinel\",\n \"Guardian\",\n \"Nomad\",\n \"Cipher\",\n \"Vector\",\n \"Matrix\",\n \"Prism\",\n \"Nebula\",\n \"Comet\",\n \"Pulse\",\n \"Vertex\",\n \"Flux\",\n \"Storm\",\n \"Blaze\",\n \"Frost\",\n \"Thunder\",\n \"Drift\"\n] as const;\n\nfunction generateUniqueSessionName(): string {\n const adj =\n SESSION_ADJECTIVES[Math.floor(Math.random() * SESSION_ADJECTIVES.length)]!;\n const noun = SESSION_NOUNS[Math.floor(Math.random() * SESSION_NOUNS.length)]!;\n return `${adj} ${noun}`;\n}\n\nfunction resolvePersistedDataSourceAlias(\n storageDirectory: string,\n storageIdentity: string\n): string {\n const metaDirectory = path.join(storageDirectory, DEVTOOLS_META_DIRECTORY);\n const aliasId = createHash(\"sha256\")\n .update(storageIdentity)\n .digest(\"hex\")\n .slice(0, 16);\n const aliasPath = path.join(\n metaDirectory,\n `${DATA_SOURCE_ALIAS_PREFIX}-${aliasId}.txt`\n );\n\n try {\n const existing = readFileSync(aliasPath, \"utf8\").trim();\n if (existing.length > 0) {\n return existing;\n }\n } catch {\n // Missing metadata is expected for a new data source.\n }\n\n const nextValue = generateUniqueSessionName();\n try {\n mkdirSync(metaDirectory, { recursive: true });\n writeFileSync(aliasPath, nextValue, \"utf8\");\n } catch {\n // The alias is a dashboard convenience; runtime startup must not depend on it.\n }\n return nextValue;\n}\n\n/**\n * Options for {@link createNodeSyncoreRuntime}.\n *\n * At minimum supply `databasePath`, `storageDirectory`, `schema`, and\n * `functions`. Everything else has sensible defaults (auto-devtools connect in\n * development, Node SQLite driver, local file storage).\n *\n * ```ts\n * createNodeSyncoreRuntime({\n * databasePath: path.join(dataDir, \"app.db\"),\n * storageDirectory: path.join(dataDir, \"storage\"),\n * schema,\n * functions,\n * });\n * ```\n */\nexport interface CreateNodeRuntimeOptions<\n TSchema extends NodeSyncoreSchema = NodeSyncoreSchema\n> {\n /**\n * Absolute or relative path to the SQLite database file.\n *\n * The file is created if it does not exist. Use an absolute path in\n * production to avoid ambiguity about the current working directory.\n */\n databasePath: string;\n /**\n * Directory where blob storage objects (images, files, etc.) are persisted.\n *\n * The directory is created automatically if it does not exist.\n */\n storageDirectory: string;\n /** The data model that defines the available tables and indexes. */\n schema: TSchema;\n /**\n * The registered function map. Use the `functions` export from\n * `syncore/_generated/functions.ts`.\n */\n functions: SyncoreRuntimeOptions<TSchema>[\"functions\"];\n /**\n * Resolved Syncore component instances. Only required when your app\n * installs Syncore component packages.\n */\n components?: SyncoreRuntimeOptions<TSchema>[\"components\"];\n /**\n * Platform capabilities injected into `ctx.capabilities` inside function\n * handlers.\n */\n capabilities?: SyncoreCapabilities;\n /** Human-readable app name shown in the devtools dashboard. */\n appName?: string;\n /** Origin label (e.g. process name) shown in devtools. */\n origin?: string;\n /** Devtools session label. Auto-generated when omitted. */\n sessionLabel?: string;\n /**\n * Platform label reported to devtools. Defaults to `\"node\"`, or\n * `\"electron-main\"` when the runtime is used inside Electron's main process.\n */\n platform?: string;\n /**\n * Devtools event sink. Pass `false` to disable devtools entirely (recommended\n * for production). Omit to auto-connect to the local devtools server when\n * running in development.\n */\n devtools?: DevtoolsSink | false;\n /**\n * Explicit devtools WebSocket server URL. Defaults to\n * `ws://localhost:3099` (the Syncore devtools default port).\n */\n devtoolsUrl?: string;\n /** Scheduler configuration for background and recurring jobs. */\n scheduler?: SchedulerOptions;\n}\n\n/**\n * Alias of {@link CreateNodeRuntimeOptions} exposed for the managed-client\n * helper.\n * @see CreateNodeRuntimeOptions\n */\nexport type WithNodeSyncoreClientOptions<\n TSchema extends NodeSyncoreSchema = NodeSyncoreSchema\n> = CreateNodeRuntimeOptions<TSchema>;\n\n/**\n * A started local Node runtime paired with its client and a dispose helper.\n *\n * Returned by `withNodeSyncoreClient()`. Call `dispose()` when you are\n * finished (e.g. in tests or short-lived scripts) to stop the runtime and\n * close the database.\n */\nexport interface ManagedNodeSyncoreClient<\n TSchema extends NodeSyncoreSchema = NodeSyncoreSchema\n> {\n /** The underlying runtime instance. */\n runtime: SyncoreRuntime<TSchema>;\n /** A ready-to-use client for calling Syncore functions. */\n client: ReturnType<SyncoreRuntime<TSchema>[\"createClient\"]>;\n /** Stop the runtime, flush pending jobs, and close the database. */\n dispose(): Promise<void>;\n}\n\n/**\n * Opaque handle returned by Syncore’s Electron IPC bridge setup.\n *\n * - `ready`: resolves when the bridge is connected and the renderer is ready to\n * receive messages.\n * - `dispose()`: tears down the bridge and removes IPC listeners.\n */\nexport interface SyncoreElectronIpcBinding {\n ready: Promise<void>;\n dispose(): Promise<void>;\n}\n\n/**\n * Minimal interface that Syncore requires from an Electron `BrowserWindow`\n * instance.\n *\n * Scoped to avoid importing Electron at the type level.\n */\nexport interface SyncoreElectronBridgeWindow {\n isDestroyed(): boolean;\n webContents: {\n send(channel: string, message: unknown): void;\n };\n}\n\n/**\n * Options for setting up Syncore’s Electron main-process IPC bridge.\n *\n * The bridge forwards database change events from the main-process runtime to\n * the renderer window over a named IPC channel.\n *\n * ```ts\n * createElectronSyncoreBridge(runtime, {\n * window: mainWindow,\n * onRendererMessage: (listener) => {\n * ipcMain.on(\"syncore\", (_e, msg) => listener(msg));\n * return () => ipcMain.removeAllListeners(\"syncore\");\n * },\n * });\n * ```\n */\nexport interface CreateElectronSyncoreBridgeOptions {\n /** The renderer window that will receive push messages. */\n window: SyncoreElectronBridgeWindow;\n /**\n * Register a listener for messages sent from the renderer. Must return an\n * unsubscribe function.\n */\n onRendererMessage(listener: (message: unknown) => void): () => void;\n /** IPC channel name. Defaults to `\"syncore\"`. */\n channel?: string;\n}\n\n/**\n * The subset of Electron’s `ipcMain` used by Syncore’s main-process helper.\n *\n * Using this narrowed interface avoids a hard runtime dependency on Electron.\n */\nexport interface SyncoreElectronIpcMain {\n on(\n channel: string,\n listener: (event: { sender: unknown }, message: unknown) => void\n ): void;\n off(\n channel: string,\n listener: (event: { sender: unknown }, message: unknown) => void\n ): void;\n}\n\n/**\n * Options for creating a client inside an Electron renderer process via the\n * preload IPC bridge.\n */\nexport interface CreateSyncoreRendererWindowClientOptions {\n /** Name of the bridge registered in the preload script. Defaults to `\"syncore\"`. */\n bridgeName?: string;\n}\n\n/**\n * Create a Syncore runtime for Node.js (or Electron’s main process) backed by\n * the built-in `node:sqlite` driver and local file storage.\n *\n * This is the recommended entry point for Node and Electron apps. It wires up\n * the SQL driver, storage adapter, devtools WebSocket connection, and\n * cross-process change signals automatically.\n *\n * ```ts\n * import path from \"node:path\";\n * import { createNodeSyncoreRuntime } from \"syncorejs/node\";\n * import schema from \"./syncore/schema\";\n * import { functions } from \"./syncore/_generated/functions\";\n *\n * const runtime = createNodeSyncoreRuntime({\n * databasePath: path.join(app.getPath(\"userData\"), \"db.sqlite\"),\n * storageDirectory: path.join(app.getPath(\"userData\"), \"storage\"),\n * schema,\n * functions,\n * });\n *\n * await runtime.start();\n * const client = runtime.createClient();\n * ```\n *\n * @param options - Configuration object. See {@link CreateNodeRuntimeOptions}.\n * @returns A configured (but not yet started) SyncoreRuntime. Call\n * `await runtime.start()` before using the client.\n */\nexport function createNodeSyncoreRuntime<TSchema extends NodeSyncoreSchema>(\n options: CreateNodeRuntimeOptions<TSchema>\n): SyncoreRuntime<TSchema> {\n const resolvedDevtoolsUrl =\n options.devtoolsUrl ?? resolveDefaultNodeDevtoolsUrl();\n const storageIdentity = `file::${path.resolve(options.databasePath)}`;\n const websocketDevtools =\n options.devtools === undefined &&\n resolvedDevtoolsUrl &&\n shouldAutoConnectNodeDevtools()\n ? createNodeWebSocketDevtoolsSink({\n url: resolvedDevtoolsUrl,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel\n ? { sessionLabel: options.sessionLabel }\n : {}),\n targetKind: \"client\",\n storageProtocol: \"file\",\n databaseLabel: path.basename(options.databasePath),\n dataSourceAlias: resolvePersistedDataSourceAlias(\n options.storageDirectory,\n storageIdentity\n ),\n storageIdentity,\n runtimeRole: \"app\",\n capabilities: createNodeDevtoolsCapabilities()\n })\n : undefined;\n const runtimeOptions: SyncoreRuntimeOptions<TSchema> = {\n schema: options.schema,\n functions: options.functions,\n ...(options.components ? { components: options.components } : {}),\n driver: new NodeSqliteDriver(options.databasePath),\n storage: new NodeFileStorageAdapter(options.storageDirectory),\n runtimeCapabilities: {\n storage: {\n available: true,\n protocol: \"file\",\n supportsRange: true\n }\n },\n platform: options.platform ?? \"node\"\n };\n if (options.capabilities) {\n runtimeOptions.capabilities = options.capabilities;\n }\n const resolvedDevtools =\n options.devtools === false\n ? undefined\n : (options.devtools ?? websocketDevtools);\n if (resolvedDevtools) {\n runtimeOptions.devtools = resolvedDevtools;\n }\n if (websocketDevtools?.externalChangeSignal) {\n runtimeOptions.externalChangeSignal =\n websocketDevtools.externalChangeSignal;\n }\n if (options.scheduler) {\n runtimeOptions.scheduler = options.scheduler;\n }\n const runtime = new SyncoreRuntime(runtimeOptions);\n websocketDevtools?.attachRuntime(runtime);\n if (websocketDevtools) {\n websocketDevtools.attachCommandHandler(\n createDevtoolsCommandHandler({\n driver: runtimeOptions.driver,\n schema: options.schema,\n functions: options.functions,\n admin: runtime.getAdmin()\n })\n );\n websocketDevtools.attachSubscriptionHost(\n createDevtoolsSubscriptionHost({\n driver: runtimeOptions.driver,\n schema: options.schema,\n functions: options.functions,\n admin: runtime.getAdmin()\n })\n );\n const stop = runtime.stop.bind(runtime);\n runtime.stop = async () => {\n websocketDevtools.dispose();\n await stop();\n };\n }\n return runtime;\n}\n\n/**\n * Create a same-process Syncore client from a started Node runtime.\n */\nexport function createNodeSyncoreClient<TSchema extends NodeSyncoreSchema>(\n runtime: SyncoreRuntime<TSchema>\n) {\n return runtime.createClient();\n}\n\n/**\n * Start a Node Syncore runtime and return its client together with a dispose helper.\n */\nexport async function createManagedNodeSyncoreClient<\n TSchema extends NodeSyncoreSchema\n>(\n options: WithNodeSyncoreClientOptions<TSchema>\n): Promise<ManagedNodeSyncoreClient<TSchema>> {\n const runtime = createNodeSyncoreRuntime(options);\n await runtime.start();\n return {\n runtime,\n client: runtime.createClient(),\n async dispose() {\n await runtime.stop();\n }\n };\n}\n\n/**\n * Run a callback with a started local Node Syncore client and always stop the runtime.\n *\n * @example\n * ```ts\n * await withNodeSyncoreClient(options, async (client) => {\n * console.log(await client.query(api.tasks.list));\n * });\n * ```\n */\nexport async function withNodeSyncoreClient<\n TSchema extends NodeSyncoreSchema,\n TResult\n>(\n options: WithNodeSyncoreClientOptions<TSchema>,\n callback: (\n client: ReturnType<SyncoreRuntime<TSchema>[\"createClient\"]>,\n runtime: SyncoreRuntime<TSchema>\n ) => Promise<TResult> | TResult\n): Promise<TResult> {\n const managed = await createManagedNodeSyncoreClient(options);\n try {\n return await callback(managed.client, managed.runtime);\n } finally {\n await managed.dispose();\n }\n}\n\n/**\n * Create the default Electron main-process bridge used to connect a BrowserWindow\n * to a Syncore runtime.\n */\nexport function createElectronSyncoreBridge(\n options: CreateElectronSyncoreBridgeOptions\n) {\n const channel = options.channel ?? \"syncore:message\";\n return createNodeIpcMessageEndpoint({\n postMessage(message: unknown) {\n if (!options.window.isDestroyed()) {\n options.window.webContents.send(channel, message);\n }\n },\n onMessage(listener: (message: unknown) => void) {\n return options.onRendererMessage(listener);\n }\n });\n}\n\n/**\n * Bind a BrowserWindow to a Syncore runtime with the default Electron IPC transport.\n */\nexport function bindElectronWindowToSyncoreRuntime(options: {\n runtime: SyncoreRuntime<NodeSyncoreSchema>;\n window: SyncoreElectronBridgeWindow;\n onRendererMessage(listener: (message: unknown) => void): () => void;\n channel?: string;\n}): SyncoreElectronIpcBinding;\nexport function bindElectronWindowToSyncoreRuntime(options: {\n runtime: SyncoreRuntime<NodeSyncoreSchema>;\n window: SyncoreElectronBridgeWindow;\n ipcMain: SyncoreElectronIpcMain;\n channel?: string;\n}): SyncoreElectronIpcBinding;\nexport function bindElectronWindowToSyncoreRuntime(options: {\n runtime: SyncoreRuntime<NodeSyncoreSchema>;\n window: SyncoreElectronBridgeWindow;\n onRendererMessage?(listener: (message: unknown) => void): () => void;\n ipcMain?: SyncoreElectronIpcMain;\n channel?: string;\n}): SyncoreElectronIpcBinding {\n const cleanupCallbacks: Array<() => void> = [];\n const channel = options.channel ?? \"syncore:message\";\n let onRendererMessage:\n | ((listener: (message: unknown) => void) => () => void)\n | undefined;\n\n if (!options.onRendererMessage) {\n if (!options.ipcMain) {\n throw new Error(\n \"bindElectronWindowToSyncoreRuntime requires either onRendererMessage() or ipcMain.\"\n );\n }\n const listeners = new Set<(message: unknown) => void>();\n const handleRendererMessage = (\n event: { sender: unknown },\n message: unknown\n ) => {\n if (event.sender !== options.window.webContents) {\n return;\n }\n for (const listener of listeners) {\n listener(message);\n }\n };\n options.ipcMain.on(channel, handleRendererMessage);\n cleanupCallbacks.push(() => {\n options.ipcMain?.off(channel, handleRendererMessage);\n listeners.clear();\n });\n onRendererMessage = (listener) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n };\n } else {\n onRendererMessage = (listener) => options.onRendererMessage!(listener);\n }\n\n const endpoint = createElectronSyncoreBridge({\n window: options.window,\n onRendererMessage,\n channel\n });\n const attachedRuntime = attachNodeIpcRuntime({\n endpoint,\n createRuntime: () => options.runtime\n });\n\n return {\n ready: attachedRuntime.ready,\n async dispose() {\n await attachedRuntime.dispose();\n endpoint.dispose();\n for (const cleanup of cleanupCallbacks) {\n cleanup();\n }\n }\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Devtools request handler */\n/* ------------------------------------------------------------------ */\n\nexport interface NodeWebSocketDevtoolsSinkOptions {\n url: string;\n reconnectDelayMs?: number;\n appName?: string;\n origin?: string;\n sessionLabel?: string;\n targetKind?: \"client\" | \"project\";\n runtimeRole?: \"app\" | \"project-target\";\n storageProtocol?: string;\n databaseLabel?: string;\n dataSourceAlias?: string;\n storageIdentity?: string;\n capabilities?: SyncoreDevtoolsCapabilities;\n}\n\nexport interface NodeWebSocketDevtoolsSink extends DevtoolsSink {\n attachRuntime(runtime: SyncoreRuntime<NodeSyncoreSchema>): void;\n attachCommandHandler(handler: DevtoolsCommandHandler): void;\n attachSubscriptionHost(host: DevtoolsSubscriptionHost): void;\n externalChangeSignal?: SyncoreExternalChangeSignal;\n dispose(): void;\n}\n\nexport function createNodeWebSocketDevtoolsSink(\n options: NodeWebSocketDevtoolsSinkOptions\n): NodeWebSocketDevtoolsSink {\n let socket: WebSocket | undefined;\n let disposed = false;\n let connectTimer: ReturnType<typeof setTimeout> | undefined;\n let getSummary: (() => SyncoreRuntimeSummary) | undefined;\n let onCommand: DevtoolsCommandHandler | undefined;\n let subscriptionHost: DevtoolsSubscriptionHost | undefined;\n const externalChangeListeners = new Set<\n (event: SyncoreExternalChangeEvent) => void\n >();\n const pendingMessages: SyncoreDevtoolsMessage[] = [];\n let latestHello:\n | {\n runtimeId: string;\n platform: string;\n }\n | undefined;\n\n const connect = () => {\n if (disposed) {\n return;\n }\n socket = new WebSocket(options.url);\n socket.on(\"open\", () => {\n if (latestHello) {\n sendNow({\n type: \"hello\",\n protocolVersion: SYNCORE_DEVTOOLS_PROTOCOL_VERSION,\n minSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,\n maxSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,\n runtimeId: latestHello.runtimeId,\n platform: latestHello.platform,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel\n ? { sessionLabel: options.sessionLabel }\n : {}),\n ...(options.targetKind ? { targetKind: options.targetKind } : {}),\n ...(options.runtimeRole ? { runtimeRole: options.runtimeRole } : {}),\n ...(options.storageProtocol\n ? { storageProtocol: options.storageProtocol }\n : {}),\n ...(options.databaseLabel\n ? { databaseLabel: options.databaseLabel }\n : {}),\n ...(options.dataSourceAlias\n ? { dataSourceAlias: options.dataSourceAlias }\n : {}),\n ...(options.storageIdentity\n ? { storageIdentity: options.storageIdentity }\n : {}),\n capabilities: options.capabilities ?? createNodeDevtoolsCapabilities()\n });\n }\n flushPendingMessages();\n });\n socket.on(\"message\", (payload) => {\n const rawPayload =\n typeof payload === \"string\"\n ? payload\n : payload instanceof Buffer\n ? payload.toString(\"utf8\")\n : Array.isArray(payload)\n ? Buffer.concat(payload).toString(\"utf8\")\n : payload instanceof ArrayBuffer\n ? Buffer.from(payload).toString(\"utf8\")\n : Buffer.from(\n payload.buffer,\n payload.byteOffset,\n payload.byteLength\n ).toString(\"utf8\");\n if (rawPayload.length === 0) {\n return;\n }\n const message = JSON.parse(rawPayload) as\n | SyncoreDevtoolsMessage\n | SyncoreDevtoolsClientMessage;\n if (message.type === \"ping\") {\n send({ type: \"pong\" });\n } else if (message.type === \"external.change\") {\n for (const listener of externalChangeListeners) {\n listener(message.event as SyncoreExternalChangeEvent);\n }\n } else if (message.type === \"command\" && onCommand) {\n onCommand(message.payload)\n .then((responsePayload) => {\n const runtimeId =\n latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"command.result\",\n commandId: message.commandId,\n runtimeId,\n payload: responsePayload\n });\n })\n .catch((err) => {\n const runtimeId =\n latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"command.result\",\n commandId: message.commandId,\n runtimeId,\n payload: {\n kind: \"error\",\n message: err instanceof Error ? err.message : \"Unknown error\"\n }\n });\n });\n } else if (message.type === \"subscribe\" && subscriptionHost) {\n void subscriptionHost.subscribe(\n message.subscriptionId,\n message.payload,\n (payload) => {\n const runtimeId =\n latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"subscription.data\",\n subscriptionId: message.subscriptionId,\n runtimeId,\n payload\n });\n }\n );\n } else if (message.type === \"unsubscribe\") {\n subscriptionHost?.unsubscribe(message.subscriptionId);\n }\n });\n socket.on(\"close\", scheduleReconnect);\n socket.on(\"error\", scheduleReconnect);\n };\n\n const scheduleReconnect = () => {\n if (disposed || connectTimer) {\n return;\n }\n connectTimer = setTimeout(() => {\n connectTimer = undefined;\n connect();\n }, options.reconnectDelayMs ?? 1200);\n };\n\n const sendNow = (message: SyncoreDevtoolsMessage) => {\n if (socket?.readyState === WebSocket.OPEN) {\n socket.send(JSON.stringify(message));\n }\n };\n\n const flushPendingMessages = () => {\n while (pendingMessages.length > 0) {\n const nextMessage = pendingMessages.shift();\n if (!nextMessage) {\n continue;\n }\n sendNow(nextMessage);\n }\n };\n\n const send = (message: SyncoreDevtoolsMessage) => {\n if (socket?.readyState === WebSocket.OPEN) {\n sendNow(message);\n return;\n }\n pendingMessages.push(message);\n };\n\n connect();\n\n const sink: NodeWebSocketDevtoolsSink = {\n emit(event) {\n if (event.type === \"runtime.connected\") {\n latestHello = {\n runtimeId: event.runtimeId,\n platform: event.platform\n };\n send({\n type: \"hello\",\n protocolVersion: SYNCORE_DEVTOOLS_PROTOCOL_VERSION,\n minSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,\n maxSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,\n runtimeId: event.runtimeId,\n platform: event.platform,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel\n ? { sessionLabel: options.sessionLabel }\n : {}),\n ...(options.targetKind ? { targetKind: options.targetKind } : {}),\n ...(options.runtimeRole ? { runtimeRole: options.runtimeRole } : {}),\n ...(options.storageProtocol\n ? { storageProtocol: options.storageProtocol }\n : {}),\n ...(options.databaseLabel\n ? { databaseLabel: options.databaseLabel }\n : {}),\n ...(options.dataSourceAlias\n ? { dataSourceAlias: options.dataSourceAlias }\n : {}),\n ...(options.storageIdentity\n ? { storageIdentity: options.storageIdentity }\n : {}),\n capabilities: options.capabilities ?? createNodeDevtoolsCapabilities()\n });\n }\n send({\n type: \"event\",\n event\n });\n },\n attachRuntime(runtime) {\n getSummary = () =>\n withRuntimeSummaryMeta(runtime.getAdmin().getRuntimeSummary(), options);\n },\n attachCommandHandler(handler) {\n onCommand = handler;\n },\n attachSubscriptionHost(host) {\n subscriptionHost = host;\n },\n dispose() {\n disposed = true;\n if (connectTimer) {\n clearTimeout(connectTimer);\n }\n subscriptionHost?.dispose();\n socket?.close();\n }\n };\n if (options.storageIdentity) {\n sink.externalChangeSignal = {\n subscribe(listener) {\n externalChangeListeners.add(listener);\n return () => {\n externalChangeListeners.delete(listener);\n };\n },\n publish(event) {\n const runtimeId = latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"external.change\",\n runtimeId,\n storageIdentity: options.storageIdentity!,\n event: event as SyncoreDevtoolsExternalChangeEvent\n });\n },\n close() {\n externalChangeListeners.clear();\n }\n };\n }\n return sink;\n}\n\nfunction withRuntimeSummaryMeta(\n summary: SyncoreRuntimeSummary,\n options: NodeWebSocketDevtoolsSinkOptions\n): SyncoreRuntimeSummary {\n return {\n ...summary,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel ? { sessionLabel: options.sessionLabel } : {}),\n ...(options.targetKind ? { targetKind: options.targetKind } : {}),\n ...(options.runtimeRole ? { runtimeRole: options.runtimeRole } : {}),\n ...(options.storageProtocol\n ? { storageProtocol: options.storageProtocol }\n : {}),\n ...(options.databaseLabel ? { databaseLabel: options.databaseLabel } : {}),\n ...(options.dataSourceAlias\n ? { dataSourceAlias: options.dataSourceAlias }\n : {}),\n ...(options.storageIdentity\n ? { storageIdentity: options.storageIdentity }\n : {}),\n capabilities: options.capabilities ?? createNodeDevtoolsCapabilities()\n };\n}\n\nfunction createNodeDevtoolsCapabilities(): SyncoreDevtoolsCapabilities {\n return {\n sql: {\n read: false,\n write: false,\n live: false,\n reason:\n \"SQL Console is provided by the Project Target for this data source.\"\n },\n data: {\n browse: true,\n mutate: true,\n importExport: true\n },\n storage: {\n browse: true,\n download: true,\n readRange: true,\n delete: true,\n maxPreviewBytes: 80_000\n },\n scheduler: {\n read: true,\n edit: true\n }\n };\n}\n\nfunction shouldAutoConnectNodeDevtools(): boolean {\n return process.env.NODE_ENV !== \"production\";\n}\n\nfunction resolveDefaultNodeDevtoolsUrl(): string | undefined {\n if (process.env.SYNCORE_DISABLE_DEVTOOLS === \"1\") {\n return undefined;\n }\n return process.env.SYNCORE_DEVTOOLS_URL ?? \"ws://127.0.0.1:4311\";\n}\n"],"mappings":"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"}
@@ -16,6 +16,10 @@ function SyncoreElectronProvider({ children, bridgeName, windowObject }) {
16
16
  return createUnavailableSyncoreClient({
17
17
  kind: "unavailable",
18
18
  reason: "ipc-unavailable",
19
+ capabilities: { storage: {
20
+ available: false,
21
+ reason: "Syncore IPC bridge is unavailable."
22
+ } },
19
23
  ...error instanceof Error ? { error } : {}
20
24
  });
21
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ipc-react.mjs","names":[],"sources":["../src/ipc-react.tsx"],"sourcesContent":["import { useEffect, useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createUnavailableSyncoreClient } from \"@syncore/core\";\nimport { SyncoreProvider } from \"@syncore/react\";\nimport { createRendererSyncoreWindowClient } from \"./ipc.js\";\n\n/**\n * Props for {@link SyncoreElectronProvider}.\n */\nexport interface SyncoreElectronProviderProps {\n /** The React subtree that should receive the renderer Syncore client. */\n children: ReactNode;\n\n /** Optional custom bridge name exposed on `window`. */\n bridgeName?: string;\n\n /** Optional window-like object for tests or custom shells. */\n windowObject?: Window & typeof globalThis;\n}\n\n/**\n * Create a renderer Syncore client from `window.syncoreBridge` and provide it to React.\n */\nexport function SyncoreElectronProvider({\n children,\n bridgeName,\n windowObject\n}: SyncoreElectronProviderProps): ReactNode {\n const resolvedWindow = windowObject ?? window;\n const client = useMemo(\n () => {\n try {\n return createRendererSyncoreWindowClient(\n resolvedWindow,\n bridgeName ?? \"syncoreBridge\"\n );\n } catch (error) {\n return createUnavailableSyncoreClient({\n kind: \"unavailable\",\n reason: \"ipc-unavailable\",\n ...(error instanceof Error ? { error } : {})\n });\n }\n },\n [bridgeName, resolvedWindow]\n );\n\n useEffect(\n () => () => {\n if (\"dispose\" in client && typeof client.dispose === \"function\") {\n client.dispose();\n }\n },\n [client]\n );\n\n return <SyncoreProvider client={client}>{children}</SyncoreProvider>;\n}\n"],"mappings":";;;;;;;;;AAuBA,SAAgB,wBAAwB,EACtC,UACA,YACA,gBAC0C;CAC1C,MAAM,iBAAiB,gBAAgB;CACvC,MAAM,SAAS,cACP;EACJ,IAAI;GACF,OAAO,kCACL,gBACA,cAAc,eAChB;EACF,SAAS,OAAO;GACd,OAAO,+BAA+B;IACpC,MAAM;IACN,QAAQ;IACR,GAAI,iBAAiB,QAAQ,EAAE,MAAM,IAAI,CAAC;GAC5C,CAAC;EACH;CACF,GACA,CAAC,YAAY,cAAc,CAC7B;CAEA,sBACc;EACV,IAAI,aAAa,UAAU,OAAO,OAAO,YAAY,YACnD,OAAO,QAAQ;CAEnB,GACA,CAAC,MAAM,CACT;CAEA,OAAO,oBAAC,iBAAD;EAAyB;EAAS;CAA0B,CAAA;AACrE"}
1
+ {"version":3,"file":"ipc-react.mjs","names":[],"sources":["../src/ipc-react.tsx"],"sourcesContent":["import { useEffect, useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createUnavailableSyncoreClient } from \"@syncore/core\";\nimport { SyncoreProvider } from \"@syncore/react\";\nimport { createRendererSyncoreWindowClient } from \"./ipc.js\";\n\n/**\n * Props for {@link SyncoreElectronProvider}.\n */\nexport interface SyncoreElectronProviderProps {\n /** The React subtree that should receive the renderer Syncore client. */\n children: ReactNode;\n\n /** Optional custom bridge name exposed on `window`. */\n bridgeName?: string;\n\n /** Optional window-like object for tests or custom shells. */\n windowObject?: Window & typeof globalThis;\n}\n\n/**\n * Create a renderer Syncore client from `window.syncoreBridge` and provide it to React.\n */\nexport function SyncoreElectronProvider({\n children,\n bridgeName,\n windowObject\n}: SyncoreElectronProviderProps): ReactNode {\n const resolvedWindow = windowObject ?? window;\n const client = useMemo(\n () => {\n try {\n return createRendererSyncoreWindowClient(\n resolvedWindow,\n bridgeName ?? \"syncoreBridge\"\n );\n } catch (error) {\n return createUnavailableSyncoreClient({\n kind: \"unavailable\",\n reason: \"ipc-unavailable\",\n capabilities: {\n storage: {\n available: false,\n reason: \"Syncore IPC bridge is unavailable.\"\n }\n },\n ...(error instanceof Error ? { error } : {})\n });\n }\n },\n [bridgeName, resolvedWindow]\n );\n\n useEffect(\n () => () => {\n if (\"dispose\" in client && typeof client.dispose === \"function\") {\n client.dispose();\n }\n },\n [client]\n );\n\n return <SyncoreProvider client={client}>{children}</SyncoreProvider>;\n}\n"],"mappings":";;;;;;;;;AAuBA,SAAgB,wBAAwB,EACtC,UACA,YACA,gBAC0C;CAC1C,MAAM,iBAAiB,gBAAgB;CACvC,MAAM,SAAS,cACP;EACJ,IAAI;GACF,OAAO,kCACL,gBACA,cAAc,eAChB;EACF,SAAS,OAAO;GACd,OAAO,+BAA+B;IACpC,MAAM;IACN,QAAQ;IACR,cAAc,EACZ,SAAS;KACP,WAAW;KACX,QAAQ;IACV,EACF;IACA,GAAI,iBAAiB,QAAQ,EAAE,MAAM,IAAI,CAAC;GAC5C,CAAC;EACH;CACF,GACA,CAAC,YAAY,cAAc,CAC7B;CAEA,sBACc;EACV,IAAI,aAAa,UAAU,OAAO,OAAO,YAAY,YACnD,OAAO,QAAQ;CAEnB,GACA,CAAC,MAAM,CACT;CAEA,OAAO,oBAAC,iBAAD;EAAyB;EAAS;CAA0B,CAAA;AACrE"}
@@ -10,7 +10,7 @@ interface BroadcastChannelExternalChangeSignalOptions {
10
10
  channelName: string;
11
11
  }
12
12
  /**
13
- * A `BroadcastChannel`-based {@link SyncoreExternalChangeSignal} that
13
+ * A `BroadcastChannel`-based SyncoreExternalChangeSignal that
14
14
  * propagates database-mutation events across all browser tabs sharing the same
15
15
  * Syncore database.
16
16
  *
@@ -46,7 +46,7 @@ interface SqlJsExternalChangeApplierOptions {
46
46
  replaceDatabase(database: SqlJsDatabase): void;
47
47
  }
48
48
  /**
49
- * A {@link SyncoreExternalChangeApplier} for sql.js (in-memory) databases.
49
+ * A SyncoreExternalChangeApplier for sql.js (in-memory) databases.
50
50
  *
51
51
  * When another tab commits a mutation and broadcasts the change event, this
52
52
  * applier loads the latest database snapshot from web persistence (OPFS or
@@ -1,6 +1,6 @@
1
1
  //#region src/external-change.ts
2
2
  /**
3
- * A `BroadcastChannel`-based {@link SyncoreExternalChangeSignal} that
3
+ * A `BroadcastChannel`-based SyncoreExternalChangeSignal that
4
4
  * propagates database-mutation events across all browser tabs sharing the same
5
5
  * Syncore database.
6
6
  *
@@ -39,7 +39,7 @@ var BroadcastChannelExternalChangeSignal = class {
39
39
  };
40
40
  };
41
41
  /**
42
- * A {@link SyncoreExternalChangeApplier} for sql.js (in-memory) databases.
42
+ * A SyncoreExternalChangeApplier for sql.js (in-memory) databases.
43
43
  *
44
44
  * When another tab commits a mutation and broadcasts the change event, this
45
45
  * applier loads the latest database snapshot from web persistence (OPFS or
@@ -1 +1 @@
1
- {"version":3,"file":"external-change.js","names":[],"sources":["../src/external-change.ts"],"sourcesContent":["import type {\n ImpactScope,\n SyncoreExternalChangeApplier,\n SyncoreExternalChangeEvent,\n SyncoreExternalChangeSignal\n} from \"@syncore/core\";\nimport type initSqlJs from \"sql.js\";\nimport type { SyncoreWebPersistence } from \"./persistence.js\";\n\ntype SqlJsDatabase = initSqlJs.Database;\n\n/** Options for constructing a {@link BroadcastChannelExternalChangeSignal}. */\nexport interface BroadcastChannelExternalChangeSignalOptions {\n /** Name of the `BroadcastChannel`, shared by all tabs with the same database. */\n channelName: string;\n}\n\n/**\n * A `BroadcastChannel`-based {@link SyncoreExternalChangeSignal} that\n * propagates database-mutation events across all browser tabs sharing the same\n * Syncore database.\n *\n * When a Syncore mutation commits, the runtime publishes a change event on this\n * channel. Other tabs subscribed to the same channel reload their queries\n * automatically, keeping all open tabs in sync without a server round-trip.\n *\n * Constructed automatically by `createWebSyncoreRuntime`. Exposed for\n * advanced setups that build the persistence layer independently.\n */\nexport class BroadcastChannelExternalChangeSignal implements SyncoreExternalChangeSignal {\n private readonly channel: BroadcastChannel | undefined;\n private readonly listeners = new Set<\n (event: SyncoreExternalChangeEvent) => void\n >();\n\n constructor(options: BroadcastChannelExternalChangeSignalOptions) {\n if (typeof BroadcastChannel !== \"undefined\") {\n this.channel = new BroadcastChannel(options.channelName);\n this.channel.addEventListener(\"message\", this.messageListener);\n }\n }\n\n subscribe(listener: (event: SyncoreExternalChangeEvent) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n publish(event: SyncoreExternalChangeEvent): void {\n this.channel?.postMessage(event);\n }\n\n close(): void {\n this.channel?.removeEventListener(\"message\", this.messageListener);\n this.channel?.close();\n }\n\n private readonly messageListener = (event: MessageEvent<unknown>) => {\n if (!isExternalChangeEvent(event.data)) {\n return;\n }\n for (const listener of this.listeners) {\n listener(event.data);\n }\n };\n}\n\n/** Options for constructing a {@link SqlJsExternalChangeApplier}. */\nexport interface SqlJsExternalChangeApplierOptions {\n /** Logical name of the Syncore database, used to load the latest snapshot from persistence. */\n databaseName: string;\n /** The web persistence layer to read the updated database bytes from. */\n persistence: SyncoreWebPersistence;\n /**\n * Factory that creates a new sql.js `Database` instance from optional\n * initial bytes. Called whenever the database needs to be swapped after an\n * external change.\n */\n createDatabase: (bytes?: Uint8Array) => SqlJsDatabase;\n /** Callback invoked with the newly created database so the runtime can swap its reference. */\n replaceDatabase(database: SqlJsDatabase): void;\n}\n\n/**\n * A {@link SyncoreExternalChangeApplier} for sql.js (in-memory) databases.\n *\n * When another tab commits a mutation and broadcasts the change event, this\n * applier loads the latest database snapshot from web persistence (OPFS or\n * IndexedDB) and swaps the in-memory `sql.js` database instance so the current\n * tab reflects the new state.\n *\n * Constructed automatically by `createWebSyncoreRuntime` when using sql.js\n * persistence. Exposed for advanced setups.\n */\nexport class SqlJsExternalChangeApplier implements SyncoreExternalChangeApplier {\n private readonly databaseName: string;\n private readonly persistence: SyncoreWebPersistence;\n private readonly createDatabase: (bytes?: Uint8Array) => SqlJsDatabase;\n private readonly replaceDatabase: (database: SqlJsDatabase) => void;\n\n constructor(options: SqlJsExternalChangeApplierOptions) {\n this.databaseName = options.databaseName;\n this.persistence = options.persistence;\n this.createDatabase = (bytes) => options.createDatabase(bytes);\n this.replaceDatabase = (database) => options.replaceDatabase(database);\n }\n\n async applyExternalChange(event: SyncoreExternalChangeEvent) {\n const databaseChanged = event.scope === \"database\" || event.scope === \"all\";\n if (databaseChanged) {\n const bytes = await this.persistence.loadDatabase(this.databaseName);\n if (bytes) {\n this.replaceDatabase(this.createDatabase(bytes));\n }\n }\n return {\n databaseChanged,\n storageChanged: event.scope === \"storage\" || event.scope === \"all\",\n changedScopes:\n event.changedScopes ??\n ([\n ...(event.changedTables ?? []).map((tableName) => `table:${tableName}`),\n ...(event.storageIds ?? []).map((storageId) => `storage:${storageId}`)\n ] as ImpactScope[])\n };\n }\n}\n\n/**\n * Derive the canonical `BroadcastChannel` name for cross-tab sync from a\n * logical database name.\n *\n * All Syncore runtimes sharing the same `databaseName` will use the same\n * channel, ensuring mutations in one tab are visible to all others.\n */\nexport function createDefaultSyncChannelName(databaseName: string): string {\n return `syncore:external:${databaseName}`;\n}\n\nfunction isExternalChangeEvent(\n value: unknown\n): value is SyncoreExternalChangeEvent {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"sourceId\" in value &&\n \"scope\" in value &&\n \"reason\" in value &&\n \"timestamp\" in value\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,IAAa,uCAAb,MAAyF;CACvF;CACA,4BAA6B,IAAI,IAE/B;CAEF,YAAY,SAAsD;EAChE,IAAI,OAAO,qBAAqB,aAAa;GAC3C,KAAK,UAAU,IAAI,iBAAiB,QAAQ,WAAW;GACvD,KAAK,QAAQ,iBAAiB,WAAW,KAAK,eAAe;EAC/D;CACF;CAEA,UAAU,UAAmE;EAC3E,KAAK,UAAU,IAAI,QAAQ;EAC3B,aAAa;GACX,KAAK,UAAU,OAAO,QAAQ;EAChC;CACF;CAEA,QAAQ,OAAyC;EAC/C,KAAK,SAAS,YAAY,KAAK;CACjC;CAEA,QAAc;EACZ,KAAK,SAAS,oBAAoB,WAAW,KAAK,eAAe;EACjE,KAAK,SAAS,MAAM;CACtB;CAEA,mBAAoC,UAAiC;EACnE,IAAI,CAAC,sBAAsB,MAAM,IAAI,GACnC;EAEF,KAAK,MAAM,YAAY,KAAK,WAC1B,SAAS,MAAM,IAAI;CAEvB;AACF;;;;;;;;;;;;AA6BA,IAAa,6BAAb,MAAgF;CAC9E;CACA;CACA;CACA;CAEA,YAAY,SAA4C;EACtD,KAAK,eAAe,QAAQ;EAC5B,KAAK,cAAc,QAAQ;EAC3B,KAAK,kBAAkB,UAAU,QAAQ,eAAe,KAAK;EAC7D,KAAK,mBAAmB,aAAa,QAAQ,gBAAgB,QAAQ;CACvE;CAEA,MAAM,oBAAoB,OAAmC;EAC3D,MAAM,kBAAkB,MAAM,UAAU,cAAc,MAAM,UAAU;EACtE,IAAI,iBAAiB;GACnB,MAAM,QAAQ,MAAM,KAAK,YAAY,aAAa,KAAK,YAAY;GACnE,IAAI,OACF,KAAK,gBAAgB,KAAK,eAAe,KAAK,CAAC;EAEnD;EACA,OAAO;GACL;GACA,gBAAgB,MAAM,UAAU,aAAa,MAAM,UAAU;GAC7D,eACE,MAAM,iBACL,CACC,IAAI,MAAM,iBAAiB,CAAC,GAAG,KAAK,cAAc,SAAS,WAAW,GACtE,IAAI,MAAM,cAAc,CAAC,GAAG,KAAK,cAAc,WAAW,WAAW,CACvE;EACJ;CACF;AACF;;;;;;;;AASA,SAAgB,6BAA6B,cAA8B;CACzE,OAAO,oBAAoB;AAC7B;AAEA,SAAS,sBACP,OACqC;CACrC,OACE,OAAO,UAAU,YACjB,UAAU,QACV,cAAc,SACd,WAAW,SACX,YAAY,SACZ,eAAe;AAEnB"}
1
+ {"version":3,"file":"external-change.js","names":[],"sources":["../src/external-change.ts"],"sourcesContent":["import type {\n ImpactScope,\n SyncoreExternalChangeApplier,\n SyncoreExternalChangeEvent,\n SyncoreExternalChangeSignal\n} from \"@syncore/core\";\nimport type initSqlJs from \"sql.js\";\nimport type { SyncoreWebPersistence } from \"./persistence.js\";\n\ntype SqlJsDatabase = initSqlJs.Database;\n\n/** Options for constructing a {@link BroadcastChannelExternalChangeSignal}. */\nexport interface BroadcastChannelExternalChangeSignalOptions {\n /** Name of the `BroadcastChannel`, shared by all tabs with the same database. */\n channelName: string;\n}\n\n/**\n * A `BroadcastChannel`-based SyncoreExternalChangeSignal that\n * propagates database-mutation events across all browser tabs sharing the same\n * Syncore database.\n *\n * When a Syncore mutation commits, the runtime publishes a change event on this\n * channel. Other tabs subscribed to the same channel reload their queries\n * automatically, keeping all open tabs in sync without a server round-trip.\n *\n * Constructed automatically by `createWebSyncoreRuntime`. Exposed for\n * advanced setups that build the persistence layer independently.\n */\nexport class BroadcastChannelExternalChangeSignal implements SyncoreExternalChangeSignal {\n private readonly channel: BroadcastChannel | undefined;\n private readonly listeners = new Set<\n (event: SyncoreExternalChangeEvent) => void\n >();\n\n constructor(options: BroadcastChannelExternalChangeSignalOptions) {\n if (typeof BroadcastChannel !== \"undefined\") {\n this.channel = new BroadcastChannel(options.channelName);\n this.channel.addEventListener(\"message\", this.messageListener);\n }\n }\n\n subscribe(listener: (event: SyncoreExternalChangeEvent) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n publish(event: SyncoreExternalChangeEvent): void {\n this.channel?.postMessage(event);\n }\n\n close(): void {\n this.channel?.removeEventListener(\"message\", this.messageListener);\n this.channel?.close();\n }\n\n private readonly messageListener = (event: MessageEvent<unknown>) => {\n if (!isExternalChangeEvent(event.data)) {\n return;\n }\n for (const listener of this.listeners) {\n listener(event.data);\n }\n };\n}\n\n/** Options for constructing a {@link SqlJsExternalChangeApplier}. */\nexport interface SqlJsExternalChangeApplierOptions {\n /** Logical name of the Syncore database, used to load the latest snapshot from persistence. */\n databaseName: string;\n /** The web persistence layer to read the updated database bytes from. */\n persistence: SyncoreWebPersistence;\n /**\n * Factory that creates a new sql.js `Database` instance from optional\n * initial bytes. Called whenever the database needs to be swapped after an\n * external change.\n */\n createDatabase: (bytes?: Uint8Array) => SqlJsDatabase;\n /** Callback invoked with the newly created database so the runtime can swap its reference. */\n replaceDatabase(database: SqlJsDatabase): void;\n}\n\n/**\n * A SyncoreExternalChangeApplier for sql.js (in-memory) databases.\n *\n * When another tab commits a mutation and broadcasts the change event, this\n * applier loads the latest database snapshot from web persistence (OPFS or\n * IndexedDB) and swaps the in-memory `sql.js` database instance so the current\n * tab reflects the new state.\n *\n * Constructed automatically by `createWebSyncoreRuntime` when using sql.js\n * persistence. Exposed for advanced setups.\n */\nexport class SqlJsExternalChangeApplier implements SyncoreExternalChangeApplier {\n private readonly databaseName: string;\n private readonly persistence: SyncoreWebPersistence;\n private readonly createDatabase: (bytes?: Uint8Array) => SqlJsDatabase;\n private readonly replaceDatabase: (database: SqlJsDatabase) => void;\n\n constructor(options: SqlJsExternalChangeApplierOptions) {\n this.databaseName = options.databaseName;\n this.persistence = options.persistence;\n this.createDatabase = (bytes) => options.createDatabase(bytes);\n this.replaceDatabase = (database) => options.replaceDatabase(database);\n }\n\n async applyExternalChange(event: SyncoreExternalChangeEvent) {\n const databaseChanged = event.scope === \"database\" || event.scope === \"all\";\n if (databaseChanged) {\n const bytes = await this.persistence.loadDatabase(this.databaseName);\n if (bytes) {\n this.replaceDatabase(this.createDatabase(bytes));\n }\n }\n return {\n databaseChanged,\n storageChanged: event.scope === \"storage\" || event.scope === \"all\",\n changedScopes:\n event.changedScopes ??\n ([\n ...(event.changedTables ?? []).map((tableName) => `table:${tableName}`),\n ...(event.storageIds ?? []).map((storageId) => `storage:${storageId}`)\n ] as ImpactScope[])\n };\n }\n}\n\n/**\n * Derive the canonical `BroadcastChannel` name for cross-tab sync from a\n * logical database name.\n *\n * All Syncore runtimes sharing the same `databaseName` will use the same\n * channel, ensuring mutations in one tab are visible to all others.\n */\nexport function createDefaultSyncChannelName(databaseName: string): string {\n return `syncore:external:${databaseName}`;\n}\n\nfunction isExternalChangeEvent(\n value: unknown\n): value is SyncoreExternalChangeEvent {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"sourceId\" in value &&\n \"scope\" in value &&\n \"reason\" in value &&\n \"timestamp\" in value\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,IAAa,uCAAb,MAAyF;CACvF;CACA,4BAA6B,IAAI,IAE/B;CAEF,YAAY,SAAsD;EAChE,IAAI,OAAO,qBAAqB,aAAa;GAC3C,KAAK,UAAU,IAAI,iBAAiB,QAAQ,WAAW;GACvD,KAAK,QAAQ,iBAAiB,WAAW,KAAK,eAAe;EAC/D;CACF;CAEA,UAAU,UAAmE;EAC3E,KAAK,UAAU,IAAI,QAAQ;EAC3B,aAAa;GACX,KAAK,UAAU,OAAO,QAAQ;EAChC;CACF;CAEA,QAAQ,OAAyC;EAC/C,KAAK,SAAS,YAAY,KAAK;CACjC;CAEA,QAAc;EACZ,KAAK,SAAS,oBAAoB,WAAW,KAAK,eAAe;EACjE,KAAK,SAAS,MAAM;CACtB;CAEA,mBAAoC,UAAiC;EACnE,IAAI,CAAC,sBAAsB,MAAM,IAAI,GACnC;EAEF,KAAK,MAAM,YAAY,KAAK,WAC1B,SAAS,MAAM,IAAI;CAEvB;AACF;;;;;;;;;;;;AA6BA,IAAa,6BAAb,MAAgF;CAC9E;CACA;CACA;CACA;CAEA,YAAY,SAA4C;EACtD,KAAK,eAAe,QAAQ;EAC5B,KAAK,cAAc,QAAQ;EAC3B,KAAK,kBAAkB,UAAU,QAAQ,eAAe,KAAK;EAC7D,KAAK,mBAAmB,aAAa,QAAQ,gBAAgB,QAAQ;CACvE;CAEA,MAAM,oBAAoB,OAAmC;EAC3D,MAAM,kBAAkB,MAAM,UAAU,cAAc,MAAM,UAAU;EACtE,IAAI,iBAAiB;GACnB,MAAM,QAAQ,MAAM,KAAK,YAAY,aAAa,KAAK,YAAY;GACnE,IAAI,OACF,KAAK,gBAAgB,KAAK,eAAe,KAAK,CAAC;EAEnD;EACA,OAAO;GACL;GACA,gBAAgB,MAAM,UAAU,aAAa,MAAM,UAAU;GAC7D,eACE,MAAM,iBACL,CACC,IAAI,MAAM,iBAAiB,CAAC,GAAG,KAAK,cAAc,SAAS,WAAW,GACtE,IAAI,MAAM,cAAc,CAAC,GAAG,KAAK,cAAc,WAAW,WAAW,CACvE;EACJ;CACF;AACF;;;;;;;;AASA,SAAgB,6BAA6B,cAA8B;CACzE,OAAO,oBAAoB;AAC7B;AAEA,SAAS,sBACP,OACqC;CACrC,OACE,OAAO,UAAU,YACjB,UAAU,QACV,cAAc,SACd,WAAW,SACX,YAAY,SACZ,eAAe;AAEnB"}