@oselvar/sveltekit-add-worker-exports 1.0.0 → 2.1.0

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
@@ -1,20 +1,21 @@
1
1
  # @oselvar/sveltekit-add-worker-exports
2
2
 
3
- A Vite plugin that makes any class-based Cloudflare Worker export (Durable Objects, Workflows, `WorkerEntrypoint` RPC, voice agents, …) work with SvelteKit on Cloudflare, in both dev and production.
3
+ A Vite plugin that makes any class-based Cloudflare Worker export (Durable Objects, Workflows, `WorkerEntrypoint` RPC, voice agents, …) and any non-fetch handler (`scheduled`, `queue`, `email`, `tail`, `trace`) work with SvelteKit on Cloudflare, in both dev and production.
4
4
 
5
- **Build mode:** SvelteKit's `adapter-cloudflare` generates `_worker.js` with only a default export (the fetch handler). Cloudflare Workers requires class-based bindings (Durable Objects, Workflows, `WorkerEntrypoint`, etc.) to be **named exports**. This plugin post-processes the build output to merge your named exports with SvelteKit's default export.
5
+ **Build mode:** SvelteKit's `adapter-cloudflare` generates `_worker.js` with only a default export (the fetch handler). Cloudflare Workers requires class-based bindings (Durable Objects, Workflows, `WorkerEntrypoint`, etc.) to be **named exports**, and non-fetch handlers (`scheduled`, `queue`, `email`, …) to be **methods on the default export**. This plugin post-processes the build output to merge both kinds onto SvelteKit's worker.
6
6
 
7
- **Dev mode:** `getPlatformProxy` (used by `adapter-cloudflare` in dev) can't run internal Durable Objects, Workflows, or other class-based bindings. This plugin starts a separate wrangler dev server that runs the real worker with hot-reload. SvelteKit `+server.ts` handlers call bindings through `platform.env.MY_BINDING.<rpc>()` as usual — the plugin rewrites those bindings to point at the sidecar via wrangler's dev registry, so cross-worker calls Just Work. WebSocket clients in dev connect to the sidecar directly on a separate port — see [WebSockets](#websockets) below.
7
+ **Dev mode:** `getPlatformProxy` (used by `adapter-cloudflare` in dev) can't run internal Durable Objects, Workflows, or other class-based bindings. This plugin starts a separate wrangler dev server that runs the real worker with hot-reload. SvelteKit `+server.ts` handlers call bindings through `platform.env.MY_BINDING.<rpc>()` as usual — the plugin rewrites those bindings to point at the sidecar via wrangler's dev registry, so cross-worker calls Just Work. WebSocket clients in dev connect to the sidecar directly on a separate port — see [WebSockets](#websockets) below. The sidecar also exposes `/__scheduled` so `scheduled` handlers can be fired manually in dev — see [Scheduled, queue, email, tail handlers](#scheduled-queue-email-tail-handlers).
8
8
 
9
9
  ## What this works with
10
10
 
11
- The plugin is binding-agnostic: if Cloudflare resolves it through a named class export plus a `wrangler.jsonc` binding, the plugin handles it. Confirmed working:
11
+ The plugin is binding-agnostic: if Cloudflare resolves it through a named class export plus a `wrangler.jsonc` binding, or through a method on the default export, the plugin handles it. Confirmed working:
12
12
 
13
13
  - Durable Objects
14
14
  - Workflows (`WorkflowEntrypoint`)
15
15
  - Voice agents
16
+ - `scheduled` (cron triggers) — see [Scheduled, queue, email, tail handlers](#scheduled-queue-email-tail-handlers)
16
17
 
17
- It should also work with `WorkerEntrypoint` RPC and any future class-based export that follows the same export-plus-binding pattern. The examples below use Durable Objects and Workflows because they're the most common, but the wiring is the same for any class-based entrypoint — export the class from your entry point, declare the binding in `wrangler.jsonc`, and call it via `platform.env.MY_BINDING` in your routes.
18
+ It should also work with `WorkerEntrypoint` RPC and any future class-based export that follows the same export-plus-binding pattern, as well as `queue`, `email`, `tail`, and `trace` — anything Cloudflare invokes as a method on the worker's default export. The examples below use Durable Objects and Workflows because they're the most common, but the wiring is the same for any class-based entrypoint — export the class from your entry point, declare the binding in `wrangler.jsonc`, and call it via `platform.env.MY_BINDING` in your routes.
18
19
 
19
20
  ## Install
20
21
 
@@ -254,10 +255,10 @@ The plugin reads your wrangler config, creates a temporary config with `main` po
254
255
 
255
256
  ### Generating typed bindings
256
257
 
257
- The dev plugin creates a temporary `.types-worker-wrangler.jsonc` with `main` pointing to your source entry point. Use it to generate fully generic Cloudflare types:
258
+ The dev plugin creates a temporary `.dev-worker-wrangler.jsonc` with `main` pointing to your source entry point. Use it to generate fully generic Cloudflare types:
258
259
 
259
260
  ```bash
260
- wrangler types --config .types-worker-wrangler.jsonc
261
+ wrangler types --config .dev-worker-wrangler.jsonc
261
262
  ```
262
263
 
263
264
  This produces typed DO bindings like `DurableObjectNamespace<MyDurableObject>` instead of the untyped `DurableObjectNamespace` you get from the default `wrangler.jsonc` (whose `main` points to the SvelteKit build output, which doesn't exist during dev).
@@ -267,12 +268,12 @@ Add this to your `package.json` scripts for convenience:
267
268
  ```json
268
269
  {
269
270
  "scripts": {
270
- "types": "wrangler types --config .types-worker-wrangler.jsonc"
271
+ "types": "wrangler types --config .dev-worker-wrangler.jsonc"
271
272
  }
272
273
  }
273
274
  ```
274
275
 
275
- Note: the `.types-worker-wrangler.jsonc` file is generated when the dev server starts. Run `pnpm dev` at least once before running `wrangler types`.
276
+ Note: the `.dev-worker-wrangler.jsonc` file is generated when the dev server starts. Run `pnpm dev` at least once before running `wrangler types`.
276
277
 
277
278
  ## Why this exists
278
279
 
@@ -286,7 +287,7 @@ Once that PR lands, `@cloudflare/vite-plugin` handles everything this plugin doe
286
287
 
287
288
  - **Build:** the plugin bundles a single worker entry that exports `default` + named classes directly — no `_worker.js` post-processing, no second esbuild pass to merge exports.
288
289
  - **Dev:** Durable Objects and Workflows run in the same worker as SvelteKit on real workerd, hot-reloaded — no sidecar via `unstable_startWorker`, no platform-proxy `script_name` rewrite, no separate WebSocket port, no dev registry routing.
289
- - **Types:** `wrangler types` resolves typed bindings (`DurableObjectNamespace<MyDO>`) from the user entry, no `.types-worker-wrangler.jsonc` indirection.
290
+ - **Types:** `wrangler types` resolves typed bindings (`DurableObjectNamespace<MyDO>`) from the user entry, no `.dev-worker-wrangler.jsonc` indirection.
290
291
 
291
292
  Side note: [cloudflare/workers-sdk#14013](https://github.com/cloudflare/workers-sdk/pull/14013) introduces an experimental `cloudflare.config.ts` flow (typed `exports` API) where the Durable Object branch currently throws `"Durable Object exports are not currently supported."` (see [`convert.ts`](https://github.com/cloudflare/workers-sdk/blob/main/packages/config/src/convert.ts)) and Workflows are commented out. This is a separate, opt-in config surface — the standard `wrangler.jsonc` path that kit#15627 uses already supports DO and Workflow named exports, so this limitation does **not** gate retirement.
292
293
 
package/dist/index.d.ts CHANGED
@@ -21,6 +21,14 @@
21
21
  * @see https://github.com/sveltejs/kit/issues/1712
22
22
  */
23
23
  import type { Plugin } from 'vite';
24
+ /**
25
+ * Parses a wrangler config file. Picks the parser by extension:
26
+ * `.toml` uses smol-toml; everything else (`.jsonc`, `.json`, or no
27
+ * extension) uses jsonc-parser, which handles plain JSON fine.
28
+ *
29
+ * Exported for tests; the dev plugin uses it via the configureServer hook.
30
+ */
31
+ export declare function parseWranglerConfig(path: string, contents: string): unknown;
24
32
  export interface AddWorkerExportsOptions {
25
33
  /** File that exports Durable Object and/or Workflow classes */
26
34
  entryPoint: string;
@@ -30,6 +38,17 @@ export interface AddWorkerExportsOptions {
30
38
  wranglerConfig?: string;
31
39
  /** Port for the dev worker server (default: 8787) */
32
40
  devPort?: number;
41
+ /**
42
+ * Handles each structured log line emitted by the sidecar worker (one entry
43
+ * per `console.log` / `console.error` / etc. call). When omitted, a default
44
+ * handler prints each line to the host's stdout (or stderr for `error`
45
+ * level) prefixed with the sidecar name. Pass `() => {}` to silence.
46
+ */
47
+ structuredLogsHandler?: (log: {
48
+ level: string;
49
+ message: string;
50
+ timestamp: number;
51
+ }) => void;
33
52
  }
34
53
  /**
35
54
  * Returns two Vite plugins: one for build (patches _worker.js) and one for
package/dist/index.js CHANGED
@@ -28,6 +28,17 @@ import { homedir } from 'node:os';
28
28
  import { join, resolve } from 'node:path';
29
29
  import { parse as parseJsonc } from 'jsonc-parser';
30
30
  import { getNodeCompat } from 'miniflare';
31
+ import { parse as parseToml } from 'smol-toml';
32
+ /**
33
+ * Parses a wrangler config file. Picks the parser by extension:
34
+ * `.toml` uses smol-toml; everything else (`.jsonc`, `.json`, or no
35
+ * extension) uses jsonc-parser, which handles plain JSON fine.
36
+ *
37
+ * Exported for tests; the dev plugin uses it via the configureServer hook.
38
+ */
39
+ export function parseWranglerConfig(path, contents) {
40
+ return path.endsWith('.toml') ? parseToml(contents) : parseJsonc(contents);
41
+ }
31
42
  const DEFAULT_DEV_PORT = 8787;
32
43
  /**
33
44
  * Mirrors wrangler's internal getRegistryPath() — wrangler doesn't export it.
@@ -133,11 +144,12 @@ function devPlugin(options) {
133
144
  let worker = null;
134
145
  let tempConfigPath = null;
135
146
  let proxyConfigPath = null;
136
- let typesConfigPath = null;
137
147
  async function readRawConfig() {
138
148
  const { unstable_readConfig } = await import('wrangler');
139
149
  const parsed = unstable_readConfig(options.wranglerConfig ? { config: options.wranglerConfig } : {});
140
- return readFile(parsed.configPath, 'utf-8');
150
+ const path = parsed.configPath;
151
+ const contents = await readFile(path, 'utf-8');
152
+ return { path, contents };
141
153
  }
142
154
  return {
143
155
  name: 'add-worker-exports-dev',
@@ -151,8 +163,8 @@ function devPlugin(options) {
151
163
  },
152
164
  async configureServer(server) {
153
165
  const { unstable_startWorker } = await import('wrangler');
154
- const rawJson = await readRawConfig();
155
- const baseConfig = parseJsonc(rawJson);
166
+ const { path: configPath, contents } = await readRawConfig();
167
+ const baseConfig = parseWranglerConfig(configPath, contents);
156
168
  const sidecarName = `${baseConfig.name ?? 'sveltekit'}-dev-worker`;
157
169
  // When CLOUDFLARE_ENV is set, wrangler suffixes the registered
158
170
  // worker name with `-${env}` (see appendEnvName in wrangler), so
@@ -167,15 +179,12 @@ function devPlugin(options) {
167
179
  devConfig.main = options.entryPoint;
168
180
  devConfig.dev = { ...devConfig.dev, port: devPort };
169
181
  delete devConfig.assets;
170
- // Serialised dev config is reused verbatim as the `wrangler types`
171
- // config — the user can run `wrangler types --config
172
- // .types-worker-wrangler.jsonc` to get typed bindings like
182
+ // The user can also pass this file to `wrangler types --config
183
+ // .dev-worker-wrangler.jsonc` to get typed bindings like
173
184
  // DurableObjectNamespace<EchoDO> resolved from the source entry.
174
185
  const devConfigJson = JSON.stringify(devConfig, null, '\t');
175
186
  tempConfigPath = resolve('.dev-worker-wrangler.jsonc');
176
- typesConfigPath = resolve('.types-worker-wrangler.jsonc');
177
187
  await writeFile(tempConfigPath, devConfigJson);
178
- await writeFile(typesConfigPath, devConfigJson);
179
188
  // Write a wrangler config for adapter-cloudflare's getPlatformProxy
180
189
  // (used by vite dev for platform.env). It can't run internal DOs or
181
190
  // Workflows itself -- those are served by the sidecar above. Rewrite
@@ -201,6 +210,7 @@ function devPlugin(options) {
201
210
  delete activeScope.migrations;
202
211
  proxyConfigPath = resolve('.platform-proxy-wrangler.jsonc');
203
212
  await writeFile(proxyConfigPath, JSON.stringify(proxyConfig, null, '\t'));
213
+ const structuredLogsHandler = options.structuredLogsHandler ?? makeDefaultWorkerLogHandler(sidecarName);
204
214
  worker = await unstable_startWorker({
205
215
  config: tempConfigPath,
206
216
  // `testScheduled` mounts a `/__scheduled` endpoint on the sidecar
@@ -208,7 +218,19 @@ function devPlugin(options) {
208
218
  // never auto-fires crons. Curl
209
219
  // `http://localhost:<devPort>/__scheduled?cron=*+*+*+*+*` to
210
220
  // trigger your `scheduled` handler.
211
- dev: { registry: getWranglerRegistryPath(), testScheduled: true },
221
+ //
222
+ // `structuredLogsHandler` (added in wrangler 4.99) routes each
223
+ // worker log line through `onWorkerLog`. Without it, wrangler
224
+ // pipes worker logs through its own Logger which can get
225
+ // swallowed when `unstable_startWorker` is driven
226
+ // programmatically — `console.log` inside the worker
227
+ // disappears. Cast is needed because peer wrangler types may
228
+ // be older.
229
+ dev: {
230
+ registry: getWranglerRegistryPath(),
231
+ testScheduled: true,
232
+ structuredLogsHandler
233
+ },
212
234
  build: {
213
235
  nodejsCompatMode: (parsedConfig) => getNodejsCompatMode(parsedConfig.compatibility_date, parsedConfig.compatibility_flags)
214
236
  }
@@ -224,13 +246,22 @@ function devPlugin(options) {
224
246
  if (proxyConfigPath) {
225
247
  await unlink(proxyConfigPath).catch(() => { });
226
248
  }
227
- if (typesConfigPath) {
228
- await unlink(typesConfigPath).catch(() => { });
229
- }
230
249
  });
231
250
  }
232
251
  };
233
252
  }
253
+ /**
254
+ * Default handler for sidecar worker logs. Writes each line to stdout (or
255
+ * stderr for `error` level) tagged with the sidecar name so it's obvious
256
+ * which process produced it.
257
+ */
258
+ function makeDefaultWorkerLogHandler(sidecarName) {
259
+ const prefix = `[${sidecarName}]`;
260
+ return ({ level, message }) => {
261
+ const stream = level === 'error' ? process.stderr : process.stdout;
262
+ stream.write(`${prefix} ${message}\n`);
263
+ };
264
+ }
234
265
  async function exists(path) {
235
266
  try {
236
267
  await access(path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oselvar/sveltekit-add-worker-exports",
3
- "version": "1.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Vite plugin that adds Durable Object and Workflow exports to SvelteKit Cloudflare workers",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -37,22 +37,24 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@release-it/conventional-changelog": "^11.0.1",
40
- "@sveltejs/kit": "^2.62.0",
41
- "@types/node": "^25.9.1",
40
+ "@sveltejs/kit": "^2.64.0",
41
+ "@types/node": "^25.9.2",
42
42
  "esbuild": "^0.28.0",
43
- "miniflare": "^4.20260601.0",
43
+ "miniflare": "^4.20260609.0",
44
44
  "release-it": "^20.2.0",
45
45
  "typescript": "^6.0.3",
46
46
  "vite": "^8.0.16",
47
- "wrangler": "^4.98.0"
47
+ "vitest": "^4.1.8",
48
+ "wrangler": "^4.99.0"
48
49
  },
49
50
  "scripts": {
50
51
  "build": "tsc",
51
- "test": "tsc --noEmit",
52
+ "test": "vitest run && tsc --noEmit",
52
53
  "prepublishOnly": "tsc",
53
54
  "release": "release-it"
54
55
  },
55
56
  "dependencies": {
56
- "jsonc-parser": "^3.3.1"
57
+ "jsonc-parser": "^3.3.1",
58
+ "smol-toml": "^1.6.1"
57
59
  }
58
60
  }
package/dist/hooks.d.ts DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * SvelteKit `Handle` re-exported for users.
3
- *
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.
8
- */
9
- import type { Handle } from '@sveltejs/kit';
10
- export declare const handle: Handle;
package/dist/hooks.js DELETED
@@ -1,9 +0,0 @@
1
- /**
2
- * SvelteKit `Handle` re-exported for users.
3
- *
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.
8
- */
9
- export const handle = ({ event, resolve }) => resolve(event);