@openworkers/adapter-sveltekit 0.2.2 → 0.3.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 +62 -4
- package/files/function-worker.js +46 -0
- package/files/shims/async_hooks.js +41 -0
- package/index.d.ts +7 -0
- package/index.js +126 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,19 +17,77 @@ import adapter from '@openworkers/adapter-sveltekit';
|
|
|
17
17
|
export default {
|
|
18
18
|
kit: {
|
|
19
19
|
adapter: adapter({
|
|
20
|
-
out: 'dist'
|
|
20
|
+
out: 'dist', // Output directory (default: 'dist')
|
|
21
|
+
functions: false // Generate mini-workers for API routes (default: false)
|
|
21
22
|
})
|
|
22
23
|
}
|
|
23
24
|
};
|
|
24
25
|
```
|
|
25
26
|
|
|
27
|
+
## Options
|
|
28
|
+
|
|
29
|
+
| Option | Type | Default | Description |
|
|
30
|
+
|--------|------|---------|-------------|
|
|
31
|
+
| `out` | `string` | `'dist'` | Output directory for the build |
|
|
32
|
+
| `functions` | `boolean` | `false` | Generate separate mini-workers for each API route |
|
|
33
|
+
|
|
26
34
|
## Output
|
|
27
35
|
|
|
28
36
|
```
|
|
29
37
|
dist/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
├── worker.js # Main SSR worker
|
|
39
|
+
├── routes.js # Route manifest for edge routing
|
|
40
|
+
├── assets/ # Static assets and prerendered pages
|
|
41
|
+
└── functions/ # Mini-workers for API routes (if functions: true)
|
|
42
|
+
├── api-hello.js
|
|
43
|
+
└── api-users.js
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Functions Mode
|
|
47
|
+
|
|
48
|
+
When `functions: true`, the adapter generates a separate mini-worker for each `+server.ts` endpoint:
|
|
49
|
+
|
|
50
|
+
- `/api/hello/+server.ts` → `functions/api-hello.js`
|
|
51
|
+
- `/api/users/+server.ts` → `functions/api-users.js`
|
|
52
|
+
|
|
53
|
+
The route mappings are included in `routes.js`:
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
{
|
|
57
|
+
"functions": [
|
|
58
|
+
{ "pattern": "/api/hello", "worker": "functions/api-hello.js" },
|
|
59
|
+
{ "pattern": "/api/users", "worker": "functions/api-users.js" }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This prepares for native project routing in the OpenWorkers runner, where each function can be deployed as a separate worker for better isolation and scaling.
|
|
65
|
+
|
|
66
|
+
## TypeScript
|
|
67
|
+
|
|
68
|
+
For proper types on `platform.env`, add `@openworkers/workers-types`:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
bun add -d @openworkers/workers-types
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Then in `src/app.d.ts`:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
/// <reference types="@openworkers/workers-types" />
|
|
78
|
+
|
|
79
|
+
declare global {
|
|
80
|
+
namespace App {
|
|
81
|
+
interface Platform {
|
|
82
|
+
env: {
|
|
83
|
+
KV: BindingKV;
|
|
84
|
+
ASSETS: BindingAssets;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export {};
|
|
33
91
|
```
|
|
34
92
|
|
|
35
93
|
## License
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mini-worker template for OpenWorkers Functions.
|
|
3
|
+
* Wraps a SvelteKit API endpoint as a standalone worker.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as handlers from 'ENDPOINT';
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
async fetch(req, env, ctx) {
|
|
10
|
+
globalThis.env = env;
|
|
11
|
+
|
|
12
|
+
const method = req.method;
|
|
13
|
+
const handler = handlers[method];
|
|
14
|
+
|
|
15
|
+
if (!handler) {
|
|
16
|
+
return new Response('Method Not Allowed', {
|
|
17
|
+
status: 405,
|
|
18
|
+
headers: { Allow: Object.keys(handlers).join(', ') }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const url = new URL(req.url);
|
|
23
|
+
|
|
24
|
+
// Build a minimal RequestEvent-like object
|
|
25
|
+
const event = {
|
|
26
|
+
request: req,
|
|
27
|
+
url,
|
|
28
|
+
params: ctx.params ?? {},
|
|
29
|
+
platform: { env, ctx },
|
|
30
|
+
getClientAddress() {
|
|
31
|
+
return req.headers.get('x-real-ip') ?? req.headers.get('x-forwarded-for') ?? '';
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
return await handler(event);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`[Function] Error in ${method} handler:`, error);
|
|
39
|
+
|
|
40
|
+
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
|
|
41
|
+
status: 500,
|
|
42
|
+
headers: { 'Content-Type': 'application/json' }
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal AsyncLocalStorage shim for OpenWorkers.
|
|
3
|
+
*
|
|
4
|
+
* OpenWorkers creates a fresh V8 context per request.
|
|
5
|
+
* Module-level state is already isolated, so no real async tracking is needed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class AsyncLocalStorage {
|
|
9
|
+
#store;
|
|
10
|
+
|
|
11
|
+
run(store, fn, ...args) {
|
|
12
|
+
this.#store = store;
|
|
13
|
+
return fn(...args);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getStore() {
|
|
17
|
+
return this.#store;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Stubs for API completeness
|
|
21
|
+
enterWith(store) {
|
|
22
|
+
this.#store = store;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
exit(fn, ...args) {
|
|
26
|
+
this.#store = undefined;
|
|
27
|
+
return fn(...args);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
disable() {
|
|
31
|
+
this.#store = undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static bind(fn) {
|
|
35
|
+
return fn;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static snapshot() {
|
|
39
|
+
return (fn, ...args) => fn(...args);
|
|
40
|
+
}
|
|
41
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -6,6 +6,13 @@ export interface AdapterOptions {
|
|
|
6
6
|
* @default 'dist'
|
|
7
7
|
*/
|
|
8
8
|
out?: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate separate mini-workers for each API route.
|
|
12
|
+
* Outputs to `dist/functions/` with routing info in `routes.js`.
|
|
13
|
+
* @default false
|
|
14
|
+
*/
|
|
15
|
+
functions?: boolean;
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
export default function plugin(options?: AdapterOptions): Adapter;
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { build } from 'esbuild';
|
|
@@ -13,6 +13,7 @@ export default function (options = {}) {
|
|
|
13
13
|
async adapt(builder) {
|
|
14
14
|
const dest = options.out ?? 'dist';
|
|
15
15
|
const assetsDir = `${dest}/assets`;
|
|
16
|
+
const functionsEnabled = options.functions ?? false;
|
|
16
17
|
|
|
17
18
|
const files = fileURLToPath(new URL('./files', import.meta.url).href);
|
|
18
19
|
const tmp = builder.getBuildDirectory('openworkers-tmp');
|
|
@@ -34,6 +35,7 @@ export default function (options = {}) {
|
|
|
34
35
|
|
|
35
36
|
// Generate 404.html fallback
|
|
36
37
|
const fallback = path.join(assetsDir, '404.html');
|
|
38
|
+
|
|
37
39
|
if (!existsSync(fallback)) {
|
|
38
40
|
writeFileSync(fallback, 'Not Found');
|
|
39
41
|
}
|
|
@@ -58,8 +60,9 @@ export default function (options = {}) {
|
|
|
58
60
|
`export { Server, manifest, prerendered, base_path };\n`
|
|
59
61
|
);
|
|
60
62
|
|
|
61
|
-
// Bundle worker with esbuild
|
|
63
|
+
// Bundle main worker with esbuild
|
|
62
64
|
const workerDest = `${dest}/worker.js`;
|
|
65
|
+
const shimAsyncHooks = posixify(path.resolve(files, 'shims/async_hooks.js'));
|
|
63
66
|
|
|
64
67
|
await build({
|
|
65
68
|
entryPoints: [`${files}/worker.js`],
|
|
@@ -69,7 +72,8 @@ export default function (options = {}) {
|
|
|
69
72
|
outfile: workerDest,
|
|
70
73
|
alias: {
|
|
71
74
|
SERVER: entryPoint,
|
|
72
|
-
MANIFEST: entryPoint
|
|
75
|
+
MANIFEST: entryPoint,
|
|
76
|
+
'node:async_hooks': shimAsyncHooks
|
|
73
77
|
},
|
|
74
78
|
external: ['node:*'],
|
|
75
79
|
minify: false,
|
|
@@ -78,6 +82,91 @@ export default function (options = {}) {
|
|
|
78
82
|
}
|
|
79
83
|
});
|
|
80
84
|
|
|
85
|
+
// Generate mini-workers for API routes if enabled
|
|
86
|
+
/** @type {Array<{pattern: string, worker: string}>} */
|
|
87
|
+
const functions = [];
|
|
88
|
+
|
|
89
|
+
if (functionsEnabled) {
|
|
90
|
+
builder.mkdirp(`${dest}/functions`);
|
|
91
|
+
|
|
92
|
+
const endpointsDir = path.join(builder.getServerDirectory(), 'entries/endpoints');
|
|
93
|
+
|
|
94
|
+
if (existsSync(endpointsDir)) {
|
|
95
|
+
const endpoints = findEndpoints(endpointsDir);
|
|
96
|
+
|
|
97
|
+
for (const endpoint of endpoints) {
|
|
98
|
+
const routePattern = endpoint.route;
|
|
99
|
+
const workerName = routePattern.replace(/\//g, '-').replace(/^-/, '') || 'index';
|
|
100
|
+
const workerFile = `functions/${workerName}.js`;
|
|
101
|
+
|
|
102
|
+
// Create entry point for this function
|
|
103
|
+
const functionEntry = `${tmp}/function-${workerName}.js`;
|
|
104
|
+
const endpointPath = posixify(endpoint.file);
|
|
105
|
+
|
|
106
|
+
writeFileSync(
|
|
107
|
+
functionEntry,
|
|
108
|
+
`import * as handlers from '${endpointPath}';\n` +
|
|
109
|
+
`export default {\n` +
|
|
110
|
+
` async fetch(req, env, ctx) {\n` +
|
|
111
|
+
` globalThis.env = env;\n` +
|
|
112
|
+
` const method = req.method;\n` +
|
|
113
|
+
` const handler = handlers[method];\n` +
|
|
114
|
+
` if (!handler) {\n` +
|
|
115
|
+
` return new Response('Method Not Allowed', {\n` +
|
|
116
|
+
` status: 405,\n` +
|
|
117
|
+
` headers: { Allow: Object.keys(handlers).join(', ') }\n` +
|
|
118
|
+
` });\n` +
|
|
119
|
+
` }\n` +
|
|
120
|
+
` const url = new URL(req.url);\n` +
|
|
121
|
+
` const event = {\n` +
|
|
122
|
+
` request: req,\n` +
|
|
123
|
+
` url,\n` +
|
|
124
|
+
` params: ctx.params ?? {},\n` +
|
|
125
|
+
` platform: { env, ctx },\n` +
|
|
126
|
+
` getClientAddress() {\n` +
|
|
127
|
+
` return req.headers.get('x-real-ip') ?? req.headers.get('x-forwarded-for') ?? '';\n` +
|
|
128
|
+
` }\n` +
|
|
129
|
+
` };\n` +
|
|
130
|
+
` try {\n` +
|
|
131
|
+
` return await handler(event);\n` +
|
|
132
|
+
` } catch (error) {\n` +
|
|
133
|
+
` console.error('[Function] Error:', error);\n` +
|
|
134
|
+
` return new Response(JSON.stringify({ error: 'Internal Server Error' }), {\n` +
|
|
135
|
+
` status: 500,\n` +
|
|
136
|
+
` headers: { 'Content-Type': 'application/json' }\n` +
|
|
137
|
+
` });\n` +
|
|
138
|
+
` }\n` +
|
|
139
|
+
` }\n` +
|
|
140
|
+
`};\n`
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Bundle the function
|
|
144
|
+
await build({
|
|
145
|
+
entryPoints: [functionEntry],
|
|
146
|
+
bundle: true,
|
|
147
|
+
format: 'esm',
|
|
148
|
+
platform: 'browser',
|
|
149
|
+
outfile: `${dest}/${workerFile}`,
|
|
150
|
+
alias: {
|
|
151
|
+
'node:async_hooks': shimAsyncHooks
|
|
152
|
+
},
|
|
153
|
+
external: ['node:*'],
|
|
154
|
+
minify: false,
|
|
155
|
+
banner: {
|
|
156
|
+
js: `// Generated by ${name} - Function: ${routePattern}\n`
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
functions.push({
|
|
161
|
+
pattern: routePattern,
|
|
162
|
+
worker: workerFile
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
builder.log.minor(` Generated function: ${routePattern} → ${workerFile}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
81
170
|
// Generate routes.js for edge routing
|
|
82
171
|
const routes = {
|
|
83
172
|
// Immutable assets (hashed filenames, cache forever)
|
|
@@ -86,6 +175,8 @@ export default function (options = {}) {
|
|
|
86
175
|
static: staticFiles.map((f) => `/${f}`),
|
|
87
176
|
// Prerendered pages (serve from assets, no worker needed)
|
|
88
177
|
prerendered: builder.prerendered.paths,
|
|
178
|
+
// Functions (API routes as separate workers)
|
|
179
|
+
functions: functions,
|
|
89
180
|
// Everything else -> SSR
|
|
90
181
|
ssr: ['/*']
|
|
91
182
|
};
|
|
@@ -99,11 +190,43 @@ export default function (options = {}) {
|
|
|
99
190
|
|
|
100
191
|
builder.log.minor(`Wrote ${workerDest}`);
|
|
101
192
|
builder.log.minor(`Wrote ${dest}/routes.js`);
|
|
193
|
+
|
|
194
|
+
if (functions.length > 0) {
|
|
195
|
+
builder.log.minor(`Wrote ${functions.length} function workers to ${dest}/functions/`);
|
|
196
|
+
}
|
|
197
|
+
|
|
102
198
|
builder.log.minor(`Wrote ${assetsDir} (${builder.prerendered.paths.length} prerendered pages)`);
|
|
103
199
|
}
|
|
104
200
|
};
|
|
105
201
|
}
|
|
106
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Find all endpoint files in the server output
|
|
205
|
+
* @param {string} dir
|
|
206
|
+
* @param {string} [base='']
|
|
207
|
+
* @returns {Array<{route: string, file: string}>}
|
|
208
|
+
*/
|
|
209
|
+
function findEndpoints(dir, base = '') {
|
|
210
|
+
const results = [];
|
|
211
|
+
const entries = readdirSync(dir);
|
|
212
|
+
|
|
213
|
+
for (const entry of entries) {
|
|
214
|
+
const fullPath = path.join(dir, entry);
|
|
215
|
+
const stat = statSync(fullPath);
|
|
216
|
+
|
|
217
|
+
if (stat.isDirectory()) {
|
|
218
|
+
results.push(...findEndpoints(fullPath, `${base}/${entry}`));
|
|
219
|
+
} else if (entry === '_server.ts.js' || entry === '_server.js.js') {
|
|
220
|
+
results.push({
|
|
221
|
+
route: base || '/',
|
|
222
|
+
file: fullPath
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return results;
|
|
228
|
+
}
|
|
229
|
+
|
|
107
230
|
/** @param {string} str */
|
|
108
231
|
function posixify(str) {
|
|
109
232
|
return str.replace(/\\/g, '/');
|