syncorejs 0.2.3 → 0.2.4

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 (69) hide show
  1. package/dist/_vendor/core/cli.d.mts.map +1 -1
  2. package/dist/_vendor/core/cli.mjs +272 -7
  3. package/dist/_vendor/core/cli.mjs.map +1 -1
  4. package/dist/_vendor/core/index.d.mts +3 -3
  5. package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
  6. package/dist/_vendor/core/runtime/devtools.mjs +131 -0
  7. package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
  8. package/dist/_vendor/core/runtime/functions.d.mts +3 -3
  9. package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
  10. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +1 -1
  11. package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -1
  12. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +1 -1
  13. package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -1
  14. package/dist/_vendor/core/runtime/internal/engines/shared.mjs +5 -1
  15. package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -1
  16. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +99 -13
  17. package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -1
  18. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +38 -4
  19. package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -1
  20. package/dist/_vendor/core/runtime/runtime.d.mts +65 -8
  21. package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
  22. package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
  23. package/dist/_vendor/core/transport.d.mts.map +1 -1
  24. package/dist/_vendor/core/transport.mjs +30 -5
  25. package/dist/_vendor/core/transport.mjs.map +1 -1
  26. package/dist/_vendor/devtools-protocol/index.d.ts +75 -1
  27. package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
  28. package/dist/_vendor/devtools-protocol/index.js.map +1 -1
  29. package/dist/_vendor/next/index.js +9 -1
  30. package/dist/_vendor/next/index.js.map +1 -1
  31. package/dist/_vendor/platform-expo/index.d.ts +1 -1
  32. package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
  33. package/dist/_vendor/platform-expo/index.js +6 -1
  34. package/dist/_vendor/platform-expo/index.js.map +1 -1
  35. package/dist/_vendor/platform-node/index.d.mts +2 -1
  36. package/dist/_vendor/platform-node/index.d.mts.map +1 -1
  37. package/dist/_vendor/platform-node/index.mjs +27 -2
  38. package/dist/_vendor/platform-node/index.mjs.map +1 -1
  39. package/dist/_vendor/platform-node/ipc-react.mjs +4 -0
  40. package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
  41. package/dist/_vendor/platform-web/external-change.d.ts +2 -2
  42. package/dist/_vendor/platform-web/external-change.js +2 -2
  43. package/dist/_vendor/platform-web/external-change.js.map +1 -1
  44. package/dist/_vendor/platform-web/index.d.ts +13 -10
  45. package/dist/_vendor/platform-web/index.d.ts.map +1 -1
  46. package/dist/_vendor/platform-web/index.js +66 -10
  47. package/dist/_vendor/platform-web/index.js.map +1 -1
  48. package/dist/_vendor/platform-web/indexeddb.d.ts +3 -3
  49. package/dist/_vendor/platform-web/indexeddb.js +3 -3
  50. package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
  51. package/dist/_vendor/platform-web/opfs.d.ts +3 -1
  52. package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
  53. package/dist/_vendor/platform-web/opfs.js +29 -3
  54. package/dist/_vendor/platform-web/opfs.js.map +1 -1
  55. package/dist/_vendor/platform-web/persistence.d.ts +31 -1
  56. package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
  57. package/dist/_vendor/platform-web/persistence.js.map +1 -1
  58. package/dist/_vendor/platform-web/react.d.ts.map +1 -1
  59. package/dist/_vendor/platform-web/react.js +9 -1
  60. package/dist/_vendor/platform-web/react.js.map +1 -1
  61. package/dist/_vendor/react/index.d.ts +6 -5
  62. package/dist/_vendor/react/index.d.ts.map +1 -1
  63. package/dist/_vendor/react/index.js +6 -5
  64. package/dist/_vendor/react/index.js.map +1 -1
  65. package/dist/_vendor/svelte/index.d.ts +8 -6
  66. package/dist/_vendor/svelte/index.d.ts.map +1 -1
  67. package/dist/_vendor/svelte/index.js +7 -5
  68. package/dist/_vendor/svelte/index.js.map +1 -1
  69. package/package.json +1 -1
@@ -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":"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"}
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"}
@@ -1,4 +1,4 @@
1
- import { CreateWebPersistenceOptions, StoredWebFile, SyncoreWebPersistence, WebPersistenceMode, createWebPersistence, isOpfsAvailable } from "./persistence.js";
1
+ import { CreateWebPersistenceOptions, StoredWebFile, StoredWebFileMetadata, StoredWebFileRange, SyncoreWebPersistence, WebPersistenceMode, createWebPersistence, isOpfsAvailable } from "./persistence.js";
2
2
  import { BroadcastChannelExternalChangeSignal, BroadcastChannelExternalChangeSignalOptions, SqlJsExternalChangeApplier, SqlJsExternalChangeApplierOptions, createDefaultSyncChannelName } from "./external-change.js";
3
3
  import { AttachWebWorkerRuntimeOptions, AttachedWebWorkerRuntime, CreateWebWorkerClientProviderOptions, ManagedWebWorkerClient, SyncoreWebWorkerClient, SyncoreWorkerMessageEndpoint, WebWorkerSyncoreSchema, WorkerQueryWatch, attachWebWorkerRuntime, createManagedWebWorkerClient, createSyncoreWebWorkerClient, createWebWorkerClient } from "./worker.js";
4
4
  import { IndexedDbPersistenceOptions, SyncoreIndexedDbPersistence } from "./indexeddb.js";
@@ -66,7 +66,8 @@ interface CreateWebRuntimeOptions<TSchema extends WebSyncoreSchema = WebSyncoreS
66
66
  driver?: SyncoreRuntimeOptions<TSchema>["driver"];
67
67
  /**
68
68
  * Custom blob storage adapter. Defaults to `BrowserFileStorageAdapter`
69
- * backed by the same persistence layer as the SQL driver.
69
+ * only when the resolved persistence layer is OPFS. IndexedDB persistence is
70
+ * data-only unless a custom adapter is provided explicitly.
70
71
  *
71
72
  * Override when you want to store files in a different location (e.g. an
72
73
  * in-memory adapter for tests).
@@ -203,8 +204,8 @@ interface WebExternalChangeSupport {
203
204
  * Create a full Syncore runtime directly in the browser (main thread or
204
205
  * shared worker).
205
206
  *
206
- * This function sets up SQL.js, the OPFS/IndexedDB persistence layer, blob
207
- * storage, cross-tab change synchronisation via `BroadcastChannel`, and
207
+ * This function sets up SQL.js, the OPFS/IndexedDB persistence layer,
208
+ * OPFS-backed blob storage when available, cross-tab change synchronisation via `BroadcastChannel`, and
208
209
  * auto-connects to the devtools server in development.
209
210
  *
210
211
  * @remarks
@@ -243,7 +244,7 @@ declare function createWebExternalChangeSupport(options: {
243
244
  *
244
245
  * Behaves identically to {@link createWebExternalChangeSupport} but accepts
245
246
  * the same options shape used by `createExpoSyncoreRuntime`, making it easy
246
- * to share config when bootstrapping an Expo web runtime.\
247
+ * to share config when bootstrapping an Expo web runtime.
247
248
  *
248
249
  * Called internally by the Expo platform adapter. Use directly only when
249
250
  * constructing the runtime outside of `createExpoSyncoreRuntime`.
@@ -348,7 +349,7 @@ interface BrowserWebSocketDevtoolsSinkOptions {
348
349
  capabilities?: SyncoreDevtoolsCapabilities;
349
350
  }
350
351
  /**
351
- * A {@link DevtoolsSink} that forwards runtime events to the Syncore devtools
352
+ * A DevtoolsSink that forwards runtime events to the Syncore devtools
352
353
  * dashboard over a persistent WebSocket connection with auto-reconnect.
353
354
  *
354
355
  * Returned by {@link createBrowserWebSocketDevtoolsSink}. You typically do not
@@ -389,8 +390,8 @@ declare function createBrowserWebSocketDevtoolsSink(options: BrowserWebSocketDev
389
390
  /**
390
391
  * Browser file/blob storage adapter backed by `SyncoreWebPersistence`.
391
392
  *
392
- * Stores binary blobs (images, documents, etc.) in the same OPFS or
393
- * IndexedDB store as the SQLite database. Pass an instance to
393
+ * Stores binary blobs (images, documents, etc.) in OPFS alongside the SQLite
394
+ * database. Pass an instance to
394
395
  * `CreateWebRuntimeOptions.storage` to enable Syncore's Storage API
395
396
  * (`ctx.storage.put`, `ctx.storage.get`, etc.) in browser functions.
396
397
  *
@@ -398,7 +399,7 @@ declare function createBrowserWebSocketDevtoolsSink(options: BrowserWebSocketDev
398
399
  * const runtime = await createWebSyncoreRuntime({
399
400
  * schema,
400
401
  * functions,
401
- * storage: (persistence) => new BrowserFileStorageAdapter(persistence, "files"),
402
+ * storage: new BrowserFileStorageAdapter(persistence, "files"),
402
403
  * });
403
404
  * ```
404
405
  */
@@ -409,9 +410,11 @@ declare class BrowserFileStorageAdapter implements SyncoreStorageAdapter {
409
410
  put(id: string, input: StorageWriteInput): Promise<StorageObject>;
410
411
  get(id: string): Promise<StorageObject | null>;
411
412
  read(id: string): Promise<Uint8Array | null>;
413
+ supportsRange(): boolean;
414
+ readRange(id: string, offset: number, length: number): Promise<Uint8Array | null>;
412
415
  delete(id: string): Promise<void>;
413
416
  list(): Promise<StorageObject[]>;
414
417
  }
415
418
  //#endregion
416
- export { AttachWebWorkerRuntimeOptions, AttachedWebWorkerRuntime, BroadcastChannelExternalChangeSignal, BroadcastChannelExternalChangeSignalOptions, BrowserFileStorageAdapter, BrowserSyncoreSchema, BrowserWebSocketDevtoolsSink, BrowserWebSocketDevtoolsSinkOptions, CreateBrowserRuntimeOptions, CreateBrowserWorkerRuntimeOptions, CreateWebPersistenceOptions, CreateWebRuntimeOptions, CreateWebWorkerClientProviderOptions, CreateWebWorkerRuntimeOptions, IndexedDbPersistenceOptions, ManagedWebWorkerClient, OpfsPersistenceOptions, SqlJsExternalChangeApplier, SqlJsExternalChangeApplierOptions, StoredWebFile, SyncoreIndexedDbPersistence, SyncoreOpfsPersistence, SyncoreWebPersistence, SyncoreWebWorkerClient, SyncoreWorkerMessageEndpoint, WebExternalChangeSupport, WebPersistenceMode, WebSyncoreSchema, WebWorkerSyncoreSchema, WorkerQueryWatch, attachWebWorkerRuntime, createBrowserSyncoreClient, createBrowserSyncoreRuntime, createBrowserWebSocketDevtoolsSink, createBrowserWorkerRuntime, createDefaultSyncChannelName, createExpoWebExternalChangeSupport, createManagedWebWorkerClient, createSyncoreWebWorkerClient, createWebExternalChangeSupport, createWebPersistence, createWebSyncoreClient, createWebSyncoreRuntime, createWebWorkerClient, createWebWorkerRuntime, isOpfsAvailable };
419
+ export { AttachWebWorkerRuntimeOptions, AttachedWebWorkerRuntime, BroadcastChannelExternalChangeSignal, BroadcastChannelExternalChangeSignalOptions, BrowserFileStorageAdapter, BrowserSyncoreSchema, BrowserWebSocketDevtoolsSink, BrowserWebSocketDevtoolsSinkOptions, CreateBrowserRuntimeOptions, CreateBrowserWorkerRuntimeOptions, CreateWebPersistenceOptions, CreateWebRuntimeOptions, CreateWebWorkerClientProviderOptions, CreateWebWorkerRuntimeOptions, IndexedDbPersistenceOptions, ManagedWebWorkerClient, OpfsPersistenceOptions, SqlJsExternalChangeApplier, SqlJsExternalChangeApplierOptions, StoredWebFile, StoredWebFileMetadata, StoredWebFileRange, SyncoreIndexedDbPersistence, SyncoreOpfsPersistence, SyncoreWebPersistence, SyncoreWebWorkerClient, SyncoreWorkerMessageEndpoint, WebExternalChangeSupport, WebPersistenceMode, WebSyncoreSchema, WebWorkerSyncoreSchema, WorkerQueryWatch, attachWebWorkerRuntime, createBrowserSyncoreClient, createBrowserSyncoreRuntime, createBrowserWebSocketDevtoolsSink, createBrowserWorkerRuntime, createDefaultSyncChannelName, createExpoWebExternalChangeSupport, createManagedWebWorkerClient, createSyncoreWebWorkerClient, createWebExternalChangeSupport, createWebPersistence, createWebSyncoreClient, createWebSyncoreRuntime, createWebWorkerClient, createWebWorkerRuntime, isOpfsAvailable };
417
420
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
@@ -14,8 +14,8 @@ const DATA_SOURCE_ALIAS_PREFIX = "data-source-alias";
14
14
  * Create a full Syncore runtime directly in the browser (main thread or
15
15
  * shared worker).
16
16
  *
17
- * This function sets up SQL.js, the OPFS/IndexedDB persistence layer, blob
18
- * storage, cross-tab change synchronisation via `BroadcastChannel`, and
17
+ * This function sets up SQL.js, the OPFS/IndexedDB persistence layer,
18
+ * OPFS-backed blob storage when available, cross-tab change synchronisation via `BroadcastChannel`, and
19
19
  * auto-connects to the devtools server in development.
20
20
  *
21
21
  * @remarks
@@ -43,7 +43,9 @@ async function createWebSyncoreRuntime(options) {
43
43
  ...wasmUrl ? { wasmUrl } : {},
44
44
  ...options.locateFile ? { locateFile: options.locateFile } : {}
45
45
  });
46
- const storage = options.storage ?? new BrowserFileStorageAdapter(persistence, options.storageNamespace ?? options.databaseName ?? "syncore");
46
+ const storageNamespace = options.storageNamespace ?? options.databaseName ?? "syncore";
47
+ const storage = options.storage ?? (persistence.storageProtocol === "opfs" ? new BrowserFileStorageAdapter(persistence, storageNamespace) : new UnavailableBrowserStorageAdapter(BROWSER_STORAGE_UNAVAILABLE_REASON));
48
+ const runtimeCapabilities = createBrowserRuntimeCapabilities(persistence, storage, Boolean(options.storage));
47
49
  const externalChangeSupport = createWebExternalChangeSupport({
48
50
  databaseName: options.databaseName ?? "syncore",
49
51
  persistence,
@@ -69,7 +71,7 @@ async function createWebSyncoreRuntime(options) {
69
71
  databaseLabel,
70
72
  dataSourceAlias,
71
73
  storageIdentity,
72
- capabilities: createBrowserDevtoolsCapabilities()
74
+ capabilities: createBrowserDevtoolsCapabilities(runtimeCapabilities)
73
75
  };
74
76
  if (appName) sinkOptions.appName = appName;
75
77
  if (origin) sinkOptions.origin = origin;
@@ -94,6 +96,7 @@ async function createWebSyncoreRuntime(options) {
94
96
  ...externalChangeSupport.applier ? { externalChangeApplier: externalChangeSupport.applier } : {},
95
97
  platform: options.platform ?? "browser",
96
98
  ...options.capabilities ? { capabilities: options.capabilities } : {},
99
+ runtimeCapabilities,
97
100
  ...resolvedDevtools ? { devtools: resolvedDevtools } : {},
98
101
  ...options.scheduler ? { scheduler: options.scheduler } : {}
99
102
  });
@@ -151,7 +154,7 @@ function createWebExternalChangeSupport(options) {
151
154
  *
152
155
  * Behaves identically to {@link createWebExternalChangeSupport} but accepts
153
156
  * the same options shape used by `createExpoSyncoreRuntime`, making it easy
154
- * to share config when bootstrapping an Expo web runtime.\
157
+ * to share config when bootstrapping an Expo web runtime.
155
158
  *
156
159
  * Called internally by the Expo platform adapter. Use directly only when
157
160
  * constructing the runtime outside of `createExpoSyncoreRuntime`.
@@ -437,7 +440,22 @@ function withRuntimeSummaryMeta(summary, options) {
437
440
  capabilities: options.capabilities ?? createBrowserDevtoolsCapabilities()
438
441
  };
439
442
  }
440
- function createBrowserDevtoolsCapabilities() {
443
+ function createBrowserRuntimeCapabilities(persistence, storage, hasExplicitStorage) {
444
+ if (!hasExplicitStorage && persistence.storageProtocol !== "opfs") return { storage: {
445
+ available: false,
446
+ reason: BROWSER_STORAGE_UNAVAILABLE_REASON,
447
+ protocol: persistence.storageProtocol,
448
+ supportsRange: false
449
+ } };
450
+ return { storage: {
451
+ available: true,
452
+ protocol: hasExplicitStorage ? "custom" : persistence.storageProtocol,
453
+ ...storage.supportsRange ? { supportsRange: storage.supportsRange() !== false } : {}
454
+ } };
455
+ }
456
+ function createBrowserDevtoolsCapabilities(runtimeCapabilities) {
457
+ const storageCapability = runtimeCapabilities?.storage;
458
+ const storageAvailable = storageCapability?.available !== false;
441
459
  return {
442
460
  sql: {
443
461
  read: false,
@@ -450,6 +468,14 @@ function createBrowserDevtoolsCapabilities() {
450
468
  mutate: true,
451
469
  importExport: true
452
470
  },
471
+ storage: {
472
+ browse: storageAvailable,
473
+ download: storageAvailable,
474
+ readRange: storageCapability?.supportsRange === true,
475
+ delete: storageAvailable,
476
+ maxPreviewBytes: 8e4,
477
+ ...!storageAvailable && storageCapability?.reason ? { reason: storageCapability.reason } : {}
478
+ },
453
479
  scheduler: {
454
480
  read: true,
455
481
  edit: true
@@ -706,14 +732,15 @@ const SESSION_NOUNS = [
706
732
  "Thunder",
707
733
  "Drift"
708
734
  ];
735
+ const BROWSER_STORAGE_UNAVAILABLE_REASON = "Browser file storage requires OPFS. IndexedDB is used for data only.";
709
736
  function generateUniqueSessionName() {
710
737
  return `${SESSION_ADJECTIVES[Math.floor(Math.random() * SESSION_ADJECTIVES.length)]} ${SESSION_NOUNS[Math.floor(Math.random() * SESSION_NOUNS.length)]}`;
711
738
  }
712
739
  /**
713
740
  * Browser file/blob storage adapter backed by `SyncoreWebPersistence`.
714
741
  *
715
- * Stores binary blobs (images, documents, etc.) in the same OPFS or
716
- * IndexedDB store as the SQLite database. Pass an instance to
742
+ * Stores binary blobs (images, documents, etc.) in OPFS alongside the SQLite
743
+ * database. Pass an instance to
717
744
  * `CreateWebRuntimeOptions.storage` to enable Syncore's Storage API
718
745
  * (`ctx.storage.put`, `ctx.storage.get`, etc.) in browser functions.
719
746
  *
@@ -721,7 +748,7 @@ function generateUniqueSessionName() {
721
748
  * const runtime = await createWebSyncoreRuntime({
722
749
  * schema,
723
750
  * functions,
724
- * storage: (persistence) => new BrowserFileStorageAdapter(persistence, "files"),
751
+ * storage: new BrowserFileStorageAdapter(persistence, "files"),
725
752
  * });
726
753
  * ```
727
754
  */
@@ -731,6 +758,7 @@ var BrowserFileStorageAdapter = class {
731
758
  constructor(persistence, namespace) {
732
759
  this.persistence = persistence;
733
760
  this.namespace = namespace;
761
+ if (persistence.storageProtocol !== "opfs") throw new Error(BROWSER_STORAGE_UNAVAILABLE_REASON);
734
762
  }
735
763
  async put(id, input) {
736
764
  const bytes = normalizeBinary(input.data);
@@ -755,11 +783,18 @@ var BrowserFileStorageAdapter = class {
755
783
  async read(id) {
756
784
  return (await this.persistence.getFile(this.namespace, id))?.bytes ?? null;
757
785
  }
786
+ supportsRange() {
787
+ return Boolean(this.persistence.getFileRange);
788
+ }
789
+ async readRange(id, offset, length) {
790
+ if (!this.persistence.getFileRange) return null;
791
+ return (await this.persistence.getFileRange(this.namespace, id, offset, length))?.bytes ?? null;
792
+ }
758
793
  async delete(id) {
759
794
  await this.persistence.deleteFile(this.namespace, id);
760
795
  }
761
796
  async list() {
762
- return (await this.persistence.listFiles(this.namespace)).map((file) => ({
797
+ return (this.persistence.listFileMetadata ? await this.persistence.listFileMetadata(this.namespace) : await this.persistence.listFiles(this.namespace)).map((file) => ({
763
798
  id: file.id,
764
799
  path: `${this.persistence.storageProtocol}://${this.namespace}/${file.id}`,
765
800
  size: file.size,
@@ -767,6 +802,27 @@ var BrowserFileStorageAdapter = class {
767
802
  }));
768
803
  }
769
804
  };
805
+ var UnavailableBrowserStorageAdapter = class {
806
+ reason;
807
+ constructor(reason) {
808
+ this.reason = reason;
809
+ }
810
+ async put() {
811
+ throw new Error(this.reason);
812
+ }
813
+ async get() {
814
+ throw new Error(this.reason);
815
+ }
816
+ async read() {
817
+ throw new Error(this.reason);
818
+ }
819
+ supportsRange() {
820
+ return false;
821
+ }
822
+ async delete() {
823
+ throw new Error(this.reason);
824
+ }
825
+ };
770
826
  function normalizeBinary(data) {
771
827
  if (typeof data === "string") return new TextEncoder().encode(data);
772
828
  if (data instanceof Uint8Array) return data;