@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.
Files changed (36) hide show
  1. package/README.md +168 -11
  2. package/dist/cli.js +13 -13
  3. package/dist/compiler/client-module-pipeline.js +5 -5
  4. package/dist/compiler/compiler-shared.d.ts +18 -0
  5. package/dist/compiler/component-pipeline.js +4 -9
  6. package/dist/compiler/config-reading.d.ts +2 -1
  7. package/dist/compiler/config-reading.js +57 -0
  8. package/dist/compiler/durable-object-pipeline.js +1 -1
  9. package/dist/compiler/import-linking.js +2 -1
  10. package/dist/compiler/index.d.ts +6 -6
  11. package/dist/compiler/index.js +54 -22
  12. package/dist/compiler/layout-pipeline.js +6 -6
  13. package/dist/compiler/parser.js +10 -11
  14. package/dist/compiler/root-layout-pipeline.js +444 -429
  15. package/dist/compiler/route-pipeline.js +36 -41
  16. package/dist/compiler/route-state-pipeline.d.ts +1 -0
  17. package/dist/compiler/route-state-pipeline.js +3 -3
  18. package/dist/compiler/routes-module-feature-blocks.js +63 -63
  19. package/dist/compiler/routes-module-runtime-shell.js +65 -55
  20. package/dist/compiler/routes-module-types.d.ts +2 -1
  21. package/dist/compiler/server-module-pipeline.js +1 -1
  22. package/dist/compiler/template.js +24 -15
  23. package/dist/compiler/worker-output-pipeline.js +2 -2
  24. package/dist/runtime/context.d.ts +4 -0
  25. package/dist/runtime/context.js +40 -2
  26. package/dist/runtime/do.js +21 -6
  27. package/dist/runtime/generated-worker.d.ts +22 -0
  28. package/dist/runtime/generated-worker.js +154 -23
  29. package/dist/runtime/index.d.ts +3 -1
  30. package/dist/runtime/index.js +1 -0
  31. package/dist/runtime/router.d.ts +5 -1
  32. package/dist/runtime/router.js +116 -31
  33. package/dist/runtime/security.d.ts +101 -0
  34. package/dist/runtime/security.js +298 -0
  35. package/dist/runtime/types.d.ts +21 -0
  36. package/package.json +1 -1
@@ -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
- * Compile a project's src/routes/ into .kuratchi/routes.js
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 } ?" an object with a fetch() method
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.js ? the stable wrangler entry point that
48
- * re-exports everything from routes.js (default fetch handler + named DO class exports).
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 (fs.existsSync(layoutFile)) {
75
- const layoutImportSource = fs.readFileSync(layoutFile, 'utf-8');
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 + '.js');
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(/\.js$/, '');
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 + '.js');
141
- const aliasCode = `// Auto-generated alias for .do handler\nexport * from './${handler.fileName}.js';\n`;
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(/\.js$/, ''));
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.js');
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.js
259
- const outFile = options.outFile ?? path.join(projectDir, '.kuratchi', 'routes.js');
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.js ? the stable wrangler entry point.
266
- // routes.js already exports the default fetch handler and all named DO classes;
267
- // worker.js explicitly re-exports them so wrangler.jsonc can reference a
268
- // stable filename while routes.js is freely regenerated.
269
- const workerFile = path.join(outDir, 'worker.js');
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) => `__html += \`${css}\\n\`;`);
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); };
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); };
85
+ ${layoutHeadAssignment}
86
+ ${layoutScriptBody ? layoutScriptBody + '\n ' : ''}${finalLayoutBody}
87
+ return __html;
88
88
  }`,
89
89
  componentNames,
90
90
  importParsed,
@@ -505,12 +505,12 @@ function collectTemplateClientDeclaredNames(template) {
505
505
  const body = match[2] || '';
506
506
  if (!isExecutableTemplateScript(attrs))
507
507
  continue;
508
- const transpiled = transpileTypeScript(body, 'template-client-script.ts');
509
- for (const name of extractTopLevelImportNames(transpiled))
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(transpiled))
511
+ for (const name of extractTopLevelDataVars(body))
512
512
  declared.add(name);
513
- for (const name of extractTopLevelFunctionNames(transpiled))
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
- const transpiledScriptBody = transpileTypeScript(scriptBody, 'route-script.ts');
1070
- const serverScriptRefs = new Set(collectReferencedIdentifiers(transpiledScriptBody));
1069
+ // TypeScript source works directly for reference collection
1070
+ const serverScriptRefs = new Set(collectReferencedIdentifiers(scriptBody));
1071
1071
  if (loadFunction) {
1072
- const transpiledLoadFunction = transpileTypeScript(loadFunction, 'route-load.ts');
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(transpiledScriptBody);
1081
+ const topLevelVars = extractTopLevelDataVars(scriptBody);
1083
1082
  for (const v of topLevelVars)
1084
1083
  dataVars.push(v);
1085
- const topLevelFns = extractTopLevelFunctionNames(transpiledScriptBody);
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
- import { transpileTypeScript } from './transpile.js';
1260
+ // TypeScript transpilation removed wrangler's esbuild handles it