@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.
- package/README.md +160 -1
- package/dist/cli.js +78 -45
- package/dist/compiler/api-route-pipeline.d.ts +8 -0
- package/dist/compiler/api-route-pipeline.js +23 -0
- package/dist/compiler/asset-pipeline.d.ts +7 -0
- package/dist/compiler/asset-pipeline.js +33 -0
- package/dist/compiler/client-module-pipeline.d.ts +25 -0
- package/dist/compiler/client-module-pipeline.js +257 -0
- package/dist/compiler/compiler-shared.d.ts +73 -0
- package/dist/compiler/compiler-shared.js +4 -0
- package/dist/compiler/component-pipeline.d.ts +15 -0
- package/dist/compiler/component-pipeline.js +158 -0
- package/dist/compiler/config-reading.d.ts +12 -0
- package/dist/compiler/config-reading.js +380 -0
- package/dist/compiler/convention-discovery.d.ts +9 -0
- package/dist/compiler/convention-discovery.js +83 -0
- package/dist/compiler/durable-object-pipeline.d.ts +9 -0
- package/dist/compiler/durable-object-pipeline.js +255 -0
- package/dist/compiler/error-page-pipeline.d.ts +1 -0
- package/dist/compiler/error-page-pipeline.js +16 -0
- package/dist/compiler/import-linking.d.ts +36 -0
- package/dist/compiler/import-linking.js +140 -0
- package/dist/compiler/index.d.ts +7 -7
- package/dist/compiler/index.js +181 -3321
- package/dist/compiler/layout-pipeline.d.ts +31 -0
- package/dist/compiler/layout-pipeline.js +155 -0
- package/dist/compiler/page-route-pipeline.d.ts +16 -0
- package/dist/compiler/page-route-pipeline.js +62 -0
- package/dist/compiler/parser.d.ts +4 -0
- package/dist/compiler/parser.js +436 -55
- package/dist/compiler/root-layout-pipeline.d.ts +10 -0
- package/dist/compiler/root-layout-pipeline.js +532 -0
- package/dist/compiler/route-discovery.d.ts +7 -0
- package/dist/compiler/route-discovery.js +87 -0
- package/dist/compiler/route-pipeline.d.ts +57 -0
- package/dist/compiler/route-pipeline.js +291 -0
- package/dist/compiler/route-state-pipeline.d.ts +26 -0
- package/dist/compiler/route-state-pipeline.js +139 -0
- package/dist/compiler/routes-module-feature-blocks.d.ts +2 -0
- package/dist/compiler/routes-module-feature-blocks.js +330 -0
- package/dist/compiler/routes-module-pipeline.d.ts +2 -0
- package/dist/compiler/routes-module-pipeline.js +6 -0
- package/dist/compiler/routes-module-runtime-shell.d.ts +2 -0
- package/dist/compiler/routes-module-runtime-shell.js +91 -0
- package/dist/compiler/routes-module-types.d.ts +45 -0
- package/dist/compiler/routes-module-types.js +1 -0
- package/dist/compiler/script-transform.d.ts +16 -0
- package/dist/compiler/script-transform.js +218 -0
- package/dist/compiler/server-module-pipeline.d.ts +13 -0
- package/dist/compiler/server-module-pipeline.js +124 -0
- package/dist/compiler/template.d.ts +13 -1
- package/dist/compiler/template.js +337 -71
- package/dist/compiler/worker-output-pipeline.d.ts +13 -0
- package/dist/compiler/worker-output-pipeline.js +37 -0
- package/dist/compiler/wrangler-sync.d.ts +14 -0
- package/dist/compiler/wrangler-sync.js +185 -0
- package/dist/runtime/app.js +15 -3
- 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 +55 -0
- package/dist/runtime/generated-worker.js +543 -0
- package/dist/runtime/index.d.ts +4 -1
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/router.d.ts +6 -1
- package/dist/runtime/router.js +125 -31
- package/dist/runtime/security.d.ts +101 -0
- package/dist/runtime/security.js +298 -0
- package/dist/runtime/types.d.ts +29 -2
- package/package.json +5 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { toSafeIdentifier, } from './compiler-shared.js';
|
|
4
|
+
import { discoverFilesWithSuffix } from './convention-discovery.js';
|
|
5
|
+
export function discoverDurableObjects(srcDir, configDoEntries, ormDatabases) {
|
|
6
|
+
const serverDir = path.join(srcDir, 'server');
|
|
7
|
+
const legacyDir = path.join(srcDir, 'durable-objects');
|
|
8
|
+
const serverDoFiles = discoverFilesWithSuffix(serverDir, '.do.ts');
|
|
9
|
+
const legacyDoFiles = discoverFilesWithSuffix(legacyDir, '.ts');
|
|
10
|
+
const discoveredFiles = Array.from(new Set([...serverDoFiles, ...legacyDoFiles]));
|
|
11
|
+
if (discoveredFiles.length === 0) {
|
|
12
|
+
return { config: configDoEntries, handlers: [] };
|
|
13
|
+
}
|
|
14
|
+
const configByBinding = new Map();
|
|
15
|
+
for (const entry of configDoEntries) {
|
|
16
|
+
configByBinding.set(entry.binding, entry);
|
|
17
|
+
}
|
|
18
|
+
const handlers = [];
|
|
19
|
+
const discoveredConfig = [];
|
|
20
|
+
const fileNameToAbsPath = new Map();
|
|
21
|
+
const seenBindings = new Set();
|
|
22
|
+
for (const absPath of discoveredFiles) {
|
|
23
|
+
const file = path.basename(absPath);
|
|
24
|
+
const source = fs.readFileSync(absPath, 'utf-8');
|
|
25
|
+
if (!/extends\s+DurableObject\b/.test(source))
|
|
26
|
+
continue;
|
|
27
|
+
const classMatch = source.match(/export\s+default\s+class\s+(\w+)\s+extends\s+DurableObject/);
|
|
28
|
+
const className = classMatch?.[1] ?? null;
|
|
29
|
+
if (!className)
|
|
30
|
+
continue;
|
|
31
|
+
const bindingMatch = source.match(/static\s+binding\s*=\s*['"](\w+)['"]/);
|
|
32
|
+
const baseName = file.replace(/\.do\.ts$/, '').replace(/\.ts$/, '');
|
|
33
|
+
const derivedBinding = baseName.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase() + '_DO';
|
|
34
|
+
const binding = bindingMatch?.[1] ?? derivedBinding;
|
|
35
|
+
if (seenBindings.has(binding)) {
|
|
36
|
+
throw new Error(`[KuratchiJS] Duplicate DO binding '${binding}' detected. Use 'static binding = "UNIQUE_NAME"' in one of the classes.`);
|
|
37
|
+
}
|
|
38
|
+
seenBindings.add(binding);
|
|
39
|
+
const classMethods = extractClassMethods(source, className);
|
|
40
|
+
const fileName = file.replace(/\.ts$/, '');
|
|
41
|
+
const existing = fileNameToAbsPath.get(fileName);
|
|
42
|
+
if (existing && existing !== absPath) {
|
|
43
|
+
throw new Error(`[KuratchiJS] Duplicate DO handler file name '${fileName}.ts' detected:\n- ${existing}\n- ${absPath}\nRename one file or move it to avoid proxy name collision.`);
|
|
44
|
+
}
|
|
45
|
+
fileNameToAbsPath.set(fileName, absPath);
|
|
46
|
+
const configEntry = configByBinding.get(binding);
|
|
47
|
+
void ormDatabases;
|
|
48
|
+
discoveredConfig.push({
|
|
49
|
+
binding,
|
|
50
|
+
className,
|
|
51
|
+
stubId: configEntry?.stubId,
|
|
52
|
+
files: [file],
|
|
53
|
+
});
|
|
54
|
+
handlers.push({
|
|
55
|
+
fileName,
|
|
56
|
+
absPath,
|
|
57
|
+
binding,
|
|
58
|
+
mode: 'class',
|
|
59
|
+
className,
|
|
60
|
+
classMethods,
|
|
61
|
+
exportedFunctions: [],
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return { config: discoveredConfig, handlers };
|
|
65
|
+
}
|
|
66
|
+
export function generateHandlerProxy(handler, opts) {
|
|
67
|
+
const doDir = path.join(opts.projectDir, '.kuratchi', 'do');
|
|
68
|
+
const origRelPath = path.relative(doDir, handler.absPath).replace(/\\/g, '/');
|
|
69
|
+
const handlerLocal = `__handler_${toSafeIdentifier(handler.fileName)}`;
|
|
70
|
+
const lifecycle = new Set(['constructor', 'fetch', 'alarm', 'webSocketMessage', 'webSocketClose', 'webSocketError']);
|
|
71
|
+
const rpcFunctions = handler.classMethods
|
|
72
|
+
.filter((method) => method.visibility === 'public' && !method.name.startsWith('_') && !lifecycle.has(method.name))
|
|
73
|
+
.map((method) => method.name);
|
|
74
|
+
const methods = handler.classMethods.map((method) => ({ ...method }));
|
|
75
|
+
const methodMap = new Map(methods.map((method) => [method.name, method]));
|
|
76
|
+
let changed = true;
|
|
77
|
+
while (changed) {
|
|
78
|
+
changed = false;
|
|
79
|
+
for (const method of methods) {
|
|
80
|
+
if (method.hasWorkerContextCalls)
|
|
81
|
+
continue;
|
|
82
|
+
for (const called of method.callsThisMethods) {
|
|
83
|
+
const target = methodMap.get(called);
|
|
84
|
+
if (target?.hasWorkerContextCalls) {
|
|
85
|
+
method.hasWorkerContextCalls = true;
|
|
86
|
+
changed = true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const workerContextMethods = methods
|
|
93
|
+
.filter((method) => method.visibility === 'public' && method.hasWorkerContextCalls)
|
|
94
|
+
.map((method) => method.name);
|
|
95
|
+
const asyncMethods = methods.filter((method) => method.isAsync).map((method) => method.name);
|
|
96
|
+
const lines = [
|
|
97
|
+
`// Auto-generated by KuratchiJS compiler �" do not edit.`,
|
|
98
|
+
`import { __getDoStub } from '${opts.runtimeDoImport}';`,
|
|
99
|
+
`import ${handlerLocal} from '${origRelPath}';`,
|
|
100
|
+
``,
|
|
101
|
+
`const __FD_TAG = '__kuratchi_form_data__';`,
|
|
102
|
+
`function __isPlainObject(__v) {`,
|
|
103
|
+
` if (!__v || typeof __v !== 'object') return false;`,
|
|
104
|
+
` const __proto = Object.getPrototypeOf(__v);`,
|
|
105
|
+
` return __proto === Object.prototype || __proto === null;`,
|
|
106
|
+
`}`,
|
|
107
|
+
`function __encodeArg(__v, __seen = new WeakSet()) {`,
|
|
108
|
+
` if (typeof FormData !== 'undefined' && __v instanceof FormData) {`,
|
|
109
|
+
` return { [__FD_TAG]: Array.from(__v.entries()) };`,
|
|
110
|
+
` }`,
|
|
111
|
+
` if (Array.isArray(__v)) return __v.map((__x) => __encodeArg(__x, __seen));`,
|
|
112
|
+
` if (__isPlainObject(__v)) {`,
|
|
113
|
+
` if (__seen.has(__v)) throw new Error('[KuratchiJS] Circular object passed to DO RPC');`,
|
|
114
|
+
` __seen.add(__v);`,
|
|
115
|
+
` const __out = {};`,
|
|
116
|
+
` for (const [__k, __val] of Object.entries(__v)) __out[__k] = __encodeArg(__val, __seen);`,
|
|
117
|
+
` __seen.delete(__v);`,
|
|
118
|
+
` return __out;`,
|
|
119
|
+
` }`,
|
|
120
|
+
` return __v;`,
|
|
121
|
+
`}`,
|
|
122
|
+
`function __decodeArg(__v) {`,
|
|
123
|
+
` if (Array.isArray(__v)) return __v.map(__decodeArg);`,
|
|
124
|
+
` if (__isPlainObject(__v)) {`,
|
|
125
|
+
` const __obj = __v;`,
|
|
126
|
+
` if (__FD_TAG in __obj) {`,
|
|
127
|
+
` const __fd = new FormData();`,
|
|
128
|
+
` const __entries = Array.isArray(__obj[__FD_TAG]) ? __obj[__FD_TAG] : [];`,
|
|
129
|
+
` for (const __pair of __entries) {`,
|
|
130
|
+
` if (Array.isArray(__pair) && __pair.length >= 2) __fd.append(String(__pair[0]), __pair[1]);`,
|
|
131
|
+
` }`,
|
|
132
|
+
` return __fd;`,
|
|
133
|
+
` }`,
|
|
134
|
+
` const __out = {};`,
|
|
135
|
+
` for (const [__k, __val] of Object.entries(__obj)) __out[__k] = __decodeArg(__val);`,
|
|
136
|
+
` return __out;`,
|
|
137
|
+
` }`,
|
|
138
|
+
` return __v;`,
|
|
139
|
+
`}`,
|
|
140
|
+
``,
|
|
141
|
+
];
|
|
142
|
+
if (workerContextMethods.length > 0) {
|
|
143
|
+
lines.push(`const __workerMethods = new Set(${JSON.stringify(workerContextMethods)});`);
|
|
144
|
+
lines.push(`const __asyncMethods = new Set(${JSON.stringify(asyncMethods)});`);
|
|
145
|
+
lines.push(`function __callWorkerMethod(__name, __args) {`);
|
|
146
|
+
lines.push(` const __self = new Proxy({}, {`);
|
|
147
|
+
lines.push(` get(_, __k) {`);
|
|
148
|
+
lines.push(` if (typeof __k !== 'string') return undefined;`);
|
|
149
|
+
lines.push(` if (__k === 'db') {`);
|
|
150
|
+
lines.push(` throw new Error("[KuratchiJS] Worker-executed DO method cannot use this.db directly. Move DB access into a non-public method and call it via this.<method>().");`);
|
|
151
|
+
lines.push(` }`);
|
|
152
|
+
lines.push(` if (__workerMethods.has(__k)) {`);
|
|
153
|
+
lines.push(` return (...__a) => ${handlerLocal}.prototype[__k].apply(__self, __a);`);
|
|
154
|
+
lines.push(` }`);
|
|
155
|
+
lines.push(` const __local = ${handlerLocal}.prototype[__k];`);
|
|
156
|
+
lines.push(` if (typeof __local === 'function' && !__asyncMethods.has(__k)) {`);
|
|
157
|
+
lines.push(` return (...__a) => __local.apply(__self, __a);`);
|
|
158
|
+
lines.push(` }`);
|
|
159
|
+
lines.push(` return async (...__a) => { const __s = await __getDoStub('${handler.binding}'); if (!__s) throw new Error('Not authenticated'); return __s[__k](...__a.map((__x) => __encodeArg(__x))); };`);
|
|
160
|
+
lines.push(` },`);
|
|
161
|
+
lines.push(` });`);
|
|
162
|
+
lines.push(` return ${handlerLocal}.prototype[__name].apply(__self, __args.map(__decodeArg));`);
|
|
163
|
+
lines.push(`}`);
|
|
164
|
+
lines.push(``);
|
|
165
|
+
}
|
|
166
|
+
for (const method of rpcFunctions) {
|
|
167
|
+
if (workerContextMethods.includes(method)) {
|
|
168
|
+
lines.push(`export async function ${method}(...a) { return __callWorkerMethod('${method}', a); }`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
lines.push(`export async function ${method}(...a) { const s = await __getDoStub('${handler.binding}'); if (!s) throw new Error('Not authenticated'); return s.${method}(...a.map((__x) => __encodeArg(__x))); }`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return lines.join('\n') + '\n';
|
|
175
|
+
}
|
|
176
|
+
function extractClassMethods(source, className) {
|
|
177
|
+
const classIdx = source.search(new RegExp(`class\\s+${className}\\s+extends\\s+DurableObject`));
|
|
178
|
+
if (classIdx === -1)
|
|
179
|
+
return [];
|
|
180
|
+
const braceStart = source.indexOf('{', classIdx);
|
|
181
|
+
if (braceStart === -1)
|
|
182
|
+
return [];
|
|
183
|
+
let depth = 0;
|
|
184
|
+
let braceEnd = braceStart;
|
|
185
|
+
for (let i = braceStart; i < source.length; i++) {
|
|
186
|
+
if (source[i] === '{')
|
|
187
|
+
depth++;
|
|
188
|
+
else if (source[i] === '}') {
|
|
189
|
+
depth--;
|
|
190
|
+
if (depth === 0) {
|
|
191
|
+
braceEnd = i;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const classBody = source.slice(braceStart + 1, braceEnd);
|
|
197
|
+
const methods = [];
|
|
198
|
+
const methodRegex = /^\s+(?:(public|private|protected)\s+)?(?:(static)\s+)?(?:(async)\s+)?([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*(?::[^{]+)?\{/gm;
|
|
199
|
+
const reserved = new Set([
|
|
200
|
+
'constructor', 'static', 'get', 'set',
|
|
201
|
+
'return', 'if', 'else', 'for', 'while', 'do', 'switch', 'case',
|
|
202
|
+
'throw', 'try', 'catch', 'finally', 'new', 'delete', 'typeof',
|
|
203
|
+
'void', 'instanceof', 'in', 'of', 'await', 'yield', 'const',
|
|
204
|
+
'let', 'var', 'function', 'class', 'import', 'export', 'default',
|
|
205
|
+
'break', 'continue', 'with', 'super', 'this',
|
|
206
|
+
]);
|
|
207
|
+
let match;
|
|
208
|
+
while ((match = methodRegex.exec(classBody)) !== null) {
|
|
209
|
+
const visibility = match[1] ?? 'public';
|
|
210
|
+
const isStatic = !!match[2];
|
|
211
|
+
const isAsync = !!match[3];
|
|
212
|
+
const name = match[4];
|
|
213
|
+
if (!name || isStatic || reserved.has(name))
|
|
214
|
+
continue;
|
|
215
|
+
const matchText = match[0] ?? '';
|
|
216
|
+
const openRel = matchText.lastIndexOf('{');
|
|
217
|
+
const openAbs = openRel >= 0 ? match.index + openRel : -1;
|
|
218
|
+
let hasWorkerContextCalls = false;
|
|
219
|
+
const callsThisMethods = [];
|
|
220
|
+
if (openAbs >= 0) {
|
|
221
|
+
let innerDepth = 0;
|
|
222
|
+
let endAbs = openAbs;
|
|
223
|
+
for (let i = openAbs; i < classBody.length; i++) {
|
|
224
|
+
const ch = classBody[i];
|
|
225
|
+
if (ch === '{')
|
|
226
|
+
innerDepth++;
|
|
227
|
+
else if (ch === '}') {
|
|
228
|
+
innerDepth--;
|
|
229
|
+
if (innerDepth === 0) {
|
|
230
|
+
endAbs = i;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const bodySource = classBody.slice(openAbs + 1, endAbs);
|
|
236
|
+
hasWorkerContextCalls = /\b(getCurrentUser|redirect|goto|getRequest|getLocals)\s*\(/.test(bodySource);
|
|
237
|
+
const called = new Set();
|
|
238
|
+
const callRegex = /\bthis\.([A-Za-z_$][\w$]*)\s*\(/g;
|
|
239
|
+
let callMatch;
|
|
240
|
+
while ((callMatch = callRegex.exec(bodySource)) !== null) {
|
|
241
|
+
called.add(callMatch[1]);
|
|
242
|
+
}
|
|
243
|
+
callsThisMethods.push(...called);
|
|
244
|
+
}
|
|
245
|
+
methods.push({
|
|
246
|
+
name,
|
|
247
|
+
visibility: visibility,
|
|
248
|
+
isStatic,
|
|
249
|
+
isAsync,
|
|
250
|
+
hasWorkerContextCalls,
|
|
251
|
+
callsThisMethods,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return methods;
|
|
255
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function compileErrorPages(routesDir: string): Map<number, string>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { compileTemplate } from './template.js';
|
|
4
|
+
export function compileErrorPages(routesDir) {
|
|
5
|
+
const compiledErrorPages = new Map();
|
|
6
|
+
for (const file of fs.readdirSync(routesDir)) {
|
|
7
|
+
const match = file.match(/^(\d{3})\.html$/);
|
|
8
|
+
if (!match)
|
|
9
|
+
continue;
|
|
10
|
+
const status = parseInt(match[1], 10);
|
|
11
|
+
const source = fs.readFileSync(path.join(routesDir, file), 'utf-8');
|
|
12
|
+
const body = compileTemplate(source);
|
|
13
|
+
compiledErrorPages.set(status, `function __error_${status}(error) {\n ${body}\n return __html;\n}`);
|
|
14
|
+
}
|
|
15
|
+
return compiledErrorPages;
|
|
16
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface ImportBinding {
|
|
2
|
+
imported: string;
|
|
3
|
+
local: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ParsedImportStatement {
|
|
6
|
+
bindings: ImportBinding[];
|
|
7
|
+
moduleSpecifier: string | null;
|
|
8
|
+
namespaceImport: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface RouteImportEntry {
|
|
11
|
+
line: string;
|
|
12
|
+
importerDir: string;
|
|
13
|
+
}
|
|
14
|
+
interface RouteQueryReference {
|
|
15
|
+
fnName: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function parseImportStatement(source: string): ParsedImportStatement;
|
|
18
|
+
export declare function collectReferencedIdentifiers(source: string): Set<string>;
|
|
19
|
+
export declare function parseNamedImportBindings(line: string): ImportBinding[];
|
|
20
|
+
export declare function filterImportsByNeededBindings(imports: string[], neededBindings: Set<string>): string[];
|
|
21
|
+
export declare function linkRouteServerImports(opts: {
|
|
22
|
+
routeServerImportEntries: RouteImportEntry[];
|
|
23
|
+
routeClientImportEntries: RouteImportEntry[];
|
|
24
|
+
actionFunctions: string[];
|
|
25
|
+
pollFunctions: string[];
|
|
26
|
+
dataGetQueries: RouteQueryReference[];
|
|
27
|
+
routeScriptReferenceSource: string;
|
|
28
|
+
resolveCompiledImportPath: (origPath: string, importerDir: string, outFileDir: string) => string;
|
|
29
|
+
outFileDir: string;
|
|
30
|
+
allocateModuleId: () => string;
|
|
31
|
+
}): {
|
|
32
|
+
fnToModule: Record<string, string>;
|
|
33
|
+
routeImportDecls: string[];
|
|
34
|
+
importStatements: string[];
|
|
35
|
+
};
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
export function parseImportStatement(source) {
|
|
3
|
+
const sourceFile = ts.createSourceFile('kuratchi-import.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
4
|
+
const statement = sourceFile.statements.find(ts.isImportDeclaration);
|
|
5
|
+
if (!statement || !ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
6
|
+
return { bindings: [], moduleSpecifier: null, namespaceImport: null };
|
|
7
|
+
}
|
|
8
|
+
const bindings = [];
|
|
9
|
+
let namespaceImport = null;
|
|
10
|
+
const clause = statement.importClause;
|
|
11
|
+
if (clause) {
|
|
12
|
+
if (clause.name) {
|
|
13
|
+
bindings.push({ imported: 'default', local: clause.name.text });
|
|
14
|
+
}
|
|
15
|
+
if (clause.namedBindings) {
|
|
16
|
+
if (ts.isNamedImports(clause.namedBindings)) {
|
|
17
|
+
for (const element of clause.namedBindings.elements) {
|
|
18
|
+
bindings.push({
|
|
19
|
+
imported: element.propertyName?.text || element.name.text,
|
|
20
|
+
local: element.name.text,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else if (ts.isNamespaceImport(clause.namedBindings)) {
|
|
25
|
+
namespaceImport = clause.namedBindings.name.text;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
bindings,
|
|
31
|
+
moduleSpecifier: statement.moduleSpecifier.text,
|
|
32
|
+
namespaceImport,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function isTypePosition(node) {
|
|
36
|
+
for (let current = node; current; current = current.parent) {
|
|
37
|
+
if (ts.isTypeNode(current))
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
function isReferenceIdentifier(node) {
|
|
43
|
+
const parent = node.parent;
|
|
44
|
+
if (!parent)
|
|
45
|
+
return false;
|
|
46
|
+
if (isTypePosition(node))
|
|
47
|
+
return false;
|
|
48
|
+
if (ts.isImportClause(parent) || ts.isImportSpecifier(parent) || ts.isNamespaceImport(parent) || ts.isNamedImports(parent))
|
|
49
|
+
return false;
|
|
50
|
+
if (ts.isExportSpecifier(parent))
|
|
51
|
+
return false;
|
|
52
|
+
if (ts.isBindingElement(parent) || ts.isParameter(parent) || ts.isVariableDeclaration(parent) || ts.isFunctionDeclaration(parent) || ts.isClassDeclaration(parent)) {
|
|
53
|
+
return parent.name !== node;
|
|
54
|
+
}
|
|
55
|
+
if (ts.isPropertyAssignment(parent) && parent.name === node)
|
|
56
|
+
return false;
|
|
57
|
+
if (ts.isShorthandPropertyAssignment(parent) && parent.name === node)
|
|
58
|
+
return true;
|
|
59
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === node)
|
|
60
|
+
return false;
|
|
61
|
+
if (ts.isQualifiedName(parent) && parent.right === node)
|
|
62
|
+
return false;
|
|
63
|
+
if (ts.isPropertyDeclaration(parent) || ts.isMethodDeclaration(parent) || ts.isGetAccessorDeclaration(parent) || ts.isSetAccessorDeclaration(parent)) {
|
|
64
|
+
return parent.name !== node;
|
|
65
|
+
}
|
|
66
|
+
if (ts.isLabeledStatement(parent) || ts.isBreakStatement(parent) || ts.isContinueStatement(parent))
|
|
67
|
+
return false;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
export function collectReferencedIdentifiers(source) {
|
|
71
|
+
const refs = new Set();
|
|
72
|
+
const sourceFile = ts.createSourceFile('kuratchi-ref.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
73
|
+
const visit = (node) => {
|
|
74
|
+
if (ts.isIdentifier(node) && isReferenceIdentifier(node)) {
|
|
75
|
+
refs.add(node.text);
|
|
76
|
+
}
|
|
77
|
+
ts.forEachChild(node, visit);
|
|
78
|
+
};
|
|
79
|
+
visit(sourceFile);
|
|
80
|
+
return refs;
|
|
81
|
+
}
|
|
82
|
+
export function parseNamedImportBindings(line) {
|
|
83
|
+
return parseImportStatement(line).bindings.filter((binding) => binding.imported !== 'default');
|
|
84
|
+
}
|
|
85
|
+
export function filterImportsByNeededBindings(imports, neededBindings) {
|
|
86
|
+
const selected = [];
|
|
87
|
+
for (const line of imports) {
|
|
88
|
+
const parsed = parseImportStatement(line);
|
|
89
|
+
const hasNeededBinding = parsed.bindings.some((binding) => neededBindings.has(binding.local))
|
|
90
|
+
|| (parsed.namespaceImport ? neededBindings.has(parsed.namespaceImport) : false);
|
|
91
|
+
if (hasNeededBinding)
|
|
92
|
+
selected.push(line);
|
|
93
|
+
}
|
|
94
|
+
return selected;
|
|
95
|
+
}
|
|
96
|
+
const RESERVED_RENDER_VARS = new Set(['params', 'breadcrumbs']);
|
|
97
|
+
export function linkRouteServerImports(opts) {
|
|
98
|
+
const fnToModule = {};
|
|
99
|
+
const routeImportDeclMap = new Map();
|
|
100
|
+
const importStatements = [];
|
|
101
|
+
const neededServerFns = new Set([
|
|
102
|
+
...opts.actionFunctions,
|
|
103
|
+
...opts.pollFunctions,
|
|
104
|
+
...opts.dataGetQueries.map((query) => query.fnName),
|
|
105
|
+
]);
|
|
106
|
+
const routeServerImports = opts.routeServerImportEntries.length > 0
|
|
107
|
+
? opts.routeServerImportEntries
|
|
108
|
+
: opts.routeClientImportEntries.filter((entry) => (filterImportsByNeededBindings([entry.line], neededServerFns).length > 0));
|
|
109
|
+
for (const entry of routeServerImports) {
|
|
110
|
+
const parsed = parseImportStatement(entry.line);
|
|
111
|
+
if (!parsed.moduleSpecifier)
|
|
112
|
+
continue;
|
|
113
|
+
const isWorkerEnvModule = parsed.moduleSpecifier === 'cloudflare:workers';
|
|
114
|
+
const isKuratchiEnvModule = parsed.moduleSpecifier === '@kuratchi/js/environment';
|
|
115
|
+
const importPath = opts.resolveCompiledImportPath(parsed.moduleSpecifier, entry.importerDir, opts.outFileDir);
|
|
116
|
+
const moduleId = opts.allocateModuleId();
|
|
117
|
+
importStatements.push(`import * as ${moduleId} from '${importPath}';`);
|
|
118
|
+
for (const binding of parsed.bindings) {
|
|
119
|
+
if ((isWorkerEnvModule && binding.imported === 'env') || (isKuratchiEnvModule && binding.imported === 'dev')) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
fnToModule[binding.local] = moduleId;
|
|
123
|
+
if (!routeImportDeclMap.has(binding.local) && !RESERVED_RENDER_VARS.has(binding.local)) {
|
|
124
|
+
const accessExpr = binding.imported === 'default' ? `${moduleId}.default` : `${moduleId}.${binding.imported}`;
|
|
125
|
+
routeImportDeclMap.set(binding.local, `const ${binding.local} = ${accessExpr};`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (parsed.namespaceImport) {
|
|
129
|
+
fnToModule[parsed.namespaceImport] = moduleId;
|
|
130
|
+
if (!routeImportDeclMap.has(parsed.namespaceImport)) {
|
|
131
|
+
routeImportDeclMap.set(parsed.namespaceImport, `const ${parsed.namespaceImport} = ${moduleId};`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
fnToModule,
|
|
137
|
+
routeImportDecls: Array.from(routeImportDeclMap.values()),
|
|
138
|
+
importStatements,
|
|
139
|
+
};
|
|
140
|
+
}
|
package/dist/compiler/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Compiler
|
|
2
|
+
* Compiler ?" scans a project's routes/ directory, parses .html files,
|
|
3
3
|
* and generates a single Worker entry point.
|
|
4
4
|
*/
|
|
5
5
|
export { parseFile } from './parser.js';
|
|
@@ -7,7 +7,7 @@ export { compileTemplate, generateRenderFunction } from './template.js';
|
|
|
7
7
|
export interface CompileOptions {
|
|
8
8
|
/** Absolute path to the project root */
|
|
9
9
|
projectDir: string;
|
|
10
|
-
/** Override path for routes.
|
|
10
|
+
/** Override path for routes.ts (default: .kuratchi/routes.ts). worker.ts is always co-located. */
|
|
11
11
|
outFile?: string;
|
|
12
12
|
/** Whether this is a dev build (sets __kuratchi_DEV__ global) */
|
|
13
13
|
isDev?: boolean;
|
|
@@ -25,12 +25,12 @@ export interface CompiledRoute {
|
|
|
25
25
|
hasRpc: boolean;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Compile a project's src/routes/ into .kuratchi/routes.
|
|
28
|
+
* Compile a project's src/routes/ into .kuratchi/routes.ts
|
|
29
29
|
*
|
|
30
|
-
* The generated module exports { app }
|
|
30
|
+
* The generated module exports { app } — an object with a fetch() method
|
|
31
31
|
* that handles routing, load functions, form actions, and rendering.
|
|
32
|
-
* Returns the path to .kuratchi/worker.
|
|
33
|
-
* re-exports everything from routes.
|
|
32
|
+
* Returns the path to .kuratchi/worker.ts — the stable wrangler entry point that
|
|
33
|
+
* re-exports everything from routes.ts (default fetch handler + named DO class exports).
|
|
34
34
|
* No src/index.ts is needed in user projects.
|
|
35
35
|
*/
|
|
36
|
-
export declare function compile(options: CompileOptions): string
|
|
36
|
+
export declare function compile(options: CompileOptions): Promise<string>;
|