@kuratchi/js 0.0.2 → 0.0.3
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/dist/compiler/index.js +316 -111
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -1
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/runtime.d.ts +5 -0
- package/dist/runtime/runtime.js +6 -0
- package/dist/runtime/types.d.ts +32 -0
- package/package.json +1 -1
package/dist/compiler/index.js
CHANGED
|
@@ -531,6 +531,8 @@ export function compile(options) {
|
|
|
531
531
|
const authConfig = readAuthConfig(projectDir);
|
|
532
532
|
// Read Durable Object config and discover handler files
|
|
533
533
|
const doConfig = readDoConfig(projectDir);
|
|
534
|
+
const containerConfig = readWorkerClassConfig(projectDir, 'containers');
|
|
535
|
+
const workflowConfig = readWorkerClassConfig(projectDir, 'workflows');
|
|
534
536
|
const doHandlers = doConfig.length > 0
|
|
535
537
|
? discoverDoHandlers(srcDir, doConfig, ormDatabases)
|
|
536
538
|
: [];
|
|
@@ -867,6 +869,8 @@ export function compile(options) {
|
|
|
867
869
|
// Collect only the components that were actually imported by routes
|
|
868
870
|
const compiledComponents = Array.from(compiledComponentCache.values());
|
|
869
871
|
// Generate the routes module
|
|
872
|
+
const runtimeImportPath = resolveRuntimeImportPath(projectDir);
|
|
873
|
+
const hasRuntime = !!runtimeImportPath;
|
|
870
874
|
const output = generateRoutesModule({
|
|
871
875
|
projectDir,
|
|
872
876
|
serverImports: allImports,
|
|
@@ -882,6 +886,8 @@ export function compile(options) {
|
|
|
882
886
|
isDev: options.isDev ?? false,
|
|
883
887
|
isLayoutAsync,
|
|
884
888
|
compiledLayoutActions,
|
|
889
|
+
hasRuntime,
|
|
890
|
+
runtimeImportPath: runtimeImportPath ?? undefined,
|
|
885
891
|
});
|
|
886
892
|
// Write to .kuratchi/routes.js
|
|
887
893
|
const outFile = options.outFile ?? path.join(projectDir, '.kuratchi', 'routes.js');
|
|
@@ -895,10 +901,19 @@ export function compile(options) {
|
|
|
895
901
|
// worker.js explicitly re-exports them so wrangler.jsonc can reference a
|
|
896
902
|
// stable filename while routes.js is freely regenerated.
|
|
897
903
|
const workerFile = path.join(outDir, 'worker.js');
|
|
904
|
+
const workerClassExports = [...containerConfig, ...workflowConfig]
|
|
905
|
+
.map((entry) => {
|
|
906
|
+
const importPath = toWorkerImportPath(projectDir, outDir, entry.file);
|
|
907
|
+
if (entry.exportKind === 'default') {
|
|
908
|
+
return `export { default as ${entry.className} } from '${importPath}';`;
|
|
909
|
+
}
|
|
910
|
+
return `export { ${entry.className} } from '${importPath}';`;
|
|
911
|
+
});
|
|
898
912
|
const workerLines = [
|
|
899
913
|
'// Auto-generated by kuratchi \u2014 do not edit.',
|
|
900
914
|
"export { default } from './routes.js';",
|
|
901
915
|
...doConfig.map(c => `export { ${c.className} } from './routes.js';`),
|
|
916
|
+
...workerClassExports,
|
|
902
917
|
'',
|
|
903
918
|
];
|
|
904
919
|
writeIfChanged(workerFile, workerLines.join('\n'));
|
|
@@ -1365,6 +1380,89 @@ function readDoConfig(projectDir) {
|
|
|
1365
1380
|
}
|
|
1366
1381
|
return entries;
|
|
1367
1382
|
}
|
|
1383
|
+
function readWorkerClassConfig(projectDir, key) {
|
|
1384
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
1385
|
+
if (!fs.existsSync(configPath))
|
|
1386
|
+
return [];
|
|
1387
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
1388
|
+
const keyIdx = source.search(new RegExp(`\\b${key}\\s*:\\s*\\{`));
|
|
1389
|
+
if (keyIdx === -1)
|
|
1390
|
+
return [];
|
|
1391
|
+
const braceStart = source.indexOf('{', keyIdx);
|
|
1392
|
+
if (braceStart === -1)
|
|
1393
|
+
return [];
|
|
1394
|
+
const body = extractBalancedBody(source, braceStart, '{', '}');
|
|
1395
|
+
if (body == null)
|
|
1396
|
+
return [];
|
|
1397
|
+
const entries = [];
|
|
1398
|
+
const expectedSuffix = key === 'containers' ? '.container' : '.workflow';
|
|
1399
|
+
const allowedExt = /\.(ts|js|mjs|cjs)$/i;
|
|
1400
|
+
const requiredFilePattern = new RegExp(`\\${expectedSuffix}\\.(ts|js|mjs|cjs)$`, 'i');
|
|
1401
|
+
const resolveClassFromFile = (binding, filePath) => {
|
|
1402
|
+
if (!requiredFilePattern.test(filePath)) {
|
|
1403
|
+
throw new Error(`[kuratchi] ${key}.${binding} must reference a file ending in "${expectedSuffix}.ts|js|mjs|cjs". Received: ${filePath}`);
|
|
1404
|
+
}
|
|
1405
|
+
if (!allowedExt.test(filePath)) {
|
|
1406
|
+
throw new Error(`[kuratchi] ${key}.${binding} file must be a TypeScript or JavaScript module. Received: ${filePath}`);
|
|
1407
|
+
}
|
|
1408
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.join(projectDir, filePath);
|
|
1409
|
+
if (!fs.existsSync(absPath)) {
|
|
1410
|
+
throw new Error(`[kuratchi] ${key}.${binding} file not found: ${filePath}`);
|
|
1411
|
+
}
|
|
1412
|
+
const fileSource = fs.readFileSync(absPath, 'utf-8');
|
|
1413
|
+
const defaultClass = fileSource.match(/export\s+default\s+class\s+(\w+)/);
|
|
1414
|
+
if (defaultClass) {
|
|
1415
|
+
return { className: defaultClass[1], exportKind: 'default' };
|
|
1416
|
+
}
|
|
1417
|
+
const namedClass = fileSource.match(/export\s+class\s+(\w+)/);
|
|
1418
|
+
if (namedClass) {
|
|
1419
|
+
return { className: namedClass[1], exportKind: 'named' };
|
|
1420
|
+
}
|
|
1421
|
+
throw new Error(`[kuratchi] ${key}.${binding} must export a class via "export class X" or "export default class X". File: ${filePath}`);
|
|
1422
|
+
};
|
|
1423
|
+
// Object form:
|
|
1424
|
+
// containers: { WORDPRESS_CONTAINER: { file: 'src/server/containers/wordpress.container.ts', className?: 'WordPressContainer' } }
|
|
1425
|
+
// workflows: { NEW_SITE_WORKFLOW: { file: 'src/server/workflows/new-site.workflow.ts', className?: 'NewSiteWorkflow' } }
|
|
1426
|
+
const objRegex = /(\w+)\s*:\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/g;
|
|
1427
|
+
let m;
|
|
1428
|
+
while ((m = objRegex.exec(body)) !== null) {
|
|
1429
|
+
const binding = m[1];
|
|
1430
|
+
const entryBody = m[2];
|
|
1431
|
+
const fileMatch = entryBody.match(/file\s*:\s*['"]([^'"]+)['"]/);
|
|
1432
|
+
if (!fileMatch)
|
|
1433
|
+
continue;
|
|
1434
|
+
const inferred = resolveClassFromFile(binding, fileMatch[1]);
|
|
1435
|
+
const classMatch = entryBody.match(/className\s*:\s*['"](\w+)['"]/);
|
|
1436
|
+
const className = classMatch?.[1] ?? inferred.className;
|
|
1437
|
+
entries.push({
|
|
1438
|
+
binding,
|
|
1439
|
+
className,
|
|
1440
|
+
file: fileMatch[1],
|
|
1441
|
+
exportKind: inferred.exportKind,
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
// String shorthand:
|
|
1445
|
+
// containers: { WORDPRESS_CONTAINER: 'src/server/containers/wordpress.container.ts' }
|
|
1446
|
+
// workflows: { NEW_SITE_WORKFLOW: 'src/server/workflows/new-site.workflow.ts' }
|
|
1447
|
+
const foundBindings = new Set(entries.map((e) => e.binding));
|
|
1448
|
+
const pairRegex = /(\w+)\s*:\s*['"]([^'"]+)['"]\s*[,}\n]/g;
|
|
1449
|
+
while ((m = pairRegex.exec(body)) !== null) {
|
|
1450
|
+
const binding = m[1];
|
|
1451
|
+
const file = m[2];
|
|
1452
|
+
if (foundBindings.has(binding))
|
|
1453
|
+
continue;
|
|
1454
|
+
if (binding === 'file' || binding === 'className')
|
|
1455
|
+
continue;
|
|
1456
|
+
const inferred = resolveClassFromFile(binding, file);
|
|
1457
|
+
entries.push({
|
|
1458
|
+
binding,
|
|
1459
|
+
className: inferred.className,
|
|
1460
|
+
file,
|
|
1461
|
+
exportKind: inferred.exportKind,
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
return entries;
|
|
1465
|
+
}
|
|
1368
1466
|
function discoverFilesWithSuffix(dir, suffix) {
|
|
1369
1467
|
if (!fs.existsSync(dir))
|
|
1370
1468
|
return [];
|
|
@@ -1666,6 +1764,9 @@ function generateRoutesModule(opts) {
|
|
|
1666
1764
|
.join('\n\n');
|
|
1667
1765
|
// Resolve path to the framework's context module from the output directory
|
|
1668
1766
|
const contextImport = `import { __setRequestContext, __esc, __setLocal, __getLocals, buildDefaultBreadcrumbs as __buildDefaultBreadcrumbs } from '${RUNTIME_CONTEXT_IMPORT}';`;
|
|
1767
|
+
const runtimeImport = opts.hasRuntime && opts.runtimeImportPath
|
|
1768
|
+
? `import __kuratchiRuntime from '${opts.runtimeImportPath}';`
|
|
1769
|
+
: '';
|
|
1669
1770
|
// Auth session init — thin cookie parsing injected into Worker entry
|
|
1670
1771
|
let authInit = '';
|
|
1671
1772
|
if (opts.authConfig && opts.authConfig.sessionEnabled) {
|
|
@@ -1931,7 +2032,7 @@ ${initLines.join('\n')}
|
|
|
1931
2032
|
${opts.isDev ? '\nglobalThis.__kuratchi_DEV__ = true;\n' : ''}
|
|
1932
2033
|
${workerImport}
|
|
1933
2034
|
${contextImport}
|
|
1934
|
-
${migrationImports ? migrationImports + '\n' : ''}${authPluginImports ? authPluginImports + '\n' : ''}${doImports ? doImports + '\n' : ''}${opts.serverImports.join('\n')}
|
|
2035
|
+
${runtimeImport ? runtimeImport + '\n' : ''}${migrationImports ? migrationImports + '\n' : ''}${authPluginImports ? authPluginImports + '\n' : ''}${doImports ? doImports + '\n' : ''}${opts.serverImports.join('\n')}
|
|
1935
2036
|
|
|
1936
2037
|
// ── Assets ──────────────────────────────────────────────────────
|
|
1937
2038
|
|
|
@@ -2075,141 +2176,245 @@ ${opts.isLayoutAsync ? 'async ' : ''}function __render(route, data) {
|
|
|
2075
2176
|
return __attachCookies(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(html), { headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
2076
2177
|
}
|
|
2077
2178
|
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
export default class extends WorkerEntrypoint {
|
|
2081
|
-
async fetch(request) {
|
|
2082
|
-
__setRequestContext(this.ctx, request);
|
|
2083
|
-
${migrationInit ? ' await __runMigrations();\n' : ''}${authInit ? ' __initAuth(request);\n' : ''}${authPluginInit ? ' __initAuthPlugins();\n' : ''}${doResolverInit ? ' __initDoResolvers();\n' : ''}
|
|
2084
|
-
const url = new URL(request.url);
|
|
2085
|
-
${ac?.hasRateLimit ? '\n // Rate limiting — check before route handlers\n { const __rlRes = await __checkRL(); if (__rlRes) return __secHeaders(__rlRes); }\n' : ''}${ac?.hasTurnstile ? ' // Turnstile bot protection\n { const __tsRes = await __checkTS(); if (__tsRes) return __secHeaders(__tsRes); }\n' : ''}${ac?.hasGuards ? ' // Route guards — redirect if not authenticated\n { const __gRes = __checkGuard(); if (__gRes) return __secHeaders(__gRes); }\n' : ''}
|
|
2179
|
+
const __runtimeDef = (typeof __kuratchiRuntime !== 'undefined' && __kuratchiRuntime && typeof __kuratchiRuntime === 'object') ? __kuratchiRuntime : {};
|
|
2180
|
+
const __runtimeEntries = Object.entries(__runtimeDef).filter(([, step]) => step && typeof step === 'object');
|
|
2086
2181
|
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
}
|
|
2182
|
+
async function __runRuntimeRequest(ctx, next) {
|
|
2183
|
+
let idx = -1;
|
|
2184
|
+
async function __dispatch(i) {
|
|
2185
|
+
if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in request phase');
|
|
2186
|
+
idx = i;
|
|
2187
|
+
const entry = __runtimeEntries[i];
|
|
2188
|
+
if (!entry) return next();
|
|
2189
|
+
const [, step] = entry;
|
|
2190
|
+
if (typeof step.request !== 'function') return __dispatch(i + 1);
|
|
2191
|
+
return await step.request(ctx, () => __dispatch(i + 1));
|
|
2192
|
+
}
|
|
2193
|
+
return __dispatch(0);
|
|
2194
|
+
}
|
|
2101
2195
|
|
|
2102
|
-
|
|
2196
|
+
async function __runRuntimeRoute(ctx, next) {
|
|
2197
|
+
let idx = -1;
|
|
2198
|
+
async function __dispatch(i) {
|
|
2199
|
+
if (i <= idx) throw new Error('[kuratchi runtime] next() called multiple times in route phase');
|
|
2200
|
+
idx = i;
|
|
2201
|
+
const entry = __runtimeEntries[i];
|
|
2202
|
+
if (!entry) return next();
|
|
2203
|
+
const [, step] = entry;
|
|
2204
|
+
if (typeof step.route !== 'function') return __dispatch(i + 1);
|
|
2205
|
+
return await step.route(ctx, () => __dispatch(i + 1));
|
|
2206
|
+
}
|
|
2207
|
+
return __dispatch(0);
|
|
2208
|
+
}
|
|
2103
2209
|
|
|
2104
|
-
|
|
2105
|
-
|
|
2210
|
+
async function __runRuntimeResponse(ctx, response) {
|
|
2211
|
+
let out = response;
|
|
2212
|
+
for (const [, step] of __runtimeEntries) {
|
|
2213
|
+
if (typeof step.response !== 'function') continue;
|
|
2214
|
+
out = await step.response(ctx, out);
|
|
2215
|
+
if (!(out instanceof Response)) {
|
|
2216
|
+
throw new Error('[kuratchi runtime] response handlers must return a Response');
|
|
2106
2217
|
}
|
|
2218
|
+
}
|
|
2219
|
+
return out;
|
|
2220
|
+
}
|
|
2107
2221
|
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
const __qArgsRaw = request.headers.get('x-kuratchi-query-args') || '[]';
|
|
2112
|
-
let __qArgs = [];
|
|
2222
|
+
async function __runRuntimeError(ctx, error) {
|
|
2223
|
+
for (const [name, step] of __runtimeEntries) {
|
|
2224
|
+
if (typeof step.error !== 'function') continue;
|
|
2113
2225
|
try {
|
|
2114
|
-
const
|
|
2115
|
-
|
|
2116
|
-
} catch {
|
|
2117
|
-
|
|
2118
|
-
if (!__getLocals().__breadcrumbs) {
|
|
2119
|
-
__setLocal('breadcrumbs', __buildDefaultBreadcrumbs(url.pathname, match.params));
|
|
2226
|
+
const handled = await step.error(ctx, error);
|
|
2227
|
+
if (handled instanceof Response) return handled;
|
|
2228
|
+
} catch (hookErr) {
|
|
2229
|
+
console.error('[kuratchi runtime] error handler failed in step', name, hookErr);
|
|
2120
2230
|
}
|
|
2231
|
+
}
|
|
2232
|
+
return null;
|
|
2233
|
+
}
|
|
2121
2234
|
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2235
|
+
// ── Exported Worker entrypoint ──────────────────────────────────
|
|
2236
|
+
|
|
2237
|
+
export default class extends WorkerEntrypoint {
|
|
2238
|
+
async fetch(request) {
|
|
2239
|
+
__setRequestContext(this.ctx, request);
|
|
2240
|
+
globalThis.__cloudflare_env__ = __env;
|
|
2241
|
+
${migrationInit ? ' await __runMigrations();\n' : ''}${authInit ? ' __initAuth(request);\n' : ''}${authPluginInit ? ' __initAuthPlugins();\n' : ''}${doResolverInit ? ' __initDoResolvers();\n' : ''}
|
|
2242
|
+
const __runtimeCtx = {
|
|
2243
|
+
request,
|
|
2244
|
+
env: __env,
|
|
2245
|
+
ctx: this.ctx,
|
|
2246
|
+
url: new URL(request.url),
|
|
2247
|
+
params: {},
|
|
2248
|
+
locals: __getLocals(),
|
|
2249
|
+
};
|
|
2250
|
+
|
|
2251
|
+
const __coreFetch = async () => {
|
|
2252
|
+
const request = __runtimeCtx.request;
|
|
2253
|
+
const url = __runtimeCtx.url;
|
|
2254
|
+
${ac?.hasRateLimit ? '\n // Rate limiting - check before route handlers\n { const __rlRes = await __checkRL(); if (__rlRes) return __secHeaders(__rlRes); }\n' : ''}${ac?.hasTurnstile ? ' // Turnstile bot protection\n { const __tsRes = await __checkTS(); if (__tsRes) return __secHeaders(__tsRes); }\n' : ''}${ac?.hasGuards ? ' // Route guards - redirect if not authenticated\n { const __gRes = __checkGuard(); if (__gRes) return __secHeaders(__gRes); }\n' : ''}
|
|
2255
|
+
|
|
2256
|
+
// Serve static assets from src/assets/ at /_assets/*
|
|
2257
|
+
if (url.pathname.startsWith('/_assets/')) {
|
|
2258
|
+
const name = url.pathname.slice('/_assets/'.length);
|
|
2259
|
+
const asset = __assets[name];
|
|
2260
|
+
if (asset) {
|
|
2261
|
+
if (request.headers.get('if-none-match') === asset.etag) {
|
|
2262
|
+
return new Response(null, { status: 304 });
|
|
2263
|
+
}
|
|
2264
|
+
return new Response(asset.content, {
|
|
2265
|
+
headers: { 'content-type': asset.mime, 'cache-control': 'public, max-age=31536000, immutable', 'etag': asset.etag }
|
|
2266
|
+
});
|
|
2136
2267
|
}
|
|
2137
|
-
|
|
2138
|
-
return __secHeaders(new Response(JSON.stringify({ ok: true, data: __rpcResult }), {
|
|
2139
|
-
headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
2140
|
-
}));
|
|
2141
|
-
} catch (err) {
|
|
2142
|
-
console.error('[kuratchi] RPC error:', err);
|
|
2143
|
-
const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' ? err.message : 'Internal Server Error';
|
|
2144
|
-
return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
|
|
2145
|
-
status: 500, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
2146
|
-
}));
|
|
2268
|
+
return __secHeaders(new Response('Not Found', { status: 404 }));
|
|
2147
2269
|
}
|
|
2148
|
-
}
|
|
2149
2270
|
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
if (!
|
|
2153
|
-
return __secHeaders(new Response('
|
|
2271
|
+
const match = __match(url.pathname);
|
|
2272
|
+
|
|
2273
|
+
if (!match) {
|
|
2274
|
+
return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(404)), { status: 404, headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
2154
2275
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
const
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2276
|
+
|
|
2277
|
+
__runtimeCtx.params = match.params;
|
|
2278
|
+
const route = routes[match.index];
|
|
2279
|
+
__setLocal('params', match.params);
|
|
2280
|
+
const __qFn = request.headers.get('x-kuratchi-query-fn') || '';
|
|
2281
|
+
const __qArgsRaw = request.headers.get('x-kuratchi-query-args') || '[]';
|
|
2282
|
+
let __qArgs = [];
|
|
2283
|
+
try {
|
|
2284
|
+
const __parsed = JSON.parse(__qArgsRaw);
|
|
2285
|
+
__qArgs = Array.isArray(__parsed) ? __parsed : [];
|
|
2286
|
+
} catch {}
|
|
2287
|
+
__setLocal('__queryOverride', __qFn ? { fn: __qFn, args: __qArgs } : null);
|
|
2288
|
+
if (!__getLocals().__breadcrumbs) {
|
|
2289
|
+
__setLocal('breadcrumbs', __buildDefaultBreadcrumbs(url.pathname, match.params));
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
// RPC call: GET ?_rpc=fnName&_args=[...] -> JSON response
|
|
2293
|
+
const __rpcName = url.searchParams.get('_rpc');
|
|
2294
|
+
if (request.method === 'GET' && __rpcName && route.rpc && Object.hasOwn(route.rpc, __rpcName)) {
|
|
2295
|
+
if (request.headers.get('x-kuratchi-rpc') !== '1') {
|
|
2296
|
+
return __secHeaders(new Response(JSON.stringify({ ok: false, error: 'Forbidden' }), {
|
|
2297
|
+
status: 403, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
2298
|
+
}));
|
|
2299
|
+
}
|
|
2163
2300
|
try {
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
await __actionFn(formData);
|
|
2301
|
+
const __rpcArgsStr = url.searchParams.get('_args');
|
|
2302
|
+
let __rpcArgs = [];
|
|
2303
|
+
if (__rpcArgsStr) {
|
|
2304
|
+
const __parsed = JSON.parse(__rpcArgsStr);
|
|
2305
|
+
__rpcArgs = Array.isArray(__parsed) ? __parsed : [];
|
|
2170
2306
|
}
|
|
2307
|
+
const __rpcResult = await route.rpc[__rpcName](...__rpcArgs);
|
|
2308
|
+
return __secHeaders(new Response(JSON.stringify({ ok: true, data: __rpcResult }), {
|
|
2309
|
+
headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
2310
|
+
}));
|
|
2171
2311
|
} catch (err) {
|
|
2172
|
-
console.error('[kuratchi]
|
|
2312
|
+
console.error('[kuratchi] RPC error:', err);
|
|
2313
|
+
const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' ? err.message : 'Internal Server Error';
|
|
2314
|
+
return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
|
|
2315
|
+
status: 500, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
2316
|
+
}));
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// Form action: POST with hidden _action field in form body
|
|
2321
|
+
if (request.method === 'POST') {
|
|
2322
|
+
if (!__isSameOrigin(request, url)) {
|
|
2323
|
+
return __secHeaders(new Response('Forbidden', { status: 403 }));
|
|
2324
|
+
}
|
|
2325
|
+
const formData = await request.formData();
|
|
2326
|
+
const actionName = formData.get('_action');
|
|
2327
|
+
const __actionFn = (actionName && route.actions && Object.hasOwn(route.actions, actionName) ? route.actions[actionName] : null)
|
|
2328
|
+
|| (actionName && __layoutActions && Object.hasOwn(__layoutActions, actionName) ? __layoutActions[actionName] : null);
|
|
2329
|
+
if (actionName && __actionFn) {
|
|
2330
|
+
// Check if this is a fetch-based action call (onclick) with JSON args
|
|
2331
|
+
const argsStr = formData.get('_args');
|
|
2332
|
+
const isFetchAction = argsStr !== null;
|
|
2333
|
+
try {
|
|
2334
|
+
if (isFetchAction) {
|
|
2335
|
+
const __parsed = JSON.parse(argsStr);
|
|
2336
|
+
const args = Array.isArray(__parsed) ? __parsed : [];
|
|
2337
|
+
await __actionFn(...args);
|
|
2338
|
+
} else {
|
|
2339
|
+
await __actionFn(formData);
|
|
2340
|
+
}
|
|
2341
|
+
} catch (err) {
|
|
2342
|
+
console.error('[kuratchi] Action error:', err);
|
|
2343
|
+
if (isFetchAction) {
|
|
2344
|
+
const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' ? err.message : 'Internal Server Error';
|
|
2345
|
+
return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
|
|
2346
|
+
status: 500, headers: { 'content-type': 'application/json' }
|
|
2347
|
+
}));
|
|
2348
|
+
}
|
|
2349
|
+
const __loaded = route.load ? await route.load(match.params) : {};
|
|
2350
|
+
const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
|
|
2351
|
+
data.params = match.params;
|
|
2352
|
+
data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
|
|
2353
|
+
data.__error = (typeof __kuratchi_DEV__ !== 'undefined' && err && err.message) ? err.message : 'Action failed';
|
|
2354
|
+
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
2355
|
+
}
|
|
2356
|
+
// Fetch-based actions return lightweight JSON (no page re-render)
|
|
2173
2357
|
if (isFetchAction) {
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
status: 500, headers: { 'content-type': 'application/json' }
|
|
2358
|
+
return __attachCookies(new Response(JSON.stringify({ ok: true }), {
|
|
2359
|
+
headers: { 'content-type': 'application/json' }
|
|
2177
2360
|
}));
|
|
2178
2361
|
}
|
|
2179
|
-
|
|
2180
|
-
const
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
2185
|
-
}
|
|
2186
|
-
// Fetch-based actions return lightweight JSON (no page re-render)
|
|
2187
|
-
if (isFetchAction) {
|
|
2188
|
-
return __attachCookies(new Response(JSON.stringify({ ok: true }), {
|
|
2189
|
-
headers: { 'content-type': 'application/json' }
|
|
2190
|
-
}));
|
|
2362
|
+
// POST-Redirect-GET: redirect to custom target or back to same URL
|
|
2363
|
+
const __locals = __getLocals();
|
|
2364
|
+
const redirectTo = __locals.__redirectTo || url.pathname;
|
|
2365
|
+
const redirectStatus = Number(__locals.__redirectStatus) || 303;
|
|
2366
|
+
return __attachCookies(new Response(null, { status: redirectStatus, headers: { 'location': redirectTo } }));
|
|
2191
2367
|
}
|
|
2192
|
-
// POST-Redirect-GET: redirect to custom target or back to same URL
|
|
2193
|
-
const __locals = __getLocals();
|
|
2194
|
-
const redirectTo = __locals.__redirectTo || url.pathname;
|
|
2195
|
-
const redirectStatus = Number(__locals.__redirectStatus) || 303;
|
|
2196
|
-
return __attachCookies(new Response(null, { status: redirectStatus, headers: { 'location': redirectTo } }));
|
|
2197
2368
|
}
|
|
2198
|
-
}
|
|
2199
2369
|
|
|
2200
|
-
|
|
2370
|
+
// GET (or unmatched POST): load + render
|
|
2371
|
+
try {
|
|
2372
|
+
const __loaded = route.load ? await route.load(match.params) : {};
|
|
2373
|
+
const data = (__loaded && typeof __loaded === 'object') ? __loaded : { value: __loaded };
|
|
2374
|
+
data.params = match.params;
|
|
2375
|
+
data.breadcrumbs = __getLocals().__breadcrumbs ?? [];
|
|
2376
|
+
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
2377
|
+
} catch (err) {
|
|
2378
|
+
console.error('[kuratchi] Route load/render error:', err);
|
|
2379
|
+
const __errDetail = typeof __kuratchi_DEV__ !== 'undefined' ? err.message : undefined;
|
|
2380
|
+
return __secHeaders(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(__error(500, __errDetail)), { status: 500, headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
2381
|
+
}
|
|
2382
|
+
};
|
|
2383
|
+
|
|
2201
2384
|
try {
|
|
2202
|
-
const
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
2385
|
+
const __requestResponse = await __runRuntimeRequest(__runtimeCtx, async () => {
|
|
2386
|
+
return __runRuntimeRoute(__runtimeCtx, __coreFetch);
|
|
2387
|
+
});
|
|
2388
|
+
return await __runRuntimeResponse(__runtimeCtx, __requestResponse);
|
|
2207
2389
|
} catch (err) {
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2390
|
+
const __handled = await __runRuntimeError(__runtimeCtx, err);
|
|
2391
|
+
if (__handled) return __secHeaders(__handled);
|
|
2392
|
+
throw err;
|
|
2211
2393
|
}
|
|
2212
2394
|
}
|
|
2213
2395
|
}
|
|
2214
2396
|
`;
|
|
2215
2397
|
}
|
|
2398
|
+
function resolveRuntimeImportPath(projectDir) {
|
|
2399
|
+
const candidates = [
|
|
2400
|
+
{ file: 'src/kuratchi.runtime.ts', importPath: '../src/kuratchi.runtime' },
|
|
2401
|
+
{ file: 'src/kuratchi.runtime.js', importPath: '../src/kuratchi.runtime' },
|
|
2402
|
+
{ file: 'src/kuratchi.runtime.mjs', importPath: '../src/kuratchi.runtime' },
|
|
2403
|
+
{ file: 'kuratchi.runtime.ts', importPath: '../kuratchi.runtime' },
|
|
2404
|
+
{ file: 'kuratchi.runtime.js', importPath: '../kuratchi.runtime' },
|
|
2405
|
+
{ file: 'kuratchi.runtime.mjs', importPath: '../kuratchi.runtime' },
|
|
2406
|
+
];
|
|
2407
|
+
for (const candidate of candidates) {
|
|
2408
|
+
if (fs.existsSync(path.join(projectDir, candidate.file))) {
|
|
2409
|
+
return candidate.importPath;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
return null;
|
|
2413
|
+
}
|
|
2414
|
+
function toWorkerImportPath(projectDir, outDir, filePath) {
|
|
2415
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.join(projectDir, filePath);
|
|
2416
|
+
let rel = path.relative(outDir, absPath).replace(/\\/g, '/');
|
|
2417
|
+
if (!rel.startsWith('.'))
|
|
2418
|
+
rel = `./${rel}`;
|
|
2419
|
+
return rel.replace(/\.(ts|js|mjs|cjs)$/, '');
|
|
2420
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* KuratchiJS
|
|
2
|
+
* KuratchiJS - Public API
|
|
3
3
|
*
|
|
4
4
|
* A thin, Cloudflare Workers-native web framework with Svelte-inspired syntax.
|
|
5
5
|
*/
|
|
6
6
|
export { createApp } from './runtime/app.js';
|
|
7
7
|
export { defineConfig } from './runtime/config.js';
|
|
8
|
+
export { defineRuntime } from './runtime/runtime.js';
|
|
8
9
|
export { getCtx, getRequest, getLocals, getParams, getParam, redirect, goto, setBreadcrumbs, getBreadcrumbs, breadcrumbsHome, breadcrumbsPrev, breadcrumbsNext, breadcrumbsCurrent, buildDefaultBreadcrumbs, } from './runtime/context.js';
|
|
9
10
|
export { kuratchiDO, doRpc } from './runtime/do.js';
|
|
10
11
|
export { extractSubdomainSlug, extractSlugFromPrefix, matchContainerViewPath, rewriteProxyLocationHeader, buildContainerRequest, createContainerEnvVars, startContainer, proxyToContainer, handleContainerRouting, forwardJsonPostToContainerDO, matchSiteViewPath, buildSiteContainerRequest, createWpContainerEnvVars, startSiteContainer, proxyToSiteContainer, } from './runtime/containers.js';
|
|
11
|
-
export type { AppConfig, kuratchiConfig, DatabaseConfig, AuthConfig, RouteContext, RouteModule } from './runtime/types.js';
|
|
12
|
+
export type { AppConfig, kuratchiConfig, DatabaseConfig, AuthConfig, RouteContext, RouteModule, RuntimeContext, RuntimeDefinition, RuntimeStep, RuntimeNext, RuntimeErrorResult, } from './runtime/types.js';
|
|
12
13
|
export type { RpcOf } from './runtime/do.js';
|
|
14
|
+
export { compile } from './compiler/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* KuratchiJS
|
|
2
|
+
* KuratchiJS - Public API
|
|
3
3
|
*
|
|
4
4
|
* A thin, Cloudflare Workers-native web framework with Svelte-inspired syntax.
|
|
5
5
|
*/
|
|
6
6
|
// Runtime
|
|
7
7
|
export { createApp } from './runtime/app.js';
|
|
8
8
|
export { defineConfig } from './runtime/config.js';
|
|
9
|
+
export { defineRuntime } from './runtime/runtime.js';
|
|
9
10
|
export { getCtx, getRequest, getLocals, getParams, getParam, redirect, goto, setBreadcrumbs, getBreadcrumbs, breadcrumbsHome, breadcrumbsPrev, breadcrumbsNext, breadcrumbsCurrent, buildDefaultBreadcrumbs, } from './runtime/context.js';
|
|
10
11
|
export { kuratchiDO, doRpc } from './runtime/do.js';
|
|
11
12
|
export { extractSubdomainSlug, extractSlugFromPrefix, matchContainerViewPath, rewriteProxyLocationHeader, buildContainerRequest, createContainerEnvVars, startContainer, proxyToContainer, handleContainerRouting, forwardJsonPostToContainerDO,
|
|
12
13
|
// Compatibility aliases
|
|
13
14
|
matchSiteViewPath, buildSiteContainerRequest, createWpContainerEnvVars, startSiteContainer, proxyToSiteContainer, } from './runtime/containers.js';
|
|
15
|
+
// Compiler (for build tooling)
|
|
16
|
+
export { compile } from './compiler/index.js';
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export { createApp } from './app.js';
|
|
2
2
|
export { defineConfig } from './config.js';
|
|
3
|
+
export { defineRuntime } from './runtime.js';
|
|
3
4
|
export { Router, filePathToPattern } from './router.js';
|
|
4
5
|
export { getCtx, getRequest, getLocals, getParams, getParam, redirect, goto, setBreadcrumbs, getBreadcrumbs, breadcrumbsHome, breadcrumbsPrev, breadcrumbsNext, breadcrumbsCurrent, buildDefaultBreadcrumbs, } from './context.js';
|
|
5
6
|
export { kuratchiDO, doRpc } from './do.js';
|
|
6
7
|
export { extractSubdomainSlug, extractSlugFromPrefix, matchContainerViewPath, rewriteProxyLocationHeader, buildContainerRequest, createContainerEnvVars, startContainer, proxyToContainer, handleContainerRouting, forwardJsonPostToContainerDO, matchSiteViewPath, buildSiteContainerRequest, createWpContainerEnvVars, startSiteContainer, proxyToSiteContainer, } from './containers.js';
|
|
7
|
-
export type { AppConfig, Env, AuthConfig, RouteContext, RouteModule, LayoutModule } from './types.js';
|
|
8
|
+
export type { AppConfig, Env, AuthConfig, RouteContext, RouteModule, LayoutModule, RuntimeContext, RuntimeDefinition, RuntimeStep, RuntimeNext, RuntimeErrorResult, } from './types.js';
|
|
8
9
|
export type { RpcOf } from './do.js';
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { createApp } from './app.js';
|
|
2
2
|
export { defineConfig } from './config.js';
|
|
3
|
+
export { defineRuntime } from './runtime.js';
|
|
3
4
|
export { Router, filePathToPattern } from './router.js';
|
|
4
5
|
export { getCtx, getRequest, getLocals, getParams, getParam, redirect, goto, setBreadcrumbs, getBreadcrumbs, breadcrumbsHome, breadcrumbsPrev, breadcrumbsNext, breadcrumbsCurrent, buildDefaultBreadcrumbs, } from './context.js';
|
|
5
6
|
export { kuratchiDO, doRpc } from './do.js';
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -115,6 +115,20 @@ export interface kuratchiConfig<E extends Env = Env> {
|
|
|
115
115
|
/** The user field path that identifies the DO stub (e.g. 'user.orgId') */
|
|
116
116
|
stubId?: string;
|
|
117
117
|
}>;
|
|
118
|
+
/** Container classes exported into the generated worker entry. */
|
|
119
|
+
containers?: Record<string, string | {
|
|
120
|
+
/** Relative path from project root. Must end in `.container.ts|js|mjs|cjs`. */
|
|
121
|
+
file: string;
|
|
122
|
+
/** Optional override; inferred from exported class in `file` when omitted. */
|
|
123
|
+
className?: string;
|
|
124
|
+
}>;
|
|
125
|
+
/** Workflow classes exported into the generated worker entry. */
|
|
126
|
+
workflows?: Record<string, string | {
|
|
127
|
+
/** Relative path from project root. Must end in `.workflow.ts|js|mjs|cjs`. */
|
|
128
|
+
file: string;
|
|
129
|
+
/** Optional override; inferred from exported class in `file` when omitted. */
|
|
130
|
+
className?: string;
|
|
131
|
+
}>;
|
|
118
132
|
}
|
|
119
133
|
/** Auth configuration for kuratchi.config.ts */
|
|
120
134
|
export interface AuthConfig {
|
|
@@ -205,3 +219,21 @@ export interface AuthConfig {
|
|
|
205
219
|
binding: string;
|
|
206
220
|
};
|
|
207
221
|
}
|
|
222
|
+
/** Runtime pipeline context - shared across runtime step handlers */
|
|
223
|
+
export interface RuntimeContext<E extends Env = Env> {
|
|
224
|
+
request: Request;
|
|
225
|
+
env: E;
|
|
226
|
+
ctx: ExecutionContext;
|
|
227
|
+
url: URL;
|
|
228
|
+
params: Record<string, string>;
|
|
229
|
+
locals: Record<string, any>;
|
|
230
|
+
}
|
|
231
|
+
export type RuntimeNext = () => Promise<Response>;
|
|
232
|
+
export type RuntimeErrorResult = Response | null | undefined | void;
|
|
233
|
+
export interface RuntimeStep<E extends Env = Env> {
|
|
234
|
+
request?: (ctx: RuntimeContext<E>, next: RuntimeNext) => Promise<Response> | Response;
|
|
235
|
+
route?: (ctx: RuntimeContext<E>, next: RuntimeNext) => Promise<Response> | Response;
|
|
236
|
+
response?: (ctx: RuntimeContext<E>, response: Response) => Promise<Response> | Response;
|
|
237
|
+
error?: (ctx: RuntimeContext<E>, error: unknown) => Promise<RuntimeErrorResult> | RuntimeErrorResult;
|
|
238
|
+
}
|
|
239
|
+
export type RuntimeDefinition<E extends Env = Env> = Record<string, RuntimeStep<E>>;
|