@openworkers/adapter-sveltekit 0.2.3 → 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/index.d.ts +7 -0
- package/index.js +123 -2
- 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
|
+
};
|
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,7 +60,7 @@ 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`;
|
|
63
65
|
const shimAsyncHooks = posixify(path.resolve(files, 'shims/async_hooks.js'));
|
|
64
66
|
|
|
@@ -80,6 +82,91 @@ export default function (options = {}) {
|
|
|
80
82
|
}
|
|
81
83
|
});
|
|
82
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
|
+
|
|
83
170
|
// Generate routes.js for edge routing
|
|
84
171
|
const routes = {
|
|
85
172
|
// Immutable assets (hashed filenames, cache forever)
|
|
@@ -88,6 +175,8 @@ export default function (options = {}) {
|
|
|
88
175
|
static: staticFiles.map((f) => `/${f}`),
|
|
89
176
|
// Prerendered pages (serve from assets, no worker needed)
|
|
90
177
|
prerendered: builder.prerendered.paths,
|
|
178
|
+
// Functions (API routes as separate workers)
|
|
179
|
+
functions: functions,
|
|
91
180
|
// Everything else -> SSR
|
|
92
181
|
ssr: ['/*']
|
|
93
182
|
};
|
|
@@ -101,11 +190,43 @@ export default function (options = {}) {
|
|
|
101
190
|
|
|
102
191
|
builder.log.minor(`Wrote ${workerDest}`);
|
|
103
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
|
+
|
|
104
198
|
builder.log.minor(`Wrote ${assetsDir} (${builder.prerendered.paths.length} prerendered pages)`);
|
|
105
199
|
}
|
|
106
200
|
};
|
|
107
201
|
}
|
|
108
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
|
+
|
|
109
230
|
/** @param {string} str */
|
|
110
231
|
function posixify(str) {
|
|
111
232
|
return str.replace(/\\/g, '/');
|