Syncore Platform Adapters
Use this skill when wiring Syncore into a concrete runtime environment or debugging adapter-specific DX and transport behavior.
Documentation Sources
Read these first:
-
docs/quickstarts/node-script.md
-
docs/quickstarts/electron.md
-
docs/quickstarts/react-web.md
-
docs/quickstarts/expo.md
-
docs/quickstarts/next-pwa.md
-
docs/guides/syncore-vs-convex.md
-
packages/platform-node/AGENTS.md
-
packages/platform-web/AGENTS.md
-
packages/platform-expo/src/index.ts
-
packages/platform-expo/src/react.tsx
-
packages/platform-node/src/index.ts
-
packages/platform-node/src/ipc-react.tsx
-
packages/platform-web/src/react.tsx
-
packages/platform-web/src/worker.ts
-
packages/next/src/index.tsx
-
packages/next/src/config.ts
-
packages/svelte/src/index.ts
-
examples/browser-esm/main.ts
-
examples/electron/src/renderer/App.tsx
-
examples/expo/lib/syncore.ts
-
examples/next-pwa/app/page.tsx
-
examples/sveltekit/package.json
Instructions
Core Rule
The Syncore runtime stays local. Adapters only provide environment-specific IO:
-
SQLite access
-
filesystem or storage APIs
-
transport to UI layers
-
timers and lifecycle hooks
Keep user functions portable and adapter setup specific.
Prefer Public Entry Points In App Docs
For app-facing setup, prefer the public syncorejs/* surface:
-
syncorejs/node
-
syncorejs/node/ipc
-
syncorejs/node/ipc/react
-
syncorejs/browser
-
syncorejs/browser/react
-
syncorejs/expo
-
syncorejs/expo/react
-
syncorejs/next
-
syncorejs/next/config
-
syncorejs/svelte
Use @syncore/* packages mainly when editing the monorepo internals themselves.
Monorepo Caveat
Some workspace fixtures import built paths directly, such as the Next example config, to keep workspace builds deterministic. Document public app usage with syncorejs/* unless the task is specifically about monorepo internals.
Node Script
For local scripts without a UI shell, use withNodeSyncoreClient :
import path from "node:path"; import { withNodeSyncoreClient } from "syncorejs/node"; import { api } from "./syncore/_generated/api.ts"; import schema from "./syncore/schema.ts"; import { functions } from "./syncore/_generated/functions.ts";
await withNodeSyncoreClient( { databasePath: path.join(process.cwd(), ".syncore", "syncore.db"), storageDirectory: path.join(process.cwd(), ".syncore", "storage"), schema, functions }, async (client) => { console.log(await client.query(api.tasks.list)); } );
Electron
Run Syncore in the main process and expose a narrow bridge to the renderer.
import path from "node:path"; import { app } from "electron"; import { createNodeSyncoreRuntime } from "syncorejs/node"; import schema from "../syncore/schema.js"; import { functions } from "../syncore/_generated/functions.js";
const runtime = createNodeSyncoreRuntime({ databasePath: path.join(app.getPath("userData"), "syncore.db"), storageDirectory: path.join(app.getPath("userData"), "storage"), schema, functions, platform: "electron-main" });
In the renderer, use SyncoreElectronProvider from syncorejs/node/ipc/react . Keep the preload bridge narrow with installSyncoreWindowBridge() .
Do not put SQLite in the renderer process.
Web Worker
For the web target, host Syncore inside a dedicated worker and talk to it through the managed client.
/// <reference lib="webworker" />
import { createBrowserWorkerRuntime } from "syncorejs/browser"; import schema from "../syncore/schema"; import { functions } from "../syncore/_generated/functions";
void createBrowserWorkerRuntime({ endpoint: self, schema, functions, databaseName: "my-syncore-app", persistenceMode: "opfs" });
import { SyncoreBrowserProvider } from "syncorejs/browser/react";
<SyncoreBrowserProvider workerUrl={new URL("./syncore.worker.ts", import.meta.url)}
{children} </SyncoreBrowserProvider>;
Expo
Use the bootstrap helper and mount SyncoreExpoProvider with a fallback while the local runtime starts.
import { createExpoSyncoreBootstrap } from "syncorejs/expo"; import schema from "../syncore/schema"; import { functions } from "../syncore/_generated/functions";
export const syncore = createExpoSyncoreBootstrap({ schema, functions, databaseName: "syncore.db", storageDirectoryName: "syncore-storage" });
Next PWA
Use the Next helpers to integrate the worker and serve the SQL.js wasm asset.
Configure next.config.ts with withSyncoreNext :
import { withSyncoreNext } from "syncorejs/next/config";
export default withSyncoreNext({ output: "export" });
Then wire the provider:
"use client";
import { SyncoreNextProvider } from "syncorejs/next";
const createWorker = () => new Worker(new URL("./syncore.worker", import.meta.url), { type: "module" });
export function AppSyncoreProvider({ children }: { children: React.ReactNode; }) { return ( <SyncoreNextProvider createWorker={createWorker}> {children} </SyncoreNextProvider> ); }
Remember the current Next flow also expects sql-wasm.wasm in public/ , plus service worker wiring when you want installable offline behavior.
Browser ESM And Svelte
The repo also demonstrates lower-level browser ESM usage through createBrowserWorkerClient(...) and Svelte bindings via syncorejs/svelte .
Reach for those patterns when React is not the UI layer.
Examples
Pick The Right Adapter
-
Node script -> syncorejs/node with withNodeSyncoreClient
-
Electron desktop app -> syncorejs/node plus IPC bridge and syncorejs/node/ipc/react
-
Browser app with worker isolation -> syncorejs/browser plus syncorejs/browser/react
-
Expo app with local SQLite -> syncorejs/expo plus syncorejs/expo/react
-
Next installable offline app -> syncorejs/next plus syncorejs/next/config
-
Svelte or SvelteKit app -> syncorejs/browser plus syncorejs/svelte
-
Browser ESM sample -> syncorejs/browser plus explicit function references
Best Practices
-
Keep the runtime in the environment best suited for local storage and lifecycle control
-
Preserve typed references across transports and clients
-
Use the official quickstarts and examples for target-specific wiring
-
Keep adapter code thin and user functions portable
-
Prefer wrapper providers in UI shells instead of hand-rolling provider setup each time
-
Validate both runtime behavior and declaration output when touching adapter types
Common Pitfalls
-
Running Electron storage or SQLite directly in the renderer
-
Breaking worker or IPC type boundaries by overconstraining transport types
-
Forgetting environment-specific assets such as sql-wasm.wasm , Next config wiring, or service worker setup
-
Solving adapter typing issues with app-level casts instead of shared fixes
-
Documenting internal @syncore/* imports where public syncorejs/* entrypoints are the intended app API
References
-
docs/quickstarts/node-script.md
-
docs/quickstarts/electron.md
-
docs/quickstarts/react-web.md
-
docs/quickstarts/expo.md
-
docs/quickstarts/next-pwa.md
-
packages/platform-node/AGENTS.md
-
packages/platform-web/AGENTS.md
-
packages/next/src/config.ts
-
packages/svelte/src/index.ts