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.
- package/dist/_vendor/core/cli.d.mts.map +1 -1
- package/dist/_vendor/core/cli.mjs +272 -7
- package/dist/_vendor/core/cli.mjs.map +1 -1
- package/dist/_vendor/core/index.d.mts +3 -3
- package/dist/_vendor/core/runtime/devtools.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/devtools.mjs +131 -0
- package/dist/_vendor/core/runtime/devtools.mjs.map +1 -1
- package/dist/_vendor/core/runtime/functions.d.mts +3 -3
- package/dist/_vendor/core/runtime/functions.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs +1 -1
- package/dist/_vendor/core/runtime/internal/engines/devtoolsEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs +1 -1
- package/dist/_vendor/core/runtime/internal/engines/reactivityEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs +5 -1
- package/dist/_vendor/core/runtime/internal/engines/shared.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs +99 -13
- package/dist/_vendor/core/runtime/internal/engines/storageEngine.mjs.map +1 -1
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs +38 -4
- package/dist/_vendor/core/runtime/internal/runtimeKernel.mjs.map +1 -1
- package/dist/_vendor/core/runtime/runtime.d.mts +65 -8
- package/dist/_vendor/core/runtime/runtime.d.mts.map +1 -1
- package/dist/_vendor/core/runtime/runtime.mjs.map +1 -1
- package/dist/_vendor/core/transport.d.mts.map +1 -1
- package/dist/_vendor/core/transport.mjs +30 -5
- package/dist/_vendor/core/transport.mjs.map +1 -1
- package/dist/_vendor/devtools-protocol/index.d.ts +75 -1
- package/dist/_vendor/devtools-protocol/index.d.ts.map +1 -1
- package/dist/_vendor/devtools-protocol/index.js.map +1 -1
- package/dist/_vendor/next/index.js +9 -1
- package/dist/_vendor/next/index.js.map +1 -1
- package/dist/_vendor/platform-expo/index.d.ts +1 -1
- package/dist/_vendor/platform-expo/index.d.ts.map +1 -1
- package/dist/_vendor/platform-expo/index.js +6 -1
- package/dist/_vendor/platform-expo/index.js.map +1 -1
- package/dist/_vendor/platform-node/index.d.mts +2 -1
- package/dist/_vendor/platform-node/index.d.mts.map +1 -1
- package/dist/_vendor/platform-node/index.mjs +27 -2
- package/dist/_vendor/platform-node/index.mjs.map +1 -1
- package/dist/_vendor/platform-node/ipc-react.mjs +4 -0
- package/dist/_vendor/platform-node/ipc-react.mjs.map +1 -1
- package/dist/_vendor/platform-web/external-change.d.ts +2 -2
- package/dist/_vendor/platform-web/external-change.js +2 -2
- package/dist/_vendor/platform-web/external-change.js.map +1 -1
- package/dist/_vendor/platform-web/index.d.ts +13 -10
- package/dist/_vendor/platform-web/index.d.ts.map +1 -1
- package/dist/_vendor/platform-web/index.js +66 -10
- package/dist/_vendor/platform-web/index.js.map +1 -1
- package/dist/_vendor/platform-web/indexeddb.d.ts +3 -3
- package/dist/_vendor/platform-web/indexeddb.js +3 -3
- package/dist/_vendor/platform-web/indexeddb.js.map +1 -1
- package/dist/_vendor/platform-web/opfs.d.ts +3 -1
- package/dist/_vendor/platform-web/opfs.d.ts.map +1 -1
- package/dist/_vendor/platform-web/opfs.js +29 -3
- package/dist/_vendor/platform-web/opfs.js.map +1 -1
- package/dist/_vendor/platform-web/persistence.d.ts +31 -1
- package/dist/_vendor/platform-web/persistence.d.ts.map +1 -1
- package/dist/_vendor/platform-web/persistence.js.map +1 -1
- package/dist/_vendor/platform-web/react.d.ts.map +1 -1
- package/dist/_vendor/platform-web/react.js +9 -1
- package/dist/_vendor/platform-web/react.js.map +1 -1
- package/dist/_vendor/react/index.d.ts +6 -5
- package/dist/_vendor/react/index.d.ts.map +1 -1
- package/dist/_vendor/react/index.js +6 -5
- package/dist/_vendor/react/index.js.map +1 -1
- package/dist/_vendor/svelte/index.d.ts +8 -6
- package/dist/_vendor/svelte/index.d.ts.map +1 -1
- package/dist/_vendor/svelte/index.js +7 -5
- package/dist/_vendor/svelte/index.js.map +1 -1
- 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":";;;;;;;;;;AAuDA,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;AAEjC,SAAS,cAAc,OAA8C;CACnE,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,KAAK,KAAK;CAE1B,IAAI,iBAAiB,YACnB,OAAO;CAET,OAAO,IAAI,WAAW,KAAK;AAC7B;AAEA,SAAS,gBAAgB,QAAoC;CAC3D,OAAO;AACT;;;;;;;;;;;;;;AAeA,IAAa,mBAAb,MAA0D;CAInC;CAHrB;CACA,mBAA2B;CAE3B,YAAY,cAA+B;EAAtB,KAAA,eAAA;EACnB,KAAK,WAAW,IAAI,aAAa,YAAY;EAC7C,KAAK,SAAS,KAAK,2BAA2B;EAC9C,KAAK,SAAS,KAAK,4BAA4B;CACjD;CAEA,MAAM,KAAK,KAA4B;EACrC,KAAK,SAAS,KAAK,GAAG;CACxB;CAEA,MAAM,IACJ,KACA,SAAoB,CAAC,GAC4C;EAEjE,MAAM,SADY,KAAK,SAAS,QAAQ,GACjB,EAAE,IAAI,GAAG,gBAAgB,MAAM,CAAC;EACvD,OAAO;GACL,SAAS,OAAO,OAAO,WAAW,CAAC;GACnC,iBACE,OAAO,OAAO,oBAAoB,WAC9B,OAAO,OAAO,eAAe,IAC7B,OAAO;EACf;CACF;CAEA,MAAM,IAAO,KAAa,SAAoB,CAAC,GAA2B;EAExE,OADkB,KAAK,SAAS,QAAQ,GACzB,EAAE,IAAI,GAAG,gBAAgB,MAAM,CAAC;CACjD;CAEA,MAAM,IAAO,KAAa,SAAoB,CAAC,GAAiB;EAE9D,OADkB,KAAK,SAAS,QAAQ,GACzB,EAAE,IAAI,GAAG,gBAAgB,MAAM,CAAC;CACjD;CAEA,MAAM,gBAAmB,UAAwC;EAC/D,IAAI,KAAK,mBAAmB,GAC1B,OAAO,KAAK,cAAc,UAAU,KAAK,oBAAoB,QAAQ;EAGvE,KAAK,oBAAoB;EACzB,KAAK,SAAS,KAAK,OAAO;EAC1B,IAAI;GACF,MAAM,SAAS,MAAM,SAAS;GAC9B,KAAK,SAAS,KAAK,QAAQ;GAC3B,OAAO;EACT,SAAS,OAAO;GACd,KAAK,SAAS,KAAK,UAAU;GAC7B,MAAM;EACR,UAAU;GACR,KAAK,oBAAoB;EAC3B;CACF;CAEA,MAAM,cAAiB,MAAc,UAAwC;EAC3E,MAAM,WAAW,KAAK,WAAW,kBAAkB,GAAG;EACtD,KAAK,SAAS,KAAK,aAAa,UAAU;EAC1C,IAAI;GACF,MAAM,SAAS,MAAM,SAAS;GAC9B,KAAK,SAAS,KAAK,qBAAqB,UAAU;GAClD,OAAO;EACT,SAAS,OAAO;GACd,KAAK,SAAS,KAAK,yBAAyB,UAAU;GACtD,KAAK,SAAS,KAAK,qBAAqB,UAAU;GAClD,MAAM;EACR;CACF;CAEA,MAAM,QAAuB;EAC3B,KAAK,SAAS,MAAM;CACtB;AACF;;;;;;;;;;;;;;AAeA,IAAa,yBAAb,MAAqE;CACtC;CAA7B,YAAY,WAAoC;EAAnB,KAAA,YAAA;CAAoB;CAEjD,SAAiB,IAAoB;EACnC,OAAO,KAAK,KAAK,KAAK,WAAW,EAAE;CACrC;CAEA,MAAM,IAAI,IAAY,OAAkD;EACtE,MAAM,MAAM,KAAK,WAAW,EAAE,WAAW,KAAK,CAAC;EAC/C,MAAM,WAAW,KAAK,SAAS,EAAE;EACjC,MAAM,QAAQ,cAAc,MAAM,IAAI;EACtC,MAAM,UAAU,UAAU,KAAK;EAC/B,OAAO;GACL;GACA,MAAM;GACN,MAAM,MAAM;GACZ,aAAa,MAAM,eAAe;EACpC;CACF;CAEA,MAAM,IAAI,IAA2C;EACnD,MAAM,WAAW,KAAK,SAAS,EAAE;EACjC,IAAI;GAEF,OAAO;IACL;IACA,MAAM;IACN,OAAM,MAJW,KAAK,QAAQ,GAInB;IACX,aAAa;GACf;EACF,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,KAAK,IAAwC;EACjD,IAAI;GACF,OAAO,MAAM,SAAS,KAAK,SAAS,EAAE,CAAC;EACzC,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,OAAO,IAA2B;EACtC,MAAM,GAAG,KAAK,SAAS,EAAE,GAAG,EAAE,OAAO,KAAK,CAAC;CAC7C;CAEA,MAAM,OAAiC;EACrC,IAAI;GACF,MAAM,UAAU,MAAM,QAAQ,KAAK,WAAW,EAAE,eAAe,KAAK,CAAC;GAerE,OAAO,MAde,QAAQ,IAC5B,QACG,QAAQ,UAAU,MAAM,OAAO,CAAC,EAChC,IAAI,OAAO,UAAU;IACpB,MAAM,WAAW,KAAK,SAAS,MAAM,IAAI;IACzC,MAAM,OAAO,MAAM,KAAK,QAAQ;IAChC,OAAO;KACL,IAAI,MAAM;KACV,MAAM;KACN,MAAM,KAAK;KACX,aAAa;IACf;GACF,CAAC,CACL;EAEF,QAAQ;GACN,OAAO,CAAC;EACV;CACF;AACF;AAEA,MAAM,qBAAqB;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,4BAAoC;CAI3C,OAAO,GAFL,mBAAmB,KAAK,MAAM,KAAK,OAAO,IAAI,mBAAmB,MAAM,GAE3D,GADD,cAAc,KAAK,MAAM,KAAK,OAAO,IAAI,cAAc,MAAM;AAE5E;AAEA,SAAS,gCACP,kBACA,iBACQ;CACR,MAAM,gBAAgB,KAAK,KAAK,kBAAkB,uBAAuB;CACzE,MAAM,UAAU,WAAW,QAAQ,EAChC,OAAO,eAAe,EACtB,OAAO,KAAK,EACZ,MAAM,GAAG,EAAE;CACd,MAAM,YAAY,KAAK,KACrB,eACA,GAAG,yBAAyB,GAAG,QAAQ,KACzC;CAEA,IAAI;EACF,MAAM,WAAW,aAAa,WAAW,MAAM,EAAE,KAAK;EACtD,IAAI,SAAS,SAAS,GACpB,OAAO;CAEX,QAAQ,CAER;CAEA,MAAM,YAAY,0BAA0B;CAC5C,IAAI;EACF,UAAU,eAAe,EAAE,WAAW,KAAK,CAAC;EAC5C,cAAc,WAAW,WAAW,MAAM;CAC5C,QAAQ,CAER;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmNA,SAAgB,yBAGd,SACyB;CACzB,MAAM,sBACJ,QAAQ,eAAe,8BAA8B;CACvD,MAAM,kBAAkB,SAAS,KAAK,QAAQ,QAAQ,YAAY;CAClE,MAAM,oBACJ,QAAQ,aAAa,KAAA,KACrB,uBACA,8BAA8B,IAC1B,gCAAgC;EAC9B,KAAK;EACL,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;EACtD,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;EACnD,GAAI,QAAQ,eACR,EAAE,cAAc,QAAQ,aAAa,IACrC,CAAC;EACL,YAAY;EACZ,iBAAiB;EACjB,eAAe,KAAK,SAAS,QAAQ,YAAY;EACjD,iBAAiB,gCACf,QAAQ,kBACR,eACF;EACA;EACA,aAAa;EACb,cAAc,+BAA+B;CAC/C,CAAC,IACD,KAAA;CACN,MAAM,iBAAiD;EACrD,QAAQ,QAAQ;EAChB,WAAW,QAAQ;EACnB,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;EAC/D,QAAQ,IAAI,iBAAiB,QAAQ,YAAY;EACjD,SAAS,IAAI,uBAAuB,QAAQ,gBAAgB;EAC5D,UAAU,QAAQ,YAAY;CAChC;CACA,IAAI,QAAQ,cACV,eAAe,eAAe,QAAQ;CAExC,MAAM,mBACJ,QAAQ,aAAa,QACjB,KAAA,IACC,QAAQ,YAAY;CAC3B,IAAI,kBACF,eAAe,WAAW;CAE5B,IAAI,mBAAmB,sBACrB,eAAe,uBAAuB,kBAAkB;CAE1D,IAAI,QAAQ,WACV,eAAe,YAAY,QAAQ;CAErC,MAAM,UAAU,IAAI,eAAe,cAAc;CACjD,mBAAmB,cAAc,OAAO;CACxC,IAAI,mBAAmB;EACrB,kBAAkB,qBAChB,6BAA6B;GAC3B,QAAQ,eAAe;GACvB,QAAQ,QAAQ;GAChB,WAAW,QAAQ;GACnB,OAAO,QAAQ,SAAS;EAC1B,CAAC,CACH;EACA,kBAAkB,uBAChB,+BAA+B;GAC7B,QAAQ,eAAe;GACvB,QAAQ,QAAQ;GAChB,WAAW,QAAQ;GACnB,OAAO,QAAQ,SAAS;EAC1B,CAAC,CACH;EACA,MAAM,OAAO,QAAQ,KAAK,KAAK,OAAO;EACtC,QAAQ,OAAO,YAAY;GACzB,kBAAkB,QAAQ;GAC1B,MAAM,KAAK;EACb;CACF;CACA,OAAO;AACT;;;;AAKA,SAAgB,wBAEd,SAAkC;CAClC,OAAO,QAAQ,aAAa;AAC9B;;;;AAKA,eAAsB,+BAGpB,SAC4C;CAC5C,MAAM,UAAU,yBAAyB,OAAO;CAChD,MAAM,QAAQ,MAAM;CACpB,OAAO;EACL;EACA,QAAQ,QAAQ,aAAa;EAC7B,MAAM,UAAU;GACd,MAAM,QAAQ,KAAK;EACrB;CACF;AACF;;;;;;;;;;;AAYA,eAAsB,sBAIpB,SACA,UAIkB;CAClB,MAAM,UAAU,MAAM,+BAA+B,OAAO;CAC5D,IAAI;EACF,OAAO,MAAM,SAAS,QAAQ,QAAQ,QAAQ,OAAO;CACvD,UAAU;EACR,MAAM,QAAQ,QAAQ;CACxB;AACF;;;;;AAMA,SAAgB,4BACd,SACA;CACA,MAAM,UAAU,QAAQ,WAAW;CACnC,OAAO,6BAA6B;EAClC,YAAY,SAAkB;GAC5B,IAAI,CAAC,QAAQ,OAAO,YAAY,GAC9B,QAAQ,OAAO,YAAY,KAAK,SAAS,OAAO;EAEpD;EACA,UAAU,UAAsC;GAC9C,OAAO,QAAQ,kBAAkB,QAAQ;EAC3C;CACF,CAAC;AACH;AAiBA,SAAgB,mCAAmC,SAMrB;CAC5B,MAAM,mBAAsC,CAAC;CAC7C,MAAM,UAAU,QAAQ,WAAW;CACnC,IAAI;CAIJ,IAAI,CAAC,QAAQ,mBAAmB;EAC9B,IAAI,CAAC,QAAQ,SACX,MAAM,IAAI,MACR,oFACF;EAEF,MAAM,4BAAY,IAAI,IAAgC;EACtD,MAAM,yBACJ,OACA,YACG;GACH,IAAI,MAAM,WAAW,QAAQ,OAAO,aAClC;GAEF,KAAK,MAAM,YAAY,WACrB,SAAS,OAAO;EAEpB;EACA,QAAQ,QAAQ,GAAG,SAAS,qBAAqB;EACjD,iBAAiB,WAAW;GAC1B,QAAQ,SAAS,IAAI,SAAS,qBAAqB;GACnD,UAAU,MAAM;EAClB,CAAC;EACD,qBAAqB,aAAa;GAChC,UAAU,IAAI,QAAQ;GACtB,aAAa,UAAU,OAAO,QAAQ;EACxC;CACF,OACE,qBAAqB,aAAa,QAAQ,kBAAmB,QAAQ;CAGvE,MAAM,WAAW,4BAA4B;EAC3C,QAAQ,QAAQ;EAChB;EACA;CACF,CAAC;CACD,MAAM,kBAAkB,qBAAqB;EAC3C;EACA,qBAAqB,QAAQ;CAC/B,CAAC;CAED,OAAO;EACL,OAAO,gBAAgB;EACvB,MAAM,UAAU;GACd,MAAM,gBAAgB,QAAQ;GAC9B,SAAS,QAAQ;GACjB,KAAK,MAAM,WAAW,kBACpB,QAAQ;EAEZ;CACF;AACF;AA6BA,SAAgB,gCACd,SAC2B;CAC3B,IAAI;CACJ,IAAI,WAAW;CACf,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM,0CAA0B,IAAI,IAElC;CACF,MAAM,kBAA4C,CAAC;CACnD,IAAI;CAOJ,MAAM,gBAAgB;EACpB,IAAI,UACF;EAEF,SAAS,IAAI,UAAU,QAAQ,GAAG;EAClC,OAAO,GAAG,cAAc;GACtB,IAAI,aACF,QAAQ;IACN,MAAM;IACN,iBAAiB;IACjB,6BACE;IACF,6BACE;IACF,WAAW,YAAY;IACvB,UAAU,YAAY;IACtB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;IACtD,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;IACnD,GAAI,QAAQ,eACR,EAAE,cAAc,QAAQ,aAAa,IACrC,CAAC;IACL,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;IAC/D,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;IAClE,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;IACL,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;IACxE,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;IACL,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;IACL,cAAc,QAAQ,gBAAgB,+BAA+B;GACvE,CAAC;GAEH,qBAAqB;EACvB,CAAC;EACD,OAAO,GAAG,YAAY,YAAY;GAChC,MAAM,aACJ,OAAO,YAAY,WACf,UACA,mBAAmB,SACjB,QAAQ,SAAS,MAAM,IACvB,MAAM,QAAQ,OAAO,IACnB,OAAO,OAAO,OAAO,EAAE,SAAS,MAAM,IACtC,mBAAmB,cACjB,OAAO,KAAK,OAAO,EAAE,SAAS,MAAM,IACpC,OAAO,KACL,QAAQ,QACR,QAAQ,YACR,QAAQ,UACV,EAAE,SAAS,MAAM;GAC7B,IAAI,WAAW,WAAW,GACxB;GAEF,MAAM,UAAU,KAAK,MAAM,UAAU;GAGrC,IAAI,QAAQ,SAAS,QACnB,KAAK,EAAE,MAAM,OAAO,CAAC;QAChB,IAAI,QAAQ,SAAS,mBAC1B,KAAK,MAAM,YAAY,yBACrB,SAAS,QAAQ,KAAmC;QAEjD,IAAI,QAAQ,SAAS,aAAa,WACvC,UAAU,QAAQ,OAAO,EACtB,MAAM,oBAAoB;IACzB,MAAM,YACJ,aAAa,aAAa,aAAa,EAAE;IAC3C,IAAI,CAAC,WACH;IAEF,KAAK;KACH,MAAM;KACN,WAAW,QAAQ;KACnB;KACA,SAAS;IACX,CAAC;GACH,CAAC,EACA,OAAO,QAAQ;IACd,MAAM,YACJ,aAAa,aAAa,aAAa,EAAE;IAC3C,IAAI,CAAC,WACH;IAEF,KAAK;KACH,MAAM;KACN,WAAW,QAAQ;KACnB;KACA,SAAS;MACP,MAAM;MACN,SAAS,eAAe,QAAQ,IAAI,UAAU;KAChD;IACF,CAAC;GACH,CAAC;QACE,IAAI,QAAQ,SAAS,eAAe,kBACzC,iBAAsB,UACpB,QAAQ,gBACR,QAAQ,UACP,YAAY;IACX,MAAM,YACJ,aAAa,aAAa,aAAa,EAAE;IAC3C,IAAI,CAAC,WACH;IAEF,KAAK;KACH,MAAM;KACN,gBAAgB,QAAQ;KACxB;KACA;IACF,CAAC;GACH,CACF;QACK,IAAI,QAAQ,SAAS,eAC1B,kBAAkB,YAAY,QAAQ,cAAc;EAExD,CAAC;EACD,OAAO,GAAG,SAAS,iBAAiB;EACpC,OAAO,GAAG,SAAS,iBAAiB;CACtC;CAEA,MAAM,0BAA0B;EAC9B,IAAI,YAAY,cACd;EAEF,eAAe,iBAAiB;GAC9B,eAAe,KAAA;GACf,QAAQ;EACV,GAAG,QAAQ,oBAAoB,IAAI;CACrC;CAEA,MAAM,WAAW,YAAoC;EACnD,IAAI,QAAQ,eAAe,UAAU,MACnC,OAAO,KAAK,KAAK,UAAU,OAAO,CAAC;CAEvC;CAEA,MAAM,6BAA6B;EACjC,OAAO,gBAAgB,SAAS,GAAG;GACjC,MAAM,cAAc,gBAAgB,MAAM;GAC1C,IAAI,CAAC,aACH;GAEF,QAAQ,WAAW;EACrB;CACF;CAEA,MAAM,QAAQ,YAAoC;EAChD,IAAI,QAAQ,eAAe,UAAU,MAAM;GACzC,QAAQ,OAAO;GACf;EACF;EACA,gBAAgB,KAAK,OAAO;CAC9B;CAEA,QAAQ;CAER,MAAM,OAAkC;EACtC,KAAK,OAAO;GACV,IAAI,MAAM,SAAS,qBAAqB;IACtC,cAAc;KACZ,WAAW,MAAM;KACjB,UAAU,MAAM;IAClB;IACA,KAAK;KACH,MAAM;KACN,iBAAiB;KACjB,6BACE;KACF,6BACE;KACF,WAAW,MAAM;KACjB,UAAU,MAAM;KAChB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;KACtD,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;KACnD,GAAI,QAAQ,eACR,EAAE,cAAc,QAAQ,aAAa,IACrC,CAAC;KACL,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;KAC/D,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;KAClE,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;KACL,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;KACxE,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;KACL,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;KACL,cAAc,QAAQ,gBAAgB,+BAA+B;IACvE,CAAC;GACH;GACA,KAAK;IACH,MAAM;IACN;GACF,CAAC;EACH;EACA,cAAc,SAAS;GACrB,mBACE,uBAAuB,QAAQ,SAAS,EAAE,kBAAkB,GAAG,OAAO;EAC1E;EACA,qBAAqB,SAAS;GAC5B,YAAY;EACd;EACA,uBAAuB,MAAM;GAC3B,mBAAmB;EACrB;EACA,UAAU;GACR,WAAW;GACX,IAAI,cACF,aAAa,YAAY;GAE3B,kBAAkB,QAAQ;GAC1B,QAAQ,MAAM;EAChB;CACF;CACA,IAAI,QAAQ,iBACV,KAAK,uBAAuB;EAC1B,UAAU,UAAU;GAClB,wBAAwB,IAAI,QAAQ;GACpC,aAAa;IACX,wBAAwB,OAAO,QAAQ;GACzC;EACF;EACA,QAAQ,OAAO;GACb,MAAM,YAAY,aAAa,aAAa,aAAa,EAAE;GAC3D,IAAI,CAAC,WACH;GAEF,KAAK;IACH,MAAM;IACN;IACA,iBAAiB,QAAQ;IAClB;GACT,CAAC;EACH;EACA,QAAQ;GACN,wBAAwB,MAAM;EAChC;CACF;CAEF,OAAO;AACT;AAEA,SAAS,uBACP,SACA,SACuB;CACvB,OAAO;EACL,GAAG;EACH,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;EACtD,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;EACnD,GAAI,QAAQ,eAAe,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;EACrE,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;EAC/D,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;EAClE,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;EACL,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;EACxE,GAAI,QAAQ,kBAAkB,EAAE,iBAAiB,QAAQ,gBAAgB,IAAI,CAAC;EAC9E,GAAI,QAAQ,kBACR,EAAE,iBAAiB,QAAQ,gBAAgB,IAC3C,CAAC;EACL,cAAc,QAAQ,gBAAgB,+BAA+B;CACvE;AACF;AAEA,SAAS,iCAA8D;CACrE,OAAO;EACL,KAAK;GACH,MAAM;GACN,OAAO;GACP,MAAM;GACN,QAAQ;EACV;EACA,MAAM;GACJ,QAAQ;GACR,QAAQ;GACR,cAAc;EAChB;EACA,WAAW;GACT,MAAM;GACN,MAAM;EACR;CACF;AACF;AAEA,SAAS,gCAAyC;CAChD,OAAO,QAAQ,IAAI,aAAa;AAClC;AAEA,SAAS,gCAAoD;CAC3D,IAAI,QAAQ,IAAI,6BAA6B,KAC3C;CAEF,OAAO,QAAQ,IAAI,wBAAwB;AAC7C"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n mkdir,\n open,\n readdir,\n readFile,\n rm,\n stat,\n writeFile\n} from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { DatabaseSync, type SQLInputValue } from \"node:sqlite\";\nimport WebSocket from \"ws\";\nimport {\n SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,\n SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,\n SYNCORE_DEVTOOLS_PROTOCOL_VERSION\n} from \"@syncore/devtools-protocol\";\nimport type {\n SyncoreDevtoolsClientMessage,\n SyncoreDevtoolsCapabilities,\n SyncoreDevtoolsExternalChangeEvent,\n SyncoreDevtoolsMessage,\n SyncoreRuntimeSummary\n} from \"@syncore/devtools-protocol\";\nimport {\n createDevtoolsCommandHandler,\n createDevtoolsSubscriptionHost,\n type DevtoolsCommandHandler,\n type DevtoolsSink,\n type DevtoolsSubscriptionHost,\n type SchedulerOptions,\n type StorageObject,\n type StorageWriteInput,\n type SyncoreCapabilities,\n type SyncoreDataModel,\n type SyncoreExternalChangeEvent,\n type SyncoreExternalChangeSignal,\n SyncoreRuntime,\n type SyncoreRuntimeOptions,\n type SyncoreSqlDriver,\n type SyncoreStorageAdapter\n} from \"@syncore/core\";\nimport { attachNodeIpcRuntime, createNodeIpcMessageEndpoint } from \"./ipc.js\";\nexport * from \"./ipc.js\";\nexport type {\n SyncoreActiveQueryInfo,\n SyncoreDevtoolsEvent,\n SyncoreRuntimeSummary\n} from \"@syncore/devtools-protocol\";\n\nexport type NodeSyncoreSchema<\n TSchema extends SyncoreDataModel = SyncoreDataModel\n> = TSchema;\n\nconst DEVTOOLS_META_DIRECTORY = \".syncore-devtools\";\nconst DATA_SOURCE_ALIAS_PREFIX = \"data-source-alias\";\n\nfunction normalizeData(input: StorageWriteInput[\"data\"]): Uint8Array {\n if (typeof input === \"string\") {\n return Buffer.from(input);\n }\n if (input instanceof Uint8Array) {\n return input;\n }\n return new Uint8Array(input);\n}\n\nfunction toSqlParameters(params: unknown[]): SQLInputValue[] {\n return params as SQLInputValue[];\n}\n\n/**\n * SQLite driver backed by Node.js’s built-in `node:sqlite` module (Node 22+).\n *\n * Opens the database at `databasePath` with `WAL` journal mode and foreign-key\n * enforcement enabled. The file is created if it does not exist.\n *\n * ```ts\n * const driver = new NodeSqliteDriver(\"./data/app.db\");\n * ```\n *\n * In most cases you should use {@link createNodeSyncoreRuntime} which\n * instantiates this driver automatically from your `databasePath` option.\n */\nexport class NodeSqliteDriver implements SyncoreSqlDriver {\n private readonly database: DatabaseSync;\n private transactionDepth = 0;\n\n constructor(readonly databasePath: string) {\n this.database = new DatabaseSync(databasePath);\n this.database.exec(\"PRAGMA foreign_keys = ON;\");\n this.database.exec(\"PRAGMA journal_mode = WAL;\");\n }\n\n async exec(sql: string): Promise<void> {\n this.database.exec(sql);\n }\n\n async run(\n sql: string,\n params: unknown[] = []\n ): Promise<{ changes: number; lastInsertRowid?: number | string }> {\n const statement = this.database.prepare(sql);\n const result = statement.run(...toSqlParameters(params));\n return {\n changes: Number(result.changes ?? 0),\n lastInsertRowid:\n typeof result.lastInsertRowid === \"bigint\"\n ? Number(result.lastInsertRowid)\n : result.lastInsertRowid\n };\n }\n\n async get<T>(sql: string, params: unknown[] = []): Promise<T | undefined> {\n const statement = this.database.prepare(sql);\n return statement.get(...toSqlParameters(params)) as T | undefined;\n }\n\n async all<T>(sql: string, params: unknown[] = []): Promise<T[]> {\n const statement = this.database.prepare(sql);\n return statement.all(...toSqlParameters(params)) as T[];\n }\n\n async withTransaction<T>(callback: () => Promise<T>): Promise<T> {\n if (this.transactionDepth > 0) {\n return this.withSavepoint(`nested_${this.transactionDepth}`, callback);\n }\n\n this.transactionDepth += 1;\n this.database.exec(\"BEGIN\");\n try {\n const result = await callback();\n this.database.exec(\"COMMIT\");\n return result;\n } catch (error) {\n this.database.exec(\"ROLLBACK\");\n throw error;\n } finally {\n this.transactionDepth -= 1;\n }\n }\n\n async withSavepoint<T>(name: string, callback: () => Promise<T>): Promise<T> {\n const safeName = name.replaceAll(/[^a-zA-Z0-9_]/g, \"_\");\n this.database.exec(`SAVEPOINT ${safeName}`);\n try {\n const result = await callback();\n this.database.exec(`RELEASE SAVEPOINT ${safeName}`);\n return result;\n } catch (error) {\n this.database.exec(`ROLLBACK TO SAVEPOINT ${safeName}`);\n this.database.exec(`RELEASE SAVEPOINT ${safeName}`);\n throw error;\n }\n }\n\n async close(): Promise<void> {\n this.database.close();\n }\n}\n\n/**\n * Blob storage adapter that reads and writes files in a local directory.\n *\n * Each stored object is saved as a flat file named by its ID inside\n * `directory`. The directory is created automatically on first write.\n *\n * ```ts\n * const storage = new NodeFileStorageAdapter(\"./data/storage\");\n * ```\n *\n * In most cases you should use {@link createNodeSyncoreRuntime} which\n * instantiates this adapter automatically from your `storageDirectory` option.\n */\nexport class NodeFileStorageAdapter implements SyncoreStorageAdapter {\n constructor(private readonly directory: string) {}\n\n private filePath(id: string): string {\n return path.join(this.directory, id);\n }\n\n async put(id: string, input: StorageWriteInput): Promise<StorageObject> {\n await mkdir(this.directory, { recursive: true });\n const filePath = this.filePath(id);\n const bytes = normalizeData(input.data);\n await writeFile(filePath, bytes);\n return {\n id,\n path: filePath,\n size: bytes.byteLength,\n contentType: input.contentType ?? null\n };\n }\n\n async get(id: string): Promise<StorageObject | null> {\n const filePath = this.filePath(id);\n try {\n const info = await stat(filePath);\n return {\n id,\n path: filePath,\n size: info.size,\n contentType: null\n };\n } catch {\n return null;\n }\n }\n\n async read(id: string): Promise<Uint8Array | null> {\n try {\n return await readFile(this.filePath(id));\n } catch {\n return null;\n }\n }\n\n async readRange(\n id: string,\n offset: number,\n length: number\n ): Promise<Uint8Array | null> {\n let handle;\n try {\n handle = await open(this.filePath(id), \"r\");\n const buffer = Buffer.alloc(Math.max(length, 0));\n const result = await handle.read(\n buffer,\n 0,\n buffer.byteLength,\n Math.max(offset, 0)\n );\n return buffer.subarray(0, result.bytesRead);\n } catch {\n return null;\n } finally {\n await handle?.close();\n }\n }\n\n async delete(id: string): Promise<void> {\n await rm(this.filePath(id), { force: true });\n }\n\n async list(): Promise<StorageObject[]> {\n try {\n const entries = await readdir(this.directory, { withFileTypes: true });\n const objects = await Promise.all(\n entries\n .filter((entry) => entry.isFile())\n .map(async (entry) => {\n const filePath = this.filePath(entry.name);\n const info = await stat(filePath);\n return {\n id: entry.name,\n path: filePath,\n size: info.size,\n contentType: null\n } satisfies StorageObject;\n })\n );\n return objects;\n } catch {\n return [];\n }\n }\n}\n\nconst SESSION_ADJECTIVES = [\n \"Acrobatic\",\n \"Bold\",\n \"Cosmic\",\n \"Daring\",\n \"Electric\",\n \"Fierce\",\n \"Golden\",\n \"Hidden\",\n \"Iron\",\n \"Jade\",\n \"Keen\",\n \"Lunar\",\n \"Mystic\",\n \"Noble\",\n \"Orbital\",\n \"Primal\",\n \"Quick\",\n \"Radiant\",\n \"Shadow\",\n \"Turbo\",\n \"Ultra\",\n \"Vivid\",\n \"Wicked\",\n \"Xenon\",\n \"Zen\",\n \"Arctic\",\n \"Binary\",\n \"Cyber\",\n \"Digital\",\n \"Ember\",\n \"Frozen\",\n \"Galactic\",\n \"Hyper\",\n \"Infra\",\n \"Jumbo\",\n \"Kinetic\",\n \"Liquid\",\n \"Magnetic\",\n \"Neon\",\n \"Onyx\",\n \"Phantom\",\n \"Quantum\",\n \"Rapid\",\n \"Sonic\",\n \"Titan\",\n \"Velvet\",\n \"Wild\",\n \"Blazing\",\n \"Crystal\",\n \"Dynamic\"\n] as const;\n\nconst SESSION_NOUNS = [\n \"Phoenix\",\n \"Dragon\",\n \"Developer\",\n \"Hacker\",\n \"Wizard\",\n \"Runner\",\n \"Ranger\",\n \"Maverick\",\n \"Spartan\",\n \"Viking\",\n \"Sentinel\",\n \"Guardian\",\n \"Nomad\",\n \"Cipher\",\n \"Vector\",\n \"Matrix\",\n \"Prism\",\n \"Nebula\",\n \"Comet\",\n \"Pulse\",\n \"Vertex\",\n \"Flux\",\n \"Storm\",\n \"Blaze\",\n \"Frost\",\n \"Thunder\",\n \"Drift\"\n] as const;\n\nfunction generateUniqueSessionName(): string {\n const adj =\n SESSION_ADJECTIVES[Math.floor(Math.random() * SESSION_ADJECTIVES.length)]!;\n const noun = SESSION_NOUNS[Math.floor(Math.random() * SESSION_NOUNS.length)]!;\n return `${adj} ${noun}`;\n}\n\nfunction resolvePersistedDataSourceAlias(\n storageDirectory: string,\n storageIdentity: string\n): string {\n const metaDirectory = path.join(storageDirectory, DEVTOOLS_META_DIRECTORY);\n const aliasId = createHash(\"sha256\")\n .update(storageIdentity)\n .digest(\"hex\")\n .slice(0, 16);\n const aliasPath = path.join(\n metaDirectory,\n `${DATA_SOURCE_ALIAS_PREFIX}-${aliasId}.txt`\n );\n\n try {\n const existing = readFileSync(aliasPath, \"utf8\").trim();\n if (existing.length > 0) {\n return existing;\n }\n } catch {\n // Missing metadata is expected for a new data source.\n }\n\n const nextValue = generateUniqueSessionName();\n try {\n mkdirSync(metaDirectory, { recursive: true });\n writeFileSync(aliasPath, nextValue, \"utf8\");\n } catch {\n // The alias is a dashboard convenience; runtime startup must not depend on it.\n }\n return nextValue;\n}\n\n/**\n * Options for {@link createNodeSyncoreRuntime}.\n *\n * At minimum supply `databasePath`, `storageDirectory`, `schema`, and\n * `functions`. Everything else has sensible defaults (auto-devtools connect in\n * development, Node SQLite driver, local file storage).\n *\n * ```ts\n * createNodeSyncoreRuntime({\n * databasePath: path.join(dataDir, \"app.db\"),\n * storageDirectory: path.join(dataDir, \"storage\"),\n * schema,\n * functions,\n * });\n * ```\n */\nexport interface CreateNodeRuntimeOptions<\n TSchema extends NodeSyncoreSchema = NodeSyncoreSchema\n> {\n /**\n * Absolute or relative path to the SQLite database file.\n *\n * The file is created if it does not exist. Use an absolute path in\n * production to avoid ambiguity about the current working directory.\n */\n databasePath: string;\n /**\n * Directory where blob storage objects (images, files, etc.) are persisted.\n *\n * The directory is created automatically if it does not exist.\n */\n storageDirectory: string;\n /** The data model that defines the available tables and indexes. */\n schema: TSchema;\n /**\n * The registered function map. Use the `functions` export from\n * `syncore/_generated/functions.ts`.\n */\n functions: SyncoreRuntimeOptions<TSchema>[\"functions\"];\n /**\n * Resolved Syncore component instances. Only required when your app\n * installs Syncore component packages.\n */\n components?: SyncoreRuntimeOptions<TSchema>[\"components\"];\n /**\n * Platform capabilities injected into `ctx.capabilities` inside function\n * handlers.\n */\n capabilities?: SyncoreCapabilities;\n /** Human-readable app name shown in the devtools dashboard. */\n appName?: string;\n /** Origin label (e.g. process name) shown in devtools. */\n origin?: string;\n /** Devtools session label. Auto-generated when omitted. */\n sessionLabel?: string;\n /**\n * Platform label reported to devtools. Defaults to `\"node\"`, or\n * `\"electron-main\"` when the runtime is used inside Electron's main process.\n */\n platform?: string;\n /**\n * Devtools event sink. Pass `false` to disable devtools entirely (recommended\n * for production). Omit to auto-connect to the local devtools server when\n * running in development.\n */\n devtools?: DevtoolsSink | false;\n /**\n * Explicit devtools WebSocket server URL. Defaults to\n * `ws://localhost:3099` (the Syncore devtools default port).\n */\n devtoolsUrl?: string;\n /** Scheduler configuration for background and recurring jobs. */\n scheduler?: SchedulerOptions;\n}\n\n/**\n * Alias of {@link CreateNodeRuntimeOptions} exposed for the managed-client\n * helper.\n * @see CreateNodeRuntimeOptions\n */\nexport type WithNodeSyncoreClientOptions<\n TSchema extends NodeSyncoreSchema = NodeSyncoreSchema\n> = CreateNodeRuntimeOptions<TSchema>;\n\n/**\n * A started local Node runtime paired with its client and a dispose helper.\n *\n * Returned by `withNodeSyncoreClient()`. Call `dispose()` when you are\n * finished (e.g. in tests or short-lived scripts) to stop the runtime and\n * close the database.\n */\nexport interface ManagedNodeSyncoreClient<\n TSchema extends NodeSyncoreSchema = NodeSyncoreSchema\n> {\n /** The underlying runtime instance. */\n runtime: SyncoreRuntime<TSchema>;\n /** A ready-to-use client for calling Syncore functions. */\n client: ReturnType<SyncoreRuntime<TSchema>[\"createClient\"]>;\n /** Stop the runtime, flush pending jobs, and close the database. */\n dispose(): Promise<void>;\n}\n\n/**\n * Opaque handle returned by Syncore’s Electron IPC bridge setup.\n *\n * - `ready`: resolves when the bridge is connected and the renderer is ready to\n * receive messages.\n * - `dispose()`: tears down the bridge and removes IPC listeners.\n */\nexport interface SyncoreElectronIpcBinding {\n ready: Promise<void>;\n dispose(): Promise<void>;\n}\n\n/**\n * Minimal interface that Syncore requires from an Electron `BrowserWindow`\n * instance.\n *\n * Scoped to avoid importing Electron at the type level.\n */\nexport interface SyncoreElectronBridgeWindow {\n isDestroyed(): boolean;\n webContents: {\n send(channel: string, message: unknown): void;\n };\n}\n\n/**\n * Options for setting up Syncore’s Electron main-process IPC bridge.\n *\n * The bridge forwards database change events from the main-process runtime to\n * the renderer window over a named IPC channel.\n *\n * ```ts\n * createElectronSyncoreBridge(runtime, {\n * window: mainWindow,\n * onRendererMessage: (listener) => {\n * ipcMain.on(\"syncore\", (_e, msg) => listener(msg));\n * return () => ipcMain.removeAllListeners(\"syncore\");\n * },\n * });\n * ```\n */\nexport interface CreateElectronSyncoreBridgeOptions {\n /** The renderer window that will receive push messages. */\n window: SyncoreElectronBridgeWindow;\n /**\n * Register a listener for messages sent from the renderer. Must return an\n * unsubscribe function.\n */\n onRendererMessage(listener: (message: unknown) => void): () => void;\n /** IPC channel name. Defaults to `\"syncore\"`. */\n channel?: string;\n}\n\n/**\n * The subset of Electron’s `ipcMain` used by Syncore’s main-process helper.\n *\n * Using this narrowed interface avoids a hard runtime dependency on Electron.\n */\nexport interface SyncoreElectronIpcMain {\n on(\n channel: string,\n listener: (event: { sender: unknown }, message: unknown) => void\n ): void;\n off(\n channel: string,\n listener: (event: { sender: unknown }, message: unknown) => void\n ): void;\n}\n\n/**\n * Options for creating a client inside an Electron renderer process via the\n * preload IPC bridge.\n */\nexport interface CreateSyncoreRendererWindowClientOptions {\n /** Name of the bridge registered in the preload script. Defaults to `\"syncore\"`. */\n bridgeName?: string;\n}\n\n/**\n * Create a Syncore runtime for Node.js (or Electron’s main process) backed by\n * the built-in `node:sqlite` driver and local file storage.\n *\n * This is the recommended entry point for Node and Electron apps. It wires up\n * the SQL driver, storage adapter, devtools WebSocket connection, and\n * cross-process change signals automatically.\n *\n * ```ts\n * import path from \"node:path\";\n * import { createNodeSyncoreRuntime } from \"syncorejs/node\";\n * import schema from \"./syncore/schema\";\n * import { functions } from \"./syncore/_generated/functions\";\n *\n * const runtime = createNodeSyncoreRuntime({\n * databasePath: path.join(app.getPath(\"userData\"), \"db.sqlite\"),\n * storageDirectory: path.join(app.getPath(\"userData\"), \"storage\"),\n * schema,\n * functions,\n * });\n *\n * await runtime.start();\n * const client = runtime.createClient();\n * ```\n *\n * @param options - Configuration object. See {@link CreateNodeRuntimeOptions}.\n * @returns A configured (but not yet started) SyncoreRuntime. Call\n * `await runtime.start()` before using the client.\n */\nexport function createNodeSyncoreRuntime<TSchema extends NodeSyncoreSchema>(\n options: CreateNodeRuntimeOptions<TSchema>\n): SyncoreRuntime<TSchema> {\n const resolvedDevtoolsUrl =\n options.devtoolsUrl ?? resolveDefaultNodeDevtoolsUrl();\n const storageIdentity = `file::${path.resolve(options.databasePath)}`;\n const websocketDevtools =\n options.devtools === undefined &&\n resolvedDevtoolsUrl &&\n shouldAutoConnectNodeDevtools()\n ? createNodeWebSocketDevtoolsSink({\n url: resolvedDevtoolsUrl,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel\n ? { sessionLabel: options.sessionLabel }\n : {}),\n targetKind: \"client\",\n storageProtocol: \"file\",\n databaseLabel: path.basename(options.databasePath),\n dataSourceAlias: resolvePersistedDataSourceAlias(\n options.storageDirectory,\n storageIdentity\n ),\n storageIdentity,\n runtimeRole: \"app\",\n capabilities: createNodeDevtoolsCapabilities()\n })\n : undefined;\n const runtimeOptions: SyncoreRuntimeOptions<TSchema> = {\n schema: options.schema,\n functions: options.functions,\n ...(options.components ? { components: options.components } : {}),\n driver: new NodeSqliteDriver(options.databasePath),\n storage: new NodeFileStorageAdapter(options.storageDirectory),\n runtimeCapabilities: {\n storage: {\n available: true,\n protocol: \"file\",\n supportsRange: true\n }\n },\n platform: options.platform ?? \"node\"\n };\n if (options.capabilities) {\n runtimeOptions.capabilities = options.capabilities;\n }\n const resolvedDevtools =\n options.devtools === false\n ? undefined\n : (options.devtools ?? websocketDevtools);\n if (resolvedDevtools) {\n runtimeOptions.devtools = resolvedDevtools;\n }\n if (websocketDevtools?.externalChangeSignal) {\n runtimeOptions.externalChangeSignal =\n websocketDevtools.externalChangeSignal;\n }\n if (options.scheduler) {\n runtimeOptions.scheduler = options.scheduler;\n }\n const runtime = new SyncoreRuntime(runtimeOptions);\n websocketDevtools?.attachRuntime(runtime);\n if (websocketDevtools) {\n websocketDevtools.attachCommandHandler(\n createDevtoolsCommandHandler({\n driver: runtimeOptions.driver,\n schema: options.schema,\n functions: options.functions,\n admin: runtime.getAdmin()\n })\n );\n websocketDevtools.attachSubscriptionHost(\n createDevtoolsSubscriptionHost({\n driver: runtimeOptions.driver,\n schema: options.schema,\n functions: options.functions,\n admin: runtime.getAdmin()\n })\n );\n const stop = runtime.stop.bind(runtime);\n runtime.stop = async () => {\n websocketDevtools.dispose();\n await stop();\n };\n }\n return runtime;\n}\n\n/**\n * Create a same-process Syncore client from a started Node runtime.\n */\nexport function createNodeSyncoreClient<TSchema extends NodeSyncoreSchema>(\n runtime: SyncoreRuntime<TSchema>\n) {\n return runtime.createClient();\n}\n\n/**\n * Start a Node Syncore runtime and return its client together with a dispose helper.\n */\nexport async function createManagedNodeSyncoreClient<\n TSchema extends NodeSyncoreSchema\n>(\n options: WithNodeSyncoreClientOptions<TSchema>\n): Promise<ManagedNodeSyncoreClient<TSchema>> {\n const runtime = createNodeSyncoreRuntime(options);\n await runtime.start();\n return {\n runtime,\n client: runtime.createClient(),\n async dispose() {\n await runtime.stop();\n }\n };\n}\n\n/**\n * Run a callback with a started local Node Syncore client and always stop the runtime.\n *\n * @example\n * ```ts\n * await withNodeSyncoreClient(options, async (client) => {\n * console.log(await client.query(api.tasks.list));\n * });\n * ```\n */\nexport async function withNodeSyncoreClient<\n TSchema extends NodeSyncoreSchema,\n TResult\n>(\n options: WithNodeSyncoreClientOptions<TSchema>,\n callback: (\n client: ReturnType<SyncoreRuntime<TSchema>[\"createClient\"]>,\n runtime: SyncoreRuntime<TSchema>\n ) => Promise<TResult> | TResult\n): Promise<TResult> {\n const managed = await createManagedNodeSyncoreClient(options);\n try {\n return await callback(managed.client, managed.runtime);\n } finally {\n await managed.dispose();\n }\n}\n\n/**\n * Create the default Electron main-process bridge used to connect a BrowserWindow\n * to a Syncore runtime.\n */\nexport function createElectronSyncoreBridge(\n options: CreateElectronSyncoreBridgeOptions\n) {\n const channel = options.channel ?? \"syncore:message\";\n return createNodeIpcMessageEndpoint({\n postMessage(message: unknown) {\n if (!options.window.isDestroyed()) {\n options.window.webContents.send(channel, message);\n }\n },\n onMessage(listener: (message: unknown) => void) {\n return options.onRendererMessage(listener);\n }\n });\n}\n\n/**\n * Bind a BrowserWindow to a Syncore runtime with the default Electron IPC transport.\n */\nexport function bindElectronWindowToSyncoreRuntime(options: {\n runtime: SyncoreRuntime<NodeSyncoreSchema>;\n window: SyncoreElectronBridgeWindow;\n onRendererMessage(listener: (message: unknown) => void): () => void;\n channel?: string;\n}): SyncoreElectronIpcBinding;\nexport function bindElectronWindowToSyncoreRuntime(options: {\n runtime: SyncoreRuntime<NodeSyncoreSchema>;\n window: SyncoreElectronBridgeWindow;\n ipcMain: SyncoreElectronIpcMain;\n channel?: string;\n}): SyncoreElectronIpcBinding;\nexport function bindElectronWindowToSyncoreRuntime(options: {\n runtime: SyncoreRuntime<NodeSyncoreSchema>;\n window: SyncoreElectronBridgeWindow;\n onRendererMessage?(listener: (message: unknown) => void): () => void;\n ipcMain?: SyncoreElectronIpcMain;\n channel?: string;\n}): SyncoreElectronIpcBinding {\n const cleanupCallbacks: Array<() => void> = [];\n const channel = options.channel ?? \"syncore:message\";\n let onRendererMessage:\n | ((listener: (message: unknown) => void) => () => void)\n | undefined;\n\n if (!options.onRendererMessage) {\n if (!options.ipcMain) {\n throw new Error(\n \"bindElectronWindowToSyncoreRuntime requires either onRendererMessage() or ipcMain.\"\n );\n }\n const listeners = new Set<(message: unknown) => void>();\n const handleRendererMessage = (\n event: { sender: unknown },\n message: unknown\n ) => {\n if (event.sender !== options.window.webContents) {\n return;\n }\n for (const listener of listeners) {\n listener(message);\n }\n };\n options.ipcMain.on(channel, handleRendererMessage);\n cleanupCallbacks.push(() => {\n options.ipcMain?.off(channel, handleRendererMessage);\n listeners.clear();\n });\n onRendererMessage = (listener) => {\n listeners.add(listener);\n return () => listeners.delete(listener);\n };\n } else {\n onRendererMessage = (listener) => options.onRendererMessage!(listener);\n }\n\n const endpoint = createElectronSyncoreBridge({\n window: options.window,\n onRendererMessage,\n channel\n });\n const attachedRuntime = attachNodeIpcRuntime({\n endpoint,\n createRuntime: () => options.runtime\n });\n\n return {\n ready: attachedRuntime.ready,\n async dispose() {\n await attachedRuntime.dispose();\n endpoint.dispose();\n for (const cleanup of cleanupCallbacks) {\n cleanup();\n }\n }\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Devtools request handler */\n/* ------------------------------------------------------------------ */\n\nexport interface NodeWebSocketDevtoolsSinkOptions {\n url: string;\n reconnectDelayMs?: number;\n appName?: string;\n origin?: string;\n sessionLabel?: string;\n targetKind?: \"client\" | \"project\";\n runtimeRole?: \"app\" | \"project-target\";\n storageProtocol?: string;\n databaseLabel?: string;\n dataSourceAlias?: string;\n storageIdentity?: string;\n capabilities?: SyncoreDevtoolsCapabilities;\n}\n\nexport interface NodeWebSocketDevtoolsSink extends DevtoolsSink {\n attachRuntime(runtime: SyncoreRuntime<NodeSyncoreSchema>): void;\n attachCommandHandler(handler: DevtoolsCommandHandler): void;\n attachSubscriptionHost(host: DevtoolsSubscriptionHost): void;\n externalChangeSignal?: SyncoreExternalChangeSignal;\n dispose(): void;\n}\n\nexport function createNodeWebSocketDevtoolsSink(\n options: NodeWebSocketDevtoolsSinkOptions\n): NodeWebSocketDevtoolsSink {\n let socket: WebSocket | undefined;\n let disposed = false;\n let connectTimer: ReturnType<typeof setTimeout> | undefined;\n let getSummary: (() => SyncoreRuntimeSummary) | undefined;\n let onCommand: DevtoolsCommandHandler | undefined;\n let subscriptionHost: DevtoolsSubscriptionHost | undefined;\n const externalChangeListeners = new Set<\n (event: SyncoreExternalChangeEvent) => void\n >();\n const pendingMessages: SyncoreDevtoolsMessage[] = [];\n let latestHello:\n | {\n runtimeId: string;\n platform: string;\n }\n | undefined;\n\n const connect = () => {\n if (disposed) {\n return;\n }\n socket = new WebSocket(options.url);\n socket.on(\"open\", () => {\n if (latestHello) {\n sendNow({\n type: \"hello\",\n protocolVersion: SYNCORE_DEVTOOLS_PROTOCOL_VERSION,\n minSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,\n maxSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,\n runtimeId: latestHello.runtimeId,\n platform: latestHello.platform,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel\n ? { sessionLabel: options.sessionLabel }\n : {}),\n ...(options.targetKind ? { targetKind: options.targetKind } : {}),\n ...(options.runtimeRole ? { runtimeRole: options.runtimeRole } : {}),\n ...(options.storageProtocol\n ? { storageProtocol: options.storageProtocol }\n : {}),\n ...(options.databaseLabel\n ? { databaseLabel: options.databaseLabel }\n : {}),\n ...(options.dataSourceAlias\n ? { dataSourceAlias: options.dataSourceAlias }\n : {}),\n ...(options.storageIdentity\n ? { storageIdentity: options.storageIdentity }\n : {}),\n capabilities: options.capabilities ?? createNodeDevtoolsCapabilities()\n });\n }\n flushPendingMessages();\n });\n socket.on(\"message\", (payload) => {\n const rawPayload =\n typeof payload === \"string\"\n ? payload\n : payload instanceof Buffer\n ? payload.toString(\"utf8\")\n : Array.isArray(payload)\n ? Buffer.concat(payload).toString(\"utf8\")\n : payload instanceof ArrayBuffer\n ? Buffer.from(payload).toString(\"utf8\")\n : Buffer.from(\n payload.buffer,\n payload.byteOffset,\n payload.byteLength\n ).toString(\"utf8\");\n if (rawPayload.length === 0) {\n return;\n }\n const message = JSON.parse(rawPayload) as\n | SyncoreDevtoolsMessage\n | SyncoreDevtoolsClientMessage;\n if (message.type === \"ping\") {\n send({ type: \"pong\" });\n } else if (message.type === \"external.change\") {\n for (const listener of externalChangeListeners) {\n listener(message.event as SyncoreExternalChangeEvent);\n }\n } else if (message.type === \"command\" && onCommand) {\n onCommand(message.payload)\n .then((responsePayload) => {\n const runtimeId =\n latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"command.result\",\n commandId: message.commandId,\n runtimeId,\n payload: responsePayload\n });\n })\n .catch((err) => {\n const runtimeId =\n latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"command.result\",\n commandId: message.commandId,\n runtimeId,\n payload: {\n kind: \"error\",\n message: err instanceof Error ? err.message : \"Unknown error\"\n }\n });\n });\n } else if (message.type === \"subscribe\" && subscriptionHost) {\n void subscriptionHost.subscribe(\n message.subscriptionId,\n message.payload,\n (payload) => {\n const runtimeId =\n latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"subscription.data\",\n subscriptionId: message.subscriptionId,\n runtimeId,\n payload\n });\n }\n );\n } else if (message.type === \"unsubscribe\") {\n subscriptionHost?.unsubscribe(message.subscriptionId);\n }\n });\n socket.on(\"close\", scheduleReconnect);\n socket.on(\"error\", scheduleReconnect);\n };\n\n const scheduleReconnect = () => {\n if (disposed || connectTimer) {\n return;\n }\n connectTimer = setTimeout(() => {\n connectTimer = undefined;\n connect();\n }, options.reconnectDelayMs ?? 1200);\n };\n\n const sendNow = (message: SyncoreDevtoolsMessage) => {\n if (socket?.readyState === WebSocket.OPEN) {\n socket.send(JSON.stringify(message));\n }\n };\n\n const flushPendingMessages = () => {\n while (pendingMessages.length > 0) {\n const nextMessage = pendingMessages.shift();\n if (!nextMessage) {\n continue;\n }\n sendNow(nextMessage);\n }\n };\n\n const send = (message: SyncoreDevtoolsMessage) => {\n if (socket?.readyState === WebSocket.OPEN) {\n sendNow(message);\n return;\n }\n pendingMessages.push(message);\n };\n\n connect();\n\n const sink: NodeWebSocketDevtoolsSink = {\n emit(event) {\n if (event.type === \"runtime.connected\") {\n latestHello = {\n runtimeId: event.runtimeId,\n platform: event.platform\n };\n send({\n type: \"hello\",\n protocolVersion: SYNCORE_DEVTOOLS_PROTOCOL_VERSION,\n minSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MIN_SUPPORTED_PROTOCOL_VERSION,\n maxSupportedProtocolVersion:\n SYNCORE_DEVTOOLS_MAX_SUPPORTED_PROTOCOL_VERSION,\n runtimeId: event.runtimeId,\n platform: event.platform,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel\n ? { sessionLabel: options.sessionLabel }\n : {}),\n ...(options.targetKind ? { targetKind: options.targetKind } : {}),\n ...(options.runtimeRole ? { runtimeRole: options.runtimeRole } : {}),\n ...(options.storageProtocol\n ? { storageProtocol: options.storageProtocol }\n : {}),\n ...(options.databaseLabel\n ? { databaseLabel: options.databaseLabel }\n : {}),\n ...(options.dataSourceAlias\n ? { dataSourceAlias: options.dataSourceAlias }\n : {}),\n ...(options.storageIdentity\n ? { storageIdentity: options.storageIdentity }\n : {}),\n capabilities: options.capabilities ?? createNodeDevtoolsCapabilities()\n });\n }\n send({\n type: \"event\",\n event\n });\n },\n attachRuntime(runtime) {\n getSummary = () =>\n withRuntimeSummaryMeta(runtime.getAdmin().getRuntimeSummary(), options);\n },\n attachCommandHandler(handler) {\n onCommand = handler;\n },\n attachSubscriptionHost(host) {\n subscriptionHost = host;\n },\n dispose() {\n disposed = true;\n if (connectTimer) {\n clearTimeout(connectTimer);\n }\n subscriptionHost?.dispose();\n socket?.close();\n }\n };\n if (options.storageIdentity) {\n sink.externalChangeSignal = {\n subscribe(listener) {\n externalChangeListeners.add(listener);\n return () => {\n externalChangeListeners.delete(listener);\n };\n },\n publish(event) {\n const runtimeId = latestHello?.runtimeId ?? getSummary?.().runtimeId;\n if (!runtimeId) {\n return;\n }\n send({\n type: \"external.change\",\n runtimeId,\n storageIdentity: options.storageIdentity!,\n event: event as SyncoreDevtoolsExternalChangeEvent\n });\n },\n close() {\n externalChangeListeners.clear();\n }\n };\n }\n return sink;\n}\n\nfunction withRuntimeSummaryMeta(\n summary: SyncoreRuntimeSummary,\n options: NodeWebSocketDevtoolsSinkOptions\n): SyncoreRuntimeSummary {\n return {\n ...summary,\n ...(options.appName ? { appName: options.appName } : {}),\n ...(options.origin ? { origin: options.origin } : {}),\n ...(options.sessionLabel ? { sessionLabel: options.sessionLabel } : {}),\n ...(options.targetKind ? { targetKind: options.targetKind } : {}),\n ...(options.runtimeRole ? { runtimeRole: options.runtimeRole } : {}),\n ...(options.storageProtocol\n ? { storageProtocol: options.storageProtocol }\n : {}),\n ...(options.databaseLabel ? { databaseLabel: options.databaseLabel } : {}),\n ...(options.dataSourceAlias\n ? { dataSourceAlias: options.dataSourceAlias }\n : {}),\n ...(options.storageIdentity\n ? { storageIdentity: options.storageIdentity }\n : {}),\n capabilities: options.capabilities ?? createNodeDevtoolsCapabilities()\n };\n}\n\nfunction createNodeDevtoolsCapabilities(): SyncoreDevtoolsCapabilities {\n return {\n sql: {\n read: false,\n write: false,\n live: false,\n reason:\n \"SQL Console is provided by the Project Target for this data source.\"\n },\n data: {\n browse: true,\n mutate: true,\n importExport: true\n },\n storage: {\n browse: true,\n download: true,\n readRange: true,\n delete: true,\n maxPreviewBytes: 80_000\n },\n scheduler: {\n read: true,\n edit: true\n }\n };\n}\n\nfunction shouldAutoConnectNodeDevtools(): boolean {\n return process.env.NODE_ENV !== \"production\";\n}\n\nfunction resolveDefaultNodeDevtoolsUrl(): string | undefined {\n if (process.env.SYNCORE_DISABLE_DEVTOOLS === \"1\") {\n return undefined;\n }\n return process.env.SYNCORE_DEVTOOLS_URL ?? \"ws://127.0.0.1:4311\";\n}\n"],"mappings":"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"}
|
|
@@ -16,6 +16,10 @@ function SyncoreElectronProvider({ children, bridgeName, windowObject }) {
|
|
|
16
16
|
return createUnavailableSyncoreClient({
|
|
17
17
|
kind: "unavailable",
|
|
18
18
|
reason: "ipc-unavailable",
|
|
19
|
+
capabilities: { storage: {
|
|
20
|
+
available: false,
|
|
21
|
+
reason: "Syncore IPC bridge is unavailable."
|
|
22
|
+
} },
|
|
19
23
|
...error instanceof Error ? { error } : {}
|
|
20
24
|
});
|
|
21
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ipc-react.mjs","names":[],"sources":["../src/ipc-react.tsx"],"sourcesContent":["import { useEffect, useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createUnavailableSyncoreClient } from \"@syncore/core\";\nimport { SyncoreProvider } from \"@syncore/react\";\nimport { createRendererSyncoreWindowClient } from \"./ipc.js\";\n\n/**\n * Props for {@link SyncoreElectronProvider}.\n */\nexport interface SyncoreElectronProviderProps {\n /** The React subtree that should receive the renderer Syncore client. */\n children: ReactNode;\n\n /** Optional custom bridge name exposed on `window`. */\n bridgeName?: string;\n\n /** Optional window-like object for tests or custom shells. */\n windowObject?: Window & typeof globalThis;\n}\n\n/**\n * Create a renderer Syncore client from `window.syncoreBridge` and provide it to React.\n */\nexport function SyncoreElectronProvider({\n children,\n bridgeName,\n windowObject\n}: SyncoreElectronProviderProps): ReactNode {\n const resolvedWindow = windowObject ?? window;\n const client = useMemo(\n () => {\n try {\n return createRendererSyncoreWindowClient(\n resolvedWindow,\n bridgeName ?? \"syncoreBridge\"\n );\n } catch (error) {\n return createUnavailableSyncoreClient({\n kind: \"unavailable\",\n reason: \"ipc-unavailable\",\n ...(error instanceof Error ? { error } : {})\n });\n }\n },\n [bridgeName, resolvedWindow]\n );\n\n useEffect(\n () => () => {\n if (\"dispose\" in client && typeof client.dispose === \"function\") {\n client.dispose();\n }\n },\n [client]\n );\n\n return <SyncoreProvider client={client}>{children}</SyncoreProvider>;\n}\n"],"mappings":";;;;;;;;;AAuBA,SAAgB,wBAAwB,EACtC,UACA,YACA,gBAC0C;CAC1C,MAAM,iBAAiB,gBAAgB;CACvC,MAAM,SAAS,cACP;EACJ,IAAI;GACF,OAAO,kCACL,gBACA,cAAc,eAChB;EACF,SAAS,OAAO;GACd,OAAO,+BAA+B;IACpC,MAAM;IACN,QAAQ;IACR,GAAI,iBAAiB,QAAQ,EAAE,MAAM,IAAI,CAAC;GAC5C,CAAC;EACH;CACF,GACA,CAAC,YAAY,cAAc,CAC7B;CAEA,sBACc;EACV,IAAI,aAAa,UAAU,OAAO,OAAO,YAAY,YACnD,OAAO,QAAQ;CAEnB,GACA,CAAC,MAAM,CACT;CAEA,OAAO,oBAAC,iBAAD;EAAyB;EAAS;CAA0B,CAAA;AACrE"}
|
|
1
|
+
{"version":3,"file":"ipc-react.mjs","names":[],"sources":["../src/ipc-react.tsx"],"sourcesContent":["import { useEffect, useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createUnavailableSyncoreClient } from \"@syncore/core\";\nimport { SyncoreProvider } from \"@syncore/react\";\nimport { createRendererSyncoreWindowClient } from \"./ipc.js\";\n\n/**\n * Props for {@link SyncoreElectronProvider}.\n */\nexport interface SyncoreElectronProviderProps {\n /** The React subtree that should receive the renderer Syncore client. */\n children: ReactNode;\n\n /** Optional custom bridge name exposed on `window`. */\n bridgeName?: string;\n\n /** Optional window-like object for tests or custom shells. */\n windowObject?: Window & typeof globalThis;\n}\n\n/**\n * Create a renderer Syncore client from `window.syncoreBridge` and provide it to React.\n */\nexport function SyncoreElectronProvider({\n children,\n bridgeName,\n windowObject\n}: SyncoreElectronProviderProps): ReactNode {\n const resolvedWindow = windowObject ?? window;\n const client = useMemo(\n () => {\n try {\n return createRendererSyncoreWindowClient(\n resolvedWindow,\n bridgeName ?? \"syncoreBridge\"\n );\n } catch (error) {\n return createUnavailableSyncoreClient({\n kind: \"unavailable\",\n reason: \"ipc-unavailable\",\n capabilities: {\n storage: {\n available: false,\n reason: \"Syncore IPC bridge is unavailable.\"\n }\n },\n ...(error instanceof Error ? { error } : {})\n });\n }\n },\n [bridgeName, resolvedWindow]\n );\n\n useEffect(\n () => () => {\n if (\"dispose\" in client && typeof client.dispose === \"function\") {\n client.dispose();\n }\n },\n [client]\n );\n\n return <SyncoreProvider client={client}>{children}</SyncoreProvider>;\n}\n"],"mappings":";;;;;;;;;AAuBA,SAAgB,wBAAwB,EACtC,UACA,YACA,gBAC0C;CAC1C,MAAM,iBAAiB,gBAAgB;CACvC,MAAM,SAAS,cACP;EACJ,IAAI;GACF,OAAO,kCACL,gBACA,cAAc,eAChB;EACF,SAAS,OAAO;GACd,OAAO,+BAA+B;IACpC,MAAM;IACN,QAAQ;IACR,cAAc,EACZ,SAAS;KACP,WAAW;KACX,QAAQ;IACV,EACF;IACA,GAAI,iBAAiB,QAAQ,EAAE,MAAM,IAAI,CAAC;GAC5C,CAAC;EACH;CACF,GACA,CAAC,YAAY,cAAc,CAC7B;CAEA,sBACc;EACV,IAAI,aAAa,UAAU,OAAO,OAAO,YAAY,YACnD,OAAO,QAAQ;CAEnB,GACA,CAAC,MAAM,CACT;CAEA,OAAO,oBAAC,iBAAD;EAAyB;EAAS;CAA0B,CAAA;AACrE"}
|
|
@@ -10,7 +10,7 @@ interface BroadcastChannelExternalChangeSignalOptions {
|
|
|
10
10
|
channelName: string;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* A `BroadcastChannel`-based
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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,
|
|
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
|
|
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
|
|
393
|
-
*
|
|
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:
|
|
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":";;;;;;;;;;;;;;;
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
716
|
-
*
|
|
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:
|
|
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;
|