@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,37 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
export function resolveRuntimeImportPath(projectDir) {
|
|
4
|
+
const candidates = [
|
|
5
|
+
{ file: 'src/server/runtime.hook.ts', importPath: '../src/server/runtime.hook' },
|
|
6
|
+
];
|
|
7
|
+
for (const candidate of candidates) {
|
|
8
|
+
if (fs.existsSync(path.join(projectDir, candidate.file))) {
|
|
9
|
+
return candidate.importPath;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
export function toWorkerImportPath(projectDir, outDir, filePath) {
|
|
15
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.join(projectDir, filePath);
|
|
16
|
+
let rel = path.relative(outDir, absPath).replace(/\\/g, '/');
|
|
17
|
+
if (!rel.startsWith('.'))
|
|
18
|
+
rel = `./${rel}`;
|
|
19
|
+
return rel.replace(/\.(ts|js|mjs|cjs)$/, '');
|
|
20
|
+
}
|
|
21
|
+
export function buildWorkerEntrypointSource(opts) {
|
|
22
|
+
const workerClassExports = opts.workerClassEntries
|
|
23
|
+
.map((entry) => {
|
|
24
|
+
const importPath = toWorkerImportPath(opts.projectDir, opts.outDir, entry.file);
|
|
25
|
+
if (entry.exportKind === 'default') {
|
|
26
|
+
return `export { default as ${entry.className} } from '${importPath}';`;
|
|
27
|
+
}
|
|
28
|
+
return `export { ${entry.className} } from '${importPath}';`;
|
|
29
|
+
});
|
|
30
|
+
return [
|
|
31
|
+
'// Auto-generated by kuratchi — do not edit.',
|
|
32
|
+
"export { default } from './routes.ts';",
|
|
33
|
+
...opts.doClassNames.map((className) => `export { ${className} } from './routes.ts';`),
|
|
34
|
+
...workerClassExports,
|
|
35
|
+
'',
|
|
36
|
+
].join('\n');
|
|
37
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface WranglerSyncEntry {
|
|
2
|
+
binding: string;
|
|
3
|
+
className: string;
|
|
4
|
+
}
|
|
5
|
+
export interface WranglerSyncConfig {
|
|
6
|
+
workflows: WranglerSyncEntry[];
|
|
7
|
+
containers: WranglerSyncEntry[];
|
|
8
|
+
durableObjects: WranglerSyncEntry[];
|
|
9
|
+
}
|
|
10
|
+
export declare function syncWranglerConfig(opts: {
|
|
11
|
+
projectDir: string;
|
|
12
|
+
config: WranglerSyncConfig;
|
|
13
|
+
writeFile: (filePath: string, content: string) => void;
|
|
14
|
+
}): void;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
function stripJsonComments(content) {
|
|
4
|
+
let result = '';
|
|
5
|
+
let i = 0;
|
|
6
|
+
let inString = false;
|
|
7
|
+
let stringChar = '';
|
|
8
|
+
while (i < content.length) {
|
|
9
|
+
const ch = content[i];
|
|
10
|
+
const next = content[i + 1];
|
|
11
|
+
if (inString) {
|
|
12
|
+
result += ch;
|
|
13
|
+
if (ch === '\\' && i + 1 < content.length) {
|
|
14
|
+
result += next;
|
|
15
|
+
i += 2;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (ch === stringChar) {
|
|
19
|
+
inString = false;
|
|
20
|
+
}
|
|
21
|
+
i++;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (ch === '"' || ch === "'") {
|
|
25
|
+
inString = true;
|
|
26
|
+
stringChar = ch;
|
|
27
|
+
result += ch;
|
|
28
|
+
i++;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (ch === '/' && next === '/') {
|
|
32
|
+
while (i < content.length && content[i] !== '\n')
|
|
33
|
+
i++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (ch === '/' && next === '*') {
|
|
37
|
+
i += 2;
|
|
38
|
+
while (i < content.length - 1 && !(content[i] === '*' && content[i + 1] === '/'))
|
|
39
|
+
i++;
|
|
40
|
+
i += 2;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
result += ch;
|
|
44
|
+
i++;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
export function syncWranglerConfig(opts) {
|
|
49
|
+
const jsoncPath = path.join(opts.projectDir, 'wrangler.jsonc');
|
|
50
|
+
const jsonPath = path.join(opts.projectDir, 'wrangler.json');
|
|
51
|
+
const tomlPath = path.join(opts.projectDir, 'wrangler.toml');
|
|
52
|
+
let configPath;
|
|
53
|
+
let isJsonc = false;
|
|
54
|
+
if (fs.existsSync(jsoncPath)) {
|
|
55
|
+
configPath = jsoncPath;
|
|
56
|
+
isJsonc = true;
|
|
57
|
+
}
|
|
58
|
+
else if (fs.existsSync(jsonPath)) {
|
|
59
|
+
configPath = jsonPath;
|
|
60
|
+
}
|
|
61
|
+
else if (fs.existsSync(tomlPath)) {
|
|
62
|
+
console.log('[kuratchi] wrangler.toml detected. Auto-sync requires wrangler.jsonc. Skipping wrangler sync.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.log('[kuratchi] Creating wrangler.jsonc with workflow config...');
|
|
67
|
+
configPath = jsoncPath;
|
|
68
|
+
isJsonc = true;
|
|
69
|
+
}
|
|
70
|
+
let rawContent = '';
|
|
71
|
+
let wranglerConfig = {};
|
|
72
|
+
if (fs.existsSync(configPath)) {
|
|
73
|
+
rawContent = fs.readFileSync(configPath, 'utf-8');
|
|
74
|
+
try {
|
|
75
|
+
const jsonContent = stripJsonComments(rawContent);
|
|
76
|
+
wranglerConfig = JSON.parse(jsonContent);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
console.error(`[kuratchi] Failed to parse ${path.basename(configPath)}: ${err.message}`);
|
|
80
|
+
console.error('[kuratchi] Skipping wrangler sync. Please fix the JSON syntax.');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
let changed = false;
|
|
85
|
+
if (opts.config.workflows.length > 0) {
|
|
86
|
+
const existingWorkflows = wranglerConfig.workflows || [];
|
|
87
|
+
const existingByBinding = new Map(existingWorkflows.map((workflow) => [workflow.binding, workflow]));
|
|
88
|
+
for (const workflow of opts.config.workflows) {
|
|
89
|
+
const name = workflow.binding.toLowerCase().replace(/_/g, '-');
|
|
90
|
+
const entry = {
|
|
91
|
+
name,
|
|
92
|
+
binding: workflow.binding,
|
|
93
|
+
class_name: workflow.className,
|
|
94
|
+
};
|
|
95
|
+
const existing = existingByBinding.get(workflow.binding);
|
|
96
|
+
if (!existing) {
|
|
97
|
+
existingWorkflows.push(entry);
|
|
98
|
+
changed = true;
|
|
99
|
+
console.log(`[kuratchi] Added workflow "${workflow.binding}" to wrangler config`);
|
|
100
|
+
}
|
|
101
|
+
else if (existing.class_name !== workflow.className) {
|
|
102
|
+
existing.class_name = workflow.className;
|
|
103
|
+
changed = true;
|
|
104
|
+
console.log(`[kuratchi] Updated workflow "${workflow.binding}" class_name to "${workflow.className}"`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const configBindings = new Set(opts.config.workflows.map((workflow) => workflow.binding));
|
|
108
|
+
const filtered = existingWorkflows.filter((workflow) => {
|
|
109
|
+
if (!configBindings.has(workflow.binding)) {
|
|
110
|
+
const expectedName = workflow.binding.toLowerCase().replace(/_/g, '-');
|
|
111
|
+
if (workflow.name === expectedName) {
|
|
112
|
+
console.log(`[kuratchi] Removed workflow "${workflow.binding}" from wrangler config`);
|
|
113
|
+
changed = true;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
if (filtered.length !== existingWorkflows.length) {
|
|
120
|
+
wranglerConfig.workflows = filtered;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
wranglerConfig.workflows = existingWorkflows;
|
|
124
|
+
}
|
|
125
|
+
if (wranglerConfig.workflows.length === 0) {
|
|
126
|
+
delete wranglerConfig.workflows;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (opts.config.containers.length > 0) {
|
|
130
|
+
const existingContainers = wranglerConfig.containers || [];
|
|
131
|
+
const existingByBinding = new Map(existingContainers.map((container) => [container.binding, container]));
|
|
132
|
+
for (const container of opts.config.containers) {
|
|
133
|
+
const name = container.binding.toLowerCase().replace(/_/g, '-');
|
|
134
|
+
const entry = {
|
|
135
|
+
name,
|
|
136
|
+
binding: container.binding,
|
|
137
|
+
class_name: container.className,
|
|
138
|
+
};
|
|
139
|
+
const existing = existingByBinding.get(container.binding);
|
|
140
|
+
if (!existing) {
|
|
141
|
+
existingContainers.push(entry);
|
|
142
|
+
changed = true;
|
|
143
|
+
console.log(`[kuratchi] Added container "${container.binding}" to wrangler config`);
|
|
144
|
+
}
|
|
145
|
+
else if (existing.class_name !== container.className) {
|
|
146
|
+
existing.class_name = container.className;
|
|
147
|
+
changed = true;
|
|
148
|
+
console.log(`[kuratchi] Updated container "${container.binding}" class_name to "${container.className}"`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
wranglerConfig.containers = existingContainers;
|
|
152
|
+
if (wranglerConfig.containers.length === 0) {
|
|
153
|
+
delete wranglerConfig.containers;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (opts.config.durableObjects.length > 0) {
|
|
157
|
+
if (!wranglerConfig.durable_objects) {
|
|
158
|
+
wranglerConfig.durable_objects = { bindings: [] };
|
|
159
|
+
}
|
|
160
|
+
const existingBindings = wranglerConfig.durable_objects.bindings || [];
|
|
161
|
+
const existingByName = new Map(existingBindings.map((binding) => [binding.name, binding]));
|
|
162
|
+
for (const durableObject of opts.config.durableObjects) {
|
|
163
|
+
const entry = {
|
|
164
|
+
name: durableObject.binding,
|
|
165
|
+
class_name: durableObject.className,
|
|
166
|
+
};
|
|
167
|
+
const existing = existingByName.get(durableObject.binding);
|
|
168
|
+
if (!existing) {
|
|
169
|
+
existingBindings.push(entry);
|
|
170
|
+
changed = true;
|
|
171
|
+
console.log(`[kuratchi] Added durable_object "${durableObject.binding}" to wrangler config`);
|
|
172
|
+
}
|
|
173
|
+
else if (existing.class_name !== durableObject.className) {
|
|
174
|
+
existing.class_name = durableObject.className;
|
|
175
|
+
changed = true;
|
|
176
|
+
console.log(`[kuratchi] Updated durable_object "${durableObject.binding}" class_name to "${durableObject.className}"`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
wranglerConfig.durable_objects.bindings = existingBindings;
|
|
180
|
+
}
|
|
181
|
+
if (!changed)
|
|
182
|
+
return;
|
|
183
|
+
const newContent = JSON.stringify(wranglerConfig, null, isJsonc ? '\t' : '\t');
|
|
184
|
+
opts.writeFile(configPath, newContent + '\n');
|
|
185
|
+
}
|
package/dist/runtime/app.js
CHANGED
|
@@ -133,13 +133,25 @@ export function createApp(config) {
|
|
|
133
133
|
}
|
|
134
134
|
/** Render a page through its layout */
|
|
135
135
|
function renderPage(route, data, layouts) {
|
|
136
|
-
const
|
|
136
|
+
const rendered = normalizeRenderOutput(route.render(data));
|
|
137
137
|
const layoutName = route.layout ?? 'default';
|
|
138
138
|
const layout = layouts[layoutName];
|
|
139
139
|
const html = layout
|
|
140
|
-
? layout.render({ content, data })
|
|
141
|
-
:
|
|
140
|
+
? layout.render({ content: rendered.html, data, head: rendered.head })
|
|
141
|
+
: rendered.html;
|
|
142
142
|
return new Response(html, {
|
|
143
143
|
headers: { 'content-type': 'text/html; charset=utf-8' },
|
|
144
144
|
});
|
|
145
145
|
}
|
|
146
|
+
function normalizeRenderOutput(output) {
|
|
147
|
+
if (typeof output === 'string') {
|
|
148
|
+
return { html: output, head: '' };
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
html: typeof output?.html === 'string' ? output.html : '',
|
|
152
|
+
head: typeof output?.head === 'string' ? output.head : '',
|
|
153
|
+
fragments: output && typeof output === 'object' && !Array.isArray(output) && typeof output.fragments === 'object'
|
|
154
|
+
? (output.fragments ?? {})
|
|
155
|
+
: {},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -57,3 +57,7 @@ export declare function __esc(v: any): string;
|
|
|
57
57
|
export declare function __rawHtml(v: any): string;
|
|
58
58
|
/** Best-effort HTML sanitizer for {@html ...} template output. */
|
|
59
59
|
export declare function __sanitizeHtml(v: any): string;
|
|
60
|
+
/** Get CSRF token for form injection (used by template compiler) */
|
|
61
|
+
export declare function __getCsrfToken(): string;
|
|
62
|
+
/** Sign a fragment ID for secure polling (used by template compiler) */
|
|
63
|
+
export declare function __signFragment(fragmentId: string): string;
|
package/dist/runtime/context.js
CHANGED
|
@@ -161,13 +161,51 @@ export function __rawHtml(v) {
|
|
|
161
161
|
/** Best-effort HTML sanitizer for {@html ...} template output. */
|
|
162
162
|
export function __sanitizeHtml(v) {
|
|
163
163
|
let html = __rawHtml(v);
|
|
164
|
+
// Remove dangerous elements entirely
|
|
164
165
|
html = html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '');
|
|
165
166
|
html = html.replace(/<iframe\b[^>]*>[\s\S]*?<\/iframe>/gi, '');
|
|
166
167
|
html = html.replace(/<object\b[^>]*>[\s\S]*?<\/object>/gi, '');
|
|
167
168
|
html = html.replace(/<embed\b[^>]*>/gi, '');
|
|
169
|
+
html = html.replace(/<base\b[^>]*>/gi, '');
|
|
170
|
+
html = html.replace(/<meta\b[^>]*>/gi, '');
|
|
171
|
+
html = html.replace(/<link\b[^>]*>/gi, '');
|
|
172
|
+
html = html.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '');
|
|
173
|
+
html = html.replace(/<template\b[^>]*>[\s\S]*?<\/template>/gi, '');
|
|
174
|
+
html = html.replace(/<noscript\b[^>]*>[\s\S]*?<\/noscript>/gi, '');
|
|
175
|
+
// Remove all event handlers (on*)
|
|
168
176
|
html = html.replace(/\son[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, '');
|
|
169
|
-
|
|
170
|
-
html = html.replace(/\s(href|src|xlink:href)\s*=\s*javascript:[
|
|
177
|
+
// Remove javascript: URLs in href, src, xlink:href, action, formaction, data
|
|
178
|
+
html = html.replace(/\s(href|src|xlink:href|action|formaction|data)\s*=\s*(["'])\s*javascript:[\s\S]*?\2/gi, ' $1="#"');
|
|
179
|
+
html = html.replace(/\s(href|src|xlink:href|action|formaction|data)\s*=\s*javascript:[^\s>]+/gi, ' $1="#"');
|
|
180
|
+
// Remove vbscript: URLs
|
|
181
|
+
html = html.replace(/\s(href|src|xlink:href|action|formaction|data)\s*=\s*(["'])\s*vbscript:[\s\S]*?\2/gi, ' $1="#"');
|
|
182
|
+
html = html.replace(/\s(href|src|xlink:href|action|formaction|data)\s*=\s*vbscript:[^\s>]+/gi, ' $1="#"');
|
|
183
|
+
// Remove data: URLs in src (can contain scripts)
|
|
184
|
+
html = html.replace(/\ssrc\s*=\s*(["'])\s*data:[\s\S]*?\1/gi, ' src="#"');
|
|
185
|
+
html = html.replace(/\ssrc\s*=\s*data:[^\s>]+/gi, ' src="#"');
|
|
186
|
+
// Remove srcdoc (can contain arbitrary HTML)
|
|
171
187
|
html = html.replace(/\ssrcdoc\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, '');
|
|
188
|
+
// Remove form-related dangerous attributes
|
|
189
|
+
html = html.replace(/\sformaction\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, '');
|
|
190
|
+
// Remove SVG-specific dangerous elements
|
|
191
|
+
html = html.replace(/<foreignObject\b[^>]*>[\s\S]*?<\/foreignObject>/gi, '');
|
|
192
|
+
html = html.replace(/<use\b[^>]*>/gi, '');
|
|
172
193
|
return html;
|
|
173
194
|
}
|
|
195
|
+
/** Get CSRF token for form injection (used by template compiler) */
|
|
196
|
+
export function __getCsrfToken() {
|
|
197
|
+
return __locals.__csrfToken || '';
|
|
198
|
+
}
|
|
199
|
+
/** Sign a fragment ID for secure polling (used by template compiler) */
|
|
200
|
+
export function __signFragment(fragmentId) {
|
|
201
|
+
const token = __locals.__csrfToken || '';
|
|
202
|
+
const routePath = __locals.__currentRoutePath || '/';
|
|
203
|
+
const payload = `${fragmentId}:${routePath}:${token}`;
|
|
204
|
+
// FNV-1a hash for fast, consistent signing
|
|
205
|
+
let hash = 2166136261;
|
|
206
|
+
for (let i = 0; i < payload.length; i++) {
|
|
207
|
+
hash ^= payload.charCodeAt(i);
|
|
208
|
+
hash = (hash * 16777619) >>> 0;
|
|
209
|
+
}
|
|
210
|
+
return `${fragmentId}:${hash.toString(36)}`;
|
|
211
|
+
}
|
package/dist/runtime/do.js
CHANGED
|
@@ -30,6 +30,10 @@ export function __getDoSelf() {
|
|
|
30
30
|
}
|
|
31
31
|
const _resolvers = new Map();
|
|
32
32
|
const _classBindings = new WeakMap();
|
|
33
|
+
const _rpcProxyCache = new WeakMap();
|
|
34
|
+
function __isDevMode() {
|
|
35
|
+
return !!globalThis.__kuratchi_DEV__;
|
|
36
|
+
}
|
|
33
37
|
/** @internal — called by compiler-generated init code */
|
|
34
38
|
export function __registerDoResolver(binding, resolver) {
|
|
35
39
|
_resolvers.set(binding, resolver);
|
|
@@ -94,36 +98,47 @@ export class kuratchiDO {
|
|
|
94
98
|
*/
|
|
95
99
|
static rpc() {
|
|
96
100
|
const klass = this;
|
|
97
|
-
|
|
101
|
+
// Cache proxy per class to avoid repeated Proxy creation
|
|
102
|
+
let cached = _rpcProxyCache.get(klass);
|
|
103
|
+
if (cached)
|
|
104
|
+
return cached;
|
|
105
|
+
const proxy = new Proxy({}, {
|
|
98
106
|
get(_, method) {
|
|
99
107
|
return async (...args) => {
|
|
100
108
|
const binding = this.binding || _classBindings.get(klass);
|
|
101
109
|
if (!binding) {
|
|
102
110
|
throw new Error(`[KuratchiJS] Missing DO binding for class '${this?.name || 'UnknownDO'}'. Add static binding or ensure compiler binding registration is active.`);
|
|
103
111
|
}
|
|
104
|
-
|
|
112
|
+
if (__isDevMode())
|
|
113
|
+
console.log(`[rpc] ${binding}.${method}() — resolving stub...`);
|
|
105
114
|
const stub = await __getDoStub(binding);
|
|
106
115
|
if (!stub) {
|
|
107
116
|
throw new Error(`[KuratchiJS] Not authenticated — cannot call '${method}' on ${binding}`);
|
|
108
117
|
}
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
if (__isDevMode()) {
|
|
119
|
+
console.log(`[rpc] ${binding}.${method}() — stub type: ${stub?.constructor?.name ?? typeof stub}`);
|
|
120
|
+
console.log(`[rpc] ${binding}.${method}() — calling with ${args.length} arg(s)...`);
|
|
121
|
+
}
|
|
111
122
|
try {
|
|
112
123
|
// Call method directly on the stub — DO NOT detach with stub[method]
|
|
113
124
|
// then .apply(). Workers RPC stubs are Proxy-based; detaching breaks
|
|
114
125
|
// the runtime's interception and causes DataCloneError trying to
|
|
115
126
|
// serialize the DurableObject as `this`.
|
|
116
127
|
const result = await stub[method](...args);
|
|
117
|
-
|
|
128
|
+
if (__isDevMode())
|
|
129
|
+
console.log(`[rpc] ${binding}.${method}() — returned, result type: ${typeof result}`);
|
|
118
130
|
return result;
|
|
119
131
|
}
|
|
120
132
|
catch (err) {
|
|
121
|
-
|
|
133
|
+
if (__isDevMode())
|
|
134
|
+
console.error(`[rpc] ${binding}.${method}() — THREW: ${err.message}`);
|
|
122
135
|
throw err;
|
|
123
136
|
}
|
|
124
137
|
};
|
|
125
138
|
},
|
|
126
139
|
});
|
|
140
|
+
_rpcProxyCache.set(klass, proxy);
|
|
141
|
+
return proxy;
|
|
127
142
|
}
|
|
128
143
|
}
|
|
129
144
|
/**
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { PageRenderOutput, RuntimeContext, RuntimeDefinition } from './types.js';
|
|
2
|
+
export interface GeneratedAssetEntry {
|
|
3
|
+
content: string;
|
|
4
|
+
mime: string;
|
|
5
|
+
etag: string;
|
|
6
|
+
}
|
|
7
|
+
export interface GeneratedApiRoute {
|
|
8
|
+
pattern: string;
|
|
9
|
+
__api: true;
|
|
10
|
+
[method: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface GeneratedPageRoute {
|
|
13
|
+
pattern: string;
|
|
14
|
+
load?: (params: Record<string, string>) => Promise<unknown> | unknown;
|
|
15
|
+
actions?: Record<string, (...args: any[]) => Promise<unknown> | unknown>;
|
|
16
|
+
rpc?: Record<string, (...args: any[]) => Promise<unknown> | unknown>;
|
|
17
|
+
/** Allowed query function names for this route (for query override validation) */
|
|
18
|
+
allowedQueries?: string[];
|
|
19
|
+
render: (data: Record<string, any>) => PageRenderOutput;
|
|
20
|
+
}
|
|
21
|
+
export interface SecurityOptions {
|
|
22
|
+
/** Enable CSRF protection (default: true) */
|
|
23
|
+
csrfEnabled?: boolean;
|
|
24
|
+
/** CSRF cookie name (default: '__kuratchi_csrf') */
|
|
25
|
+
csrfCookieName?: string;
|
|
26
|
+
/** CSRF header name (default: 'x-kuratchi-csrf') */
|
|
27
|
+
csrfHeaderName?: string;
|
|
28
|
+
/** Require authentication for RPC (default: false) */
|
|
29
|
+
rpcRequireAuth?: boolean;
|
|
30
|
+
/** Require authentication for form actions (default: false) */
|
|
31
|
+
actionRequireAuth?: boolean;
|
|
32
|
+
/** Content Security Policy directive string */
|
|
33
|
+
contentSecurityPolicy?: string | null;
|
|
34
|
+
/** Strict-Transport-Security header value */
|
|
35
|
+
strictTransportSecurity?: string | null;
|
|
36
|
+
/** Permissions-Policy header value */
|
|
37
|
+
permissionsPolicy?: string | null;
|
|
38
|
+
}
|
|
39
|
+
export interface GeneratedWorkerOptions {
|
|
40
|
+
routes: Array<GeneratedPageRoute | GeneratedApiRoute>;
|
|
41
|
+
layout: (content: string, head?: string) => Promise<string> | string;
|
|
42
|
+
layoutActions: Record<string, (...args: any[]) => Promise<unknown> | unknown>;
|
|
43
|
+
assetsPrefix: string;
|
|
44
|
+
assets: Record<string, GeneratedAssetEntry>;
|
|
45
|
+
errorPages: Record<number, (detail?: string) => string>;
|
|
46
|
+
runtimeDefinition?: RuntimeDefinition;
|
|
47
|
+
workflowStatusRpc?: Record<string, (instanceId: string) => Promise<unknown>>;
|
|
48
|
+
initializeRequest?: (ctx: RuntimeContext) => Promise<void> | void;
|
|
49
|
+
preRouteChecks?: (ctx: RuntimeContext) => Promise<Response | null | undefined> | Response | null | undefined;
|
|
50
|
+
/** Security configuration */
|
|
51
|
+
security?: SecurityOptions;
|
|
52
|
+
}
|
|
53
|
+
export declare function createGeneratedWorker(opts: GeneratedWorkerOptions): {
|
|
54
|
+
fetch(request: Request, env: Record<string, any>, ctx: ExecutionContext): Promise<Response>;
|
|
55
|
+
};
|