@emkodev/emroute 1.7.3 → 1.8.0-beta.2
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 +1 -1
- package/core/component/abstract.component.ts +74 -0
- package/{src → core}/component/page.component.ts +3 -61
- package/core/component/widget.component.ts +54 -0
- package/core/pipeline/pipeline.ts +224 -0
- package/{src/renderer/ssr → core/renderer}/html.renderer.ts +26 -47
- package/{src/renderer/ssr → core/renderer}/md.renderer.ts +22 -41
- package/{src/renderer/ssr → core/renderer}/ssr.renderer.ts +44 -58
- package/{src/route → core/router}/route.resolver.ts +1 -10
- package/core/router/route.trie.ts +175 -0
- package/core/runtime/abstract.runtime.ts +47 -0
- package/core/server/emroute.server.ts +324 -0
- package/core/type/component.type.ts +39 -0
- package/core/type/element.type.ts +10 -0
- package/core/type/logger.type.ts +20 -0
- package/core/type/markdown.type.ts +8 -0
- package/core/type/route-tree.type.ts +28 -0
- package/core/type/route.type.ts +75 -0
- package/core/type/widget.type.ts +27 -0
- package/core/util/html.util.ts +50 -0
- package/{src → core}/util/md.util.ts +3 -5
- package/{src/route → core/util}/route-tree.util.ts +0 -2
- package/{src → core}/util/widget-resolve.util.ts +15 -46
- package/{src → core}/widget/widget.parser.ts +2 -23
- package/core/widget/widget.registry.ts +36 -0
- package/dist/core/component/abstract.component.d.ts +48 -0
- package/dist/core/component/abstract.component.js +42 -0
- package/dist/core/component/abstract.component.js.map +1 -0
- package/dist/core/component/page.component.d.ts +23 -0
- package/dist/core/component/page.component.js +49 -0
- package/dist/core/component/page.component.js.map +1 -0
- package/dist/core/component/widget.component.d.ts +17 -0
- package/dist/core/component/widget.component.js +37 -0
- package/dist/core/component/widget.component.js.map +1 -0
- package/dist/core/pipeline/pipeline.d.ts +61 -0
- package/dist/core/pipeline/pipeline.js +189 -0
- package/dist/core/pipeline/pipeline.js.map +1 -0
- package/dist/{src/renderer/ssr → core/renderer}/html.renderer.d.ts +8 -24
- package/dist/{src/renderer/ssr → core/renderer}/html.renderer.js +20 -35
- package/dist/core/renderer/html.renderer.js.map +1 -0
- package/dist/{src/renderer/ssr → core/renderer}/md.renderer.d.ts +6 -21
- package/dist/{src/renderer/ssr → core/renderer}/md.renderer.js +16 -32
- package/dist/core/renderer/md.renderer.js.map +1 -0
- package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.d.ts +11 -27
- package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.js +33 -37
- package/dist/core/renderer/ssr.renderer.js.map +1 -0
- package/dist/{src/route → core/router}/route.resolver.d.ts +1 -8
- package/dist/{src/route → core/router}/route.resolver.js +0 -1
- package/dist/core/router/route.resolver.js.map +1 -0
- package/dist/core/router/route.trie.d.ts +32 -0
- package/dist/core/router/route.trie.js +152 -0
- package/dist/core/router/route.trie.js.map +1 -0
- package/dist/core/runtime/abstract.runtime.d.ts +32 -0
- package/dist/core/runtime/abstract.runtime.js +26 -0
- package/dist/core/runtime/abstract.runtime.js.map +1 -0
- package/dist/core/server/emroute.server.d.ts +48 -0
- package/dist/core/server/emroute.server.js +239 -0
- package/dist/core/server/emroute.server.js.map +1 -0
- package/dist/core/server/server.type.d.ts +45 -0
- package/dist/core/server/server.type.js +11 -0
- package/dist/core/server/server.type.js.map +1 -0
- package/dist/core/type/component.type.d.ts +37 -0
- package/dist/core/type/component.type.js +7 -0
- package/dist/core/type/component.type.js.map +1 -0
- package/dist/core/type/element.type.d.ts +9 -0
- package/dist/core/type/element.type.js +5 -0
- package/dist/core/type/element.type.js.map +1 -0
- package/dist/core/type/logger.type.d.ts +14 -0
- package/dist/core/type/logger.type.js +8 -0
- package/dist/core/type/logger.type.js.map +1 -0
- package/dist/core/type/markdown.type.d.ts +7 -0
- package/dist/core/type/markdown.type.js +5 -0
- package/dist/core/type/markdown.type.js.map +1 -0
- package/dist/{src → core}/type/route-tree.type.d.ts +0 -12
- package/dist/{src → core}/type/route-tree.type.js +0 -1
- package/dist/core/type/route-tree.type.js.map +1 -0
- package/dist/core/type/route.type.d.ts +62 -0
- package/dist/core/type/route.type.js +7 -0
- package/dist/core/type/route.type.js.map +1 -0
- package/dist/core/type/widget.type.d.ts +27 -0
- package/dist/core/type/widget.type.js +5 -0
- package/dist/core/type/widget.type.js.map +1 -0
- package/dist/core/util/html.util.d.ts +14 -0
- package/dist/core/util/html.util.js +43 -0
- package/dist/core/util/html.util.js.map +1 -0
- package/dist/{src → core}/util/md.util.d.ts +0 -1
- package/dist/{src → core}/util/md.util.js +0 -2
- package/dist/core/util/md.util.js.map +1 -0
- package/dist/{src/route → core/util}/route-tree.util.js +0 -2
- package/dist/core/util/route-tree.util.js.map +1 -0
- package/dist/core/util/widget-resolve.util.d.ts +28 -0
- package/dist/{src → core}/util/widget-resolve.util.js +12 -42
- package/dist/core/util/widget-resolve.util.js.map +1 -0
- package/dist/{src → core}/widget/widget.parser.d.ts +0 -13
- package/dist/{src → core}/widget/widget.parser.js +1 -22
- package/dist/core/widget/widget.parser.js.map +1 -0
- package/dist/core/widget/widget.registry.d.ts +14 -0
- package/dist/core/widget/widget.registry.js +26 -0
- package/dist/core/widget/widget.registry.js.map +1 -0
- package/dist/emroute.js +1092 -1220
- package/dist/emroute.js.map +36 -5
- package/dist/runtime/abstract.runtime.d.ts +41 -7
- package/dist/runtime/abstract.runtime.js +404 -9
- package/dist/runtime/abstract.runtime.js.map +1 -1
- package/dist/runtime/bun/fs/bun-fs.runtime.d.ts +1 -0
- package/dist/runtime/bun/fs/bun-fs.runtime.js +15 -1
- package/dist/runtime/bun/fs/bun-fs.runtime.js.map +1 -1
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.d.ts +2 -0
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js +8 -0
- package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js.map +1 -1
- package/dist/runtime/fetch.runtime.d.ts +3 -3
- package/dist/runtime/fetch.runtime.js +3 -3
- package/dist/runtime/sitemap.generator.d.ts +1 -1
- package/dist/runtime/sitemap.generator.js +1 -1
- package/dist/runtime/sitemap.generator.js.map +1 -1
- package/dist/runtime/universal/fs/universal-fs.runtime.d.ts +1 -0
- package/dist/runtime/universal/fs/universal-fs.runtime.js +15 -1
- package/dist/runtime/universal/fs/universal-fs.runtime.js.map +1 -1
- package/dist/server/build.util.d.ts +9 -10
- package/dist/server/build.util.js +11 -31
- package/dist/server/build.util.js.map +1 -1
- package/dist/server/codegen.util.d.ts +1 -1
- package/dist/server/emroute.server.d.ts +8 -35
- package/dist/server/emroute.server.js +7 -351
- package/dist/server/emroute.server.js.map +1 -1
- package/dist/server/esbuild-manifest.plugin.js +1 -1
- package/dist/server/esbuild-manifest.plugin.js.map +1 -1
- package/dist/server/server-api.type.d.ts +3 -71
- package/dist/server/server-api.type.js +1 -8
- package/dist/server/server-api.type.js.map +1 -1
- package/dist/src/element/component.element.d.ts +6 -14
- package/dist/src/element/component.element.js +13 -40
- package/dist/src/element/component.element.js.map +1 -1
- package/dist/src/element/markdown.element.d.ts +2 -2
- package/dist/src/element/markdown.element.js +3 -2
- package/dist/src/element/markdown.element.js.map +1 -1
- package/dist/src/index.d.ts +15 -14
- package/dist/src/index.js +8 -8
- package/dist/src/index.js.map +1 -1
- package/dist/src/renderer/spa/emroute.app.d.ts +50 -0
- package/dist/src/renderer/spa/emroute.app.js +246 -0
- package/dist/src/renderer/spa/emroute.app.js.map +1 -0
- package/dist/src/renderer/spa/mod.d.ts +17 -16
- package/dist/src/renderer/spa/mod.js +9 -9
- package/dist/src/renderer/spa/mod.js.map +1 -1
- package/dist/src/renderer/spa/thin-client.d.ts +3 -3
- package/dist/src/renderer/spa/thin-client.js +7 -7
- package/dist/src/renderer/spa/thin-client.js.map +1 -1
- package/dist/src/route/route.core.d.ts +3 -3
- package/dist/src/util/html.util.d.ts +5 -22
- package/dist/src/util/html.util.js +8 -56
- package/dist/src/util/html.util.js.map +1 -1
- package/dist/src/widget/breadcrumb.widget.d.ts +2 -2
- package/dist/src/widget/breadcrumb.widget.js +2 -2
- package/dist/src/widget/breadcrumb.widget.js.map +1 -1
- package/dist/src/widget/page-title.widget.d.ts +1 -1
- package/dist/src/widget/page-title.widget.js +1 -1
- package/dist/src/widget/page-title.widget.js.map +1 -1
- package/package.json +8 -8
- package/runtime/abstract.runtime.ts +433 -17
- package/runtime/bun/fs/bun-fs.runtime.ts +15 -1
- package/runtime/bun/sqlite/bun-sqlite.runtime.ts +9 -0
- package/runtime/fetch.runtime.ts +3 -3
- package/runtime/sitemap.generator.ts +2 -2
- package/runtime/universal/fs/universal-fs.runtime.ts +15 -1
- package/server/build.util.ts +17 -43
- package/server/codegen.util.ts +1 -1
- package/server/emroute.server.ts +12 -426
- package/src/element/component.element.ts +14 -54
- package/src/element/markdown.element.ts +4 -3
- package/src/index.ts +22 -19
- package/src/renderer/spa/{thin-client.ts → emroute.app.ts} +19 -20
- package/src/renderer/spa/mod.ts +22 -22
- package/src/util/html.util.ts +16 -61
- package/src/widget/breadcrumb.widget.ts +3 -3
- package/src/widget/page-title.widget.ts +1 -1
- package/dist/src/component/abstract.component.d.ts +0 -199
- package/dist/src/component/abstract.component.js +0 -84
- package/dist/src/component/abstract.component.js.map +0 -1
- package/dist/src/component/page.component.d.ts +0 -74
- package/dist/src/component/page.component.js +0 -107
- package/dist/src/component/page.component.js.map +0 -1
- package/dist/src/component/widget.component.d.ts +0 -47
- package/dist/src/component/widget.component.js +0 -69
- package/dist/src/component/widget.component.js.map +0 -1
- package/dist/src/renderer/ssr/html.renderer.js.map +0 -1
- package/dist/src/renderer/ssr/md.renderer.js.map +0 -1
- package/dist/src/renderer/ssr/ssr.renderer.js.map +0 -1
- package/dist/src/route/route-tree.util.js.map +0 -1
- package/dist/src/route/route.matcher.d.ts +0 -86
- package/dist/src/route/route.matcher.js +0 -214
- package/dist/src/route/route.matcher.js.map +0 -1
- package/dist/src/route/route.resolver.js.map +0 -1
- package/dist/src/route/route.trie.d.ts +0 -38
- package/dist/src/route/route.trie.js +0 -206
- package/dist/src/route/route.trie.js.map +0 -1
- package/dist/src/type/element.type.d.ts +0 -19
- package/dist/src/type/element.type.js +0 -9
- package/dist/src/type/element.type.js.map +0 -1
- package/dist/src/type/logger.type.d.ts +0 -17
- package/dist/src/type/logger.type.js +0 -9
- package/dist/src/type/logger.type.js.map +0 -1
- package/dist/src/type/markdown.type.d.ts +0 -20
- package/dist/src/type/markdown.type.js +0 -2
- package/dist/src/type/markdown.type.js.map +0 -1
- package/dist/src/type/route-tree.type.js.map +0 -1
- package/dist/src/type/route.type.d.ts +0 -94
- package/dist/src/type/route.type.js +0 -8
- package/dist/src/type/route.type.js.map +0 -1
- package/dist/src/type/widget.type.d.ts +0 -55
- package/dist/src/type/widget.type.js +0 -10
- package/dist/src/type/widget.type.js.map +0 -1
- package/dist/src/util/logger.util.d.ts +0 -26
- package/dist/src/util/logger.util.js +0 -80
- package/dist/src/util/logger.util.js.map +0 -1
- package/dist/src/util/md.util.js.map +0 -1
- package/dist/src/util/widget-resolve.util.d.ts +0 -52
- package/dist/src/util/widget-resolve.util.js.map +0 -1
- package/dist/src/widget/widget.parser.js.map +0 -1
- package/dist/src/widget/widget.registry.d.ts +0 -23
- package/dist/src/widget/widget.registry.js +0 -42
- package/dist/src/widget/widget.registry.js.map +0 -1
- package/runtime/bun/esbuild-runtime-loader.plugin.ts +0 -112
- package/server/esbuild-manifest.plugin.ts +0 -209
- package/server/server-api.type.ts +0 -101
- package/src/component/abstract.component.ts +0 -231
- package/src/component/widget.component.ts +0 -85
- package/src/route/route.core.ts +0 -371
- package/src/route/route.trie.ts +0 -265
- package/src/type/element.type.ts +0 -22
- package/src/type/logger.type.ts +0 -24
- package/src/type/markdown.type.ts +0 -21
- package/src/type/route-tree.type.ts +0 -51
- package/src/type/route.type.ts +0 -124
- package/src/type/widget.type.ts +0 -65
- package/src/util/logger.util.ts +0 -83
- package/src/widget/widget.registry.ts +0 -51
- /package/dist/{src/route → core/util}/route-tree.util.d.ts +0 -0
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
* Generates Markdown strings for LLM consumption, text clients, curl.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { RouteConfig, RouteInfo } from '
|
|
9
|
-
import type { ComponentContext } from '
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
12
|
-
import { DEFAULT_ROOT_ROUTE } from '
|
|
13
|
-
import { STATUS_MESSAGES } from '
|
|
14
|
-
import { resolveRecursively } from '
|
|
15
|
-
import { parseWidgetBlocks, replaceWidgetBlocks } from '
|
|
8
|
+
import type { RouteConfig, RouteInfo } from '../type/route.type.ts';
|
|
9
|
+
import type { ComponentContext } from '../type/component.type.ts';
|
|
10
|
+
import type { PageComponent } from '../component/page.component.ts';
|
|
11
|
+
import type { Pipeline } from '../pipeline/pipeline.ts';
|
|
12
|
+
import { DEFAULT_ROOT_ROUTE } from '../pipeline/pipeline.ts';
|
|
13
|
+
import { STATUS_MESSAGES } from '../util/html.util.ts';
|
|
14
|
+
import { resolveRecursively } from '../util/widget-resolve.util.ts';
|
|
15
|
+
import { parseWidgetBlocks, replaceWidgetBlocks } from '../widget/widget.parser.ts';
|
|
16
16
|
import { SsrRenderer, type SsrRendererOptions } from './ssr.renderer.ts';
|
|
17
17
|
|
|
18
18
|
const BARE_SLOT_BLOCK = '```router-slot\n```';
|
|
@@ -21,17 +21,13 @@ function routerSlotBlock(pattern: string): string {
|
|
|
21
21
|
return `\`\`\`router-slot\n{"pattern":"${pattern}"}\n\`\`\``;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
export type SsrMdRouterOptions = SsrRendererOptions;
|
|
24
|
+
export type SsrMdRendererOptions = SsrRendererOptions;
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
* SSR Markdown Router for server-side markdown rendering.
|
|
29
|
-
*/
|
|
30
|
-
export class SsrMdRouter extends SsrRenderer {
|
|
26
|
+
export class SsrMdRenderer extends SsrRenderer {
|
|
31
27
|
protected override readonly label = 'SSR MD';
|
|
32
28
|
|
|
33
|
-
constructor(
|
|
34
|
-
super(
|
|
29
|
+
constructor(pipeline: Pipeline, options: SsrMdRendererOptions = {}) {
|
|
30
|
+
super(pipeline, options);
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
protected override injectSlot(parent: string, child: string, parentPattern: string): string {
|
|
@@ -44,9 +40,6 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
44
40
|
.trim();
|
|
45
41
|
}
|
|
46
42
|
|
|
47
|
-
/**
|
|
48
|
-
* Render a single route's content to Markdown.
|
|
49
|
-
*/
|
|
50
43
|
protected override async renderRouteContent(
|
|
51
44
|
routeInfo: RouteInfo,
|
|
52
45
|
route: RouteConfig,
|
|
@@ -61,15 +54,14 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
61
54
|
let content = rawContent;
|
|
62
55
|
|
|
63
56
|
// Attribute bare router-slot blocks with this route's pattern
|
|
64
|
-
// (before widget resolution so widget-internal blocks are not affected)
|
|
65
57
|
content = content.replaceAll(BARE_SLOT_BLOCK, routerSlotBlock(route.pattern));
|
|
66
58
|
|
|
67
|
-
// Resolve fenced widget blocks
|
|
59
|
+
// Resolve fenced widget blocks
|
|
68
60
|
if (this.widgets) {
|
|
69
61
|
content = await this.resolveWidgets(content, routeInfo);
|
|
70
62
|
}
|
|
71
63
|
|
|
72
|
-
return { content, ...(title
|
|
64
|
+
return { content, ...(title !== undefined ? { title } : {}) };
|
|
73
65
|
}
|
|
74
66
|
|
|
75
67
|
protected override renderContent(
|
|
@@ -91,10 +83,6 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
91
83
|
return `# Internal Server Error\n\nPath: \`${url.pathname}\``;
|
|
92
84
|
}
|
|
93
85
|
|
|
94
|
-
/**
|
|
95
|
-
* Resolve fenced widget blocks in markdown content.
|
|
96
|
-
* Replaces ```widget:name blocks with rendered markdown output.
|
|
97
|
-
*/
|
|
98
86
|
private resolveWidgets(
|
|
99
87
|
content: string,
|
|
100
88
|
routeInfo: RouteInfo,
|
|
@@ -114,9 +102,10 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
114
102
|
|
|
115
103
|
try {
|
|
116
104
|
let files: { html?: string; md?: string } | undefined;
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
|
|
105
|
+
const modulePath = this.widgets!.getModulePath(block.widgetName);
|
|
106
|
+
if (modulePath) {
|
|
107
|
+
const mod = await this.pipeline.loadModule(modulePath);
|
|
108
|
+
files = this.pipeline.getModuleFiles(mod);
|
|
120
109
|
}
|
|
121
110
|
|
|
122
111
|
const baseContext: ComponentContext = {
|
|
@@ -125,8 +114,8 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
125
114
|
searchParams: routeInfo.url.searchParams,
|
|
126
115
|
...(files ? { files } : {}),
|
|
127
116
|
};
|
|
128
|
-
const context: ComponentContext = this.
|
|
129
|
-
? this.
|
|
117
|
+
const context: ComponentContext = this.pipeline.contextProvider
|
|
118
|
+
? this.pipeline.contextProvider(baseContext)
|
|
130
119
|
: baseContext;
|
|
131
120
|
const data = await widget.getData({ params: block.params, context });
|
|
132
121
|
return widget.renderMarkdown({ data, params: block.params, context });
|
|
@@ -135,16 +124,8 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
135
124
|
}
|
|
136
125
|
},
|
|
137
126
|
replaceWidgetBlocks,
|
|
127
|
+
0,
|
|
128
|
+
this.logger,
|
|
138
129
|
);
|
|
139
130
|
}
|
|
140
131
|
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Create SSR Markdown router.
|
|
144
|
-
*/
|
|
145
|
-
export function createSsrMdRouter(
|
|
146
|
-
resolver: RouteResolver,
|
|
147
|
-
options?: SsrMdRouterOptions,
|
|
148
|
-
): SsrMdRouter {
|
|
149
|
-
return new SsrMdRouter(resolver, options);
|
|
150
|
-
}
|
|
@@ -9,40 +9,33 @@ import type {
|
|
|
9
9
|
MatchedRoute,
|
|
10
10
|
RouteConfig,
|
|
11
11
|
RouteInfo,
|
|
12
|
-
} from '
|
|
13
|
-
import {
|
|
14
|
-
import type {
|
|
15
|
-
import defaultPageComponent, { type PageComponent } from '
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import type { RouteResolver } from '../../route/route.resolver.ts';
|
|
23
|
-
import type { WidgetRegistry } from '../../widget/widget.registry.ts';
|
|
24
|
-
|
|
25
|
-
/** Base options for SSR renderers */
|
|
26
|
-
export interface SsrRendererOptions extends RouteCoreOptions {
|
|
27
|
-
/** Widget registry for server-side widget rendering */
|
|
12
|
+
} from '../type/route.type.ts';
|
|
13
|
+
import type { ComponentContext } from '../type/component.type.ts';
|
|
14
|
+
import type { Logger } from '../type/logger.type.ts';
|
|
15
|
+
import defaultPageComponent, { type PageComponent } from '../component/page.component.ts';
|
|
16
|
+
import { DEFAULT_ROOT_ROUTE, type Pipeline } from '../pipeline/pipeline.ts';
|
|
17
|
+
import { assertSafeRedirect } from '../util/html.util.ts';
|
|
18
|
+
import type { WidgetRegistry } from '../widget/widget.registry.ts';
|
|
19
|
+
|
|
20
|
+
/** Options for SSR renderers. */
|
|
21
|
+
export interface SsrRendererOptions {
|
|
28
22
|
widgets?: WidgetRegistry;
|
|
29
|
-
/** Widget companion file paths, keyed by widget name */
|
|
30
|
-
widgetFiles?: Record<string, { html?: string; md?: string; css?: string }>;
|
|
31
23
|
}
|
|
32
24
|
|
|
33
25
|
/**
|
|
34
26
|
* Abstract SSR renderer with shared routing pipeline.
|
|
35
27
|
*/
|
|
36
28
|
export abstract class SsrRenderer {
|
|
37
|
-
protected
|
|
29
|
+
protected readonly pipeline: Pipeline;
|
|
38
30
|
protected widgets: WidgetRegistry | null;
|
|
39
|
-
protected widgetFiles: Record<string, { html?: string; md?: string; css?: string }>;
|
|
40
31
|
protected abstract readonly label: string;
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
protected readonly logger: Logger;
|
|
34
|
+
|
|
35
|
+
constructor(pipeline: Pipeline, options: SsrRendererOptions = {}) {
|
|
36
|
+
this.pipeline = pipeline;
|
|
37
|
+
this.logger = pipeline.logger;
|
|
44
38
|
this.widgets = options.widgets ?? null;
|
|
45
|
-
this.widgetFiles = options.widgetFiles ?? {};
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
/**
|
|
@@ -52,17 +45,17 @@ export abstract class SsrRenderer {
|
|
|
52
45
|
url: URL,
|
|
53
46
|
signal?: AbortSignal,
|
|
54
47
|
): Promise<{ content: string; status: number; title?: string; redirect?: string }> {
|
|
55
|
-
const matched = this.
|
|
48
|
+
const matched = await this.pipeline.match(url);
|
|
56
49
|
|
|
57
50
|
if (!matched) {
|
|
58
|
-
const statusPage = this.
|
|
51
|
+
const statusPage = await this.pipeline.getStatusPage(404);
|
|
59
52
|
if (statusPage) {
|
|
60
53
|
try {
|
|
61
54
|
const ri: RouteInfo = { url, params: {} };
|
|
62
55
|
const result = await this.renderRouteContent(ri, statusPage, undefined, signal);
|
|
63
|
-
return { content: this.stripSlots(result.content), status: 404, ...(result.title
|
|
56
|
+
return { content: this.stripSlots(result.content), status: 404, ...(result.title !== undefined ? { title: result.title } : {}) };
|
|
64
57
|
} catch (e) {
|
|
65
|
-
logger.error(
|
|
58
|
+
this.logger.error(
|
|
66
59
|
`[${this.label}] Failed to render 404 status page for ${url.pathname}`,
|
|
67
60
|
e instanceof Error ? e : undefined,
|
|
68
61
|
);
|
|
@@ -73,7 +66,7 @@ export abstract class SsrRenderer {
|
|
|
73
66
|
|
|
74
67
|
// Handle redirect
|
|
75
68
|
if (matched.route.type === 'redirect') {
|
|
76
|
-
const module = await this.
|
|
69
|
+
const module = await this.pipeline.loadModule<{ default: { to: string; status?: number } }>(
|
|
77
70
|
matched.route.modulePath,
|
|
78
71
|
);
|
|
79
72
|
const redirectConfig = module.default;
|
|
@@ -85,14 +78,14 @@ export abstract class SsrRenderer {
|
|
|
85
78
|
};
|
|
86
79
|
}
|
|
87
80
|
|
|
88
|
-
const routeInfo = this.
|
|
81
|
+
const routeInfo = this.pipeline.toRouteInfo(matched, url);
|
|
89
82
|
|
|
90
83
|
try {
|
|
91
84
|
const { content, title } = await this.renderPage(routeInfo, matched, signal);
|
|
92
|
-
return { content, status: 200, ...(title
|
|
85
|
+
return { content, status: 200, ...(title !== undefined ? { title } : {}) };
|
|
93
86
|
} catch (error) {
|
|
94
87
|
if (error instanceof Response) {
|
|
95
|
-
const statusPage = this.
|
|
88
|
+
const statusPage = await this.pipeline.getStatusPage(error.status);
|
|
96
89
|
if (statusPage) {
|
|
97
90
|
try {
|
|
98
91
|
const ri: RouteInfo = { url, params: {} };
|
|
@@ -100,10 +93,10 @@ export abstract class SsrRenderer {
|
|
|
100
93
|
return {
|
|
101
94
|
content: this.stripSlots(result.content),
|
|
102
95
|
status: error.status,
|
|
103
|
-
...(result.title
|
|
96
|
+
...(result.title !== undefined ? { title: result.title } : {}),
|
|
104
97
|
};
|
|
105
98
|
} catch (e) {
|
|
106
|
-
logger.error(
|
|
99
|
+
this.logger.error(
|
|
107
100
|
`[${this.label}] Failed to render ${error.status} status page for ${url.pathname}`,
|
|
108
101
|
e instanceof Error ? e : undefined,
|
|
109
102
|
);
|
|
@@ -111,18 +104,18 @@ export abstract class SsrRenderer {
|
|
|
111
104
|
}
|
|
112
105
|
return { content: this.renderStatusPage(error.status, url), status: error.status };
|
|
113
106
|
}
|
|
114
|
-
logger.error(
|
|
107
|
+
this.logger.error(
|
|
115
108
|
`[${this.label}] Error rendering ${url.pathname}:`,
|
|
116
109
|
error instanceof Error ? error : undefined,
|
|
117
110
|
);
|
|
118
111
|
|
|
119
|
-
const boundary = this.
|
|
112
|
+
const boundary = await this.pipeline.findErrorBoundary(url.pathname);
|
|
120
113
|
if (boundary) {
|
|
121
114
|
const result = await this.tryRenderErrorModule(boundary.modulePath, url, 'boundary');
|
|
122
115
|
if (result) return result;
|
|
123
116
|
}
|
|
124
117
|
|
|
125
|
-
const errorHandler = this.
|
|
118
|
+
const errorHandler = await this.pipeline.getErrorHandler();
|
|
126
119
|
if (errorHandler) {
|
|
127
120
|
const result = await this.tryRenderErrorModule(errorHandler.modulePath, url, 'handler');
|
|
128
121
|
if (result) return result;
|
|
@@ -140,13 +133,12 @@ export abstract class SsrRenderer {
|
|
|
140
133
|
matched: MatchedRoute,
|
|
141
134
|
signal?: AbortSignal,
|
|
142
135
|
): Promise<{ content: string; title?: string }> {
|
|
143
|
-
const hierarchy = this.
|
|
136
|
+
const hierarchy = this.pipeline.buildRouteHierarchy(matched.route.pattern);
|
|
144
137
|
|
|
145
|
-
// Resolve routes for each hierarchy segment (skip missing / duplicate wildcard)
|
|
146
138
|
const segments: { route: RouteConfig; isLeaf: boolean }[] = [];
|
|
147
139
|
for (let i = 0; i < hierarchy.length; i++) {
|
|
148
|
-
const routePattern = hierarchy[i]
|
|
149
|
-
let route = this.
|
|
140
|
+
const routePattern = hierarchy[i]!;
|
|
141
|
+
let route = await this.pipeline.findRoute(routePattern);
|
|
150
142
|
|
|
151
143
|
if (!route && routePattern === '/') {
|
|
152
144
|
route = DEFAULT_ROOT_ROUTE;
|
|
@@ -158,20 +150,18 @@ export abstract class SsrRenderer {
|
|
|
158
150
|
segments.push({ route, isLeaf: i === hierarchy.length - 1 });
|
|
159
151
|
}
|
|
160
152
|
|
|
161
|
-
// Fire all renderRouteContent calls in parallel
|
|
162
153
|
const results = await Promise.all(
|
|
163
154
|
segments.map(({ route, isLeaf }) =>
|
|
164
155
|
this.renderRouteContent(routeInfo, route, isLeaf, signal),
|
|
165
156
|
),
|
|
166
157
|
);
|
|
167
158
|
|
|
168
|
-
// Sequential slot injection
|
|
169
159
|
let result = '';
|
|
170
160
|
let pageTitle: string | undefined;
|
|
171
161
|
let lastRenderedPattern = '';
|
|
172
162
|
|
|
173
163
|
for (let i = 0; i < segments.length; i++) {
|
|
174
|
-
const { content, title } = results[i]
|
|
164
|
+
const { content, title } = results[i]!;
|
|
175
165
|
|
|
176
166
|
if (title) {
|
|
177
167
|
pageTitle = title;
|
|
@@ -182,7 +172,7 @@ export abstract class SsrRenderer {
|
|
|
182
172
|
} else {
|
|
183
173
|
const injected = this.injectSlot(result, content, lastRenderedPattern);
|
|
184
174
|
if (injected === result) {
|
|
185
|
-
logger.warn(
|
|
175
|
+
this.logger.warn(
|
|
186
176
|
`[${this.label}] Route "${lastRenderedPattern}" has no <router-slot> ` +
|
|
187
177
|
`for child route "${hierarchy[i]}" to render into. ` +
|
|
188
178
|
`Add <router-slot></router-slot> to the parent template.`,
|
|
@@ -191,12 +181,12 @@ export abstract class SsrRenderer {
|
|
|
191
181
|
result = injected;
|
|
192
182
|
}
|
|
193
183
|
|
|
194
|
-
lastRenderedPattern = segments[i]
|
|
184
|
+
lastRenderedPattern = segments[i]!.route.pattern;
|
|
195
185
|
}
|
|
196
186
|
|
|
197
187
|
result = this.stripSlots(result);
|
|
198
188
|
|
|
199
|
-
return { content: result, ...(pageTitle
|
|
189
|
+
return { content: result, ...(pageTitle !== undefined ? { title: pageTitle } : {}) };
|
|
200
190
|
}
|
|
201
191
|
|
|
202
192
|
protected abstract renderRouteContent(
|
|
@@ -216,25 +206,24 @@ export abstract class SsrRenderer {
|
|
|
216
206
|
const files = route.files ?? {};
|
|
217
207
|
|
|
218
208
|
const tsModule = files.ts ?? files.js;
|
|
219
|
-
const
|
|
220
|
-
?
|
|
221
|
-
:
|
|
209
|
+
const loadedModule = tsModule
|
|
210
|
+
? await this.pipeline.loadModule<{ default: PageComponent }>(tsModule)
|
|
211
|
+
: undefined;
|
|
212
|
+
const component: PageComponent = loadedModule?.default ?? defaultPageComponent;
|
|
222
213
|
|
|
223
|
-
const context = await this.
|
|
214
|
+
const context = await this.pipeline.buildContext(routeInfo, route, signal, isLeaf, loadedModule);
|
|
224
215
|
const data = await component.getData({ params: routeInfo.params, ...(signal ? { signal } : {}), context });
|
|
225
216
|
const content = this.renderContent(component, { data, params: routeInfo.params, context });
|
|
226
217
|
const title = component.getTitle({ data, params: routeInfo.params, context });
|
|
227
218
|
|
|
228
|
-
return { content, ...(title
|
|
219
|
+
return { content, ...(title !== undefined ? { title } : {}) };
|
|
229
220
|
}
|
|
230
221
|
|
|
231
|
-
/** Render a component to the output format (HTML or Markdown). */
|
|
232
222
|
protected abstract renderContent(
|
|
233
223
|
component: PageComponent,
|
|
234
224
|
args: PageComponent['RenderArgs'],
|
|
235
225
|
): string;
|
|
236
226
|
|
|
237
|
-
/** Render a component for error boundary/handler with minimal context. */
|
|
238
227
|
protected renderComponent(
|
|
239
228
|
component: PageComponent,
|
|
240
229
|
data: unknown,
|
|
@@ -245,14 +234,13 @@ export abstract class SsrRenderer {
|
|
|
245
234
|
|
|
246
235
|
private static readonly EMPTY_URL = new URL('http://error');
|
|
247
236
|
|
|
248
|
-
/** Try to load and render an error boundary or handler module. Returns null on failure. */
|
|
249
237
|
private async tryRenderErrorModule(
|
|
250
238
|
modulePath: string,
|
|
251
239
|
url: URL,
|
|
252
240
|
kind: 'boundary' | 'handler',
|
|
253
241
|
): Promise<{ content: string; status: number } | null> {
|
|
254
242
|
try {
|
|
255
|
-
const module = await this.
|
|
243
|
+
const module = await this.pipeline.loadModule<{ default: PageComponent }>(modulePath);
|
|
256
244
|
const component = module.default;
|
|
257
245
|
const minCtx: ComponentContext = {
|
|
258
246
|
url: SsrRenderer.EMPTY_URL,
|
|
@@ -264,7 +252,7 @@ export abstract class SsrRenderer {
|
|
|
264
252
|
const content = this.renderComponent(component, data, minCtx);
|
|
265
253
|
return { content, status: 500 };
|
|
266
254
|
} catch (e) {
|
|
267
|
-
logger.error(
|
|
255
|
+
this.logger.error(
|
|
268
256
|
`[${this.label}] Error ${kind} failed for ${url.pathname}`,
|
|
269
257
|
e instanceof Error ? e : undefined,
|
|
270
258
|
);
|
|
@@ -278,9 +266,7 @@ export abstract class SsrRenderer {
|
|
|
278
266
|
|
|
279
267
|
protected abstract renderErrorPage(error: unknown, url: URL): string;
|
|
280
268
|
|
|
281
|
-
/** Inject child content into the slot owned by parentPattern. */
|
|
282
269
|
protected abstract injectSlot(parent: string, child: string, parentPattern: string): string;
|
|
283
270
|
|
|
284
|
-
/** Strip all unconsumed slot placeholders from the final result. */
|
|
285
271
|
protected abstract stripSlots(result: string): string;
|
|
286
272
|
}
|
|
@@ -5,29 +5,20 @@
|
|
|
5
5
|
* provides O(depth) matching, error boundary lookup, and hierarchy traversal.
|
|
6
6
|
*
|
|
7
7
|
* Implementations: RouteTrie (in-memory trie from RouteNode tree).
|
|
8
|
-
* RouteCore depends on this interface, not on the algorithm.
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
import type { RouteNode } from '../type/route-tree.type.ts';
|
|
12
11
|
|
|
13
12
|
/** Result of matching a URL pathname against the route tree. */
|
|
14
13
|
export interface ResolvedRoute {
|
|
15
|
-
/** The matched route node. */
|
|
16
14
|
readonly node: RouteNode;
|
|
17
|
-
/** URL pattern reconstructed from the tree path (e.g. "/projects/:id"). */
|
|
18
15
|
readonly pattern: string;
|
|
19
|
-
/** Extracted URL parameters (e.g. { id: "42" }). */
|
|
20
16
|
readonly params: Record<string, string>;
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
/** Route lookup interface. Decouples matching algorithm from the
|
|
19
|
+
/** Route lookup interface. Decouples matching algorithm from the server. */
|
|
24
20
|
export interface RouteResolver {
|
|
25
|
-
/** Match a URL pathname to a route. */
|
|
26
21
|
match(pathname: string): ResolvedRoute | undefined;
|
|
27
|
-
|
|
28
|
-
/** Find the most specific error boundary for a pathname. */
|
|
29
22
|
findErrorBoundary(pathname: string): string | undefined;
|
|
30
|
-
|
|
31
|
-
/** Look up a route node by its exact pattern (e.g. "/projects/:id"). */
|
|
32
23
|
findRoute(pattern: string): RouteNode | undefined;
|
|
33
24
|
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route Trie
|
|
3
|
+
*
|
|
4
|
+
* Default RouteResolver implementation. O(depth) route matching over
|
|
5
|
+
* the RouteNode tree.
|
|
6
|
+
*
|
|
7
|
+
* Walks the RouteNode tree directly — no conversion step, no internal state
|
|
8
|
+
* beyond the tree reference. Each URL segment is matched in order:
|
|
9
|
+
* static → dynamic (:param) → wildcard (:rest*). Backtracking handles
|
|
10
|
+
* cases where a dynamic path leads to a dead end but a wildcard at an
|
|
11
|
+
* ancestor would match.
|
|
12
|
+
*
|
|
13
|
+
* Static segment matching is case-sensitive, per RFC 3986.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { RouteNode } from '../type/route-tree.type.ts';
|
|
17
|
+
import type { ResolvedRoute, RouteResolver } from './route.resolver.ts';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default RouteResolver implementation.
|
|
21
|
+
* Walks the RouteNode tree directly — no conversion, no Maps.
|
|
22
|
+
*/
|
|
23
|
+
export class RouteTrie implements RouteResolver {
|
|
24
|
+
constructor(private readonly tree: RouteNode) {}
|
|
25
|
+
|
|
26
|
+
match(pathname: string): ResolvedRoute | undefined {
|
|
27
|
+
pathname = this.normalizePath(pathname);
|
|
28
|
+
if (pathname === '/') {
|
|
29
|
+
if (this.tree.files || this.tree.redirect) {
|
|
30
|
+
return { node: this.tree, pattern: '/', params: {} };
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return this.walk(this.tree, this.splitSegments(pathname), 0, {}, '/');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
findErrorBoundary(pathname: string): string | undefined {
|
|
38
|
+
pathname = this.normalizePath(pathname);
|
|
39
|
+
if (pathname === '/') return this.tree.errorBoundary;
|
|
40
|
+
return this.walkForBoundary(this.tree, this.splitSegments(pathname), 0, this.tree.errorBoundary);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
findRoute(pattern: string): RouteNode | undefined {
|
|
44
|
+
if (pattern === '/') {
|
|
45
|
+
return (this.tree.files || this.tree.redirect) ? this.tree : undefined;
|
|
46
|
+
}
|
|
47
|
+
const segments = this.splitSegments(pattern);
|
|
48
|
+
let node = this.tree;
|
|
49
|
+
for (const segment of segments) {
|
|
50
|
+
let child: RouteNode | undefined;
|
|
51
|
+
if (segment.startsWith(':') && segment.endsWith('*')) {
|
|
52
|
+
child = node.wildcard?.child;
|
|
53
|
+
} else if (segment.startsWith(':')) {
|
|
54
|
+
child = node.dynamic?.child;
|
|
55
|
+
} else {
|
|
56
|
+
child = node.children?.[segment];
|
|
57
|
+
}
|
|
58
|
+
if (!child) return undefined;
|
|
59
|
+
node = child;
|
|
60
|
+
}
|
|
61
|
+
return (node.files || node.redirect) ? node : undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Private helpers ─────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
private safeDecode(segment: string): string {
|
|
67
|
+
try {
|
|
68
|
+
return decodeURIComponent(segment);
|
|
69
|
+
} catch {
|
|
70
|
+
return segment;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private splitSegments(pathname: string): string[] {
|
|
75
|
+
return pathname.substring(1).split('/');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private normalizePath(pathname: string): string {
|
|
79
|
+
if (pathname.length > 1 && pathname.endsWith('/')) {
|
|
80
|
+
pathname = pathname.slice(0, -1);
|
|
81
|
+
}
|
|
82
|
+
if (!pathname.startsWith('/')) {
|
|
83
|
+
pathname = '/' + pathname;
|
|
84
|
+
}
|
|
85
|
+
return pathname;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private walk(
|
|
89
|
+
node: RouteNode,
|
|
90
|
+
segments: string[],
|
|
91
|
+
index: number,
|
|
92
|
+
params: Record<string, string>,
|
|
93
|
+
pattern: string,
|
|
94
|
+
): ResolvedRoute | undefined {
|
|
95
|
+
if (index === segments.length) {
|
|
96
|
+
if (node.files || node.redirect) {
|
|
97
|
+
return { node, pattern, params: { ...params } };
|
|
98
|
+
}
|
|
99
|
+
if (node.wildcard && (node.wildcard.child.files || node.wildcard.child.redirect)) {
|
|
100
|
+
const wp = pattern === '/' ? `/:${node.wildcard.param}*` : `${pattern}/:${node.wildcard.param}*`;
|
|
101
|
+
return {
|
|
102
|
+
node: node.wildcard.child,
|
|
103
|
+
pattern: wp,
|
|
104
|
+
params: { ...params, [node.wildcard.param]: '' },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const segment = segments[index]!;
|
|
111
|
+
|
|
112
|
+
// Static
|
|
113
|
+
const staticChild = node.children?.[segment];
|
|
114
|
+
if (staticChild) {
|
|
115
|
+
const childPattern = pattern === '/' ? `/${segment}` : `${pattern}/${segment}`;
|
|
116
|
+
const result = this.walk(staticChild, segments, index + 1, params, childPattern);
|
|
117
|
+
if (result) return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Dynamic
|
|
121
|
+
if (node.dynamic) {
|
|
122
|
+
const { param, child } = node.dynamic;
|
|
123
|
+
params[param] = this.safeDecode(segment);
|
|
124
|
+
const childPattern = pattern === '/' ? `/:${param}` : `${pattern}/:${param}`;
|
|
125
|
+
const result = this.walk(child, segments, index + 1, params, childPattern);
|
|
126
|
+
if (result) return result;
|
|
127
|
+
delete params[param];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Wildcard
|
|
131
|
+
if (node.wildcard && (node.wildcard.child.files || node.wildcard.child.redirect)) {
|
|
132
|
+
const { param, child } = node.wildcard;
|
|
133
|
+
let rest = this.safeDecode(segment);
|
|
134
|
+
for (let i = index + 1; i < segments.length; i++) {
|
|
135
|
+
rest += '/' + this.safeDecode(segments[i]!);
|
|
136
|
+
}
|
|
137
|
+
const wp = pattern === '/' ? `/:${param}*` : `${pattern}/:${param}*`;
|
|
138
|
+
return {
|
|
139
|
+
node: child,
|
|
140
|
+
pattern: wp,
|
|
141
|
+
params: { ...params, [param]: rest },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private walkForBoundary(
|
|
149
|
+
node: RouteNode,
|
|
150
|
+
segments: string[],
|
|
151
|
+
index: number,
|
|
152
|
+
deepest: string | undefined,
|
|
153
|
+
): string | undefined {
|
|
154
|
+
if (index === segments.length) {
|
|
155
|
+
return node.errorBoundary ?? deepest;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const segment = segments[index]!;
|
|
159
|
+
|
|
160
|
+
const staticChild = node.children?.[segment];
|
|
161
|
+
if (staticChild) {
|
|
162
|
+
return this.walkForBoundary(staticChild, segments, index + 1, staticChild.errorBoundary ?? deepest);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (node.dynamic) {
|
|
166
|
+
return this.walkForBoundary(node.dynamic.child, segments, index + 1, node.dynamic.child.errorBoundary ?? deepest);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (node.wildcard) {
|
|
170
|
+
return node.wildcard.child.errorBoundary ?? deepest;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return deepest;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract Runtime
|
|
3
|
+
*
|
|
4
|
+
* Storage contract. Speaks Request/Response.
|
|
5
|
+
* Concrete implementations decide how to store, cache, scan, and serve.
|
|
6
|
+
*
|
|
7
|
+
* Three access patterns:
|
|
8
|
+
* - handle() — raw passthrough
|
|
9
|
+
* - query() — read (Response or string)
|
|
10
|
+
* - command() — write/delete
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type FetchParams = Parameters<typeof fetch>;
|
|
14
|
+
export type FetchReturn = ReturnType<typeof fetch>;
|
|
15
|
+
|
|
16
|
+
/** Well-known manifest paths (convention between Runtime and consumers). */
|
|
17
|
+
export const ROUTES_MANIFEST_PATH = '/routes.manifest.json';
|
|
18
|
+
export const WIDGETS_MANIFEST_PATH = '/widgets.manifest.json';
|
|
19
|
+
export const ELEMENTS_MANIFEST_PATH = '/elements.manifest.json';
|
|
20
|
+
|
|
21
|
+
export abstract class Runtime {
|
|
22
|
+
/** Raw passthrough — same signature as fetch(). */
|
|
23
|
+
abstract handle(resource: FetchParams[0], init?: FetchParams[1]): FetchReturn;
|
|
24
|
+
|
|
25
|
+
/** Read. Returns Response, or string with { as: 'text' }. */
|
|
26
|
+
abstract query(
|
|
27
|
+
resource: FetchParams[0],
|
|
28
|
+
options: FetchParams[1] & { as: 'text' },
|
|
29
|
+
): Promise<string>;
|
|
30
|
+
abstract query(
|
|
31
|
+
resource: FetchParams[0],
|
|
32
|
+
options?: FetchParams[1],
|
|
33
|
+
): FetchReturn;
|
|
34
|
+
|
|
35
|
+
/** Write (PUT) or delete (DELETE). */
|
|
36
|
+
abstract command(resource: FetchParams[0], options?: FetchParams[1]): FetchReturn;
|
|
37
|
+
|
|
38
|
+
/** Dynamically import a module from storage. */
|
|
39
|
+
loadModule(_path: string): Promise<unknown> {
|
|
40
|
+
throw new Error(`loadModule not implemented for ${this.constructor.name}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Transpile TypeScript to JavaScript. */
|
|
44
|
+
transpile(_source: string): Promise<string> {
|
|
45
|
+
throw new Error(`transpile not implemented for ${this.constructor.name}`);
|
|
46
|
+
}
|
|
47
|
+
}
|