@kuratchi/js 0.0.10 → 0.0.12
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/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/compiler/index.js +41 -20
- package/dist/compiler/parser.js +15 -0
- package/dist/runtime/request.js +1 -1
- package/package.json +5 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kuratchi contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ Kuratchi routes are server-first.
|
|
|
54
54
|
- Top-level route `<script>` blocks run on the server.
|
|
55
55
|
- Template expressions, `if`, and `for` blocks render on the server.
|
|
56
56
|
- `src/server` is for private server-only modules and reusable backend logic.
|
|
57
|
+
- `src/server/runtime.hook.ts` is the server runtime hook entrypoint for request interception.
|
|
57
58
|
- Reactive `$:` code is the browser-only escape hatch.
|
|
58
59
|
|
|
59
60
|
Route files are not client files. They are server-rendered routes that can opt into small browser-side reactive behavior when needed.
|
|
@@ -542,6 +543,38 @@ const postSlug = slug;
|
|
|
542
543
|
- `slug` is `params.slug` when the matched route defines a `slug` param.
|
|
543
544
|
- `headers`, `method`, and `params` are also exported from `@kuratchi/js/request`.
|
|
544
545
|
- Use `getRequest()` when you want the raw native `Request` object.
|
|
546
|
+
|
|
547
|
+
## Runtime Hook
|
|
548
|
+
|
|
549
|
+
Optional server runtime hook file. Export a `RuntimeDefinition` from `src/server/runtime.hook.ts`
|
|
550
|
+
to intercept requests before they reach the framework router. Use it for agent routing,
|
|
551
|
+
pre-route auth, or custom response/error handling.
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
import type { RuntimeDefinition } from '@kuratchi/js';
|
|
555
|
+
|
|
556
|
+
const runtime: RuntimeDefinition = {
|
|
557
|
+
agents: {
|
|
558
|
+
async request(ctx, next) {
|
|
559
|
+
if (!ctx.url.pathname.startsWith('/agents/')) {
|
|
560
|
+
return next();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return new Response('Agent response');
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
export default runtime;
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
`ctx` includes:
|
|
572
|
+
|
|
573
|
+
- `ctx.url` - parsed URL
|
|
574
|
+
- `ctx.request` - raw Request
|
|
575
|
+
- `ctx.env` - Cloudflare env bindings
|
|
576
|
+
- `next()` - pass control to the next handler
|
|
577
|
+
|
|
545
578
|
## Environment bindings
|
|
546
579
|
|
|
547
580
|
Cloudflare env is server-only.
|
package/dist/compiler/index.js
CHANGED
|
@@ -725,6 +725,8 @@ export function compile(options) {
|
|
|
725
725
|
// 500.html receives `error` as a variable; others don't need it
|
|
726
726
|
compiledErrorPages.set(status, `function __error_${status}(error) {\n ${body}\n return __html;\n}`);
|
|
727
727
|
}
|
|
728
|
+
// Read assets prefix from kuratchi.config.ts (default: /assets/)
|
|
729
|
+
const assetsPrefix = readAssetsPrefix(projectDir);
|
|
728
730
|
// Read kuratchi.config.ts at build time to discover ORM database configs
|
|
729
731
|
const ormDatabases = readOrmConfig(projectDir);
|
|
730
732
|
// Read auth config from kuratchi.config.ts
|
|
@@ -1202,7 +1204,7 @@ export function compile(options) {
|
|
|
1202
1204
|
});
|
|
1203
1205
|
compiledRoutes.push(routeObj);
|
|
1204
1206
|
}
|
|
1205
|
-
// Scan src/assets/ for static files to embed
|
|
1207
|
+
// Scan src/assets/ for static files to embed (recursive)
|
|
1206
1208
|
const assetsDir = path.join(srcDir, 'assets');
|
|
1207
1209
|
const compiledAssets = [];
|
|
1208
1210
|
if (fs.existsSync(assetsDir)) {
|
|
@@ -1213,15 +1215,23 @@ export function compile(options) {
|
|
|
1213
1215
|
'.svg': 'image/svg+xml',
|
|
1214
1216
|
'.txt': 'text/plain; charset=utf-8',
|
|
1215
1217
|
};
|
|
1216
|
-
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1218
|
+
const scanAssets = (dir, prefix) => {
|
|
1219
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1220
|
+
if (entry.isDirectory()) {
|
|
1221
|
+
scanAssets(path.join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
1225
|
+
const mime = mimeTypes[ext];
|
|
1226
|
+
if (!mime)
|
|
1227
|
+
continue;
|
|
1228
|
+
const content = fs.readFileSync(path.join(dir, entry.name), 'utf-8');
|
|
1229
|
+
const etag = '"' + crypto.createHash('md5').update(content).digest('hex').slice(0, 12) + '"';
|
|
1230
|
+
const name = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
1231
|
+
compiledAssets.push({ name, content, mime, etag });
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
scanAssets(assetsDir, '');
|
|
1225
1235
|
}
|
|
1226
1236
|
// Collect only the components that were actually imported by routes
|
|
1227
1237
|
const compiledComponents = Array.from(compiledComponentCache.values());
|
|
@@ -1254,6 +1264,7 @@ export function compile(options) {
|
|
|
1254
1264
|
compiledLayoutActions,
|
|
1255
1265
|
hasRuntime,
|
|
1256
1266
|
runtimeImportPath,
|
|
1267
|
+
assetsPrefix,
|
|
1257
1268
|
});
|
|
1258
1269
|
// Write to .kuratchi/routes.js
|
|
1259
1270
|
const outFile = options.outFile ?? path.join(projectDir, '.kuratchi', 'routes.js');
|
|
@@ -1720,7 +1731,7 @@ function buildRouteObject(opts) {
|
|
|
1720
1731
|
return { ${loadReturnVars.join(', ')} };`;
|
|
1721
1732
|
}
|
|
1722
1733
|
}
|
|
1723
|
-
parts.push(` async load(
|
|
1734
|
+
parts.push(` async load(__routeParams = {}) {
|
|
1724
1735
|
${loadBody}${returnObj}
|
|
1725
1736
|
}`);
|
|
1726
1737
|
}
|
|
@@ -2039,6 +2050,21 @@ function resolveClassExportFromFile(absPath, errorLabel) {
|
|
|
2039
2050
|
}
|
|
2040
2051
|
throw new Error(`[kuratchi] ${errorLabel} must export a class via "export class X" or "export default class X". File: ${absPath}`);
|
|
2041
2052
|
}
|
|
2053
|
+
function readAssetsPrefix(projectDir) {
|
|
2054
|
+
const configPath = path.join(projectDir, 'kuratchi.config.ts');
|
|
2055
|
+
if (!fs.existsSync(configPath))
|
|
2056
|
+
return '/assets/';
|
|
2057
|
+
const source = fs.readFileSync(configPath, 'utf-8');
|
|
2058
|
+
const match = source.match(/assetsPrefix\s*:\s*['"]([^'"]+)['"]/);
|
|
2059
|
+
if (!match)
|
|
2060
|
+
return '/assets/';
|
|
2061
|
+
let prefix = match[1];
|
|
2062
|
+
if (!prefix.startsWith('/'))
|
|
2063
|
+
prefix = '/' + prefix;
|
|
2064
|
+
if (!prefix.endsWith('/'))
|
|
2065
|
+
prefix += '/';
|
|
2066
|
+
return prefix;
|
|
2067
|
+
}
|
|
2042
2068
|
function discoverConventionClassFiles(projectDir, dir, suffix, errorLabel) {
|
|
2043
2069
|
const absDir = path.join(projectDir, dir);
|
|
2044
2070
|
const files = discoverFilesWithSuffix(absDir, suffix);
|
|
@@ -2933,9 +2959,9 @@ ${migrationInit ? ' await __runMigrations();\n' : ''}${authInit ? ' __init
|
|
|
2933
2959
|
const url = __runtimeCtx.url;
|
|
2934
2960
|
${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' : ''}
|
|
2935
2961
|
|
|
2936
|
-
// Serve static assets from src/assets/
|
|
2937
|
-
if (url.pathname.startsWith('
|
|
2938
|
-
const name = url.pathname.slice('
|
|
2962
|
+
// Serve static assets from src/assets/
|
|
2963
|
+
if (url.pathname.startsWith('${opts.assetsPrefix}')) {
|
|
2964
|
+
const name = url.pathname.slice('${opts.assetsPrefix}'.length);
|
|
2939
2965
|
const asset = __assets[name];
|
|
2940
2966
|
if (asset) {
|
|
2941
2967
|
if (request.headers.get('if-none-match') === asset.etag) {
|
|
@@ -3116,12 +3142,7 @@ ${ac?.hasRateLimit ? '\n // Rate limiting - check before route handlers\n
|
|
|
3116
3142
|
}
|
|
3117
3143
|
function resolveRuntimeImportPath(projectDir) {
|
|
3118
3144
|
const candidates = [
|
|
3119
|
-
{ file: 'src/
|
|
3120
|
-
{ file: 'src/kuratchi.runtime.js', importPath: '../src/kuratchi.runtime' },
|
|
3121
|
-
{ file: 'src/kuratchi.runtime.mjs', importPath: '../src/kuratchi.runtime' },
|
|
3122
|
-
{ file: 'kuratchi.runtime.ts', importPath: '../kuratchi.runtime' },
|
|
3123
|
-
{ file: 'kuratchi.runtime.js', importPath: '../kuratchi.runtime' },
|
|
3124
|
-
{ file: 'kuratchi.runtime.mjs', importPath: '../kuratchi.runtime' },
|
|
3145
|
+
{ file: 'src/server/runtime.hook.ts', importPath: '../src/server/runtime.hook' },
|
|
3125
3146
|
];
|
|
3126
3147
|
for (const candidate of candidates) {
|
|
3127
3148
|
if (fs.existsSync(path.join(projectDir, candidate.file))) {
|
package/dist/compiler/parser.js
CHANGED
|
@@ -770,6 +770,21 @@ export function parseFile(source, options = {}) {
|
|
|
770
770
|
if (!dataVars.includes(name))
|
|
771
771
|
dataVars.push(name);
|
|
772
772
|
}
|
|
773
|
+
// Server import named bindings are also data vars (available in templates)
|
|
774
|
+
for (const line of serverImports) {
|
|
775
|
+
const namesMatch = line.match(/import\s*\{([^}]+)\}/);
|
|
776
|
+
if (!namesMatch)
|
|
777
|
+
continue;
|
|
778
|
+
for (const part of namesMatch[1].split(',')) {
|
|
779
|
+
const trimmed = part.trim();
|
|
780
|
+
if (!trimmed)
|
|
781
|
+
continue;
|
|
782
|
+
const segments = trimmed.split(/\s+as\s+/);
|
|
783
|
+
const localName = (segments[1] || segments[0]).trim();
|
|
784
|
+
if (localName && !dataVars.includes(localName))
|
|
785
|
+
dataVars.push(localName);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
773
788
|
}
|
|
774
789
|
const hasLoad = scriptBody.length > 0 || !!loadFunction;
|
|
775
790
|
// Strip HTML comments from the template before scanning for action references.
|
package/dist/runtime/request.js
CHANGED
|
@@ -8,7 +8,7 @@ export let slug = undefined;
|
|
|
8
8
|
function __syncDerivedState() {
|
|
9
9
|
pathname = url.pathname;
|
|
10
10
|
searchParams = url.searchParams;
|
|
11
|
-
slug = params.slug;
|
|
11
|
+
slug = params.slug ?? Object.values(params)[0];
|
|
12
12
|
}
|
|
13
13
|
export function __setRequestState(request) {
|
|
14
14
|
url = new URL(request.url);
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kuratchi/js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"description": "A thin, Cloudflare Workers-native web framework with Svelte-inspired syntax",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "./dist/index.js",
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
8
9
|
"bin": {
|
|
9
|
-
"kuratchi": "
|
|
10
|
+
"kuratchi": "dist/cli.js"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"dist",
|
|
13
|
-
"README.md"
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
14
16
|
],
|
|
15
17
|
"scripts": {
|
|
16
18
|
"build": "tsc -p tsconfig.build.json",
|