syncore-platform-adapters
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.mddocs/quickstarts/electron.mddocs/quickstarts/react-web.mddocs/quickstarts/expo.mddocs/quickstarts/next-pwa.mddocs/guides/syncore-vs-convex.mdpackages/platform-node/AGENTS.mdpackages/platform-web/AGENTS.mdpackages/platform-expo/src/index.tspackages/platform-expo/src/react.tsxpackages/platform-node/src/index.tspackages/platform-node/src/ipc-react.tsxpackages/platform-web/src/react.tsxpackages/platform-web/src/worker.tspackages/next/src/index.tsxpackages/next/src/config.tspackages/svelte/src/index.tsexamples/browser-esm/main.tsexamples/electron/src/renderer/App.tsxexamples/expo/lib/syncore.tsexamples/next-pwa/app/page.tsxexamples/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/nodesyncorejs/node/ipcsyncorejs/node/ipc/reactsyncorejs/browsersyncorejs/browser/reactsyncorejs/exposyncorejs/expo/reactsyncorejs/nextsyncorejs/next/configsyncorejs/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/nodewithwithNodeSyncoreClient - Electron desktop app ->
syncorejs/nodeplus IPC bridge andsyncorejs/node/ipc/react - Browser app with worker isolation ->
syncorejs/browserplussyncorejs/browser/react - Expo app with local SQLite ->
syncorejs/expoplussyncorejs/expo/react - Next installable offline app ->
syncorejs/nextplussyncorejs/next/config - Svelte or SvelteKit app ->
syncorejs/browserplussyncorejs/svelte - Browser ESM sample ->
syncorejs/browserplus 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 publicsyncorejs/*entrypoints are the intended app API
References
docs/quickstarts/node-script.mddocs/quickstarts/electron.mddocs/quickstarts/react-web.mddocs/quickstarts/expo.mddocs/quickstarts/next-pwa.mdpackages/platform-node/AGENTS.mdpackages/platform-web/AGENTS.mdpackages/next/src/config.tspackages/svelte/src/index.ts