@kuratchi/js 0.0.18 → 0.0.20
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 +172 -9
- package/dist/compiler/client-module-pipeline.d.ts +8 -0
- package/dist/compiler/client-module-pipeline.js +181 -30
- package/dist/compiler/compiler-shared.d.ts +23 -0
- package/dist/compiler/config-reading.js +27 -1
- package/dist/compiler/convention-discovery.d.ts +2 -0
- package/dist/compiler/convention-discovery.js +16 -0
- package/dist/compiler/durable-object-pipeline.d.ts +1 -0
- package/dist/compiler/durable-object-pipeline.js +459 -119
- package/dist/compiler/import-linking.js +1 -1
- package/dist/compiler/index.js +41 -2
- package/dist/compiler/page-route-pipeline.js +31 -2
- package/dist/compiler/parser.d.ts +1 -0
- package/dist/compiler/parser.js +47 -4
- package/dist/compiler/root-layout-pipeline.js +26 -1
- package/dist/compiler/route-discovery.js +5 -5
- package/dist/compiler/route-pipeline.d.ts +2 -0
- package/dist/compiler/route-pipeline.js +28 -4
- package/dist/compiler/routes-module-feature-blocks.js +149 -17
- package/dist/compiler/routes-module-types.d.ts +1 -0
- package/dist/compiler/template.d.ts +4 -0
- package/dist/compiler/template.js +50 -18
- package/dist/compiler/worker-output-pipeline.js +2 -0
- package/dist/compiler/wrangler-sync.d.ts +3 -0
- package/dist/compiler/wrangler-sync.js +25 -11
- package/dist/create.js +6 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/runtime/context.d.ts +6 -0
- package/dist/runtime/context.js +22 -1
- package/dist/runtime/generated-worker.d.ts +1 -0
- package/dist/runtime/generated-worker.js +11 -7
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/schema.d.ts +49 -0
- package/dist/runtime/schema.js +148 -0
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/validation.d.ts +26 -0
- package/dist/runtime/validation.js +147 -0
- package/package.json +5 -1
package/dist/compiler/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export { compileTemplate, generateRenderFunction } from './template.js';
|
|
|
29
29
|
const FRAMEWORK_PACKAGE_NAME = getFrameworkPackageName();
|
|
30
30
|
const RUNTIME_CONTEXT_IMPORT = `${FRAMEWORK_PACKAGE_NAME}/runtime/context.js`;
|
|
31
31
|
const RUNTIME_DO_IMPORT = `${FRAMEWORK_PACKAGE_NAME}/runtime/do.js`;
|
|
32
|
+
const RUNTIME_SCHEMA_IMPORT = `${FRAMEWORK_PACKAGE_NAME}/runtime/schema.js`;
|
|
32
33
|
const RUNTIME_WORKER_IMPORT = `${FRAMEWORK_PACKAGE_NAME}/runtime/generated-worker.js`;
|
|
33
34
|
function getFrameworkPackageName() {
|
|
34
35
|
try {
|
|
@@ -151,6 +152,7 @@ export async function compile(options) {
|
|
|
151
152
|
const proxyCode = generateHandlerProxy(handler, {
|
|
152
153
|
projectDir,
|
|
153
154
|
runtimeDoImport: RUNTIME_DO_IMPORT,
|
|
155
|
+
runtimeSchemaImport: RUNTIME_SCHEMA_IMPORT,
|
|
154
156
|
});
|
|
155
157
|
const proxyFile = path.join(doProxyDir, handler.fileName + '.ts');
|
|
156
158
|
const proxyFileDir = path.dirname(proxyFile);
|
|
@@ -168,7 +170,7 @@ export async function compile(options) {
|
|
|
168
170
|
if (handler.fileName.endsWith('.do')) {
|
|
169
171
|
const aliasFileName = handler.fileName.slice(0, -3);
|
|
170
172
|
const aliasProxyFile = path.join(doProxyDir, aliasFileName + '.ts');
|
|
171
|
-
const aliasCode = `// Auto-generated alias for .do handler\nexport * from './${handler.fileName}.ts';\n`;
|
|
173
|
+
const aliasCode = `// Auto-generated alias for .do handler\nexport * from './${path.basename(handler.fileName)}.ts';\n`;
|
|
172
174
|
const aliasProxyDir = path.dirname(aliasProxyFile);
|
|
173
175
|
if (!fs.existsSync(aliasProxyDir))
|
|
174
176
|
fs.mkdirSync(aliasProxyDir, { recursive: true });
|
|
@@ -220,7 +222,7 @@ export async function compile(options) {
|
|
|
220
222
|
}));
|
|
221
223
|
continue;
|
|
222
224
|
}
|
|
223
|
-
// -- Page route (
|
|
225
|
+
// -- Page route (index.html) --
|
|
224
226
|
const source = fileContents.get(fullPath) ?? fs.readFileSync(fullPath, 'utf-8');
|
|
225
227
|
const parsed = parseFile(source, { kind: 'route', filePath: fullPath });
|
|
226
228
|
const routeState = assembleRouteState({
|
|
@@ -285,6 +287,7 @@ export async function compile(options) {
|
|
|
285
287
|
assetsPrefix,
|
|
286
288
|
runtimeContextImport: RUNTIME_CONTEXT_IMPORT,
|
|
287
289
|
runtimeDoImport: RUNTIME_DO_IMPORT,
|
|
290
|
+
runtimeSchemaImport: RUNTIME_SCHEMA_IMPORT,
|
|
288
291
|
runtimeWorkerImport: RUNTIME_WORKER_IMPORT,
|
|
289
292
|
});
|
|
290
293
|
// Write to .kuratchi/routes.ts
|
|
@@ -308,12 +311,26 @@ export async function compile(options) {
|
|
|
308
311
|
}));
|
|
309
312
|
writeIfChanged(path.join(outDir, 'worker.js'), buildCompatEntrypointSource('./worker.ts'));
|
|
310
313
|
// Auto-sync wrangler.jsonc with workflow/container/DO config from kuratchi.config.ts
|
|
314
|
+
// Also sync the static assets directory when src/assets/ exists, so Cloudflare Workers
|
|
315
|
+
// serves them natively without any manual wrangler.jsonc edits from the user.
|
|
316
|
+
const srcAssetsDir = path.join(srcDir, 'assets');
|
|
317
|
+
let syncedAssetsDirectory;
|
|
318
|
+
if (fs.existsSync(srcAssetsDir)) {
|
|
319
|
+
// Mirror src/assets/ into .kuratchi/public/<prefix>/ so Cloudflare serves them at the
|
|
320
|
+
// correct URL (e.g. /assets/app.css) — the directory passed to wrangler is the parent.
|
|
321
|
+
const prefixSegment = assetsPrefix.replace(/^\/|\/$/g, ''); // '/assets/' -> 'assets'
|
|
322
|
+
const publicDir = path.join(projectDir, '.kuratchi', 'public');
|
|
323
|
+
const publicAssetsDir = prefixSegment ? path.join(publicDir, prefixSegment) : publicDir;
|
|
324
|
+
copyDirIfChanged(srcAssetsDir, publicAssetsDir);
|
|
325
|
+
syncedAssetsDirectory = path.relative(projectDir, publicDir).replace(/\\/g, '/');
|
|
326
|
+
}
|
|
311
327
|
syncWranglerConfigPipeline({
|
|
312
328
|
projectDir,
|
|
313
329
|
config: {
|
|
314
330
|
workflows: workflowConfig,
|
|
315
331
|
containers: containerConfig,
|
|
316
332
|
durableObjects: doConfig,
|
|
333
|
+
assetsDirectory: syncedAssetsDirectory,
|
|
317
334
|
},
|
|
318
335
|
writeFile: writeIfChanged,
|
|
319
336
|
});
|
|
@@ -332,3 +349,25 @@ function writeIfChanged(filePath, content) {
|
|
|
332
349
|
}
|
|
333
350
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
334
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Recursively copy files from src to dest, skipping files whose content is already identical.
|
|
354
|
+
* Used to mirror src/assets/ into .kuratchi/public/ for Cloudflare Workers Static Assets.
|
|
355
|
+
*/
|
|
356
|
+
function copyDirIfChanged(src, dest) {
|
|
357
|
+
if (!fs.existsSync(dest))
|
|
358
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
359
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
360
|
+
const srcPath = path.join(src, entry.name);
|
|
361
|
+
const destPath = path.join(dest, entry.name);
|
|
362
|
+
if (entry.isDirectory()) {
|
|
363
|
+
copyDirIfChanged(srcPath, destPath);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
const srcBuf = fs.readFileSync(srcPath);
|
|
367
|
+
const destBuf = fs.existsSync(destPath) ? fs.readFileSync(destPath) : null;
|
|
368
|
+
if (!destBuf || !srcBuf.equals(destBuf)) {
|
|
369
|
+
fs.writeFileSync(destPath, srcBuf);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
@@ -42,10 +42,38 @@ export function compilePageRoute(opts) {
|
|
|
42
42
|
query.rpcId = rpcNameMap.get(query.fnName);
|
|
43
43
|
}
|
|
44
44
|
const clientRouteRegistry = opts.clientModuleCompiler.createRouteRegistry(opts.routeIndex, opts.routeState.routeBrowserImportEntries);
|
|
45
|
+
const awaitQueryBindings = new Map(opts.routeState.mergedParsed.dataGetQueries
|
|
46
|
+
.filter((query) => !!query.awaitExpr)
|
|
47
|
+
.map((query) => [query.awaitExpr, { asName: query.asName, rpcId: query.rpcId || rpcNameMap.get(query.fnName) || query.fnName }]));
|
|
45
48
|
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 });
|
|
49
|
+
const renderBody = compileTemplate(renderSections.bodyTemplate, routeComponentNames, actionNames, rpcNameMap, { emitCall: '__emit', enableFragmentManifest: true, clientRouteRegistry, awaitQueryBindings });
|
|
50
|
+
const renderHeadBody = compileTemplate(renderSections.headTemplate, routeComponentNames, actionNames, rpcNameMap, { clientRouteRegistry, awaitQueryBindings });
|
|
48
51
|
const clientEntryAsset = clientRouteRegistry.buildEntryAsset();
|
|
52
|
+
const clientServerProxyBindings = clientRouteRegistry.getServerProxyBindings();
|
|
53
|
+
const clientServerProxyModules = new Map();
|
|
54
|
+
const extraRpcBindings = [];
|
|
55
|
+
const seenClientServerRpcIds = new Set();
|
|
56
|
+
for (const binding of clientServerProxyBindings) {
|
|
57
|
+
const moduleKey = `${binding.importerDir}::${binding.moduleSpecifier}`;
|
|
58
|
+
let moduleId = clientServerProxyModules.get(moduleKey);
|
|
59
|
+
if (!moduleId) {
|
|
60
|
+
moduleId = opts.allocateModuleId();
|
|
61
|
+
clientServerProxyModules.set(moduleKey, moduleId);
|
|
62
|
+
const importPath = opts.resolveCompiledImportPath(binding.moduleSpecifier, binding.importerDir, outFileDir);
|
|
63
|
+
opts.pushImport(`import * as ${moduleId} from '${importPath}';`);
|
|
64
|
+
}
|
|
65
|
+
if (!seenClientServerRpcIds.has(binding.rpcId)) {
|
|
66
|
+
seenClientServerRpcIds.add(binding.rpcId);
|
|
67
|
+
extraRpcBindings.push({
|
|
68
|
+
name: `${binding.moduleSpecifier}:${binding.importedName}`,
|
|
69
|
+
rpcId: binding.rpcId,
|
|
70
|
+
expression: binding.importedName === 'default' ? `${moduleId}.default` : `${moduleId}.${binding.importedName}`,
|
|
71
|
+
schemaExpression: binding.importedName === 'default'
|
|
72
|
+
? 'undefined'
|
|
73
|
+
: `${moduleId}.schemas?.[${JSON.stringify(binding.importedName)}]`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
49
77
|
const clientModuleHref = clientEntryAsset ? `${opts.assetsPrefix}${clientEntryAsset.assetName}` : null;
|
|
50
78
|
const routePlan = analyzeRouteBuild({
|
|
51
79
|
pattern: opts.pattern,
|
|
@@ -55,6 +83,7 @@ export function compilePageRoute(opts) {
|
|
|
55
83
|
parsed: opts.routeState.mergedParsed,
|
|
56
84
|
fnToModule,
|
|
57
85
|
rpcNameMap,
|
|
86
|
+
extraRpcBindings,
|
|
58
87
|
componentStyles: opts.componentCompiler.collectStyles(routeComponentNames),
|
|
59
88
|
clientModuleHref,
|
|
60
89
|
});
|
package/dist/compiler/parser.js
CHANGED
|
@@ -451,10 +451,37 @@ function extractCallExpression(value) {
|
|
|
451
451
|
const match = expr.match(/^([A-Za-z_$][\w$]*)\(([\s\S]*)\)$/);
|
|
452
452
|
if (!match)
|
|
453
453
|
return null;
|
|
454
|
-
return {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
454
|
+
return { fnName: match[1], argsExpr: (match[2] || '').trim() };
|
|
455
|
+
}
|
|
456
|
+
function extractAwaitCallExpression(expr) {
|
|
457
|
+
const match = expr.trim().match(/^await\s+([A-Za-z_$][\w$]*)\(([\s\S]*)\)$/);
|
|
458
|
+
if (!match)
|
|
459
|
+
return null;
|
|
460
|
+
return { fnName: match[1], argsExpr: (match[2] || '').trim() };
|
|
461
|
+
}
|
|
462
|
+
function collectAwaitTemplateQueries(template) {
|
|
463
|
+
const source = template.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '');
|
|
464
|
+
const queries = [];
|
|
465
|
+
const seen = new Map();
|
|
466
|
+
for (let i = 0; i < source.length; i++) {
|
|
467
|
+
if (source[i] !== '{')
|
|
468
|
+
continue;
|
|
469
|
+
const closeIdx = findMatchingToken(source, i, '{', '}');
|
|
470
|
+
if (closeIdx === -1)
|
|
471
|
+
continue;
|
|
472
|
+
const inner = source.slice(i + 1, closeIdx).trim();
|
|
473
|
+
const call = extractAwaitCallExpression(inner);
|
|
474
|
+
if (call) {
|
|
475
|
+
const awaitExpr = `${call.fnName}(${call.argsExpr})`;
|
|
476
|
+
if (!seen.has(awaitExpr)) {
|
|
477
|
+
const asName = `__await_query_${queries.length}`;
|
|
478
|
+
seen.set(awaitExpr, asName);
|
|
479
|
+
queries.push({ fnName: call.fnName, argsExpr: call.argsExpr, asName, awaitExpr });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
i = closeIdx;
|
|
483
|
+
}
|
|
484
|
+
return queries;
|
|
458
485
|
}
|
|
459
486
|
function extractTopLevelImportNames(source) {
|
|
460
487
|
const sourceFile = ts.createSourceFile('kuratchi-inline-client.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
@@ -1144,6 +1171,22 @@ export function parseFile(source, options = {}) {
|
|
|
1144
1171
|
if (!exists)
|
|
1145
1172
|
dataGetQueries.push({ fnName: getCall.fnName, argsExpr: getCall.argsExpr, asName, key });
|
|
1146
1173
|
}
|
|
1174
|
+
for (const awaitQuery of collectAwaitTemplateQueries(templateWithoutComments)) {
|
|
1175
|
+
if (!pollFunctions.includes(awaitQuery.fnName))
|
|
1176
|
+
pollFunctions.push(awaitQuery.fnName);
|
|
1177
|
+
if (!dataVars.includes(awaitQuery.asName))
|
|
1178
|
+
dataVars.push(awaitQuery.asName);
|
|
1179
|
+
const exists = dataGetQueries.some((query) => query.awaitExpr === awaitQuery.awaitExpr);
|
|
1180
|
+
if (!exists) {
|
|
1181
|
+
dataGetQueries.push({
|
|
1182
|
+
fnName: awaitQuery.fnName,
|
|
1183
|
+
argsExpr: awaitQuery.argsExpr,
|
|
1184
|
+
asName: awaitQuery.asName,
|
|
1185
|
+
key: awaitQuery.asName,
|
|
1186
|
+
awaitExpr: awaitQuery.awaitExpr,
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1147
1190
|
for (const clientBinding of routeClientImportBindings) {
|
|
1148
1191
|
const idx = actionFunctions.indexOf(clientBinding);
|
|
1149
1192
|
if (idx !== -1)
|
|
@@ -206,6 +206,20 @@ const BRIDGE_SOURCE = `(function(){
|
|
|
206
206
|
if(!keys.length){ location.reload(); return Promise.resolve(); }
|
|
207
207
|
return Promise.all(keys.map(function(k){ return replaceBlocksWithKey('key:' + k); })).then(function(){});
|
|
208
208
|
}
|
|
209
|
+
function refreshRemoteReads(){
|
|
210
|
+
var seen = Object.create(null);
|
|
211
|
+
var tasks = [];
|
|
212
|
+
by('[data-remote-read][data-get]').forEach(function(el){
|
|
213
|
+
var fn = el.getAttribute('data-get');
|
|
214
|
+
if(!fn) return;
|
|
215
|
+
var args = String(el.getAttribute('data-get-args') || '[]');
|
|
216
|
+
var key = String(fn) + '|' + args;
|
|
217
|
+
if(seen[key]) return;
|
|
218
|
+
seen[key] = true;
|
|
219
|
+
tasks.push(replaceBlocksByDescriptor(fn, args));
|
|
220
|
+
});
|
|
221
|
+
return Promise.all(tasks).then(function(){});
|
|
222
|
+
}
|
|
209
223
|
function act(e){
|
|
210
224
|
var clientSel = '[data-client-event="' + e.type + '"]';
|
|
211
225
|
var clientEl = e.target && e.target.closest ? e.target.closest(clientSel) : null;
|
|
@@ -301,6 +315,9 @@ const BRIDGE_SOURCE = `(function(){
|
|
|
301
315
|
} else {
|
|
302
316
|
autoLoadQueries();
|
|
303
317
|
}
|
|
318
|
+
window.addEventListener('kuratchi:invalidate-reads', function(){
|
|
319
|
+
refreshRemoteReads().catch(function(err){ console.error('[kuratchi] remote read refresh error:', err); });
|
|
320
|
+
});
|
|
304
321
|
document.addEventListener('click', function(e){
|
|
305
322
|
var b = e.target && e.target.closest ? e.target.closest('[command="fill-dialog"]') : null;
|
|
306
323
|
if(!b) return;
|
|
@@ -354,8 +371,16 @@ const BRIDGE_SOURCE = `(function(){
|
|
|
354
371
|
(function tick(){
|
|
355
372
|
setTimeout(function(){
|
|
356
373
|
fetch(location.pathname + location.search, { headers: { 'x-kuratchi-fragment': pollId } })
|
|
357
|
-
.then(function(r){
|
|
374
|
+
.then(function(r){
|
|
375
|
+
if(r.status === 404){
|
|
376
|
+
el.remove();
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
if(!r.ok) throw new Error('Poll fragment request failed: ' + r.status);
|
|
380
|
+
return r.text();
|
|
381
|
+
})
|
|
358
382
|
.then(function(html){
|
|
383
|
+
if(html === null) return;
|
|
359
384
|
if(prevHtml !== html){
|
|
360
385
|
el.innerHTML = html;
|
|
361
386
|
prevHtml = html;
|
|
@@ -26,9 +26,9 @@ export function discoverRoutes(routesDir) {
|
|
|
26
26
|
for (const entry of entries) {
|
|
27
27
|
if (entry.isDirectory()) {
|
|
28
28
|
const childPrefix = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
29
|
-
const pageFile = path.join(dir, entry.name, '
|
|
29
|
+
const pageFile = path.join(dir, entry.name, 'index.html');
|
|
30
30
|
if (fs.existsSync(pageFile)) {
|
|
31
|
-
const routeFile = `${childPrefix}/
|
|
31
|
+
const routeFile = `${childPrefix}/index.html`;
|
|
32
32
|
if (!registered.has(routeFile)) {
|
|
33
33
|
registered.add(routeFile);
|
|
34
34
|
results.push({ file: routeFile, name: childPrefix, layouts: getLayoutsForPrefix(childPrefix), type: 'page' });
|
|
@@ -56,15 +56,15 @@ export function discoverRoutes(routesDir) {
|
|
|
56
56
|
}
|
|
57
57
|
continue;
|
|
58
58
|
}
|
|
59
|
-
if (entry.name === '
|
|
60
|
-
const routeFile = prefix ? `${prefix}/
|
|
59
|
+
if (entry.name === 'index.html') {
|
|
60
|
+
const routeFile = prefix ? `${prefix}/index.html` : 'index.html';
|
|
61
61
|
if (!registered.has(routeFile)) {
|
|
62
62
|
registered.add(routeFile);
|
|
63
63
|
results.push({ file: routeFile, name: prefix || 'index', layouts: getLayoutsForPrefix(prefix), type: 'page' });
|
|
64
64
|
}
|
|
65
65
|
continue;
|
|
66
66
|
}
|
|
67
|
-
if (entry.name.endsWith('.html') && entry.name !== '
|
|
67
|
+
if (entry.name.endsWith('.html') && entry.name !== 'index.html') {
|
|
68
68
|
const name = prefix
|
|
69
69
|
? `${prefix}/${entry.name.replace('.html', '')}`
|
|
70
70
|
: entry.name.replace('.html', '');
|
|
@@ -15,6 +15,7 @@ export interface RouteRpcBinding {
|
|
|
15
15
|
name: string;
|
|
16
16
|
rpcId: string;
|
|
17
17
|
expression: string;
|
|
18
|
+
schemaExpression: string;
|
|
18
19
|
}
|
|
19
20
|
export interface RouteLoadPlan {
|
|
20
21
|
mode: 'none' | 'explicit' | 'generated';
|
|
@@ -49,6 +50,7 @@ interface AnalyzeRouteOptions {
|
|
|
49
50
|
parsed: RoutePipelineParsedFile;
|
|
50
51
|
fnToModule: Record<string, string>;
|
|
51
52
|
rpcNameMap?: Map<string, string>;
|
|
53
|
+
extraRpcBindings?: RouteRpcBinding[];
|
|
52
54
|
componentStyles: string[];
|
|
53
55
|
clientModuleHref?: string | null;
|
|
54
56
|
}
|
|
@@ -20,7 +20,7 @@ function buildLoadQueryStateCode(opts) {
|
|
|
20
20
|
lines.push(`let ${asName} = { state: 'loading', loading: true, error: null, data: null, empty: false, success: false };`);
|
|
21
21
|
lines.push(`const __qOverride_${asName} = __getLocals().__queryOverride;`);
|
|
22
22
|
lines.push(`const __qArgs_${asName} = ${defaultArgs};`);
|
|
23
|
-
lines.push(`const __qShouldRun_${asName} =
|
|
23
|
+
lines.push(`const __qShouldRun_${asName} = !__qOverride_${asName} || (__qOverride_${asName}.fn === '${rpcId}' && Array.isArray(__qOverride_${asName}.args) && JSON.stringify(__qOverride_${asName}.args) === JSON.stringify(__qArgs_${asName}));`);
|
|
24
24
|
lines.push(`if (__qShouldRun_${asName}) {`);
|
|
25
25
|
lines.push(` try {`);
|
|
26
26
|
lines.push(` const __qData_${asName} = await ${qualifiedFn}(...__qArgs_${asName});`);
|
|
@@ -83,7 +83,7 @@ function assertRoutePlanInvariants(opts) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
export function analyzeRouteBuild(opts) {
|
|
86
|
-
const { pattern, renderBody, renderHeadBody, isDev, parsed, fnToModule, rpcNameMap, componentStyles, clientModuleHref } = opts;
|
|
86
|
+
const { pattern, renderBody, renderHeadBody, isDev, parsed, fnToModule, rpcNameMap, extraRpcBindings, componentStyles, clientModuleHref } = opts;
|
|
87
87
|
const hasFns = Object.keys(fnToModule).length > 0;
|
|
88
88
|
const queryDefs = parsed.dataGetQueries ?? [];
|
|
89
89
|
const queryVars = queryDefs.map((query) => query.asName);
|
|
@@ -94,10 +94,18 @@ export function analyzeRouteBuild(opts) {
|
|
|
94
94
|
const routeImportDecls = routeImportDeclLines.join('\n');
|
|
95
95
|
const importedBindingNames = new Set(Object.keys(fnToModule));
|
|
96
96
|
const renderScopeActionNames = new Set(parsed.actionFunctions);
|
|
97
|
+
// Filter out params/breadcrumbs from render prelude since they come from data destructuring
|
|
98
|
+
const reservedRenderVars = new Set(['params', 'breadcrumbs']);
|
|
97
99
|
const renderImportPrelude = routeImportDeclLines
|
|
98
100
|
.filter((statement) => {
|
|
99
101
|
const declaredName = extractDeclaredConstName(statement);
|
|
100
|
-
|
|
102
|
+
if (!declaredName)
|
|
103
|
+
return true;
|
|
104
|
+
if (renderScopeActionNames.has(declaredName))
|
|
105
|
+
return false;
|
|
106
|
+
if (reservedRenderVars.has(declaredName))
|
|
107
|
+
return false;
|
|
108
|
+
return true;
|
|
101
109
|
})
|
|
102
110
|
.join('\n');
|
|
103
111
|
let scriptBody = '';
|
|
@@ -191,9 +199,21 @@ export function analyzeRouteBuild(opts) {
|
|
|
191
199
|
? parsed.pollFunctions.map((name) => {
|
|
192
200
|
const moduleId = fnToModule[name];
|
|
193
201
|
const rpcId = rpcNameMap?.get(name) || name;
|
|
194
|
-
|
|
202
|
+
const schemaExpression = moduleId
|
|
203
|
+
? `${moduleId}.schemas?.[${JSON.stringify(name)}]`
|
|
204
|
+
: `(typeof schemas !== 'undefined' ? schemas?.[${JSON.stringify(name)}] : undefined)`;
|
|
205
|
+
return { name, rpcId, expression: moduleId ? `${moduleId}.${name}` : name, schemaExpression };
|
|
195
206
|
})
|
|
196
207
|
: [];
|
|
208
|
+
if (extraRpcBindings?.length) {
|
|
209
|
+
const seenRpcIds = new Set(rpc.map((binding) => binding.rpcId));
|
|
210
|
+
for (const binding of extraRpcBindings) {
|
|
211
|
+
if (seenRpcIds.has(binding.rpcId))
|
|
212
|
+
continue;
|
|
213
|
+
seenRpcIds.add(binding.rpcId);
|
|
214
|
+
rpc.push(binding);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
197
217
|
assertRoutePlanInvariants({
|
|
198
218
|
pattern,
|
|
199
219
|
loadReturnVars: loadPlan.returnVars,
|
|
@@ -242,6 +262,10 @@ export function emitRouteObject(plan) {
|
|
|
242
262
|
.map((rpc) => `'${rpc.rpcId}': ${rpc.expression}`)
|
|
243
263
|
.join(', ');
|
|
244
264
|
parts.push(` rpc: { ${rpcEntries} }`);
|
|
265
|
+
const rpcSchemaEntries = plan.rpc
|
|
266
|
+
.map((rpc) => `'${rpc.rpcId}': ${rpc.schemaExpression}`)
|
|
267
|
+
.join(', ');
|
|
268
|
+
parts.push(` rpcSchemas: { ${rpcSchemaEntries} }`);
|
|
245
269
|
// Also emit allowedQueries for query override validation
|
|
246
270
|
const allowedQueryNames = plan.rpc.map((rpc) => `'${rpc.rpcId}'`).join(', ');
|
|
247
271
|
parts.push(` allowedQueries: [${allowedQueryNames}]`);
|
|
@@ -2,7 +2,7 @@ import * as path from 'node:path';
|
|
|
2
2
|
import { toSafeIdentifier } from './compiler-shared.js';
|
|
3
3
|
export function buildRoutesModuleFeatureBlocks(opts) {
|
|
4
4
|
const workerImport = `import { env as __env } from 'cloudflare:workers';`;
|
|
5
|
-
const contextImport = `import { __setRequestContext, __esc, __rawHtml, __sanitizeHtml, __setLocal, __getLocals, __getCsrfToken, __signFragment, buildDefaultBreadcrumbs as __buildDefaultBreadcrumbs } from '${opts.runtimeContextImport}';`;
|
|
5
|
+
const contextImport = `import { __setRequestContext, __pushRequestContext, __esc, __rawHtml, __sanitizeHtml, __setLocal, __getLocals, __getCsrfToken, __signFragment, buildDefaultBreadcrumbs as __buildDefaultBreadcrumbs } from '${opts.runtimeContextImport}';`;
|
|
6
6
|
const runtimeImport = opts.hasRuntime && opts.runtimeImportPath
|
|
7
7
|
? `import __kuratchiRuntime from '${opts.runtimeImportPath}';`
|
|
8
8
|
: '';
|
|
@@ -182,7 +182,10 @@ function buildDurableObjectBlock(opts) {
|
|
|
182
182
|
doImportLines.push(`import { DurableObject as __DO } from 'cloudflare:workers';`);
|
|
183
183
|
doImportLines.push(`import { initDO as __initDO } from '@kuratchi/orm';`);
|
|
184
184
|
doImportLines.push(`import { __registerDoResolver, __registerDoClassBinding, __setDoContext } from '${opts.runtimeDoImport}';`);
|
|
185
|
+
doImportLines.push(`import { validateSchemaInput as __validateSchemaInput } from '${opts.runtimeSchemaImport}';`);
|
|
185
186
|
doImportLines.push(`const __DO_FD_TAG = '__kuratchi_form_data__';`);
|
|
187
|
+
doImportLines.push(`const __DO_RPC_CTX_TAG = '__kuratchi_rpc_context__';`);
|
|
188
|
+
doImportLines.push(`const __DO_RPC_RESULT_TAG = '__kuratchi_rpc_result__';`);
|
|
186
189
|
doImportLines.push(`function __isDoPlainObject(__v) {`);
|
|
187
190
|
doImportLines.push(` if (!__v || typeof __v !== 'object') return false;`);
|
|
188
191
|
doImportLines.push(` const __proto = Object.getPrototypeOf(__v);`);
|
|
@@ -203,6 +206,38 @@ function buildDurableObjectBlock(opts) {
|
|
|
203
206
|
doImportLines.push(` }`);
|
|
204
207
|
doImportLines.push(` return __v;`);
|
|
205
208
|
doImportLines.push(`}`);
|
|
209
|
+
doImportLines.push(`function __extractDoRpcContext(__args) {`);
|
|
210
|
+
doImportLines.push(` if (!Array.isArray(__args) || __args.length === 0) return { args: __args, context: null };`);
|
|
211
|
+
doImportLines.push(` const __tail = __args[__args.length - 1];`);
|
|
212
|
+
doImportLines.push(` if (!__isDoPlainObject(__tail) || !(__DO_RPC_CTX_TAG in __tail)) return { args: __args, context: null };`);
|
|
213
|
+
doImportLines.push(` return { args: __args.slice(0, -1), context: __tail[__DO_RPC_CTX_TAG] || null };`);
|
|
214
|
+
doImportLines.push(`}`);
|
|
215
|
+
doImportLines.push(`function __wrapDoRpcResult(__value) {`);
|
|
216
|
+
doImportLines.push(` const __locals = __getLocals();`);
|
|
217
|
+
doImportLines.push(` return { [__DO_RPC_RESULT_TAG]: { value: __value, effects: { redirectTo: __locals.__redirectTo ?? null, redirectStatus: __locals.__redirectStatus ?? null, setCookieHeaders: Array.isArray(__locals.__setCookieHeaders) ? __locals.__setCookieHeaders : [] } } };`);
|
|
218
|
+
doImportLines.push(`}`);
|
|
219
|
+
doImportLines.push(`function __invokeDoRpc(__self, __methodName, __fn, __args) {`);
|
|
220
|
+
doImportLines.push(` __setDoContext(__self);`);
|
|
221
|
+
doImportLines.push(` const { args: __callArgs, context: __rpcContext } = __extractDoRpcContext(__args);`);
|
|
222
|
+
doImportLines.push(` const __decoded = (__callArgs ?? []).map(__decodeDoArg);`);
|
|
223
|
+
doImportLines.push(` const __schema = __self?.constructor?.schemas?.[__methodName];`);
|
|
224
|
+
doImportLines.push(` const __validated = __validateSchemaInput(__schema, __decoded);`);
|
|
225
|
+
doImportLines.push(` if (!__rpcContext) return __fn.apply(__self, __validated);`);
|
|
226
|
+
doImportLines.push(` const __restore = __pushRequestContext(__rpcContext, __self.ctx, __self.env);`);
|
|
227
|
+
doImportLines.push(` const __finish = (__value) => __wrapDoRpcResult(__value);`);
|
|
228
|
+
doImportLines.push(` const __fail = (__err) => { if (__err && __err.isRedirectError) return __wrapDoRpcResult(undefined); throw __err; };`);
|
|
229
|
+
doImportLines.push(` try {`);
|
|
230
|
+
doImportLines.push(` const __result = __fn.apply(__self, __validated);`);
|
|
231
|
+
doImportLines.push(` if (__result && typeof __result.then === 'function') {`);
|
|
232
|
+
doImportLines.push(` return __result.then(__finish, __fail).finally(__restore);`);
|
|
233
|
+
doImportLines.push(` }`);
|
|
234
|
+
doImportLines.push(` const __wrapped = __finish(__result);`);
|
|
235
|
+
doImportLines.push(` __restore();`);
|
|
236
|
+
doImportLines.push(` return __wrapped;`);
|
|
237
|
+
doImportLines.push(` } catch (__err) {`);
|
|
238
|
+
doImportLines.push(` try { return __fail(__err); } finally { __restore(); }`);
|
|
239
|
+
doImportLines.push(` }`);
|
|
240
|
+
doImportLines.push(`}`);
|
|
206
241
|
doImportLines.push(`import { getCurrentUser as __getCU, getOrgStubByName as __getOSBN } from '@kuratchi/auth';`);
|
|
207
242
|
const handlersByBinding = new Map();
|
|
208
243
|
for (const handler of opts.doHandlers) {
|
|
@@ -213,6 +248,10 @@ function buildDurableObjectBlock(opts) {
|
|
|
213
248
|
for (const doEntry of opts.doConfig) {
|
|
214
249
|
const handlers = handlersByBinding.get(doEntry.binding) ?? [];
|
|
215
250
|
const ormDb = opts.ormDatabases.find((db) => db.binding === doEntry.binding);
|
|
251
|
+
const fnHandlers = handlers.filter((h) => h.mode === 'function');
|
|
252
|
+
const initHandlers = fnHandlers.filter((h) => h.exportedFunctions.includes('onInit'));
|
|
253
|
+
const alarmHandlers = fnHandlers.filter((h) => h.exportedFunctions.includes('onAlarm'));
|
|
254
|
+
const messageHandlers = fnHandlers.filter((h) => h.exportedFunctions.includes('onMessage'));
|
|
216
255
|
if (ormDb) {
|
|
217
256
|
const schemaPath = ormDb.schemaImportPath.replace(/^\.\//, '../');
|
|
218
257
|
doImportLines.push(`import { ${ormDb.schemaExportName} as __doSchema_${doEntry.binding} } from '${schemaPath}';`);
|
|
@@ -225,22 +264,114 @@ function buildDurableObjectBlock(opts) {
|
|
|
225
264
|
if (!handlerImportPath.startsWith('.'))
|
|
226
265
|
handlerImportPath = './' + handlerImportPath;
|
|
227
266
|
const handlerVar = `__handler_${toSafeIdentifier(handler.fileName)}`;
|
|
228
|
-
|
|
267
|
+
if (handler.mode === 'class') {
|
|
268
|
+
if (handler.exportKind === 'named' && handler.className) {
|
|
269
|
+
doImportLines.push(`import { ${handler.className} as ${handlerVar} } from '${handlerImportPath}';`);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
doImportLines.push(`import ${handlerVar} from '${handlerImportPath}';`);
|
|
273
|
+
}
|
|
274
|
+
for (const [index, contributor] of (handler.classContributors ?? []).entries()) {
|
|
275
|
+
let contributorImportPath = path
|
|
276
|
+
.relative(path.join(opts.projectDir, '.kuratchi'), contributor.absPath)
|
|
277
|
+
.replace(/\\/g, '/')
|
|
278
|
+
.replace(/\.ts$/, '.js');
|
|
279
|
+
if (!contributorImportPath.startsWith('.'))
|
|
280
|
+
contributorImportPath = './' + contributorImportPath;
|
|
281
|
+
const contributorVar = `__handler_${toSafeIdentifier(`${handler.fileName}__${contributor.className}_${index}`)}`;
|
|
282
|
+
if (contributor.exportKind === 'named') {
|
|
283
|
+
doImportLines.push(`import { ${contributor.className} as ${contributorVar} } from '${contributorImportPath}';`);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
doImportLines.push(`import ${contributorVar} from '${contributorImportPath}';`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
doImportLines.push(`import * as ${handlerVar} from '${handlerImportPath}';`);
|
|
292
|
+
}
|
|
229
293
|
}
|
|
294
|
+
// Generate the DO class
|
|
295
|
+
doClassLines.push(`export class ${doEntry.className} extends __DO {`);
|
|
296
|
+
doClassLines.push(` constructor(ctx, env) {`);
|
|
297
|
+
doClassLines.push(` super(ctx, env);`);
|
|
230
298
|
if (ormDb) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
299
|
+
doClassLines.push(` this.db = __initDO(ctx.storage.sql, __doSchema_${doEntry.binding});`);
|
|
300
|
+
}
|
|
301
|
+
for (const handler of handlers.filter((h) => h.mode === 'class')) {
|
|
302
|
+
const handlerVar = `__handler_${toSafeIdentifier(handler.fileName)}`;
|
|
303
|
+
const handlerInstanceVar = `__instance_${toSafeIdentifier(handler.fileName)}`;
|
|
304
|
+
doClassLines.push(` const ${handlerInstanceVar} = new ${handlerVar}(ctx, env);`);
|
|
305
|
+
doClassLines.push(` Object.assign(this, ${handlerInstanceVar});`);
|
|
235
306
|
}
|
|
236
|
-
|
|
237
|
-
const handler = handlers[0];
|
|
307
|
+
for (const handler of initHandlers) {
|
|
238
308
|
const handlerVar = `__handler_${toSafeIdentifier(handler.fileName)}`;
|
|
239
|
-
doClassLines.push(`
|
|
309
|
+
doClassLines.push(` __setDoContext(this);`);
|
|
310
|
+
doClassLines.push(` Promise.resolve(${handlerVar}.onInit.call(this)).catch((err) => console.error('[KuratchiJS] DO onInit failed:', err?.message || err));`);
|
|
240
311
|
}
|
|
312
|
+
doClassLines.push(` }`);
|
|
313
|
+
if (ormDb) {
|
|
314
|
+
doClassLines.push(...buildOrmDoActivityLogLines(doEntry.binding));
|
|
315
|
+
}
|
|
316
|
+
if (alarmHandlers.length > 0) {
|
|
317
|
+
doClassLines.push(` async alarm(...args) {`);
|
|
318
|
+
doClassLines.push(` __setDoContext(this);`);
|
|
319
|
+
for (const handler of alarmHandlers) {
|
|
320
|
+
const handlerVar = `__handler_${toSafeIdentifier(handler.fileName)}`;
|
|
321
|
+
doClassLines.push(` await ${handlerVar}.onAlarm.call(this, ...args);`);
|
|
322
|
+
}
|
|
323
|
+
doClassLines.push(` }`);
|
|
324
|
+
}
|
|
325
|
+
if (messageHandlers.length > 0) {
|
|
326
|
+
doClassLines.push(` webSocketMessage(...args) {`);
|
|
327
|
+
doClassLines.push(` __setDoContext(this);`);
|
|
328
|
+
for (const handler of messageHandlers) {
|
|
329
|
+
const handlerVar = `__handler_${toSafeIdentifier(handler.fileName)}`;
|
|
330
|
+
doClassLines.push(` ${handlerVar}.onMessage.call(this, ...args);`);
|
|
331
|
+
}
|
|
332
|
+
doClassLines.push(` }`);
|
|
333
|
+
}
|
|
334
|
+
doClassLines.push(` static schemas = {};`);
|
|
335
|
+
doClassLines.push(`}`);
|
|
336
|
+
// Apply prototype methods from each class handler (and its contributors) onto the generated class
|
|
241
337
|
for (const handler of handlers) {
|
|
242
338
|
const handlerVar = `__handler_${toSafeIdentifier(handler.fileName)}`;
|
|
243
|
-
|
|
339
|
+
if (handler.mode === 'class') {
|
|
340
|
+
const classSourceVars = [
|
|
341
|
+
handlerVar,
|
|
342
|
+
...(handler.classContributors ?? []).map((c, i) => `__handler_${toSafeIdentifier(`${handler.fileName}__${c.className}_${i}`)}`),
|
|
343
|
+
];
|
|
344
|
+
doClassLines.push(`{`);
|
|
345
|
+
doClassLines.push(` for (const __source of [${classSourceVars.join(', ')}]) {`);
|
|
346
|
+
doClassLines.push(` const __seen = new Set();`);
|
|
347
|
+
doClassLines.push(` for (let __p = __source.prototype; __p && __p !== __DO.prototype && __p !== Object.prototype; __p = Object.getPrototypeOf(__p)) {`);
|
|
348
|
+
doClassLines.push(` for (const __k of Object.getOwnPropertyNames(__p)) {`);
|
|
349
|
+
doClassLines.push(` if (__k === 'constructor' || __seen.has(__k)) continue;`);
|
|
350
|
+
doClassLines.push(` const __desc = Object.getOwnPropertyDescriptor(__p, __k);`);
|
|
351
|
+
doClassLines.push(` const __fn = __desc?.value;`);
|
|
352
|
+
doClassLines.push(` if (typeof __fn !== 'function') continue;`);
|
|
353
|
+
doClassLines.push(` __seen.add(__k);`);
|
|
354
|
+
doClassLines.push(` ${doEntry.className}.prototype[__k] = function(...__a){ return __invokeDoRpc(this, __k, __fn, __a); };`);
|
|
355
|
+
doClassLines.push(` }`);
|
|
356
|
+
doClassLines.push(` }`);
|
|
357
|
+
doClassLines.push(` }`);
|
|
358
|
+
doClassLines.push(`}`);
|
|
359
|
+
doClassLines.push(`Object.assign(${doEntry.className}.schemas, ${handlerVar}.schemas || {});`);
|
|
360
|
+
for (const [index] of (handler.classContributors ?? []).entries()) {
|
|
361
|
+
const contributorVar = `__handler_${toSafeIdentifier(`${handler.fileName}__${handler.classContributors[index].className}_${index}`)}`;
|
|
362
|
+
doClassLines.push(`Object.assign(${doEntry.className}.schemas, ${contributorVar}.schemas || {});`);
|
|
363
|
+
}
|
|
364
|
+
doResolverLines.push(` __registerDoClassBinding(${handlerVar}, '${doEntry.binding}');`);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
const lifecycle = new Set(['onInit', 'onAlarm', 'onMessage']);
|
|
368
|
+
for (const fn of handler.exportedFunctions) {
|
|
369
|
+
if (lifecycle.has(fn))
|
|
370
|
+
continue;
|
|
371
|
+
doClassLines.push(`${doEntry.className}.prototype[${JSON.stringify(fn)}] = function(...__a){ return __invokeDoRpc(this, ${JSON.stringify(fn)}, ${handlerVar}.${fn}, __a); };`);
|
|
372
|
+
}
|
|
373
|
+
doClassLines.push(`Object.assign(${doEntry.className}.schemas, ${handlerVar}.schemas || {});`);
|
|
374
|
+
}
|
|
244
375
|
}
|
|
245
376
|
if (doEntry.stubId) {
|
|
246
377
|
const fieldPath = doEntry.stubId.startsWith('user.') ? `__u.${doEntry.stubId.slice(5)}` : doEntry.stubId;
|
|
@@ -261,13 +392,8 @@ function buildDurableObjectBlock(opts) {
|
|
|
261
392
|
doResolverInit: `\nfunction __initDoResolvers() {\n${doResolverLines.join('\n')}\n}\n`,
|
|
262
393
|
};
|
|
263
394
|
}
|
|
264
|
-
function
|
|
395
|
+
function buildOrmDoActivityLogLines(binding) {
|
|
265
396
|
return [
|
|
266
|
-
`export class ${className} extends ${baseClass} {`,
|
|
267
|
-
` constructor(ctx, env) {`,
|
|
268
|
-
` super(ctx, env);`,
|
|
269
|
-
` this.db = __initDO(ctx.storage.sql, __doSchema_${binding});`,
|
|
270
|
-
` }`,
|
|
271
397
|
` async __kuratchiLogActivity(payload) {`,
|
|
272
398
|
` const now = new Date().toISOString();`,
|
|
273
399
|
` try {`,
|
|
@@ -302,8 +428,8 @@ function buildOrmDurableObjectClassLines(className, binding, baseClass) {
|
|
|
302
428
|
` if (Number.isFinite(limit) && limit > 0) return rows.slice(0, Math.floor(limit));`,
|
|
303
429
|
` return rows;`,
|
|
304
430
|
` }`,
|
|
305
|
-
`}`,
|
|
306
431
|
];
|
|
432
|
+
void binding;
|
|
307
433
|
}
|
|
308
434
|
function buildWorkflowStatusRpc(opts) {
|
|
309
435
|
if (opts.workflowConfig.length === 0)
|
|
@@ -311,10 +437,12 @@ function buildWorkflowStatusRpc(opts) {
|
|
|
311
437
|
const rpcLines = [];
|
|
312
438
|
rpcLines.push(`\n// Workflow Status RPCs (auto-generated)`);
|
|
313
439
|
rpcLines.push(`const __workflowStatusRpc = {`);
|
|
440
|
+
const rpcNames = [];
|
|
314
441
|
for (const workflow of opts.workflowConfig) {
|
|
315
442
|
const baseName = workflow.file.split('/').pop()?.replace(/\.workflow\.ts$/, '') || '';
|
|
316
443
|
const camelName = baseName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
317
444
|
const rpcName = `${camelName}WorkflowStatus`;
|
|
445
|
+
rpcNames.push(rpcName);
|
|
318
446
|
rpcLines.push(` '${rpcName}': async (instanceId) => {`);
|
|
319
447
|
rpcLines.push(` if (!instanceId) return { status: 'unknown', error: { name: 'Error', message: 'Missing instanceId' } };`);
|
|
320
448
|
rpcLines.push(` try {`);
|
|
@@ -326,5 +454,9 @@ function buildWorkflowStatusRpc(opts) {
|
|
|
326
454
|
rpcLines.push(` },`);
|
|
327
455
|
}
|
|
328
456
|
rpcLines.push(`};`);
|
|
457
|
+
// Export individual functions for use in route templates
|
|
458
|
+
for (const rpcName of rpcNames) {
|
|
459
|
+
rpcLines.push(`const ${rpcName} = __workflowStatusRpc['${rpcName}'];`);
|
|
460
|
+
}
|
|
329
461
|
return rpcLines.join('\n');
|
|
330
462
|
}
|
|
@@ -28,6 +28,10 @@ export interface CompileTemplateOptions {
|
|
|
28
28
|
enableFragmentManifest?: boolean;
|
|
29
29
|
appendNewline?: boolean;
|
|
30
30
|
clientRouteRegistry?: ClientRouteRegistry;
|
|
31
|
+
awaitQueryBindings?: Map<string, {
|
|
32
|
+
asName: string;
|
|
33
|
+
rpcId: string;
|
|
34
|
+
}>;
|
|
31
35
|
}
|
|
32
36
|
export declare function splitTemplateRenderSections(template: string): TemplateRenderSections;
|
|
33
37
|
/**
|