diff --git a/.envrc b/.envrc index 5072098a8..18a4d9f9b 100644 --- a/.envrc +++ b/.envrc @@ -1,2 +1,3 @@ # shellcheck shell=bash +export GH_HOST=github.com source_env_if_exists .envrc.local diff --git a/apps/engine/package.json b/apps/engine/package.json index 5ba06e622..b763bca85 100644 --- a/apps/engine/package.json +++ b/apps/engine/package.json @@ -14,6 +14,11 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js" }, + "./lib": { + "bun": "./src/lib/index.ts", + "types": "./dist/lib/index.d.ts", + "import": "./dist/lib/index.js" + }, "./cli": { "bun": "./src/cli/command.ts", "types": "./dist/cli/command.d.ts", diff --git a/apps/engine/src/__generated__/openapi.d.ts b/apps/engine/src/__generated__/openapi.d.ts index 9f90086ad..386e8c469 100644 --- a/apps/engine/src/__generated__/openapi.d.ts +++ b/apps/engine/src/__generated__/openapi.d.ts @@ -532,6 +532,12 @@ export interface components { /** @description External ID for STS AssumeRole */ external_id?: string; }; + /** @description Enable experimental PGlite support (required to use pglite config or file:///memory:// URLs) */ + allow_experimental_pglite?: boolean; + pglite?: true | { + /** @description Directory for persistent storage (omit for in-memory) */ + data_dir?: string; + }; /** @description PEM-encoded CA certificate for SSL verification (required for verify-ca / verify-full with a private CA) */ ssl_ca_pem?: string; }; diff --git a/apps/engine/src/__generated__/openapi.json b/apps/engine/src/__generated__/openapi.json index f7d8ecf2b..bf9be2afd 100644 --- a/apps/engine/src/__generated__/openapi.json +++ b/apps/engine/src/__generated__/openapi.json @@ -1572,6 +1572,28 @@ "additionalProperties": false, "description": "AWS RDS IAM authentication config" }, + "allow_experimental_pglite": { + "type": "boolean", + "description": "Enable experimental PGlite support (required to use pglite config or file:///memory:// URLs)" + }, + "pglite": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "data_dir": { + "type": "string", + "description": "Directory for persistent storage (omit for in-memory)" + } + }, + "additionalProperties": false + } + ] + }, "ssl_ca_pem": { "type": "string", "description": "PEM-encoded CA certificate for SSL verification (required for verify-ca / verify-full with a private CA)" diff --git a/apps/service/src/__generated__/openapi.d.ts b/apps/service/src/__generated__/openapi.d.ts index d1c5a5b6c..d7570e156 100644 --- a/apps/service/src/__generated__/openapi.d.ts +++ b/apps/service/src/__generated__/openapi.d.ts @@ -467,6 +467,12 @@ export interface components { /** @description External ID for STS AssumeRole */ external_id?: string; }; + /** @description Enable experimental PGlite support (required to use pglite config or file:///memory:// URLs) */ + allow_experimental_pglite?: boolean; + pglite?: true | { + /** @description Directory for persistent storage (omit for in-memory) */ + data_dir?: string; + }; /** @description PEM-encoded CA certificate for SSL verification (required for verify-ca / verify-full with a private CA) */ ssl_ca_pem?: string; }; diff --git a/apps/service/src/__generated__/openapi.json b/apps/service/src/__generated__/openapi.json index d24c1a737..4b26b8f1d 100644 --- a/apps/service/src/__generated__/openapi.json +++ b/apps/service/src/__generated__/openapi.json @@ -1783,6 +1783,28 @@ "additionalProperties": false, "description": "AWS RDS IAM authentication config" }, + "allow_experimental_pglite": { + "type": "boolean", + "description": "Enable experimental PGlite support (required to use pglite config or file:///memory:// URLs)" + }, + "pglite": { + "anyOf": [ + { + "type": "boolean", + "const": true + }, + { + "type": "object", + "properties": { + "data_dir": { + "type": "string", + "description": "Directory for persistent storage (omit for in-memory)" + } + }, + "additionalProperties": false + } + ] + }, "ssl_ca_pem": { "type": "string", "description": "PEM-encoded CA certificate for SSL verification (required for verify-ca / verify-full with a private CA)" diff --git a/apps/visualizer/package.json b/apps/visualizer/package.json index da9d6fe7d..2673c4721 100644 --- a/apps/visualizer/package.json +++ b/apps/visualizer/package.json @@ -12,7 +12,7 @@ "@codemirror/lang-sql": "^6.7.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.26.0", - "@electric-sql/pglite": "^0.2.0", + "@electric-sql/pglite": "^0.4.5", "@stripe/sync-source-stripe": "workspace:*", "codemirror": "^6.0.1", "next": "^15", diff --git a/examples/browser-sync/index.html b/examples/browser-sync/index.html new file mode 100644 index 000000000..e5c0ffe67 --- /dev/null +++ b/examples/browser-sync/index.html @@ -0,0 +1,12 @@ + + + + + + Stripe Sync Engine — Browser + + +
+ + + diff --git a/examples/browser-sync/package.json b/examples/browser-sync/package.json new file mode 100644 index 000000000..2b0bdcc68 --- /dev/null +++ b/examples/browser-sync/package.json @@ -0,0 +1,31 @@ +{ + "name": "@stripe/sync-example-browser", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@electric-sql/pglite": "^0.4.5", + "@stripe/sync-destination-postgres": "workspace:*", + "@stripe/sync-engine": "workspace:*", + "@stripe/sync-protocol": "workspace:*", + "@stripe/sync-source-stripe": "workspace:*", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/react": "^19", + "@types/react-dom": "^19", + "@vitejs/plugin-react": "^4", + "buffer": "^6.0.3", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "stream-browserify": "^3.0.0", + "typescript": "^5", + "vite": "^6", + "vite-plugin-node-polyfills": "^0.26.0" + } +} diff --git a/examples/browser-sync/src/App.tsx b/examples/browser-sync/src/App.tsx new file mode 100644 index 000000000..c6cb9b39c --- /dev/null +++ b/examples/browser-sync/src/App.tsx @@ -0,0 +1,106 @@ +import { useState, useRef, useCallback } from 'react' +import { startSync } from './lib/sync' + +export default function App() { + const [apiKey, setApiKey] = useState('') + const [status, setStatus] = useState<'idle' | 'running' | 'error'>('idle') + const [messages, setMessages] = useState([]) + const [query, setQuery] = useState('') + const [queryResult, setQueryResult] = useState('') + const abortRef = useRef(null) + + const addMessage = useCallback((msg: string) => { + setMessages((prev) => [...prev.slice(-200), msg]) + }, []) + + const handleStart = async () => { + if (!apiKey) return + setStatus('running') + setMessages([]) + abortRef.current = new AbortController() + + try { + await startSync({ + apiKey, + websocket: true, + signal: abortRef.current.signal, + onMessage: (msg: unknown) => { + const m = msg as { type?: string; record?: { stream?: string } } + if (m.type === 'record') { + addMessage(`record: ${m.record?.stream}`) + } else { + addMessage(JSON.stringify(m).slice(0, 120)) + } + }, + }) + } catch (err) { + if ((err as Error).name !== 'AbortError') { + setStatus('error') + addMessage(`Error: ${(err as Error).message}`) + } + } + } + + const handleStop = () => { + abortRef.current?.abort() + setStatus('idle') + } + + return ( +
+

Stripe Sync Engine — Browser

+ +
+ setApiKey(e.target.value)} + style={{ width: '400px', padding: '0.5rem', fontFamily: 'monospace' }} + /> + {status === 'idle' ? ( + + ) : ( + + )} + {status} +
+ +
+ {messages.map((m, i) => ( +
{m}
+ ))} +
+ +
+