@emkodev/emroute 1.7.2 → 1.8.0-beta.1
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 +19 -45
- package/{src/renderer/ssr → core/renderer}/md.renderer.ts +21 -40
- package/{src/renderer/ssr → core/renderer}/ssr.renderer.ts +42 -53
- 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 +346 -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 +0 -2
- package/{src/route → core/util}/route-tree.util.ts +0 -2
- package/{src → core}/util/widget-resolve.util.ts +15 -45
- package/{src → core}/widget/widget.parser.ts +0 -21
- package/core/widget/widget.registry.ts +24 -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 +12 -33
- 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 +14 -31
- package/dist/core/renderer/md.renderer.js.map +1 -0
- package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.d.ts +11 -17
- package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.js +34 -36
- 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 +261 -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 +32 -0
- package/dist/{src → core}/util/widget-resolve.util.js +11 -41
- 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 +0 -21
- package/dist/core/widget/widget.parser.js.map +1 -0
- package/dist/core/widget/widget.registry.d.ts +13 -0
- package/dist/core/widget/widget.registry.js +19 -0
- package/dist/core/widget/widget.registry.js.map +1 -0
- package/dist/emroute.js +1137 -1151
- package/dist/emroute.js.map +36 -5
- package/dist/runtime/abstract.runtime.d.ts +50 -5
- package/dist/runtime/abstract.runtime.js +446 -6
- 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 +18 -2
- 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 +11 -1
- 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 +18 -2
- 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 +45 -36
- 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 -341
- 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 -68
- 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 +4 -7
- package/dist/src/element/component.element.js +23 -22
- 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 +4 -3
- package/dist/src/element/markdown.element.js.map +1 -1
- package/dist/src/index.d.ts +15 -13
- 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 +34 -12
- package/dist/src/renderer/spa/thin-client.js.map +1 -1
- package/dist/src/route/route.core.d.ts +3 -3
- package/dist/src/route/route.core.js +21 -7
- package/dist/src/route/route.core.js.map +1 -1
- 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 +483 -8
- package/runtime/bun/fs/bun-fs.runtime.ts +17 -1
- package/runtime/bun/sqlite/bun-sqlite.runtime.ts +11 -0
- package/runtime/fetch.runtime.ts +3 -3
- package/runtime/sitemap.generator.ts +2 -2
- package/runtime/universal/fs/universal-fs.runtime.ts +17 -1
- package/server/build.util.ts +53 -47
- package/server/codegen.util.ts +1 -1
- package/server/emroute.server.ts +12 -412
- package/src/element/component.element.ts +24 -31
- package/src/element/markdown.element.ts +5 -4
- package/src/index.ts +22 -18
- package/src/renderer/spa/{thin-client.ts → emroute.app.ts} +46 -22
- 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/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 -97
- package/src/component/abstract.component.ts +0 -231
- package/src/component/widget.component.ts +0 -85
- package/src/route/route.core.ts +0 -362
- package/src/route/route.trie.ts +0 -265
- 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,13 +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 {
|
|
10
|
-
import type { PageComponent } from '
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
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';
|
|
15
16
|
import { SsrRenderer, type SsrRendererOptions } from './ssr.renderer.ts';
|
|
16
17
|
|
|
17
18
|
const BARE_SLOT_BLOCK = '```router-slot\n```';
|
|
@@ -20,17 +21,13 @@ function routerSlotBlock(pattern: string): string {
|
|
|
20
21
|
return `\`\`\`router-slot\n{"pattern":"${pattern}"}\n\`\`\``;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
export type SsrMdRouterOptions = SsrRendererOptions;
|
|
24
|
+
export type SsrMdRendererOptions = SsrRendererOptions;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
* SSR Markdown Router for server-side markdown rendering.
|
|
28
|
-
*/
|
|
29
|
-
export class SsrMdRouter extends SsrRenderer {
|
|
26
|
+
export class SsrMdRenderer extends SsrRenderer {
|
|
30
27
|
protected override readonly label = 'SSR MD';
|
|
31
28
|
|
|
32
|
-
constructor(
|
|
33
|
-
super(
|
|
29
|
+
constructor(pipeline: Pipeline, options: SsrMdRendererOptions = {}) {
|
|
30
|
+
super(pipeline, options);
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
protected override injectSlot(parent: string, child: string, parentPattern: string): string {
|
|
@@ -43,9 +40,6 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
43
40
|
.trim();
|
|
44
41
|
}
|
|
45
42
|
|
|
46
|
-
/**
|
|
47
|
-
* Render a single route's content to Markdown.
|
|
48
|
-
*/
|
|
49
43
|
protected override async renderRouteContent(
|
|
50
44
|
routeInfo: RouteInfo,
|
|
51
45
|
route: RouteConfig,
|
|
@@ -60,15 +54,14 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
60
54
|
let content = rawContent;
|
|
61
55
|
|
|
62
56
|
// Attribute bare router-slot blocks with this route's pattern
|
|
63
|
-
// (before widget resolution so widget-internal blocks are not affected)
|
|
64
57
|
content = content.replaceAll(BARE_SLOT_BLOCK, routerSlotBlock(route.pattern));
|
|
65
58
|
|
|
66
|
-
// Resolve fenced widget blocks
|
|
59
|
+
// Resolve fenced widget blocks
|
|
67
60
|
if (this.widgets) {
|
|
68
61
|
content = await this.resolveWidgets(content, routeInfo);
|
|
69
62
|
}
|
|
70
63
|
|
|
71
|
-
return { content, title };
|
|
64
|
+
return { content, ...(title !== undefined ? { title } : {}) };
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
protected override renderContent(
|
|
@@ -90,10 +83,6 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
90
83
|
return `# Internal Server Error\n\nPath: \`${url.pathname}\``;
|
|
91
84
|
}
|
|
92
85
|
|
|
93
|
-
/**
|
|
94
|
-
* Resolve fenced widget blocks in markdown content.
|
|
95
|
-
* Replaces ```widget:name blocks with rendered markdown output.
|
|
96
|
-
*/
|
|
97
86
|
private resolveWidgets(
|
|
98
87
|
content: string,
|
|
99
88
|
routeInfo: RouteInfo,
|
|
@@ -115,17 +104,17 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
115
104
|
let files: { html?: string; md?: string } | undefined;
|
|
116
105
|
const filePaths = this.widgetFiles[block.widgetName] ?? widget.files;
|
|
117
106
|
if (filePaths) {
|
|
118
|
-
files = await this.
|
|
107
|
+
files = await this.pipeline.loadFiles(filePaths);
|
|
119
108
|
}
|
|
120
109
|
|
|
121
|
-
const baseContext = {
|
|
110
|
+
const baseContext: ComponentContext = {
|
|
122
111
|
...routeInfo,
|
|
123
112
|
pathname: routeInfo.url.pathname,
|
|
124
113
|
searchParams: routeInfo.url.searchParams,
|
|
125
|
-
files,
|
|
114
|
+
...(files ? { files } : {}),
|
|
126
115
|
};
|
|
127
|
-
const context = this.
|
|
128
|
-
? this.
|
|
116
|
+
const context: ComponentContext = this.pipeline.contextProvider
|
|
117
|
+
? this.pipeline.contextProvider(baseContext)
|
|
129
118
|
: baseContext;
|
|
130
119
|
const data = await widget.getData({ params: block.params, context });
|
|
131
120
|
return widget.renderMarkdown({ data, params: block.params, context });
|
|
@@ -134,16 +123,8 @@ export class SsrMdRouter extends SsrRenderer {
|
|
|
134
123
|
}
|
|
135
124
|
},
|
|
136
125
|
replaceWidgetBlocks,
|
|
126
|
+
0,
|
|
127
|
+
this.logger,
|
|
137
128
|
);
|
|
138
129
|
}
|
|
139
130
|
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Create SSR Markdown router.
|
|
143
|
-
*/
|
|
144
|
-
export function createSsrMdRouter(
|
|
145
|
-
resolver: RouteResolver,
|
|
146
|
-
options?: SsrMdRouterOptions,
|
|
147
|
-
): SsrMdRouter {
|
|
148
|
-
return new SsrMdRouter(resolver, options);
|
|
149
|
-
}
|
|
@@ -9,24 +9,17 @@ 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
23
|
widgetFiles?: Record<string, { html?: string; md?: string; css?: string }>;
|
|
31
24
|
}
|
|
32
25
|
|
|
@@ -34,13 +27,16 @@ export interface SsrRendererOptions extends RouteCoreOptions {
|
|
|
34
27
|
* Abstract SSR renderer with shared routing pipeline.
|
|
35
28
|
*/
|
|
36
29
|
export abstract class SsrRenderer {
|
|
37
|
-
protected
|
|
30
|
+
protected readonly pipeline: Pipeline;
|
|
38
31
|
protected widgets: WidgetRegistry | null;
|
|
39
32
|
protected widgetFiles: Record<string, { html?: string; md?: string; css?: string }>;
|
|
40
33
|
protected abstract readonly label: string;
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
protected readonly logger: Logger;
|
|
36
|
+
|
|
37
|
+
constructor(pipeline: Pipeline, options: SsrRendererOptions = {}) {
|
|
38
|
+
this.pipeline = pipeline;
|
|
39
|
+
this.logger = pipeline.logger;
|
|
44
40
|
this.widgets = options.widgets ?? null;
|
|
45
41
|
this.widgetFiles = options.widgetFiles ?? {};
|
|
46
42
|
}
|
|
@@ -52,17 +48,17 @@ export abstract class SsrRenderer {
|
|
|
52
48
|
url: URL,
|
|
53
49
|
signal?: AbortSignal,
|
|
54
50
|
): Promise<{ content: string; status: number; title?: string; redirect?: string }> {
|
|
55
|
-
const matched = this.
|
|
51
|
+
const matched = await this.pipeline.match(url);
|
|
56
52
|
|
|
57
53
|
if (!matched) {
|
|
58
|
-
const statusPage = this.
|
|
54
|
+
const statusPage = await this.pipeline.getStatusPage(404);
|
|
59
55
|
if (statusPage) {
|
|
60
56
|
try {
|
|
61
57
|
const ri: RouteInfo = { url, params: {} };
|
|
62
58
|
const result = await this.renderRouteContent(ri, statusPage, undefined, signal);
|
|
63
|
-
return { content: this.stripSlots(result.content), status: 404, title: result.title };
|
|
59
|
+
return { content: this.stripSlots(result.content), status: 404, ...(result.title !== undefined ? { title: result.title } : {}) };
|
|
64
60
|
} catch (e) {
|
|
65
|
-
logger.error(
|
|
61
|
+
this.logger.error(
|
|
66
62
|
`[${this.label}] Failed to render 404 status page for ${url.pathname}`,
|
|
67
63
|
e instanceof Error ? e : undefined,
|
|
68
64
|
);
|
|
@@ -73,7 +69,7 @@ export abstract class SsrRenderer {
|
|
|
73
69
|
|
|
74
70
|
// Handle redirect
|
|
75
71
|
if (matched.route.type === 'redirect') {
|
|
76
|
-
const module = await this.
|
|
72
|
+
const module = await this.pipeline.loadModule<{ default: { to: string; status?: number } }>(
|
|
77
73
|
matched.route.modulePath,
|
|
78
74
|
);
|
|
79
75
|
const redirectConfig = module.default;
|
|
@@ -85,14 +81,14 @@ export abstract class SsrRenderer {
|
|
|
85
81
|
};
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
const routeInfo = this.
|
|
84
|
+
const routeInfo = this.pipeline.toRouteInfo(matched, url);
|
|
89
85
|
|
|
90
86
|
try {
|
|
91
87
|
const { content, title } = await this.renderPage(routeInfo, matched, signal);
|
|
92
|
-
return { content, status: 200, title };
|
|
88
|
+
return { content, status: 200, ...(title !== undefined ? { title } : {}) };
|
|
93
89
|
} catch (error) {
|
|
94
90
|
if (error instanceof Response) {
|
|
95
|
-
const statusPage = this.
|
|
91
|
+
const statusPage = await this.pipeline.getStatusPage(error.status);
|
|
96
92
|
if (statusPage) {
|
|
97
93
|
try {
|
|
98
94
|
const ri: RouteInfo = { url, params: {} };
|
|
@@ -100,10 +96,10 @@ export abstract class SsrRenderer {
|
|
|
100
96
|
return {
|
|
101
97
|
content: this.stripSlots(result.content),
|
|
102
98
|
status: error.status,
|
|
103
|
-
title: result.title,
|
|
99
|
+
...(result.title !== undefined ? { title: result.title } : {}),
|
|
104
100
|
};
|
|
105
101
|
} catch (e) {
|
|
106
|
-
logger.error(
|
|
102
|
+
this.logger.error(
|
|
107
103
|
`[${this.label}] Failed to render ${error.status} status page for ${url.pathname}`,
|
|
108
104
|
e instanceof Error ? e : undefined,
|
|
109
105
|
);
|
|
@@ -111,18 +107,18 @@ export abstract class SsrRenderer {
|
|
|
111
107
|
}
|
|
112
108
|
return { content: this.renderStatusPage(error.status, url), status: error.status };
|
|
113
109
|
}
|
|
114
|
-
logger.error(
|
|
110
|
+
this.logger.error(
|
|
115
111
|
`[${this.label}] Error rendering ${url.pathname}:`,
|
|
116
112
|
error instanceof Error ? error : undefined,
|
|
117
113
|
);
|
|
118
114
|
|
|
119
|
-
const boundary = this.
|
|
115
|
+
const boundary = await this.pipeline.findErrorBoundary(url.pathname);
|
|
120
116
|
if (boundary) {
|
|
121
117
|
const result = await this.tryRenderErrorModule(boundary.modulePath, url, 'boundary');
|
|
122
118
|
if (result) return result;
|
|
123
119
|
}
|
|
124
120
|
|
|
125
|
-
const errorHandler = this.
|
|
121
|
+
const errorHandler = await this.pipeline.getErrorHandler();
|
|
126
122
|
if (errorHandler) {
|
|
127
123
|
const result = await this.tryRenderErrorModule(errorHandler.modulePath, url, 'handler');
|
|
128
124
|
if (result) return result;
|
|
@@ -140,13 +136,12 @@ export abstract class SsrRenderer {
|
|
|
140
136
|
matched: MatchedRoute,
|
|
141
137
|
signal?: AbortSignal,
|
|
142
138
|
): Promise<{ content: string; title?: string }> {
|
|
143
|
-
const hierarchy = this.
|
|
139
|
+
const hierarchy = this.pipeline.buildRouteHierarchy(matched.route.pattern);
|
|
144
140
|
|
|
145
|
-
// Resolve routes for each hierarchy segment (skip missing / duplicate wildcard)
|
|
146
141
|
const segments: { route: RouteConfig; isLeaf: boolean }[] = [];
|
|
147
142
|
for (let i = 0; i < hierarchy.length; i++) {
|
|
148
143
|
const routePattern = hierarchy[i];
|
|
149
|
-
let route = this.
|
|
144
|
+
let route = await this.pipeline.findRoute(routePattern);
|
|
150
145
|
|
|
151
146
|
if (!route && routePattern === '/') {
|
|
152
147
|
route = DEFAULT_ROOT_ROUTE;
|
|
@@ -158,14 +153,12 @@ export abstract class SsrRenderer {
|
|
|
158
153
|
segments.push({ route, isLeaf: i === hierarchy.length - 1 });
|
|
159
154
|
}
|
|
160
155
|
|
|
161
|
-
// Fire all renderRouteContent calls in parallel
|
|
162
156
|
const results = await Promise.all(
|
|
163
157
|
segments.map(({ route, isLeaf }) =>
|
|
164
158
|
this.renderRouteContent(routeInfo, route, isLeaf, signal),
|
|
165
159
|
),
|
|
166
160
|
);
|
|
167
161
|
|
|
168
|
-
// Sequential slot injection
|
|
169
162
|
let result = '';
|
|
170
163
|
let pageTitle: string | undefined;
|
|
171
164
|
let lastRenderedPattern = '';
|
|
@@ -182,7 +175,7 @@ export abstract class SsrRenderer {
|
|
|
182
175
|
} else {
|
|
183
176
|
const injected = this.injectSlot(result, content, lastRenderedPattern);
|
|
184
177
|
if (injected === result) {
|
|
185
|
-
logger.warn(
|
|
178
|
+
this.logger.warn(
|
|
186
179
|
`[${this.label}] Route "${lastRenderedPattern}" has no <router-slot> ` +
|
|
187
180
|
`for child route "${hierarchy[i]}" to render into. ` +
|
|
188
181
|
`Add <router-slot></router-slot> to the parent template.`,
|
|
@@ -196,7 +189,7 @@ export abstract class SsrRenderer {
|
|
|
196
189
|
|
|
197
190
|
result = this.stripSlots(result);
|
|
198
191
|
|
|
199
|
-
return { content: result, title: pageTitle };
|
|
192
|
+
return { content: result, ...(pageTitle !== undefined ? { title: pageTitle } : {}) };
|
|
200
193
|
}
|
|
201
194
|
|
|
202
195
|
protected abstract renderRouteContent(
|
|
@@ -216,25 +209,24 @@ export abstract class SsrRenderer {
|
|
|
216
209
|
const files = route.files ?? {};
|
|
217
210
|
|
|
218
211
|
const tsModule = files.ts ?? files.js;
|
|
219
|
-
const
|
|
220
|
-
?
|
|
221
|
-
:
|
|
212
|
+
const loadedModule = tsModule
|
|
213
|
+
? await this.pipeline.loadModule<{ default: PageComponent }>(tsModule)
|
|
214
|
+
: undefined;
|
|
215
|
+
const component: PageComponent = loadedModule?.default ?? defaultPageComponent;
|
|
222
216
|
|
|
223
|
-
const context = await this.
|
|
224
|
-
const data = await component.getData({ params: routeInfo.params, signal, context });
|
|
217
|
+
const context = await this.pipeline.buildContext(routeInfo, route, signal, isLeaf, loadedModule);
|
|
218
|
+
const data = await component.getData({ params: routeInfo.params, ...(signal ? { signal } : {}), context });
|
|
225
219
|
const content = this.renderContent(component, { data, params: routeInfo.params, context });
|
|
226
220
|
const title = component.getTitle({ data, params: routeInfo.params, context });
|
|
227
221
|
|
|
228
|
-
return { content, title };
|
|
222
|
+
return { content, ...(title !== undefined ? { title } : {}) };
|
|
229
223
|
}
|
|
230
224
|
|
|
231
|
-
/** Render a component to the output format (HTML or Markdown). */
|
|
232
225
|
protected abstract renderContent(
|
|
233
226
|
component: PageComponent,
|
|
234
227
|
args: PageComponent['RenderArgs'],
|
|
235
228
|
): string;
|
|
236
229
|
|
|
237
|
-
/** Render a component for error boundary/handler with minimal context. */
|
|
238
230
|
protected renderComponent(
|
|
239
231
|
component: PageComponent,
|
|
240
232
|
data: unknown,
|
|
@@ -245,14 +237,13 @@ export abstract class SsrRenderer {
|
|
|
245
237
|
|
|
246
238
|
private static readonly EMPTY_URL = new URL('http://error');
|
|
247
239
|
|
|
248
|
-
/** Try to load and render an error boundary or handler module. Returns null on failure. */
|
|
249
240
|
private async tryRenderErrorModule(
|
|
250
241
|
modulePath: string,
|
|
251
242
|
url: URL,
|
|
252
243
|
kind: 'boundary' | 'handler',
|
|
253
244
|
): Promise<{ content: string; status: number } | null> {
|
|
254
245
|
try {
|
|
255
|
-
const module = await this.
|
|
246
|
+
const module = await this.pipeline.loadModule<{ default: PageComponent }>(modulePath);
|
|
256
247
|
const component = module.default;
|
|
257
248
|
const minCtx: ComponentContext = {
|
|
258
249
|
url: SsrRenderer.EMPTY_URL,
|
|
@@ -264,7 +255,7 @@ export abstract class SsrRenderer {
|
|
|
264
255
|
const content = this.renderComponent(component, data, minCtx);
|
|
265
256
|
return { content, status: 500 };
|
|
266
257
|
} catch (e) {
|
|
267
|
-
logger.error(
|
|
258
|
+
this.logger.error(
|
|
268
259
|
`[${this.label}] Error ${kind} failed for ${url.pathname}`,
|
|
269
260
|
e instanceof Error ? e : undefined,
|
|
270
261
|
);
|
|
@@ -278,9 +269,7 @@ export abstract class SsrRenderer {
|
|
|
278
269
|
|
|
279
270
|
protected abstract renderErrorPage(error: unknown, url: URL): string;
|
|
280
271
|
|
|
281
|
-
/** Inject child content into the slot owned by parentPattern. */
|
|
282
272
|
protected abstract injectSlot(parent: string, child: string, parentPattern: string): string;
|
|
283
273
|
|
|
284
|
-
/** Strip all unconsumed slot placeholders from the final result. */
|
|
285
274
|
protected abstract stripSlots(result: string): string;
|
|
286
275
|
}
|
|
@@ -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(segments[index]);
|
|
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
|
+
}
|