@kuratchi/js 0.0.15 → 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 (70) hide show
  1. package/README.md +160 -1
  2. package/dist/cli.js +78 -45
  3. package/dist/compiler/api-route-pipeline.d.ts +8 -0
  4. package/dist/compiler/api-route-pipeline.js +23 -0
  5. package/dist/compiler/asset-pipeline.d.ts +7 -0
  6. package/dist/compiler/asset-pipeline.js +33 -0
  7. package/dist/compiler/client-module-pipeline.d.ts +25 -0
  8. package/dist/compiler/client-module-pipeline.js +257 -0
  9. package/dist/compiler/compiler-shared.d.ts +73 -0
  10. package/dist/compiler/compiler-shared.js +4 -0
  11. package/dist/compiler/component-pipeline.d.ts +15 -0
  12. package/dist/compiler/component-pipeline.js +158 -0
  13. package/dist/compiler/config-reading.d.ts +12 -0
  14. package/dist/compiler/config-reading.js +380 -0
  15. package/dist/compiler/convention-discovery.d.ts +9 -0
  16. package/dist/compiler/convention-discovery.js +83 -0
  17. package/dist/compiler/durable-object-pipeline.d.ts +9 -0
  18. package/dist/compiler/durable-object-pipeline.js +255 -0
  19. package/dist/compiler/error-page-pipeline.d.ts +1 -0
  20. package/dist/compiler/error-page-pipeline.js +16 -0
  21. package/dist/compiler/import-linking.d.ts +36 -0
  22. package/dist/compiler/import-linking.js +140 -0
  23. package/dist/compiler/index.d.ts +7 -7
  24. package/dist/compiler/index.js +181 -3321
  25. package/dist/compiler/layout-pipeline.d.ts +31 -0
  26. package/dist/compiler/layout-pipeline.js +155 -0
  27. package/dist/compiler/page-route-pipeline.d.ts +16 -0
  28. package/dist/compiler/page-route-pipeline.js +62 -0
  29. package/dist/compiler/parser.d.ts +4 -0
  30. package/dist/compiler/parser.js +436 -55
  31. package/dist/compiler/root-layout-pipeline.d.ts +10 -0
  32. package/dist/compiler/root-layout-pipeline.js +532 -0
  33. package/dist/compiler/route-discovery.d.ts +7 -0
  34. package/dist/compiler/route-discovery.js +87 -0
  35. package/dist/compiler/route-pipeline.d.ts +57 -0
  36. package/dist/compiler/route-pipeline.js +291 -0
  37. package/dist/compiler/route-state-pipeline.d.ts +26 -0
  38. package/dist/compiler/route-state-pipeline.js +139 -0
  39. package/dist/compiler/routes-module-feature-blocks.d.ts +2 -0
  40. package/dist/compiler/routes-module-feature-blocks.js +330 -0
  41. package/dist/compiler/routes-module-pipeline.d.ts +2 -0
  42. package/dist/compiler/routes-module-pipeline.js +6 -0
  43. package/dist/compiler/routes-module-runtime-shell.d.ts +2 -0
  44. package/dist/compiler/routes-module-runtime-shell.js +91 -0
  45. package/dist/compiler/routes-module-types.d.ts +45 -0
  46. package/dist/compiler/routes-module-types.js +1 -0
  47. package/dist/compiler/script-transform.d.ts +16 -0
  48. package/dist/compiler/script-transform.js +218 -0
  49. package/dist/compiler/server-module-pipeline.d.ts +13 -0
  50. package/dist/compiler/server-module-pipeline.js +124 -0
  51. package/dist/compiler/template.d.ts +13 -1
  52. package/dist/compiler/template.js +337 -71
  53. package/dist/compiler/worker-output-pipeline.d.ts +13 -0
  54. package/dist/compiler/worker-output-pipeline.js +37 -0
  55. package/dist/compiler/wrangler-sync.d.ts +14 -0
  56. package/dist/compiler/wrangler-sync.js +185 -0
  57. package/dist/runtime/app.js +15 -3
  58. package/dist/runtime/context.d.ts +4 -0
  59. package/dist/runtime/context.js +40 -2
  60. package/dist/runtime/do.js +21 -6
  61. package/dist/runtime/generated-worker.d.ts +55 -0
  62. package/dist/runtime/generated-worker.js +543 -0
  63. package/dist/runtime/index.d.ts +4 -1
  64. package/dist/runtime/index.js +2 -0
  65. package/dist/runtime/router.d.ts +6 -1
  66. package/dist/runtime/router.js +125 -31
  67. package/dist/runtime/security.d.ts +101 -0
  68. package/dist/runtime/security.js +298 -0
  69. package/dist/runtime/types.d.ts +29 -2
  70. package/package.json +5 -1
@@ -0,0 +1,31 @@
1
+ import { type ParsedFile } from './parser.js';
2
+ import type { ComponentCompiler } from './component-pipeline.js';
3
+ import type { ClientModuleCompiler } from './client-module-pipeline.js';
4
+ export interface LayoutBuildPlan {
5
+ compiledLayout: string | null;
6
+ componentNames: Map<string, string>;
7
+ importParsed: ParsedFile | null;
8
+ }
9
+ export declare function compileLayoutPlan(opts: {
10
+ renderSource: string;
11
+ importSource: string;
12
+ layoutFile: string;
13
+ isDev: boolean;
14
+ componentCompiler: ComponentCompiler;
15
+ clientModuleCompiler?: ClientModuleCompiler;
16
+ assetsPrefix?: string;
17
+ clientScopeId?: string;
18
+ }): LayoutBuildPlan;
19
+ export declare function finalizeLayoutPlan(opts: {
20
+ plan: LayoutBuildPlan;
21
+ layoutFile: string;
22
+ projectDir: string;
23
+ resolveCompiledImportPath: (origPath: string, importerDir: string, outFileDir: string) => string;
24
+ allocateModuleId: () => string;
25
+ pushImport: (statement: string) => void;
26
+ componentCompiler: ComponentCompiler;
27
+ }): {
28
+ compiledLayout: string | null;
29
+ compiledLayoutActions: string | null;
30
+ isLayoutAsync: boolean;
31
+ };
@@ -0,0 +1,155 @@
1
+ import * as path from 'node:path';
2
+ import { parseFile, stripTopLevelImports } from './parser.js';
3
+ import { compileTemplate } from './template.js';
4
+ import { parseImportStatement, parseNamedImportBindings } from './import-linking.js';
5
+ import { buildDevAliasDeclarations, rewriteImportedFunctionCalls } from './script-transform.js';
6
+ const HEAD_MARKER = '<!--__KURATCHI_HEAD__-->';
7
+ function injectLayoutHeadPlaceholder(source, marker) {
8
+ const lower = source.toLowerCase();
9
+ const idx = lower.lastIndexOf('</head>');
10
+ if (idx !== -1) {
11
+ return source.slice(0, idx) + `${marker}\n` + source.slice(idx);
12
+ }
13
+ return `${marker}\n${source}`;
14
+ }
15
+ function buildLayoutBrowserImportEntries(importParsed, layoutFile) {
16
+ const importerDir = path.dirname(layoutFile);
17
+ const entries = new Map();
18
+ const addEntry = (line) => {
19
+ if (!entries.has(line)) {
20
+ entries.set(line, { line, importerDir });
21
+ }
22
+ };
23
+ for (const line of importParsed.routeClientImports) {
24
+ addEntry(line);
25
+ }
26
+ for (const line of importParsed.serverImports) {
27
+ const parsed = parseImportStatement(line);
28
+ if (parsed.moduleSpecifier?.startsWith('$shared/')) {
29
+ addEntry(line);
30
+ }
31
+ }
32
+ return Array.from(entries.values());
33
+ }
34
+ export function compileLayoutPlan(opts) {
35
+ const importParsed = parseFile(opts.importSource, { kind: 'layout', filePath: opts.layoutFile });
36
+ let renderTemplateSource = opts.renderSource;
37
+ const importScriptMatch = opts.importSource.match(/^(\s*)<script(\s[^>]*)?\s*>([\s\S]*?)<\/script>/);
38
+ if (importScriptMatch) {
39
+ const body = importScriptMatch[3].trim();
40
+ const isServerScript = !/\$\s*:/.test(body);
41
+ if (isServerScript) {
42
+ renderTemplateSource = opts.renderSource.replace(importScriptMatch[0], '').trim();
43
+ }
44
+ }
45
+ const componentNames = opts.componentCompiler.collectComponentMap(importParsed.componentImports);
46
+ const browserImportEntries = opts.clientModuleCompiler
47
+ ? buildLayoutBrowserImportEntries(importParsed, opts.layoutFile)
48
+ : [];
49
+ const clientRouteRegistry = opts.clientModuleCompiler && opts.clientScopeId && browserImportEntries.length > 0
50
+ ? opts.clientModuleCompiler.createRegistry(opts.clientScopeId, browserImportEntries)
51
+ : null;
52
+ const hasClientBindings = !!clientRouteRegistry?.hasBindings();
53
+ const hasLayoutScript = !!(importParsed.script && (componentNames.size > 0 || importParsed.hasLoad));
54
+ if (hasLayoutScript || hasClientBindings) {
55
+ let layoutTemplate = injectLayoutHeadPlaceholder(renderTemplateSource, '{@raw __layoutHead}');
56
+ layoutTemplate = layoutTemplate.replace(/<slot\s*><\/slot>/g, '{@raw __content}');
57
+ layoutTemplate = layoutTemplate.replace(/<slot\s*\/>/g, '{@raw __content}');
58
+ const layoutActionNames = new Set(importParsed.actionFunctions);
59
+ for (const fnName of opts.componentCompiler.resolveActionProps(importParsed.template, componentNames)) {
60
+ layoutActionNames.add(fnName);
61
+ }
62
+ const layoutRenderBody = compileTemplate(layoutTemplate, componentNames, layoutActionNames, undefined, { clientRouteRegistry: clientRouteRegistry ?? undefined });
63
+ const layoutComponentStyles = opts.componentCompiler.collectStyles(componentNames);
64
+ let finalLayoutBody = layoutRenderBody;
65
+ if (layoutComponentStyles.length > 0) {
66
+ const lines = layoutRenderBody.split('\n');
67
+ const styleLines = layoutComponentStyles.map((css) => `__parts.push(\`${css}\\n\`);`);
68
+ finalLayoutBody = [lines[0], ...styleLines, ...lines.slice(1)].join('\n');
69
+ }
70
+ let layoutScriptBody = importParsed.script
71
+ ? stripTopLevelImports(importParsed.script)
72
+ : '';
73
+ const layoutDevDecls = buildDevAliasDeclarations(importParsed.devAliases, opts.isDev);
74
+ layoutScriptBody = [layoutDevDecls, layoutScriptBody].filter(Boolean).join('\n');
75
+ const clientEntryAsset = clientRouteRegistry?.buildEntryAsset() ?? null;
76
+ const clientModuleHref = clientEntryAsset && opts.assetsPrefix
77
+ ? `${opts.assetsPrefix}${clientEntryAsset.assetName}`
78
+ : null;
79
+ const layoutHeadAssignment = clientModuleHref
80
+ ? `const __layoutHead = __head + ${JSON.stringify(`<script type="module" src="${clientModuleHref}"></script>\n`)};`
81
+ : `const __layoutHead = __head;`;
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;
88
+ }`,
89
+ componentNames,
90
+ importParsed,
91
+ };
92
+ }
93
+ const slotMarker = '<slot></slot>';
94
+ const layoutSource = injectLayoutHeadPlaceholder(renderTemplateSource, HEAD_MARKER);
95
+ const slotIdx = layoutSource.indexOf(slotMarker);
96
+ if (slotIdx === -1) {
97
+ throw new Error('layout.html must contain <slot></slot>');
98
+ }
99
+ const escLayout = (source) => source.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, () => '\\$');
100
+ const before = escLayout(layoutSource.slice(0, slotIdx));
101
+ const after = escLayout(layoutSource.slice(slotIdx + slotMarker.length));
102
+ return {
103
+ compiledLayout: `const __layoutBefore = \`${before}\`;\nconst __layoutAfter = \`${after}\`;\nfunction __layout(content, head = '') {\n return (__layoutBefore + content + __layoutAfter).replace(${JSON.stringify(HEAD_MARKER)}, head);\n}`,
104
+ componentNames,
105
+ importParsed,
106
+ };
107
+ }
108
+ export function finalizeLayoutPlan(opts) {
109
+ let compiledLayout = opts.plan.compiledLayout;
110
+ let compiledLayoutActions = null;
111
+ if (compiledLayout && opts.plan.importParsed && opts.plan.importParsed.serverImports.length > 0) {
112
+ const layoutFileDir = path.dirname(opts.layoutFile);
113
+ const outFileDir = path.join(opts.projectDir, '.kuratchi');
114
+ const layoutFnToModule = {};
115
+ for (const imp of opts.plan.importParsed.serverImports) {
116
+ const pathMatch = imp.match(/from\s+['"]([^'"]+)['"]/);
117
+ if (!pathMatch)
118
+ continue;
119
+ const origPath = pathMatch[1];
120
+ const importPath = opts.resolveCompiledImportPath(origPath, layoutFileDir, outFileDir);
121
+ const moduleId = opts.allocateModuleId();
122
+ opts.pushImport(`import * as ${moduleId} from '${importPath}';`);
123
+ for (const binding of parseNamedImportBindings(imp)) {
124
+ layoutFnToModule[binding.local] = moduleId;
125
+ }
126
+ const starMatch = imp.match(/import\s*\*\s*as\s+(\w+)/);
127
+ if (starMatch) {
128
+ layoutFnToModule[starMatch[1]] = moduleId;
129
+ }
130
+ }
131
+ compiledLayout = rewriteImportedFunctionCalls(compiledLayout, layoutFnToModule);
132
+ const layoutActionNames = new Set(opts.plan.importParsed.actionFunctions);
133
+ for (const fnName of opts.componentCompiler.resolveActionProps(opts.plan.importParsed.template, opts.plan.componentNames)) {
134
+ layoutActionNames.add(fnName);
135
+ }
136
+ if (layoutActionNames.size > 0) {
137
+ const actionEntries = Array.from(layoutActionNames)
138
+ .filter((fnName) => fnName in layoutFnToModule)
139
+ .map((fnName) => `'${fnName}': ${layoutFnToModule[fnName]}.${fnName}`)
140
+ .join(', ');
141
+ if (actionEntries) {
142
+ compiledLayoutActions = `{ ${actionEntries} }`;
143
+ }
144
+ }
145
+ }
146
+ const isLayoutAsync = !!(compiledLayout && /\bawait\b/.test(compiledLayout));
147
+ if (compiledLayout && isLayoutAsync) {
148
+ compiledLayout = compiledLayout.replace(/^function __layout\(/, 'async function __layout(');
149
+ }
150
+ return {
151
+ compiledLayout,
152
+ compiledLayoutActions,
153
+ isLayoutAsync,
154
+ };
155
+ }
@@ -0,0 +1,16 @@
1
+ import type { ComponentCompiler } from './component-pipeline.js';
2
+ import type { ClientModuleCompiler } from './client-module-pipeline.js';
3
+ import type { RouteStatePlan } from './route-state-pipeline.js';
4
+ export declare function compilePageRoute(opts: {
5
+ pattern: string;
6
+ routeIndex: number;
7
+ projectDir: string;
8
+ isDev: boolean;
9
+ routeState: RouteStatePlan;
10
+ componentCompiler: ComponentCompiler;
11
+ clientModuleCompiler: ClientModuleCompiler;
12
+ assetsPrefix: string;
13
+ resolveCompiledImportPath: (origPath: string, importerDir: string, outFileDir: string) => string;
14
+ allocateModuleId: () => string;
15
+ pushImport: (statement: string) => void;
16
+ }): string;
@@ -0,0 +1,62 @@
1
+ import * as path from 'node:path';
2
+ import { compileTemplate, splitTemplateRenderSections } from './template.js';
3
+ import { analyzeRouteBuild, emitRouteObject } from './route-pipeline.js';
4
+ import { linkRouteServerImports } from './import-linking.js';
5
+ export function compilePageRoute(opts) {
6
+ const outFileDir = path.join(opts.projectDir, '.kuratchi');
7
+ const routeModuleLinkPlan = linkRouteServerImports({
8
+ routeServerImportEntries: opts.routeState.routeServerImportEntries,
9
+ routeClientImportEntries: opts.routeState.routeClientImportEntries,
10
+ actionFunctions: opts.routeState.mergedParsed.actionFunctions,
11
+ pollFunctions: opts.routeState.mergedParsed.pollFunctions,
12
+ dataGetQueries: opts.routeState.mergedParsed.dataGetQueries,
13
+ routeScriptReferenceSource: opts.routeState.routeScriptReferenceSource,
14
+ resolveCompiledImportPath: opts.resolveCompiledImportPath,
15
+ outFileDir,
16
+ allocateModuleId: opts.allocateModuleId,
17
+ });
18
+ for (const statement of routeModuleLinkPlan.importStatements) {
19
+ opts.pushImport(statement);
20
+ }
21
+ opts.routeState.routeImportDecls.push(...routeModuleLinkPlan.routeImportDecls);
22
+ const fnToModule = routeModuleLinkPlan.fnToModule;
23
+ const routeComponentNames = opts.componentCompiler.collectComponentMap(opts.routeState.mergedParsed.componentImports);
24
+ for (const routeFnName of opts.componentCompiler.resolveActionProps(opts.routeState.effectiveTemplate, routeComponentNames, (fnName) => fnName in fnToModule)) {
25
+ if (!opts.routeState.mergedParsed.actionFunctions.includes(routeFnName)) {
26
+ opts.routeState.mergedParsed.actionFunctions.push(routeFnName);
27
+ }
28
+ }
29
+ const dataVarsSet = new Set(opts.routeState.mergedParsed.dataVars);
30
+ const actionNames = new Set(opts.routeState.mergedParsed.actionFunctions.filter((fnName) => fnName in fnToModule || dataVarsSet.has(fnName)));
31
+ const rpcNameMap = new Map();
32
+ let rpcCounter = 0;
33
+ for (const fnName of opts.routeState.mergedParsed.pollFunctions) {
34
+ if (!rpcNameMap.has(fnName)) {
35
+ rpcNameMap.set(fnName, `rpc_${opts.routeIndex}_${rpcCounter++}`);
36
+ }
37
+ }
38
+ for (const query of opts.routeState.mergedParsed.dataGetQueries) {
39
+ if (!rpcNameMap.has(query.fnName)) {
40
+ rpcNameMap.set(query.fnName, `rpc_${opts.routeIndex}_${rpcCounter++}`);
41
+ }
42
+ query.rpcId = rpcNameMap.get(query.fnName);
43
+ }
44
+ const clientRouteRegistry = opts.clientModuleCompiler.createRouteRegistry(opts.routeIndex, opts.routeState.routeBrowserImportEntries);
45
+ const renderSections = splitTemplateRenderSections(opts.routeState.effectiveTemplate);
46
+ const renderBody = compileTemplate(renderSections.bodyTemplate, routeComponentNames, actionNames, rpcNameMap, { emitCall: '__emit', enableFragmentManifest: true, clientRouteRegistry });
47
+ const renderHeadBody = compileTemplate(renderSections.headTemplate, routeComponentNames, actionNames, rpcNameMap, { clientRouteRegistry });
48
+ const clientEntryAsset = clientRouteRegistry.buildEntryAsset();
49
+ const clientModuleHref = clientEntryAsset ? `${opts.assetsPrefix}${clientEntryAsset.assetName}` : null;
50
+ const routePlan = analyzeRouteBuild({
51
+ pattern: opts.pattern,
52
+ renderBody,
53
+ renderHeadBody,
54
+ isDev: opts.isDev,
55
+ parsed: opts.routeState.mergedParsed,
56
+ fnToModule,
57
+ rpcNameMap,
58
+ componentStyles: opts.componentCompiler.collectStyles(routeComponentNames),
59
+ clientModuleHref,
60
+ });
61
+ return emitRouteObject(routePlan);
62
+ }
@@ -36,6 +36,10 @@ export interface ParsedFile {
36
36
  }>;
37
37
  /** Imports found in a top-level client script block */
38
38
  clientImports: string[];
39
+ /** Top-level route/layout imports from $client/* */
40
+ routeClientImports: string[];
41
+ /** Local binding names introduced by top-level $client/* imports */
42
+ routeClientImportBindings: string[];
39
43
  /** Top-level names returned from explicit load() */
40
44
  loadReturnVars: string[];
41
45
  /** Local aliases for Cloudflare Workers env imported from cloudflare:workers */