@kuratchi/js 0.0.1 → 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/README.md +392 -29
- package/dist/cli.js +32 -8
- package/dist/compiler/index.d.ts +4 -2
- package/dist/compiler/index.js +340 -120
- package/dist/create.js +156 -18
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/runtime/context.d.ts +0 -7
- package/dist/runtime/context.js +0 -11
- package/dist/runtime/index.d.ts +3 -2
- package/dist/runtime/index.js +2 -1
- 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 +2 -6
package/dist/compiler/index.js
CHANGED
|
@@ -17,10 +17,10 @@ function getFrameworkPackageName() {
|
|
|
17
17
|
try {
|
|
18
18
|
const raw = fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf-8');
|
|
19
19
|
const parsed = JSON.parse(raw);
|
|
20
|
-
return parsed.name || '
|
|
20
|
+
return parsed.name || '@kuratchi/js';
|
|
21
21
|
}
|
|
22
22
|
catch {
|
|
23
|
-
return '
|
|
23
|
+
return '@kuratchi/js';
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
function compactInlineJs(source) {
|
|
@@ -36,7 +36,9 @@ function compactInlineJs(source) {
|
|
|
36
36
|
*
|
|
37
37
|
* The generated module exports { app } — an object with a fetch() method
|
|
38
38
|
* that handles routing, load functions, form actions, and rendering.
|
|
39
|
-
*
|
|
39
|
+
* Returns the path to .kuratchi/worker.js — the stable wrangler entry point that
|
|
40
|
+
* re-exports everything from routes.js (default fetch handler + named DO class exports).
|
|
41
|
+
* No src/index.ts is needed in user projects.
|
|
40
42
|
*/
|
|
41
43
|
export function compile(options) {
|
|
42
44
|
const { projectDir } = options;
|
|
@@ -182,8 +184,8 @@ export function compile(options) {
|
|
|
182
184
|
function by(sel, root){ return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
|
|
183
185
|
var __refreshSeq = Object.create(null);
|
|
184
186
|
function syncGroup(group){
|
|
185
|
-
var items = by('[data-select-item
|
|
186
|
-
var masters = by('[data-select-all
|
|
187
|
+
var items = by('[data-select-item]').filter(function(el){ return el.getAttribute('data-select-item') === group; });
|
|
188
|
+
var masters = by('[data-select-all]').filter(function(el){ return el.getAttribute('data-select-all') === group; });
|
|
187
189
|
if(!items.length || !masters.length) return;
|
|
188
190
|
var all = items.every(function(i){ return !!i.checked; });
|
|
189
191
|
var any = items.some(function(i){ return !!i.checked; });
|
|
@@ -306,6 +308,7 @@ export function compile(options) {
|
|
|
306
308
|
if(g && !g.hasAttribute('data-as') && !g.hasAttribute('data-action')){
|
|
307
309
|
var getUrl = g.getAttribute('data-get');
|
|
308
310
|
if(getUrl){
|
|
311
|
+
if(/^[a-z][a-z0-9+\-.]*:/i.test(getUrl) && !/^https?:/i.test(getUrl)) return;
|
|
309
312
|
e.preventDefault();
|
|
310
313
|
location.assign(getUrl);
|
|
311
314
|
return;
|
|
@@ -446,7 +449,7 @@ export function compile(options) {
|
|
|
446
449
|
if(!t || !t.getAttribute) return;
|
|
447
450
|
var gAll = t.getAttribute('data-select-all');
|
|
448
451
|
if(gAll){
|
|
449
|
-
by('[data-select-item
|
|
452
|
+
by('[data-select-item]').filter(function(i){ return i.getAttribute('data-select-item') === gAll; }).forEach(function(i){ i.checked = !!t.checked; });
|
|
450
453
|
syncGroup(gAll);
|
|
451
454
|
return;
|
|
452
455
|
}
|
|
@@ -528,6 +531,8 @@ export function compile(options) {
|
|
|
528
531
|
const authConfig = readAuthConfig(projectDir);
|
|
529
532
|
// Read Durable Object config and discover handler files
|
|
530
533
|
const doConfig = readDoConfig(projectDir);
|
|
534
|
+
const containerConfig = readWorkerClassConfig(projectDir, 'containers');
|
|
535
|
+
const workflowConfig = readWorkerClassConfig(projectDir, 'workflows');
|
|
531
536
|
const doHandlers = doConfig.length > 0
|
|
532
537
|
? discoverDoHandlers(srcDir, doConfig, ormDatabases)
|
|
533
538
|
: [];
|
|
@@ -864,6 +869,8 @@ export function compile(options) {
|
|
|
864
869
|
// Collect only the components that were actually imported by routes
|
|
865
870
|
const compiledComponents = Array.from(compiledComponentCache.values());
|
|
866
871
|
// Generate the routes module
|
|
872
|
+
const runtimeImportPath = resolveRuntimeImportPath(projectDir);
|
|
873
|
+
const hasRuntime = !!runtimeImportPath;
|
|
867
874
|
const output = generateRoutesModule({
|
|
868
875
|
projectDir,
|
|
869
876
|
serverImports: allImports,
|
|
@@ -879,6 +886,8 @@ export function compile(options) {
|
|
|
879
886
|
isDev: options.isDev ?? false,
|
|
880
887
|
isLayoutAsync,
|
|
881
888
|
compiledLayoutActions,
|
|
889
|
+
hasRuntime,
|
|
890
|
+
runtimeImportPath: runtimeImportPath ?? undefined,
|
|
882
891
|
});
|
|
883
892
|
// Write to .kuratchi/routes.js
|
|
884
893
|
const outFile = options.outFile ?? path.join(projectDir, '.kuratchi', 'routes.js');
|
|
@@ -887,7 +896,28 @@ export function compile(options) {
|
|
|
887
896
|
fs.mkdirSync(outDir, { recursive: true });
|
|
888
897
|
}
|
|
889
898
|
writeIfChanged(outFile, output);
|
|
890
|
-
|
|
899
|
+
// Generate .kuratchi/worker.js — the stable wrangler entry point.
|
|
900
|
+
// routes.js already exports the default fetch handler and all named DO classes;
|
|
901
|
+
// worker.js explicitly re-exports them so wrangler.jsonc can reference a
|
|
902
|
+
// stable filename while routes.js is freely regenerated.
|
|
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
|
+
});
|
|
912
|
+
const workerLines = [
|
|
913
|
+
'// Auto-generated by kuratchi \u2014 do not edit.',
|
|
914
|
+
"export { default } from './routes.js';",
|
|
915
|
+
...doConfig.map(c => `export { ${c.className} } from './routes.js';`),
|
|
916
|
+
...workerClassExports,
|
|
917
|
+
'',
|
|
918
|
+
];
|
|
919
|
+
writeIfChanged(workerFile, workerLines.join('\n'));
|
|
920
|
+
return workerFile;
|
|
891
921
|
}
|
|
892
922
|
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
893
923
|
/**
|
|
@@ -1127,7 +1157,7 @@ function buildRouteObject(opts) {
|
|
|
1127
1157
|
body = [body, queryLines.join('\n')].filter(Boolean).join('\n');
|
|
1128
1158
|
}
|
|
1129
1159
|
// Rewrite imported function calls: fnName( → __mN.fnName(
|
|
1130
|
-
//
|
|
1160
|
+
// Rewrite imported function calls to use the module namespace
|
|
1131
1161
|
for (const [fnName, moduleId] of Object.entries(fnToModule)) {
|
|
1132
1162
|
if (!/^[A-Za-z_$][\w$]*$/.test(fnName))
|
|
1133
1163
|
continue;
|
|
@@ -1350,6 +1380,89 @@ function readDoConfig(projectDir) {
|
|
|
1350
1380
|
}
|
|
1351
1381
|
return entries;
|
|
1352
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
|
+
}
|
|
1353
1466
|
function discoverFilesWithSuffix(dir, suffix) {
|
|
1354
1467
|
if (!fs.existsSync(dir))
|
|
1355
1468
|
return [];
|
|
@@ -1650,7 +1763,10 @@ function generateRoutesModule(opts) {
|
|
|
1650
1763
|
.map(([status, fn]) => fn)
|
|
1651
1764
|
.join('\n\n');
|
|
1652
1765
|
// Resolve path to the framework's context module from the output directory
|
|
1653
|
-
const contextImport = `import { __setRequestContext,
|
|
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
|
+
: '';
|
|
1654
1770
|
// Auth session init — thin cookie parsing injected into Worker entry
|
|
1655
1771
|
let authInit = '';
|
|
1656
1772
|
if (opts.authConfig && opts.authConfig.sessionEnabled) {
|
|
@@ -1916,7 +2032,7 @@ ${initLines.join('\n')}
|
|
|
1916
2032
|
${opts.isDev ? '\nglobalThis.__kuratchi_DEV__ = true;\n' : ''}
|
|
1917
2033
|
${workerImport}
|
|
1918
2034
|
${contextImport}
|
|
1919
|
-
${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')}
|
|
1920
2036
|
|
|
1921
2037
|
// ── Assets ──────────────────────────────────────────────────────
|
|
1922
2038
|
|
|
@@ -2060,141 +2176,245 @@ ${opts.isLayoutAsync ? 'async ' : ''}function __render(route, data) {
|
|
|
2060
2176
|
return __attachCookies(new Response(${opts.isLayoutAsync ? 'await ' : ''}__layout(html), { headers: { 'content-type': 'text/html; charset=utf-8' } }));
|
|
2061
2177
|
}
|
|
2062
2178
|
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
export default class extends WorkerEntrypoint {
|
|
2066
|
-
async fetch(request) {
|
|
2067
|
-
__setRequestContext(this.ctx, request);
|
|
2068
|
-
__setEnvCompat(this.env);
|
|
2069
|
-
${migrationInit ? ' await __runMigrations();\n' : ''}${authInit ? ' __initAuth(request);\n' : ''}${authPluginInit ? ' __initAuthPlugins();\n' : ''}${doResolverInit ? ' __initDoResolvers();\n' : ''}
|
|
2070
|
-
const url = new URL(request.url);
|
|
2071
|
-
${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');
|
|
2072
2181
|
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
}
|
|
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
|
+
}
|
|
2087
2195
|
|
|
2088
|
-
|
|
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
|
+
}
|
|
2089
2209
|
|
|
2090
|
-
|
|
2091
|
-
|
|
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');
|
|
2092
2217
|
}
|
|
2218
|
+
}
|
|
2219
|
+
return out;
|
|
2220
|
+
}
|
|
2093
2221
|
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
const __qArgsRaw = request.headers.get('x-kuratchi-query-args') || '[]';
|
|
2098
|
-
let __qArgs = [];
|
|
2222
|
+
async function __runRuntimeError(ctx, error) {
|
|
2223
|
+
for (const [name, step] of __runtimeEntries) {
|
|
2224
|
+
if (typeof step.error !== 'function') continue;
|
|
2099
2225
|
try {
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2102
|
-
} catch {
|
|
2103
|
-
|
|
2104
|
-
if (!__getLocals().__breadcrumbs) {
|
|
2105
|
-
__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);
|
|
2106
2230
|
}
|
|
2231
|
+
}
|
|
2232
|
+
return null;
|
|
2233
|
+
}
|
|
2107
2234
|
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
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
|
+
});
|
|
2122
2267
|
}
|
|
2123
|
-
|
|
2124
|
-
return __secHeaders(new Response(JSON.stringify({ ok: true, data: __rpcResult }), {
|
|
2125
|
-
headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
2126
|
-
}));
|
|
2127
|
-
} catch (err) {
|
|
2128
|
-
console.error('[kuratchi] RPC error:', err);
|
|
2129
|
-
const __errMsg = typeof __kuratchi_DEV__ !== 'undefined' ? err.message : 'Internal Server Error';
|
|
2130
|
-
return __secHeaders(new Response(JSON.stringify({ ok: false, error: __errMsg }), {
|
|
2131
|
-
status: 500, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' }
|
|
2132
|
-
}));
|
|
2268
|
+
return __secHeaders(new Response('Not Found', { status: 404 }));
|
|
2133
2269
|
}
|
|
2134
|
-
}
|
|
2135
2270
|
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
if (!
|
|
2139
|
-
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' } }));
|
|
2140
2275
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
const
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
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
|
+
}
|
|
2148
2300
|
try {
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
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 : [];
|
|
2155
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
|
+
}));
|
|
2156
2311
|
} catch (err) {
|
|
2157
|
-
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)
|
|
2158
2357
|
if (isFetchAction) {
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
status: 500, headers: { 'content-type': 'application/json' }
|
|
2358
|
+
return __attachCookies(new Response(JSON.stringify({ ok: true }), {
|
|
2359
|
+
headers: { 'content-type': 'application/json' }
|
|
2162
2360
|
}));
|
|
2163
2361
|
}
|
|
2164
|
-
|
|
2165
|
-
const
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
return ${opts.isLayoutAsync ? 'await ' : ''}__render(route, data);
|
|
2170
|
-
}
|
|
2171
|
-
// Fetch-based actions return lightweight JSON (no page re-render)
|
|
2172
|
-
if (isFetchAction) {
|
|
2173
|
-
return new Response(JSON.stringify({ ok: true }), {
|
|
2174
|
-
headers: { 'content-type': 'application/json' }
|
|
2175
|
-
});
|
|
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 } }));
|
|
2176
2367
|
}
|
|
2177
|
-
// POST-Redirect-GET: redirect to custom target or back to same URL
|
|
2178
|
-
const __locals = __getLocals();
|
|
2179
|
-
const redirectTo = __locals.__redirectTo || url.pathname;
|
|
2180
|
-
const redirectStatus = Number(__locals.__redirectStatus) || 303;
|
|
2181
|
-
return __attachCookies(new Response(null, { status: redirectStatus, headers: { 'location': redirectTo } }));
|
|
2182
2368
|
}
|
|
2183
|
-
}
|
|
2184
2369
|
|
|
2185
|
-
|
|
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
|
+
|
|
2186
2384
|
try {
|
|
2187
|
-
const
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
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);
|
|
2192
2389
|
} catch (err) {
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2390
|
+
const __handled = await __runRuntimeError(__runtimeCtx, err);
|
|
2391
|
+
if (__handled) return __secHeaders(__handled);
|
|
2392
|
+
throw err;
|
|
2196
2393
|
}
|
|
2197
2394
|
}
|
|
2198
2395
|
}
|
|
2199
2396
|
`;
|
|
2200
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
|
+
}
|