@oselvar/sveltekit-add-worker-exports 0.1.2 → 0.2.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 +108 -9
- package/dist/index.js +19 -0
- package/package.json +50 -51
package/README.md
CHANGED
|
@@ -16,26 +16,62 @@ pnpm add -D @oselvar/sveltekit-add-worker-exports
|
|
|
16
16
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
|
-
Create a
|
|
19
|
+
Create a worker entry point that exports your Durable Object classes and a default fetch handler. The fetch handler is only used by the wrangler dev server — in production, SvelteKit's route handlers handle all requests.
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
22
|
// src/lib/server/index.ts
|
|
23
23
|
export { MyDurableObject } from './MyDurableObject';
|
|
24
|
+
export { MyWorkflow } from './MyWorkflow';
|
|
25
|
+
export { default } from './devHandler';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Workflow classes (extending `WorkflowEntrypoint`) are exported the same way as Durable Objects — the plugin merges them into `_worker.js` as named exports. Declare them in `wrangler.jsonc` under `workflows`, and they become available as bindings (e.g. `env.MY_WORKFLOW.create({ params })`) in both dev and production.
|
|
29
|
+
|
|
30
|
+
Both the dev handler and the production SvelteKit route need to do the same thing: validate the upgrade header and forward the request to a Durable Object. Extract that into a small helper so the two callers stay in sync:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// src/lib/server/forwardWebSocket.ts
|
|
34
|
+
export async function forwardWebSocket<T extends Rpc.DurableObjectBranded | undefined>(
|
|
35
|
+
request: Request,
|
|
36
|
+
namespace: DurableObjectNamespace<T>,
|
|
37
|
+
name: string
|
|
38
|
+
): Promise<Response> {
|
|
39
|
+
if (request.headers.get('upgrade') !== 'websocket') {
|
|
40
|
+
return new Response('Expected WebSocket', { status: 426 });
|
|
41
|
+
}
|
|
42
|
+
const id = namespace.idFromName(name);
|
|
43
|
+
return namespace.get(id).fetch(request);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The dev handler parses the URL and delegates. It's only used by the wrangler dev sidecar:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// src/lib/server/devHandler.ts
|
|
51
|
+
import { forwardWebSocket } from './forwardWebSocket';
|
|
24
52
|
|
|
25
53
|
export default {
|
|
26
54
|
async fetch(request: Request, env: Env): Promise<Response> {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (match && request.headers.get('Upgrade') === 'websocket') {
|
|
31
|
-
const id = env.MY_DO.idFromName(match[1]);
|
|
32
|
-
return env.MY_DO.get(id).fetch(request);
|
|
33
|
-
}
|
|
34
|
-
return new Response('Not found', { status: 404 });
|
|
55
|
+
const match = new URL(request.url).pathname.match(/^\/ws\/(.+)$/);
|
|
56
|
+
if (!match) return new Response('Not found', { status: 404 });
|
|
57
|
+
return forwardWebSocket(request, env.MY_DO, match[1]);
|
|
35
58
|
}
|
|
36
59
|
};
|
|
37
60
|
```
|
|
38
61
|
|
|
62
|
+
In production, the dev handler is *not* used — SvelteKit serves the same path through a `+server.ts` route, which delegates to the same helper:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// src/routes/ws/[id]/+server.ts
|
|
66
|
+
import { forwardWebSocket } from '$lib/server/forwardWebSocket';
|
|
67
|
+
import type { RequestHandler } from './$types';
|
|
68
|
+
|
|
69
|
+
export const GET: RequestHandler = ({ params, request, platform }) =>
|
|
70
|
+
forwardWebSocket(request, platform!.env.MY_DO, params.id);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Now any change to the upgrade-and-forward logic (auth, rate-limiting, response shape) lives in one place and applies to both dev and production.
|
|
74
|
+
|
|
39
75
|
Add the plugin to your `vite.config.ts` **after** `sveltekit()`:
|
|
40
76
|
|
|
41
77
|
```typescript
|
|
@@ -51,6 +87,23 @@ export default defineConfig({
|
|
|
51
87
|
});
|
|
52
88
|
```
|
|
53
89
|
|
|
90
|
+
Point `adapter-cloudflare`'s platform proxy at the generated `.platform-proxy-wrangler.jsonc`. This is a copy of your wrangler config with internal Durable Object bindings, Workflows, and migrations stripped — those are served by the dev sidecar on a separate port, and `getPlatformProxy` would otherwise log warnings about classes it can't load:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
// svelte.config.js
|
|
94
|
+
import adapter from '@sveltejs/adapter-cloudflare';
|
|
95
|
+
|
|
96
|
+
export default {
|
|
97
|
+
kit: {
|
|
98
|
+
adapter: adapter({
|
|
99
|
+
platformProxy: {
|
|
100
|
+
configPath: '.platform-proxy-wrangler.jsonc'
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
54
107
|
### Dev mode: connecting to Durable Objects
|
|
55
108
|
|
|
56
109
|
In dev mode, the plugin starts a wrangler dev server on a separate port and injects `__DEV_WORKER_PORT__` as a compile-time constant. Use it to connect your client:
|
|
@@ -78,6 +131,29 @@ declare global {
|
|
|
78
131
|
|
|
79
132
|
The plugin auto-discovers your `wrangler.jsonc` (or `wrangler.toml`) and reads DO bindings, migrations, and compatibility settings from it. It overrides only the `main` entry point to use your source file instead of the SvelteKit build output.
|
|
80
133
|
|
|
134
|
+
### Testing the production build locally
|
|
135
|
+
|
|
136
|
+
`vite dev` exercises the dev handler via the wrangler-dev sidecar; it does *not* exercise your `+server.ts` route or the merged `_worker.js`. To verify the production wiring (named DO exports + SvelteKit routes in the same worker), run wrangler against the build output:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pnpm build # produces .svelte-kit/cloudflare/_worker.js with merged exports
|
|
140
|
+
pnpm wrangler dev # uses wrangler.jsonc → main: .svelte-kit/cloudflare/_worker.js
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This serves the exact bundle that gets deployed, with local Durable Object storage. Connect a WebSocket client to `ws://localhost:8787/ws/<id>` and confirm you get a `101 Switching Protocols` response — that proves the request flowed through the SvelteKit `+server.ts` and into your DO.
|
|
144
|
+
|
|
145
|
+
A handy shortcut is to add a `preview` script to `package.json`:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"scripts": {
|
|
150
|
+
"preview": "wrangler dev"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
> Note: `vite preview` is *not* suitable here — it only serves static assets and cannot run Durable Objects. Always use `wrangler dev` to preview the production worker.
|
|
156
|
+
|
|
81
157
|
## Options
|
|
82
158
|
|
|
83
159
|
| Option | Type | Required | Default | Description |
|
|
@@ -111,6 +187,29 @@ The plugin reads your wrangler config, creates a temporary config with `main` po
|
|
|
111
187
|
- Real workerd runtime (not emulated)
|
|
112
188
|
- Hot-reload when you change DO code
|
|
113
189
|
- Same WebSocket protocol as production
|
|
190
|
+
- Fully typed Durable Object bindings (see below)
|
|
191
|
+
|
|
192
|
+
### Generating typed bindings
|
|
193
|
+
|
|
194
|
+
The dev plugin creates a temporary `.dev-worker-wrangler.jsonc` with `main` pointing to your source entry point. You can use this to generate fully generic Cloudflare types:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
wrangler types --config .dev-worker-wrangler.jsonc
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
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).
|
|
201
|
+
|
|
202
|
+
Add this to your `package.json` scripts for convenience:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"scripts": {
|
|
207
|
+
"types": "wrangler types --config .dev-worker-wrangler.jsonc"
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Note: the `.dev-worker-wrangler.jsonc` file is generated when the dev server starts. Run `pnpm dev` at least once before running `wrangler types`.
|
|
114
213
|
|
|
115
214
|
## Why this exists
|
|
116
215
|
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ function devPlugin(options) {
|
|
|
76
76
|
const devPort = options.devPort ?? DEFAULT_DEV_PORT;
|
|
77
77
|
let worker = null;
|
|
78
78
|
let tempConfigPath = null;
|
|
79
|
+
let proxyConfigPath = null;
|
|
79
80
|
return {
|
|
80
81
|
name: 'add-worker-exports-dev',
|
|
81
82
|
apply: 'serve',
|
|
@@ -99,6 +100,21 @@ function devPlugin(options) {
|
|
|
99
100
|
delete devConfig.assets;
|
|
100
101
|
tempConfigPath = resolve('.dev-worker-wrangler.jsonc');
|
|
101
102
|
await writeFile(tempConfigPath, JSON.stringify(devConfig, null, '\t'));
|
|
103
|
+
// Also write a wrangler config for adapter-cloudflare's getPlatformProxy
|
|
104
|
+
// (used by vite dev for platform.env). It can't run internal DOs or
|
|
105
|
+
// Workflows -- those are served by the sidecar above. Strip them so it
|
|
106
|
+
// doesn't emit warnings about classes it can't load.
|
|
107
|
+
const proxyConfig = parseJsonc(rawJson);
|
|
108
|
+
if (proxyConfig.durable_objects?.bindings) {
|
|
109
|
+
proxyConfig.durable_objects.bindings = proxyConfig.durable_objects.bindings.filter((b) => b.script_name);
|
|
110
|
+
if (proxyConfig.durable_objects.bindings.length === 0) {
|
|
111
|
+
delete proxyConfig.durable_objects;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
delete proxyConfig.workflows;
|
|
115
|
+
delete proxyConfig.migrations;
|
|
116
|
+
proxyConfigPath = resolve('.platform-proxy-wrangler.jsonc');
|
|
117
|
+
await writeFile(proxyConfigPath, JSON.stringify(proxyConfig, null, '\t'));
|
|
102
118
|
worker = await unstable_startWorker({ config: tempConfigPath });
|
|
103
119
|
server.httpServer?.on('close', async () => {
|
|
104
120
|
if (worker) {
|
|
@@ -108,6 +124,9 @@ function devPlugin(options) {
|
|
|
108
124
|
if (tempConfigPath) {
|
|
109
125
|
await unlink(tempConfigPath).catch(() => { });
|
|
110
126
|
}
|
|
127
|
+
if (proxyConfigPath) {
|
|
128
|
+
await unlink(proxyConfigPath).catch(() => { });
|
|
129
|
+
}
|
|
111
130
|
});
|
|
112
131
|
}
|
|
113
132
|
};
|
package/package.json
CHANGED
|
@@ -1,52 +1,51 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
2
|
+
"name": "@oselvar/sveltekit-add-worker-exports",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Vite plugin that adds Durable Object and Workflow exports to SvelteKit Cloudflare workers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/oselvar/sveltekit-add-worker-exports.git"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"sveltekit",
|
|
25
|
+
"cloudflare",
|
|
26
|
+
"workers",
|
|
27
|
+
"durable-objects",
|
|
28
|
+
"workflows",
|
|
29
|
+
"vite-plugin"
|
|
30
|
+
],
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"esbuild": ">=0.17",
|
|
33
|
+
"vite": ">=5",
|
|
34
|
+
"wrangler": ">=4"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"esbuild": "^0.25.0",
|
|
39
|
+
"np": "^11.2.0",
|
|
40
|
+
"typescript": "^5.0.0",
|
|
41
|
+
"vite": "^6.0.0",
|
|
42
|
+
"wrangler": "^4.84.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"jsonc-parser": "^3.3.1"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc",
|
|
49
|
+
"test": "tsc --noEmit"
|
|
50
|
+
}
|
|
51
|
+
}
|