@kuratchi/js 0.0.16 → 0.0.17
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 +168 -11
- package/dist/cli.js +13 -13
- package/dist/compiler/client-module-pipeline.js +5 -5
- package/dist/compiler/compiler-shared.d.ts +18 -0
- package/dist/compiler/component-pipeline.js +4 -9
- package/dist/compiler/config-reading.d.ts +2 -1
- package/dist/compiler/config-reading.js +57 -0
- package/dist/compiler/durable-object-pipeline.js +1 -1
- package/dist/compiler/import-linking.js +2 -1
- package/dist/compiler/index.d.ts +6 -6
- package/dist/compiler/index.js +54 -22
- package/dist/compiler/layout-pipeline.js +6 -6
- package/dist/compiler/parser.js +10 -11
- package/dist/compiler/root-layout-pipeline.js +444 -429
- package/dist/compiler/route-pipeline.js +36 -41
- package/dist/compiler/route-state-pipeline.d.ts +1 -0
- package/dist/compiler/route-state-pipeline.js +3 -3
- package/dist/compiler/routes-module-feature-blocks.js +63 -63
- package/dist/compiler/routes-module-runtime-shell.js +65 -55
- package/dist/compiler/routes-module-types.d.ts +2 -1
- package/dist/compiler/server-module-pipeline.js +1 -1
- package/dist/compiler/template.js +24 -15
- package/dist/compiler/worker-output-pipeline.js +2 -2
- package/dist/runtime/context.d.ts +4 -0
- package/dist/runtime/context.js +40 -2
- package/dist/runtime/do.js +21 -6
- package/dist/runtime/generated-worker.d.ts +22 -0
- package/dist/runtime/generated-worker.js +154 -23
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/router.d.ts +5 -1
- package/dist/runtime/router.js +116 -31
- package/dist/runtime/security.d.ts +101 -0
- package/dist/runtime/security.js +298 -0
- package/dist/runtime/types.d.ts +21 -0
- package/package.json +1 -1
package/dist/compiler/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { compileAssets } from './asset-pipeline.js';
|
|
|
7
7
|
import { compileApiRoute } from './api-route-pipeline.js';
|
|
8
8
|
import { createClientModuleCompiler } from './client-module-pipeline.js';
|
|
9
9
|
import { createComponentCompiler } from './component-pipeline.js';
|
|
10
|
-
import { readAssetsPrefix, readAuthConfig, readDoConfig, readOrmConfig, readUiConfigValues, readUiTheme, } from './config-reading.js';
|
|
10
|
+
import { readAssetsPrefix, readAuthConfig, readDoConfig, readOrmConfig, readSecurityConfig, readUiConfigValues, readUiTheme, } from './config-reading.js';
|
|
11
11
|
import { discoverContainerFiles, discoverConventionClassFiles, discoverWorkflowFiles, } from './convention-discovery.js';
|
|
12
12
|
import { discoverDurableObjects, generateHandlerProxy } from './durable-object-pipeline.js';
|
|
13
13
|
import { compileErrorPages } from './error-page-pipeline.js';
|
|
@@ -22,6 +22,7 @@ import { buildWorkerEntrypointSource, resolveRuntimeImportPath as resolveRuntime
|
|
|
22
22
|
import { syncWranglerConfig as syncWranglerConfigPipeline } from './wrangler-sync.js';
|
|
23
23
|
import { filePathToPattern } from '../runtime/router.js';
|
|
24
24
|
import * as fs from 'node:fs';
|
|
25
|
+
import * as fsp from 'node:fs/promises';
|
|
25
26
|
import * as path from 'node:path';
|
|
26
27
|
export { parseFile } from './parser.js';
|
|
27
28
|
export { compileTemplate, generateRenderFunction } from './template.js';
|
|
@@ -40,15 +41,40 @@ function getFrameworkPackageName() {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
/**
|
|
43
|
-
*
|
|
44
|
+
* Pre-read all route files and their layouts in parallel for better I/O performance.
|
|
45
|
+
* Returns a Map from file path to content.
|
|
46
|
+
*/
|
|
47
|
+
async function preReadFiles(routesDir, routeFiles) {
|
|
48
|
+
const filesToRead = new Set();
|
|
49
|
+
// Collect all unique files to read
|
|
50
|
+
for (const rf of routeFiles) {
|
|
51
|
+
filesToRead.add(path.join(routesDir, rf.file));
|
|
52
|
+
for (const layout of rf.layouts) {
|
|
53
|
+
filesToRead.add(path.join(routesDir, layout));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Also include root layout if it exists
|
|
57
|
+
const rootLayout = path.join(routesDir, 'layout.html');
|
|
58
|
+
if (fs.existsSync(rootLayout)) {
|
|
59
|
+
filesToRead.add(rootLayout);
|
|
60
|
+
}
|
|
61
|
+
// Read all files in parallel
|
|
62
|
+
const entries = await Promise.all(Array.from(filesToRead).map(async (filePath) => {
|
|
63
|
+
const content = await fsp.readFile(filePath, 'utf-8');
|
|
64
|
+
return [filePath, content];
|
|
65
|
+
}));
|
|
66
|
+
return new Map(entries);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Compile a project's src/routes/ into .kuratchi/routes.ts
|
|
44
70
|
*
|
|
45
|
-
* The generated module exports { app }
|
|
71
|
+
* The generated module exports { app } — an object with a fetch() method
|
|
46
72
|
* that handles routing, load functions, form actions, and rendering.
|
|
47
|
-
* Returns the path to .kuratchi/worker.
|
|
48
|
-
* re-exports everything from routes.
|
|
73
|
+
* Returns the path to .kuratchi/worker.ts — the stable wrangler entry point that
|
|
74
|
+
* re-exports everything from routes.ts (default fetch handler + named DO class exports).
|
|
49
75
|
* No src/index.ts is needed in user projects.
|
|
50
76
|
*/
|
|
51
|
-
export function compile(options) {
|
|
77
|
+
export async function compile(options) {
|
|
52
78
|
const { projectDir } = options;
|
|
53
79
|
const srcDir = path.join(projectDir, 'src');
|
|
54
80
|
const routesDir = path.join(srcDir, 'routes');
|
|
@@ -57,6 +83,8 @@ export function compile(options) {
|
|
|
57
83
|
}
|
|
58
84
|
// Discover all .html route files
|
|
59
85
|
const routeFiles = discoverRoutesPipeline(routesDir);
|
|
86
|
+
// Pre-read all files in parallel for better I/O performance
|
|
87
|
+
const fileContents = await preReadFiles(routesDir, routeFiles);
|
|
60
88
|
const componentCompiler = createComponentCompiler({
|
|
61
89
|
projectDir,
|
|
62
90
|
srcDir,
|
|
@@ -71,8 +99,8 @@ export function compile(options) {
|
|
|
71
99
|
const layoutFile = path.join(routesDir, 'layout.html');
|
|
72
100
|
let compiledLayout = null;
|
|
73
101
|
let layoutPlan = null;
|
|
74
|
-
if (
|
|
75
|
-
const layoutImportSource =
|
|
102
|
+
if (fileContents.has(layoutFile)) {
|
|
103
|
+
const layoutImportSource = fileContents.get(layoutFile);
|
|
76
104
|
const themeCSS = readUiTheme(projectDir);
|
|
77
105
|
const uiConfigValues = readUiConfigValues(projectDir);
|
|
78
106
|
const source = prepareRootLayoutSource({
|
|
@@ -100,6 +128,8 @@ export function compile(options) {
|
|
|
100
128
|
const ormDatabases = readOrmConfig(projectDir);
|
|
101
129
|
// Read auth config from kuratchi.config.ts
|
|
102
130
|
const authConfig = readAuthConfig(projectDir);
|
|
131
|
+
// Read security config from kuratchi.config.ts
|
|
132
|
+
const securityConfig = readSecurityConfig(projectDir);
|
|
103
133
|
// Auto-discover Durable Objects from .do.ts files (config optional, only needed for stubId)
|
|
104
134
|
const configDoEntries = readDoConfig(projectDir);
|
|
105
135
|
const { config: doConfig, handlers: doHandlers } = discoverDurableObjects(srcDir, configDoEntries, ormDatabases);
|
|
@@ -122,13 +152,13 @@ export function compile(options) {
|
|
|
122
152
|
projectDir,
|
|
123
153
|
runtimeDoImport: RUNTIME_DO_IMPORT,
|
|
124
154
|
});
|
|
125
|
-
const proxyFile = path.join(doProxyDir, handler.fileName + '.
|
|
155
|
+
const proxyFile = path.join(doProxyDir, handler.fileName + '.ts');
|
|
126
156
|
const proxyFileDir = path.dirname(proxyFile);
|
|
127
157
|
if (!fs.existsSync(proxyFileDir))
|
|
128
158
|
fs.mkdirSync(proxyFileDir, { recursive: true });
|
|
129
159
|
writeIfChanged(proxyFile, proxyCode);
|
|
130
160
|
const handlerAbsNoExt = handler.absPath.replace(/\\/g, '/').replace(/\.ts$/, '');
|
|
131
|
-
const proxyAbsNoExt = proxyFile.replace(/\\/g, '/').replace(/\.
|
|
161
|
+
const proxyAbsNoExt = proxyFile.replace(/\\/g, '/').replace(/\.ts$/, '');
|
|
132
162
|
registerDoProxyPath(handlerAbsNoExt, proxyAbsNoExt);
|
|
133
163
|
// Backward-compatible alias for '.do' suffix.
|
|
134
164
|
registerDoProxyPath(handlerAbsNoExt.replace(/\.do$/, ''), proxyAbsNoExt.replace(/\.do$/, ''));
|
|
@@ -137,13 +167,13 @@ export function compile(options) {
|
|
|
137
167
|
registerDoProxyPath(path.join(srcDir, 'durable-objects', handler.fileName.replace(/\.do$/, '')).replace(/\\/g, '/'), proxyAbsNoExt.replace(/\.do$/, ''));
|
|
138
168
|
if (handler.fileName.endsWith('.do')) {
|
|
139
169
|
const aliasFileName = handler.fileName.slice(0, -3);
|
|
140
|
-
const aliasProxyFile = path.join(doProxyDir, aliasFileName + '.
|
|
141
|
-
const aliasCode = `// Auto-generated alias for .do handler\nexport * from './${handler.fileName}.
|
|
170
|
+
const aliasProxyFile = path.join(doProxyDir, aliasFileName + '.ts');
|
|
171
|
+
const aliasCode = `// Auto-generated alias for .do handler\nexport * from './${handler.fileName}.ts';\n`;
|
|
142
172
|
const aliasProxyDir = path.dirname(aliasProxyFile);
|
|
143
173
|
if (!fs.existsSync(aliasProxyDir))
|
|
144
174
|
fs.mkdirSync(aliasProxyDir, { recursive: true });
|
|
145
175
|
writeIfChanged(aliasProxyFile, aliasCode);
|
|
146
|
-
registerDoProxyPath(handlerAbsNoExt.replace(/\.do$/, ''), aliasProxyFile.replace(/\\/g, '/').replace(/\.
|
|
176
|
+
registerDoProxyPath(handlerAbsNoExt.replace(/\.do$/, ''), aliasProxyFile.replace(/\\/g, '/').replace(/\.ts$/, ''));
|
|
147
177
|
}
|
|
148
178
|
}
|
|
149
179
|
}
|
|
@@ -191,13 +221,14 @@ export function compile(options) {
|
|
|
191
221
|
continue;
|
|
192
222
|
}
|
|
193
223
|
// -- Page route (page.html) --
|
|
194
|
-
const source = fs.readFileSync(fullPath, 'utf-8');
|
|
224
|
+
const source = fileContents.get(fullPath) ?? fs.readFileSync(fullPath, 'utf-8');
|
|
195
225
|
const parsed = parseFile(source, { kind: 'route', filePath: fullPath });
|
|
196
226
|
const routeState = assembleRouteState({
|
|
197
227
|
parsed,
|
|
198
228
|
fullPath,
|
|
199
229
|
routesDir,
|
|
200
230
|
layoutRelativePaths: rf.layouts,
|
|
231
|
+
fileContents,
|
|
201
232
|
});
|
|
202
233
|
compiledRoutes.push(compilePageRoute({
|
|
203
234
|
pattern,
|
|
@@ -228,7 +259,7 @@ export function compile(options) {
|
|
|
228
259
|
// so that $durable-objects/* and other project imports get rewritten to their proxies.
|
|
229
260
|
const runtimeAbs = path.resolve(path.join(projectDir, '.kuratchi'), rawRuntimeImportPath);
|
|
230
261
|
const transformedRuntimePath = serverModuleCompiler.transformModule(runtimeAbs);
|
|
231
|
-
const outFile = options.outFile ?? path.join(projectDir, '.kuratchi', 'routes.
|
|
262
|
+
const outFile = options.outFile ?? path.join(projectDir, '.kuratchi', 'routes.ts');
|
|
232
263
|
runtimeImportPath = serverModuleCompiler.toModuleSpecifier(outFile, transformedRuntimePath);
|
|
233
264
|
}
|
|
234
265
|
const hasRuntime = !!runtimeImportPath;
|
|
@@ -242,6 +273,7 @@ export function compile(options) {
|
|
|
242
273
|
compiledErrorPages,
|
|
243
274
|
ormDatabases,
|
|
244
275
|
authConfig,
|
|
276
|
+
securityConfig,
|
|
245
277
|
doConfig,
|
|
246
278
|
doHandlers,
|
|
247
279
|
workflowConfig,
|
|
@@ -255,18 +287,18 @@ export function compile(options) {
|
|
|
255
287
|
runtimeDoImport: RUNTIME_DO_IMPORT,
|
|
256
288
|
runtimeWorkerImport: RUNTIME_WORKER_IMPORT,
|
|
257
289
|
});
|
|
258
|
-
// Write to .kuratchi/routes.
|
|
259
|
-
const outFile = options.outFile ?? path.join(projectDir, '.kuratchi', 'routes.
|
|
290
|
+
// Write to .kuratchi/routes.ts
|
|
291
|
+
const outFile = options.outFile ?? path.join(projectDir, '.kuratchi', 'routes.ts');
|
|
260
292
|
const outDir = path.dirname(outFile);
|
|
261
293
|
if (!fs.existsSync(outDir)) {
|
|
262
294
|
fs.mkdirSync(outDir, { recursive: true });
|
|
263
295
|
}
|
|
264
296
|
writeIfChanged(outFile, output);
|
|
265
|
-
// Generate .kuratchi/worker.
|
|
266
|
-
// routes.
|
|
267
|
-
// worker.
|
|
268
|
-
// stable filename while routes.
|
|
269
|
-
const workerFile = path.join(outDir, 'worker.
|
|
297
|
+
// Generate .kuratchi/worker.ts — the stable wrangler entry point.
|
|
298
|
+
// routes.ts already exports the default fetch handler and all named DO classes;
|
|
299
|
+
// worker.ts explicitly re-exports them so wrangler.jsonc can reference a
|
|
300
|
+
// stable filename while routes.ts is freely regenerated.
|
|
301
|
+
const workerFile = path.join(outDir, 'worker.ts');
|
|
270
302
|
writeIfChanged(workerFile, buildWorkerEntrypointSource({
|
|
271
303
|
projectDir,
|
|
272
304
|
outDir,
|
|
@@ -64,7 +64,7 @@ export function compileLayoutPlan(opts) {
|
|
|
64
64
|
let finalLayoutBody = layoutRenderBody;
|
|
65
65
|
if (layoutComponentStyles.length > 0) {
|
|
66
66
|
const lines = layoutRenderBody.split('\n');
|
|
67
|
-
const styleLines = layoutComponentStyles.map((css) => `
|
|
67
|
+
const styleLines = layoutComponentStyles.map((css) => `__parts.push(\`${css}\\n\`);`);
|
|
68
68
|
finalLayoutBody = [lines[0], ...styleLines, ...lines.slice(1)].join('\n');
|
|
69
69
|
}
|
|
70
70
|
let layoutScriptBody = importParsed.script
|
|
@@ -80,11 +80,11 @@ export function compileLayoutPlan(opts) {
|
|
|
80
80
|
? `const __layoutHead = __head + ${JSON.stringify(`<script type="module" src="${clientModuleHref}"></script>\n`)};`
|
|
81
81
|
: `const __layoutHead = __head;`;
|
|
82
82
|
return {
|
|
83
|
-
compiledLayout: `function __layout(__content, __head = '') {
|
|
84
|
-
const __esc = (v) => { if (v == null) return ''; return String(v).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); };
|
|
85
|
-
${layoutHeadAssignment}
|
|
86
|
-
${layoutScriptBody ? layoutScriptBody + '\n ' : ''}${finalLayoutBody}
|
|
87
|
-
return __html;
|
|
83
|
+
compiledLayout: `function __layout(__content, __head = '') {
|
|
84
|
+
const __esc = (v) => { if (v == null) return ''; return String(v).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); };
|
|
85
|
+
${layoutHeadAssignment}
|
|
86
|
+
${layoutScriptBody ? layoutScriptBody + '\n ' : ''}${finalLayoutBody}
|
|
87
|
+
return __html;
|
|
88
88
|
}`,
|
|
89
89
|
componentNames,
|
|
90
90
|
importParsed,
|
package/dist/compiler/parser.js
CHANGED
|
@@ -505,12 +505,12 @@ function collectTemplateClientDeclaredNames(template) {
|
|
|
505
505
|
const body = match[2] || '';
|
|
506
506
|
if (!isExecutableTemplateScript(attrs))
|
|
507
507
|
continue;
|
|
508
|
-
|
|
509
|
-
for (const name of extractTopLevelImportNames(
|
|
508
|
+
// TypeScript source works directly for reference collection
|
|
509
|
+
for (const name of extractTopLevelImportNames(body))
|
|
510
510
|
declared.add(name);
|
|
511
|
-
for (const name of extractTopLevelDataVars(
|
|
511
|
+
for (const name of extractTopLevelDataVars(body))
|
|
512
512
|
declared.add(name);
|
|
513
|
-
for (const name of extractTopLevelFunctionNames(
|
|
513
|
+
for (const name of extractTopLevelFunctionNames(body))
|
|
514
514
|
declared.add(name);
|
|
515
515
|
}
|
|
516
516
|
return Array.from(declared);
|
|
@@ -1066,11 +1066,10 @@ export function parseFile(source, options = {}) {
|
|
|
1066
1066
|
if (workerEnvAliases.length > 0 && kind !== 'route') {
|
|
1067
1067
|
throw buildEnvAccessError(kind, options.filePath, `Imported env from cloudflare:workers in a ${kind} script.`);
|
|
1068
1068
|
}
|
|
1069
|
-
|
|
1070
|
-
const serverScriptRefs = new Set(collectReferencedIdentifiers(
|
|
1069
|
+
// TypeScript source works directly for reference collection
|
|
1070
|
+
const serverScriptRefs = new Set(collectReferencedIdentifiers(scriptBody));
|
|
1071
1071
|
if (loadFunction) {
|
|
1072
|
-
const
|
|
1073
|
-
for (const ref of collectReferencedIdentifiers(transpiledLoadFunction))
|
|
1072
|
+
for (const ref of collectReferencedIdentifiers(loadFunction))
|
|
1074
1073
|
serverScriptRefs.add(ref);
|
|
1075
1074
|
}
|
|
1076
1075
|
const leakedClientScriptBindings = routeClientImportBindings.filter((name) => serverScriptRefs.has(name));
|
|
@@ -1079,10 +1078,10 @@ export function parseFile(source, options = {}) {
|
|
|
1079
1078
|
`Top-level $client imports cannot be used in server route code: ${leakedClientScriptBindings.join(', ')}.\n` +
|
|
1080
1079
|
`Move this usage to a client event handler or client bridge code, or import from $shared instead.`);
|
|
1081
1080
|
}
|
|
1082
|
-
const topLevelVars = extractTopLevelDataVars(
|
|
1081
|
+
const topLevelVars = extractTopLevelDataVars(scriptBody);
|
|
1083
1082
|
for (const v of topLevelVars)
|
|
1084
1083
|
dataVars.push(v);
|
|
1085
|
-
const topLevelFns = extractTopLevelFunctionNames(
|
|
1084
|
+
const topLevelFns = extractTopLevelFunctionNames(scriptBody);
|
|
1086
1085
|
for (const fn of topLevelFns) {
|
|
1087
1086
|
if (!dataVars.includes(fn))
|
|
1088
1087
|
dataVars.push(fn);
|
|
@@ -1258,4 +1257,4 @@ export function parseFile(source, options = {}) {
|
|
|
1258
1257
|
devAliases,
|
|
1259
1258
|
};
|
|
1260
1259
|
}
|
|
1261
|
-
|
|
1260
|
+
// TypeScript transpilation removed — wrangler's esbuild handles it
|