@oselvar/sveltekit-add-worker-exports 0.3.1 → 0.3.2

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/README.md CHANGED
@@ -87,7 +87,7 @@ export default defineConfig({
87
87
  });
88
88
  ```
89
89
 
90
- Point `adapter-cloudflare`'s platform proxy at the generated `.platform-proxy-wrangler.jsonc`. The plugin writes this file with internal Durable Object bindings rewritten to cross-worker form (each gets a `script_name` pointing at the sidecar). Workflows can't survive `getPlatformProxy` (wrangler unconditionally deletes them), so the plugin replaces them with a service binding pointing at the sidecar; a small SvelteKit hook synthesizes the missing `Workflow` API on top of that bridge — see [Dev mode: calling Workflows](#dev-mode-calling-workflows) below. Without this config path, `getPlatformProxy` would try to run the DO classes itself and warn that it can't:
90
+ Point `adapter-cloudflare`'s platform proxy at the generated `.platform-proxy-wrangler.jsonc`. The plugin writes this file with internal Durable Object and Workflow bindings rewritten to cross-worker form (each gets a `script_name` pointing at the sidecar), so `platform.env.MY_DO` and `platform.env.MY_WORKFLOW` calls in `+server.ts` reach the sidecar via the wrangler dev registry. Without this config path, `getPlatformProxy` would try to run the DO/Workflow classes itself and warn that it can't:
91
91
 
92
92
  ```javascript
93
93
  // svelte.config.js
@@ -129,29 +129,11 @@ declare global {
129
129
  }
130
130
  ```
131
131
 
132
- The plugin auto-discovers your `wrangler.jsonc` (or `wrangler.toml`) and reads DO bindings, workflows, migrations, and compatibility settings from it. It overrides only the `main` entry point to use a generated wrapper (in `node_modules/.cache/sveltekit-add-worker-exports/`) that re-exports your classes and adds the workflow bridge routes.
132
+ The plugin auto-discovers your `wrangler.jsonc` (or `wrangler.toml`) and reads DO bindings, workflows, migrations, and compatibility settings from it. It overrides only the `main` entry point to point at your source entry.
133
133
 
134
134
  ### Dev mode: calling Workflows
135
135
 
136
- Workflow bindings can't be passed through `getPlatformProxy` — wrangler unconditionally strips them ([source](https://github.com/cloudflare/workers-sdk/blob/main/packages/wrangler/src/api/integrations/platform/index.ts)). The plugin works around this by adding a service binding to the sidecar and shipping a SvelteKit `Handle` that synthesizes `Workflow`-shaped objects on `platform.env`.
137
-
138
- Wire it up in `src/hooks.server.ts`:
139
-
140
- ```typescript
141
- export { handle } from '@oselvar/sveltekit-add-worker-exports/hooks';
142
- ```
143
-
144
- If you already have a `handle`, compose with `sequence`:
145
-
146
- ```typescript
147
- import { sequence } from '@sveltejs/kit/hooks';
148
- import { handle as sweHandle } from '@oselvar/sveltekit-add-worker-exports/hooks';
149
- import { handle as myHandle } from './my-handle';
150
-
151
- export const handle = sequence(sweHandle, myHandle);
152
- ```
153
-
154
- After that, `+server.ts` can call workflows the same way it does in production:
136
+ `+server.ts` calls workflows the same way it does in production:
155
137
 
156
138
  ```typescript
157
139
  export const POST: RequestHandler = async ({ params, request, platform }) => {
@@ -163,7 +145,7 @@ export const POST: RequestHandler = async ({ params, request, platform }) => {
163
145
  };
164
146
  ```
165
147
 
166
- In production the hook is a no-op — it reads its config from runtime globals that only the dev plugin sets, and `platform.env.MY_WORKFLOW` is the real Cloudflare binding.
148
+ The sidecar runs the real `WorkflowEntrypoint` class; calls reach it via the `script_name` rewrite in the platform-proxy config. This requires `wrangler >= 4.98.0` ([cloudflare/workers-sdk#13863](https://github.com/cloudflare/workers-sdk/pull/13863)).
167
149
 
168
150
  ### Testing the production build locally
169
151
 
@@ -243,7 +225,7 @@ Add this to your `package.json` scripts for convenience:
243
225
  }
244
226
  ```
245
227
 
246
- Note: the `.types-worker-wrangler.jsonc` file is generated when the dev server starts. Run `pnpm dev` at least once before running `wrangler types`. (The plugin also writes `.dev-worker-wrangler.jsonc` for the runtime sidecar, but its `main` points at a generated bridge wrapper in `node_modules/.cache/`, which can't be used for type generation — wrangler doesn't follow `export *` re-exports out of node_modules.)
228
+ Note: the `.types-worker-wrangler.jsonc` file is generated when the dev server starts. Run `pnpm dev` at least once before running `wrangler types`.
247
229
 
248
230
  ## Why this exists
249
231
 
package/dist/hooks.d.ts CHANGED
@@ -1,13 +1,10 @@
1
1
  /**
2
2
  * SvelteKit `Handle` re-exported for users.
3
3
  *
4
- * Implementation lives in `./bridge/runtime-hook` while the workflow bridge
5
- * exists. Once miniflare ships cross-worker workflow routing
6
- * (cloudflare/workers-sdk#7459), replace this file with a no-op handle:
7
- *
8
- * import type { Handle } from '@sveltejs/kit';
9
- * export const handle: Handle = ({ event, resolve }) => resolve(event);
10
- *
11
- * and delete `src/bridge/`.
4
+ * Kept as a no-op for backwards compatibility. Wrangler 4.98.0 fixed
5
+ * cross-worker workflow routing in `getPlatformProxy`
6
+ * (cloudflare/workers-sdk#13863), so `platform.env.MY_WORKFLOW` is now a
7
+ * real binding in both dev and production — no synthesis required.
12
8
  */
13
- export { handle } from './bridge/runtime-hook.js';
9
+ import type { Handle } from '@sveltejs/kit';
10
+ export declare const handle: Handle;
package/dist/hooks.js CHANGED
@@ -1,13 +1,9 @@
1
1
  /**
2
2
  * SvelteKit `Handle` re-exported for users.
3
3
  *
4
- * Implementation lives in `./bridge/runtime-hook` while the workflow bridge
5
- * exists. Once miniflare ships cross-worker workflow routing
6
- * (cloudflare/workers-sdk#7459), replace this file with a no-op handle:
7
- *
8
- * import type { Handle } from '@sveltejs/kit';
9
- * export const handle: Handle = ({ event, resolve }) => resolve(event);
10
- *
11
- * and delete `src/bridge/`.
4
+ * Kept as a no-op for backwards compatibility. Wrangler 4.98.0 fixed
5
+ * cross-worker workflow routing in `getPlatformProxy`
6
+ * (cloudflare/workers-sdk#13863), so `platform.env.MY_WORKFLOW` is now a
7
+ * real binding in both dev and production — no synthesis required.
12
8
  */
13
- export { handle } from './bridge/runtime-hook.js';
9
+ export const handle = ({ event, resolve }) => resolve(event);
package/dist/index.d.ts CHANGED
@@ -7,18 +7,13 @@
7
7
  * exports. This plugin post-processes the build output to merge them.
8
8
  *
9
9
  * **Dev mode:** `getPlatformProxy` (used by adapter-cloudflare in dev) can't
10
- * run internal Durable Objects. This plugin starts a separate wrangler dev
11
- * server via `unstable_startWorker` that runs the real DO worker. Clients
12
- * connect directly to it via WebSocket on a separate port.
13
- *
14
- * Workflows in dev are best-effort: if the installed wrangler/miniflare
15
- * supports cross-worker workflow routing (cloudflare/workers-sdk#7459),
16
- * `platform.env.MY_WORKFLOW.create(...)` works directly. Otherwise the bridge
17
- * in `src/bridge/` synthesises the binding via an HTTP fallback. Once the
18
- * upstream patch lands, delete `src/bridge/`, drop the `setupBridge` import
19
- * and call below, replace `wrapperPath` with `options.entryPoint`, remove
20
- * the `serviceBinding` insertion, and replace `src/hooks.ts` with a no-op
21
- * handle.
10
+ * run internal Durable Objects or Workflows. This plugin starts a separate
11
+ * wrangler dev server via `unstable_startWorker` that runs the real
12
+ * DO/Workflow worker. Internal DO and Workflow bindings in the platform-proxy
13
+ * config get a `script_name` pointing at that sidecar; miniflare routes calls
14
+ * across the dev registry (cloudflare/workers-sdk#7459, fixed in wrangler
15
+ * 4.98.0). Clients connect directly to the sidecar via WebSocket on a
16
+ * separate port.
22
17
  *
23
18
  * Usage in vite.config.ts:
24
19
  * addWorkerExports({ entryPoint: 'src/lib/server/index.ts' })
package/dist/index.js CHANGED
@@ -7,18 +7,13 @@
7
7
  * exports. This plugin post-processes the build output to merge them.
8
8
  *
9
9
  * **Dev mode:** `getPlatformProxy` (used by adapter-cloudflare in dev) can't
10
- * run internal Durable Objects. This plugin starts a separate wrangler dev
11
- * server via `unstable_startWorker` that runs the real DO worker. Clients
12
- * connect directly to it via WebSocket on a separate port.
13
- *
14
- * Workflows in dev are best-effort: if the installed wrangler/miniflare
15
- * supports cross-worker workflow routing (cloudflare/workers-sdk#7459),
16
- * `platform.env.MY_WORKFLOW.create(...)` works directly. Otherwise the bridge
17
- * in `src/bridge/` synthesises the binding via an HTTP fallback. Once the
18
- * upstream patch lands, delete `src/bridge/`, drop the `setupBridge` import
19
- * and call below, replace `wrapperPath` with `options.entryPoint`, remove
20
- * the `serviceBinding` insertion, and replace `src/hooks.ts` with a no-op
21
- * handle.
10
+ * run internal Durable Objects or Workflows. This plugin starts a separate
11
+ * wrangler dev server via `unstable_startWorker` that runs the real
12
+ * DO/Workflow worker. Internal DO and Workflow bindings in the platform-proxy
13
+ * config get a `script_name` pointing at that sidecar; miniflare routes calls
14
+ * across the dev registry (cloudflare/workers-sdk#7459, fixed in wrangler
15
+ * 4.98.0). Clients connect directly to the sidecar via WebSocket on a
16
+ * separate port.
22
17
  *
23
18
  * Usage in vite.config.ts:
24
19
  * addWorkerExports({ entryPoint: 'src/lib/server/index.ts' })
@@ -31,7 +26,6 @@ import { access, readFile, rename, writeFile, unlink } from 'node:fs/promises';
31
26
  import { homedir } from 'node:os';
32
27
  import { join, resolve } from 'node:path';
33
28
  import { parse as parseJsonc } from 'jsonc-parser';
34
- import { setupBridge } from './bridge/setup.js';
35
29
  const DEFAULT_DEV_PORT = 8787;
36
30
  const NODE_BUILTINS = [
37
31
  'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console',
@@ -135,17 +129,10 @@ function devPlugin(options) {
135
129
  let tempConfigPath = null;
136
130
  let proxyConfigPath = null;
137
131
  let typesConfigPath = null;
138
- let bridgeDispose = null;
139
- let cached = null;
140
- async function readConfig() {
141
- if (cached)
142
- return cached;
132
+ async function readRawConfig() {
143
133
  const { unstable_readConfig } = await import('wrangler');
144
134
  const parsed = unstable_readConfig(options.wranglerConfig ? { config: options.wranglerConfig } : {});
145
- const rawJson = await readFile(parsed.configPath, 'utf-8');
146
- const workflowBindings = (parsed.workflows ?? []).map((w) => w.binding);
147
- cached = { rawJson, workflowBindings };
148
- return cached;
135
+ return readFile(parsed.configPath, 'utf-8');
149
136
  }
150
137
  return {
151
138
  name: 'add-worker-exports-dev',
@@ -159,24 +146,12 @@ function devPlugin(options) {
159
146
  },
160
147
  async configureServer(server) {
161
148
  const { unstable_startWorker } = await import('wrangler');
162
- const { rawJson, workflowBindings } = await readConfig();
149
+ const rawJson = await readRawConfig();
163
150
  const sidecarName = `${parseJsonc(rawJson).name ?? 'sveltekit'}-dev-worker`;
164
- // Set up the workflow HTTP bridge (see src/bridge/). This is the
165
- // fallback used when the installed wrangler/miniflare strips
166
- // workflow bindings from getPlatformProxy. When the upstream
167
- // patch lands (cloudflare/workers-sdk#7459), delete src/bridge,
168
- // remove this call, and use options.entryPoint as the dev
169
- // config's `main` directly (no service binding either).
170
- const bridge = await setupBridge({
171
- userEntryAbs: resolve(options.entryPoint),
172
- workflowBindings,
173
- sidecarName
174
- });
175
- bridgeDispose = bridge.dispose;
176
151
  // Copy the raw config and override main, name, and dev port
177
152
  const devConfig = parseJsonc(rawJson);
178
153
  devConfig.name = sidecarName;
179
- devConfig.main = bridge.wrapperPath;
154
+ devConfig.main = options.entryPoint;
180
155
  devConfig.dev = { ...devConfig.dev, port: devPort };
181
156
  delete devConfig.assets;
182
157
  tempConfigPath = resolve('.dev-worker-wrangler.jsonc');
@@ -184,13 +159,10 @@ function devPlugin(options) {
184
159
  // Write a wrangler config for adapter-cloudflare's getPlatformProxy
185
160
  // (used by vite dev for platform.env). It can't run internal DOs or
186
161
  // Workflows itself -- those are served by the sidecar above. Rewrite
187
- // internal DO bindings as cross-worker bindings (script_name pointing
188
- // at the sidecar) so platform.env.MY_DO calls in +server.ts reach the
189
- // sidecar via the wrangler dev registry. Workflows get the same
190
- // rewrite; on patched wrangler/miniflare miniflare routes them via
191
- // the dev-registry-proxy. On unpatched, wrangler strips them and the
192
- // bridge service binding (added below) drives the SvelteKit hook
193
- // fallback.
162
+ // internal DO and Workflow bindings as cross-worker bindings
163
+ // (script_name pointing at the sidecar) so platform.env.MY_DO and
164
+ // platform.env.MY_WORKFLOW calls in +server.ts reach the sidecar via
165
+ // the wrangler dev registry.
194
166
  const proxyConfig = parseJsonc(rawJson);
195
167
  if (proxyConfig.durable_objects?.bindings) {
196
168
  proxyConfig.durable_objects.bindings = proxyConfig.durable_objects.bindings.map((b) => b.script_name ? b : { ...b, script_name: sidecarName });
@@ -198,21 +170,13 @@ function devPlugin(options) {
198
170
  if (Array.isArray(proxyConfig.workflows)) {
199
171
  proxyConfig.workflows = proxyConfig.workflows.map((w) => w.script_name ? w : { ...w, script_name: sidecarName });
200
172
  }
201
- if (workflowBindings.length > 0) {
202
- proxyConfig.services = [
203
- ...(proxyConfig.services ?? []),
204
- bridge.serviceBinding
205
- ];
206
- }
207
173
  delete proxyConfig.migrations;
208
174
  proxyConfigPath = resolve('.platform-proxy-wrangler.jsonc');
209
175
  await writeFile(proxyConfigPath, JSON.stringify(proxyConfig, null, '\t'));
210
176
  // A third config used only by `wrangler types`. Identical to the dev
211
- // config except `main` points at the user's source entry, so wrangler
212
- // can resolve typed bindings (DurableObjectNamespace<EchoDO>, etc.).
213
- // Pointing types at our generated wrapper produces untyped bindings
214
- // because wrangler's type resolver doesn't follow `export *` re-exports
215
- // out of node_modules.
177
+ // config but kept separate so the user can run `wrangler types
178
+ // --config .types-worker-wrangler.jsonc` to get typed bindings like
179
+ // DurableObjectNamespace<EchoDO> resolved from the source entry.
216
180
  const typesConfig = parseJsonc(rawJson);
217
181
  typesConfig.name = sidecarName;
218
182
  typesConfig.main = options.entryPoint;
@@ -241,10 +205,6 @@ function devPlugin(options) {
241
205
  if (typesConfigPath) {
242
206
  await unlink(typesConfigPath).catch(() => { });
243
207
  }
244
- if (bridgeDispose) {
245
- await bridgeDispose();
246
- bridgeDispose = null;
247
- }
248
208
  });
249
209
  }
250
210
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oselvar/sveltekit-add-worker-exports",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Vite plugin that adds Durable Object and Workflow exports to SvelteKit Cloudflare workers",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -14,10 +14,6 @@
14
14
  "./hooks": {
15
15
  "types": "./dist/hooks.d.ts",
16
16
  "import": "./dist/hooks.js"
17
- },
18
- "./dev-bridge": {
19
- "types": "./dist/bridge/dev-handler.d.ts",
20
- "import": "./dist/bridge/dev-handler.js"
21
17
  }
22
18
  },
23
19
  "files": [
@@ -44,19 +40,21 @@
44
40
  "wrangler": ">=4"
45
41
  },
46
42
  "devDependencies": {
47
- "@sveltejs/kit": "^2.61.0",
48
- "@types/node": "^22.19.19",
49
- "esbuild": "^0.25.12",
50
- "miniflare": "^4.20260521.0",
51
- "np": "^11.2.1",
52
- "typescript": "^5.9.3",
53
- "vite": "^6.4.2",
54
- "wrangler": "^4.94.0"
43
+ "@release-it/conventional-changelog": "^11.0.1",
44
+ "@sveltejs/kit": "^2.62.0",
45
+ "@types/node": "^25.9.1",
46
+ "esbuild": "^0.28.0",
47
+ "miniflare": "^4.20260601.0",
48
+ "release-it": "^20.2.0",
49
+ "typescript": "^6.0.3",
50
+ "vite": "^8.0.16",
51
+ "wrangler": "^4.98.0"
55
52
  },
56
53
  "scripts": {
57
54
  "build": "tsc",
58
55
  "test": "tsc --noEmit",
59
- "prepublishOnly": "tsc"
56
+ "prepublishOnly": "tsc",
57
+ "release": "release-it"
60
58
  },
61
59
  "dependencies": {
62
60
  "jsonc-parser": "^3.3.1"
@@ -1,27 +0,0 @@
1
- /**
2
- * Workerd-side bridge handler. Bundled into the dev sidecar by wrangler via
3
- * the cache wrapper at `node_modules/.cache/sveltekit-add-worker-exports/`.
4
- *
5
- * The wrapper imports `createBridgeFetch` from this file and uses it as the
6
- * default fetch handler. Routes `/__swe/wf/*` are handled here; anything else
7
- * delegates to the user's own default fetch handler.
8
- *
9
- * This file is part of the workflow-bridge fallback that exists because
10
- * `getPlatformProxy()` in unpatched wrangler/miniflare strips workflow
11
- * bindings. Once the upstream patch lands (cloudflare/workers-sdk#7459),
12
- * the entire `src/bridge/` directory can be deleted.
13
- */
14
- type CtxLike = {
15
- waitUntil(promise: Promise<unknown>): void;
16
- };
17
- type UserFetch = (request: Request, env: unknown, ctx: CtxLike) => Promise<Response> | Response;
18
- export interface BridgeOptions {
19
- workflowBindings: readonly string[];
20
- userFetch?: UserFetch;
21
- }
22
- /**
23
- * Returns a `fetch` handler that intercepts `/__swe/wf/*` and forwards
24
- * everything else to `userFetch`.
25
- */
26
- export declare function createBridgeFetch(options: BridgeOptions): (request: Request, env: unknown, ctx: CtxLike) => Promise<Response>;
27
- export {};
@@ -1,88 +0,0 @@
1
- /**
2
- * Workerd-side bridge handler. Bundled into the dev sidecar by wrangler via
3
- * the cache wrapper at `node_modules/.cache/sveltekit-add-worker-exports/`.
4
- *
5
- * The wrapper imports `createBridgeFetch` from this file and uses it as the
6
- * default fetch handler. Routes `/__swe/wf/*` are handled here; anything else
7
- * delegates to the user's own default fetch handler.
8
- *
9
- * This file is part of the workflow-bridge fallback that exists because
10
- * `getPlatformProxy()` in unpatched wrangler/miniflare strips workflow
11
- * bindings. Once the upstream patch lands (cloudflare/workers-sdk#7459),
12
- * the entire `src/bridge/` directory can be deleted.
13
- */
14
- /**
15
- * Returns a `fetch` handler that intercepts `/__swe/wf/*` and forwards
16
- * everything else to `userFetch`.
17
- */
18
- export function createBridgeFetch(options) {
19
- const bindings = new Set(options.workflowBindings);
20
- return async (request, env, ctx) => {
21
- const url = new URL(request.url);
22
- if (url.pathname.startsWith('/__swe/wf/')) {
23
- return handleWf(url.pathname.slice('/__swe/wf/'.length), request, env, bindings);
24
- }
25
- if (options.userFetch) {
26
- return options.userFetch(request, env, ctx);
27
- }
28
- return new Response('Not found', { status: 404 });
29
- };
30
- }
31
- async function handleWf(rest, request, env, bindings) {
32
- const parts = rest.split('/').map(decodeURIComponent);
33
- const [binding, op, ...tail] = parts;
34
- if (!binding || !bindings.has(binding)) {
35
- return new Response('unknown workflow binding: ' + binding, { status: 404 });
36
- }
37
- const wf = env[binding];
38
- if (!wf) {
39
- return new Response('binding ' + binding + ' missing on sidecar', { status: 500 });
40
- }
41
- try {
42
- if (op === 'create' && request.method === 'POST') {
43
- const opts = await request.json();
44
- const inst = await wf.create(opts);
45
- return Response.json({ id: inst.id });
46
- }
47
- if (op === 'createBatch' && request.method === 'POST') {
48
- const batch = (await request.json());
49
- const insts = await wf.createBatch(batch);
50
- return Response.json(insts.map((i) => ({ id: i.id })));
51
- }
52
- if (op === 'get' && tail.length === 1) {
53
- const inst = await wf.get(tail[0]);
54
- return Response.json({ id: inst.id });
55
- }
56
- if (op === 'instance' && tail.length === 2) {
57
- const [id, method] = tail;
58
- const inst = await wf.get(id);
59
- switch (method) {
60
- case 'pause':
61
- await inst.pause();
62
- return new Response(null, { status: 204 });
63
- case 'resume':
64
- await inst.resume();
65
- return new Response(null, { status: 204 });
66
- case 'terminate':
67
- await inst.terminate();
68
- return new Response(null, { status: 204 });
69
- case 'restart':
70
- await inst.restart();
71
- return new Response(null, { status: 204 });
72
- case 'status':
73
- return Response.json(await inst.status());
74
- case 'sendEvent': {
75
- const body = (await request.json());
76
- await inst.sendEvent(body);
77
- return new Response(null, { status: 204 });
78
- }
79
- }
80
- return new Response('unknown method: ' + method, { status: 404 });
81
- }
82
- return new Response('unknown op: ' + op, { status: 404 });
83
- }
84
- catch (err) {
85
- const message = err instanceof Error ? err.message : String(err);
86
- return new Response('error: ' + message, { status: 500 });
87
- }
88
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * SvelteKit `Handle` that injects fake Workflow bindings into `event.platform.env`
3
- * during `vite dev`. In production this is a no-op.
4
- *
5
- * Why: wrangler's `getPlatformProxy` (used by `adapter-cloudflare` in dev) deletes
6
- * workflow bindings before they reach miniflare, so `+server.ts` can't call
7
- * `platform.env.MY_WORKFLOW.create(...)` directly. The companion vite plugin
8
- * exposes a service binding (`__SWE_BRIDGE`) that fetches into a sidecar wrangler
9
- * dev worker which has the real workflow binding. This hook synthesizes a
10
- * `Workflow`-shaped object per declared workflow that proxies API calls through
11
- * that bridge.
12
- *
13
- * Usage in `src/hooks.server.ts`:
14
- *
15
- * export { handle } from '@oselvar/sveltekit-add-worker-exports/hooks';
16
- *
17
- * If you already have a `handle`, compose with `sequence`:
18
- *
19
- * import { sequence } from '@sveltejs/kit/hooks';
20
- * import { handle as sweHandle } from '@oselvar/sveltekit-add-worker-exports/hooks';
21
- * export const handle = sequence(sweHandle, myHandle);
22
- */
23
- import type { Handle } from '@sveltejs/kit';
24
- /**
25
- * Reads bridge config from `globalThis`, set by the vite dev plugin at startup.
26
- *
27
- * We can't rely on vite `define` / compile-time substitution here because this
28
- * file ships in node_modules and vite doesn't transform external modules in
29
- * SSR by default. Plain runtime globals dodge that entirely and stay
30
- * `undefined` in production (workerd), making this hook a no-op there.
31
- */
32
- export declare const handle: Handle;
@@ -1,105 +0,0 @@
1
- /**
2
- * SvelteKit `Handle` that injects fake Workflow bindings into `event.platform.env`
3
- * during `vite dev`. In production this is a no-op.
4
- *
5
- * Why: wrangler's `getPlatformProxy` (used by `adapter-cloudflare` in dev) deletes
6
- * workflow bindings before they reach miniflare, so `+server.ts` can't call
7
- * `platform.env.MY_WORKFLOW.create(...)` directly. The companion vite plugin
8
- * exposes a service binding (`__SWE_BRIDGE`) that fetches into a sidecar wrangler
9
- * dev worker which has the real workflow binding. This hook synthesizes a
10
- * `Workflow`-shaped object per declared workflow that proxies API calls through
11
- * that bridge.
12
- *
13
- * Usage in `src/hooks.server.ts`:
14
- *
15
- * export { handle } from '@oselvar/sveltekit-add-worker-exports/hooks';
16
- *
17
- * If you already have a `handle`, compose with `sequence`:
18
- *
19
- * import { sequence } from '@sveltejs/kit/hooks';
20
- * import { handle as sweHandle } from '@oselvar/sveltekit-add-worker-exports/hooks';
21
- * export const handle = sequence(sweHandle, myHandle);
22
- */
23
- /**
24
- * Reads bridge config from `globalThis`, set by the vite dev plugin at startup.
25
- *
26
- * We can't rely on vite `define` / compile-time substitution here because this
27
- * file ships in node_modules and vite doesn't transform external modules in
28
- * SSR by default. Plain runtime globals dodge that entirely and stay
29
- * `undefined` in production (workerd), making this hook a no-op there.
30
- */
31
- export const handle = async ({ event, resolve }) => {
32
- const g = globalThis;
33
- const bridgeName = g.__SWE_BRIDGE_NAME__;
34
- const workflows = g.__SWE_WORKFLOWS__;
35
- if (!bridgeName || !workflows || workflows.length === 0)
36
- return resolve(event);
37
- const env = event.platform?.env;
38
- if (!env)
39
- return resolve(event);
40
- const bridge = env[bridgeName];
41
- if (!bridge)
42
- return resolve(event);
43
- for (const name of workflows) {
44
- if (env[name])
45
- continue;
46
- env[name] = makeFakeWorkflow(bridge, name);
47
- }
48
- return resolve(event);
49
- };
50
- function makeFakeWorkflow(bridge, binding) {
51
- const base = `http://swe/__swe/wf/${encodeURIComponent(binding)}`;
52
- return {
53
- async create(opts) {
54
- const { id } = await postJson(bridge, `${base}/create`, opts ?? {});
55
- return makeFakeInstance(bridge, binding, id);
56
- },
57
- async createBatch(batch) {
58
- const ids = await postJson(bridge, `${base}/createBatch`, batch);
59
- return ids.map(({ id }) => makeFakeInstance(bridge, binding, id));
60
- },
61
- async get(id) {
62
- await getOk(bridge, `${base}/get/${encodeURIComponent(id)}`);
63
- return makeFakeInstance(bridge, binding, id);
64
- }
65
- };
66
- }
67
- function makeFakeInstance(bridge, binding, id) {
68
- const base = `http://swe/__swe/wf/${encodeURIComponent(binding)}/instance/${encodeURIComponent(id)}`;
69
- const post = async (method, body) => {
70
- const res = await bridge.fetch(`${base}/${method}`, {
71
- method: 'POST',
72
- body: body === undefined ? undefined : JSON.stringify(body)
73
- });
74
- if (!res.ok) {
75
- throw new Error(`workflow ${method} failed: ${res.status} ${await res.text()}`);
76
- }
77
- const text = await res.text();
78
- return text ? JSON.parse(text) : undefined;
79
- };
80
- return {
81
- id,
82
- pause: () => post('pause').then(() => undefined),
83
- resume: () => post('resume').then(() => undefined),
84
- terminate: () => post('terminate').then(() => undefined),
85
- restart: () => post('restart').then(() => undefined),
86
- status: () => post('status'),
87
- sendEvent: (opts) => post('sendEvent', opts).then(() => undefined)
88
- };
89
- }
90
- // Pass URL string + init (not a pre-built Request): miniflare's internal undici
91
- // won't accept a Request from a different realm — it stringifies it and tries
92
- // to parse `[object Request]` as a URL.
93
- async function postJson(bridge, url, body) {
94
- const res = await bridge.fetch(url, { method: 'POST', body: JSON.stringify(body) });
95
- if (!res.ok) {
96
- throw new Error(`bridge POST ${url} failed: ${res.status} ${await res.text()}`);
97
- }
98
- return (await res.json());
99
- }
100
- async function getOk(bridge, url) {
101
- const res = await bridge.fetch(url);
102
- if (!res.ok) {
103
- throw new Error(`bridge GET ${url} failed: ${res.status} ${await res.text()}`);
104
- }
105
- }
@@ -1,31 +0,0 @@
1
- /**
2
- * Vite-plugin-side bridge setup. Called from `src/index.ts` while the workflow
3
- * bridge exists. Encapsulates everything bridge-specific:
4
- *
5
- * - generates the cache wrapper at `node_modules/.cache/...` so the dev
6
- * sidecar serves the `/__swe/wf/*` routes used by the runtime hook
7
- * - publishes config on `globalThis` for the SvelteKit `handle` to read
8
- * - returns the proxy-config service binding to add and a disposer
9
- *
10
- * Once miniflare ships cross-worker workflow routing
11
- * (cloudflare/workers-sdk#7459), delete `src/bridge/` and the call to
12
- * `setupBridge` in `src/index.ts`.
13
- */
14
- export declare const BRIDGE_BINDING = "__SWE_BRIDGE";
15
- export interface BridgeContext {
16
- /** Path to the generated sidecar wrapper. Use as the dev config's `main`. */
17
- wrapperPath: string;
18
- /** Service binding entry to merge into the platform-proxy wrangler config. */
19
- serviceBinding: {
20
- binding: string;
21
- service: string;
22
- };
23
- /** Best-effort cleanup of generated artifacts. */
24
- dispose(): Promise<void>;
25
- }
26
- export interface SetupBridgeOptions {
27
- userEntryAbs: string;
28
- workflowBindings: readonly string[];
29
- sidecarName: string;
30
- }
31
- export declare function setupBridge(options: SetupBridgeOptions): Promise<BridgeContext>;
@@ -1,66 +0,0 @@
1
- /**
2
- * Vite-plugin-side bridge setup. Called from `src/index.ts` while the workflow
3
- * bridge exists. Encapsulates everything bridge-specific:
4
- *
5
- * - generates the cache wrapper at `node_modules/.cache/...` so the dev
6
- * sidecar serves the `/__swe/wf/*` routes used by the runtime hook
7
- * - publishes config on `globalThis` for the SvelteKit `handle` to read
8
- * - returns the proxy-config service binding to add and a disposer
9
- *
10
- * Once miniflare ships cross-worker workflow routing
11
- * (cloudflare/workers-sdk#7459), delete `src/bridge/` and the call to
12
- * `setupBridge` in `src/index.ts`.
13
- */
14
- import { mkdir, unlink, writeFile } from 'node:fs/promises';
15
- import { dirname, join, relative, resolve } from 'node:path';
16
- export const BRIDGE_BINDING = '__SWE_BRIDGE';
17
- const CACHE_DIR_REL = 'node_modules/.cache/sveltekit-add-worker-exports';
18
- const WRAPPER_FILENAME = 'dev-worker-entry.ts';
19
- export async function setupBridge(options) {
20
- const cacheDir = resolve(CACHE_DIR_REL);
21
- await mkdir(cacheDir, { recursive: true });
22
- const wrapperPath = join(cacheDir, WRAPPER_FILENAME);
23
- const userEntryRel = relative(dirname(wrapperPath), options.userEntryAbs)
24
- .replace(/\\/g, '/')
25
- .replace(/\.(ts|tsx|js|jsx|mjs|mts)$/, '');
26
- await writeFile(wrapperPath, generateWrapperSource(userEntryRel, options.workflowBindings));
27
- // The runtime hook reads these from globalThis. We can't use vite's
28
- // `define` because dist/hooks.js lives in node_modules and vite doesn't
29
- // transform external modules in SSR by default — substitutions wouldn't
30
- // reach the file.
31
- globalThis.__SWE_BRIDGE_NAME__ = BRIDGE_BINDING;
32
- globalThis.__SWE_WORKFLOWS__ = options.workflowBindings;
33
- return {
34
- wrapperPath,
35
- serviceBinding: { binding: BRIDGE_BINDING, service: options.sidecarName },
36
- dispose: async () => {
37
- await unlink(wrapperPath).catch(() => { });
38
- }
39
- };
40
- }
41
- /**
42
- * Tiny glue file that wrangler bundles into the dev sidecar. The substantive
43
- * bridge logic lives in `./dev-handler.ts`, which the wrapper imports as a
44
- * package subpath. Keeping the template this small makes it unlikely to
45
- * harbour TypeScript errors that wouldn't be caught by tsc on the rest of
46
- * the package.
47
- */
48
- function generateWrapperSource(userEntryRel, workflowBindings) {
49
- const importPath = JSON.stringify(userEntryRel);
50
- const bindingList = JSON.stringify(workflowBindings);
51
- return `// Auto-generated by @oselvar/sveltekit-add-worker-exports.
52
- // Bridges Workflow bindings to SvelteKit dev. Do not edit.
53
- import * as user from ${importPath};
54
- export * from ${importPath};
55
- import { createBridgeFetch } from '@oselvar/sveltekit-add-worker-exports/dev-bridge';
56
-
57
- const userDefault = (user as { default?: { fetch?: ExportedHandlerFetchHandler } }).default;
58
-
59
- export default {
60
- \tfetch: createBridgeFetch({
61
- \t\tworkflowBindings: ${bindingList},
62
- \t\tuserFetch: userDefault?.fetch?.bind(userDefault)
63
- \t})
64
- } satisfies ExportedHandler;
65
- `;
66
- }