@emkodev/emroute 1.8.2-beta.1 → 1.10.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/widget.component.ts +1 -1
- package/core/pipeline/pipeline.ts +11 -1
- package/core/renderer/html.renderer.ts +16 -18
- package/core/renderer/md.renderer.ts +3 -5
- package/core/renderer/ssr.renderer.ts +21 -5
- package/core/server/emroute.server.ts +9 -62
- package/core/util/widget-resolve.util.ts +2 -2
- package/core/widget/widget.registry.ts +14 -28
- package/dist/core/component/widget.component.d.ts +1 -1
- package/dist/core/component/widget.component.js +1 -1
- package/dist/core/pipeline/pipeline.d.ts +1 -0
- package/dist/core/pipeline/pipeline.js +9 -1
- package/dist/core/pipeline/pipeline.js.map +1 -1
- package/dist/core/renderer/html.renderer.js +10 -12
- package/dist/core/renderer/html.renderer.js.map +1 -1
- package/dist/core/renderer/md.renderer.js +3 -5
- package/dist/core/renderer/md.renderer.js.map +1 -1
- package/dist/core/renderer/ssr.renderer.d.ts +6 -4
- package/dist/core/renderer/ssr.renderer.js +16 -3
- package/dist/core/renderer/ssr.renderer.js.map +1 -1
- package/dist/core/server/emroute.server.d.ts +2 -4
- package/dist/core/server/emroute.server.js +7 -52
- package/dist/core/server/emroute.server.js.map +1 -1
- package/dist/core/util/widget-resolve.util.d.ts +1 -3
- package/dist/core/util/widget-resolve.util.js +2 -2
- package/dist/core/util/widget-resolve.util.js.map +1 -1
- package/dist/core/widget/widget.registry.d.ts +5 -10
- package/dist/core/widget/widget.registry.js +15 -20
- package/dist/core/widget/widget.registry.js.map +1 -1
- package/dist/emroute.js +449 -95
- package/dist/emroute.js.map +17 -14
- package/dist/runtime/cache.runtime.d.ts +31 -0
- package/dist/runtime/cache.runtime.js +107 -0
- package/dist/runtime/cache.runtime.js.map +1 -0
- package/dist/runtime/idb.runtime.d.ts +31 -0
- package/dist/runtime/idb.runtime.js +178 -0
- package/dist/runtime/idb.runtime.js.map +1 -0
- package/dist/src/element/component.element.d.ts +4 -1
- package/dist/src/element/component.element.js +4 -1
- package/dist/src/element/component.element.js.map +1 -1
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +0 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/renderer/spa/emroute.app.d.ts +3 -0
- package/dist/src/renderer/spa/emroute.app.js +6 -4
- package/dist/src/renderer/spa/emroute.app.js.map +1 -1
- package/dist/src/renderer/spa/mod.d.ts +4 -3
- package/dist/src/renderer/spa/mod.js +5 -3
- package/dist/src/renderer/spa/mod.js.map +1 -1
- package/dist/src/service-worker/emroute.sw.d.ts +54 -0
- package/dist/src/service-worker/emroute.sw.js +181 -0
- package/dist/src/service-worker/emroute.sw.js.map +1 -0
- package/dist/src/service-worker/mod.d.ts +8 -0
- package/dist/src/service-worker/mod.js +9 -0
- package/dist/src/service-worker/mod.js.map +1 -0
- package/package.json +16 -1
- package/runtime/cache.runtime.ts +127 -0
- package/runtime/idb.runtime.ts +203 -0
- package/src/element/component.element.ts +4 -1
- package/src/index.ts +0 -1
- package/src/renderer/spa/emroute.app.ts +11 -4
- package/src/renderer/spa/mod.ts +6 -4
- package/src/service-worker/emroute.sw.ts +264 -0
- package/src/service-worker/mod.ts +9 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emroute Service Worker
|
|
3
|
+
*
|
|
4
|
+
* Runs Emroute inside a ServiceWorker. Intercepts fetch events and
|
|
5
|
+
* serves pages from split storage — framework assets in Cache API,
|
|
6
|
+
* user content (pages, widgets, manifests) in IndexedDB.
|
|
7
|
+
*
|
|
8
|
+
* Consumer creates their own sw.ts, calls `createEmrouteSW()` with
|
|
9
|
+
* options, and the returned handler wires install/activate/fetch.
|
|
10
|
+
*/
|
|
11
|
+
import type { ContextProvider } from '../../core/type/component.type.ts';
|
|
12
|
+
import type { MarkdownRenderer } from '../../core/type/markdown.type.ts';
|
|
13
|
+
import type { SpaMode } from '../../core/type/widget.type.ts';
|
|
14
|
+
import type { BasePath } from '../../core/server/emroute.server.ts';
|
|
15
|
+
/** Options for the emroute ServiceWorker. */
|
|
16
|
+
export interface EmrouteSWOptions {
|
|
17
|
+
/** Cache name — version this to bust stale caches. */
|
|
18
|
+
cacheName: string;
|
|
19
|
+
/** Framework asset paths to precache into Cache API (e.g. emroute.js, app.js). */
|
|
20
|
+
precache: string[];
|
|
21
|
+
/** User content paths to precache into IndexedDB (e.g. pages, widgets, manifests). */
|
|
22
|
+
content?: string[];
|
|
23
|
+
/** IndexedDB database name for user content. Defaults to 'emroute-content'. */
|
|
24
|
+
dbName?: string;
|
|
25
|
+
/** SPA mode (defaults to 'only'). */
|
|
26
|
+
spa?: SpaMode;
|
|
27
|
+
/** Base paths. */
|
|
28
|
+
basePath?: BasePath;
|
|
29
|
+
/** Title for the HTML shell. */
|
|
30
|
+
title?: string;
|
|
31
|
+
/** Markdown renderer (if pages use .md). */
|
|
32
|
+
markdownRenderer?: MarkdownRenderer;
|
|
33
|
+
/** Context provider for pages and widgets. */
|
|
34
|
+
extendContext?: ContextProvider;
|
|
35
|
+
/**
|
|
36
|
+
* Origin to fetch precache files from during install.
|
|
37
|
+
* Defaults to self.location.origin.
|
|
38
|
+
*/
|
|
39
|
+
origin?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Wire emroute into the ServiceWorker lifecycle.
|
|
43
|
+
*
|
|
44
|
+
* Call this from your sw.ts:
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { createEmrouteSW } from '@emkodev/emroute/sw';
|
|
47
|
+
* createEmrouteSW({
|
|
48
|
+
* cacheName: 'my-app-v1',
|
|
49
|
+
* precache: ['/emroute.js', '/app.js', '/importmap.json', '/main.css'],
|
|
50
|
+
* content: ['/routes.manifest.json', '/routes/index.page.js'],
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function createEmrouteSW(options: EmrouteSWOptions): void;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emroute Service Worker
|
|
3
|
+
*
|
|
4
|
+
* Runs Emroute inside a ServiceWorker. Intercepts fetch events and
|
|
5
|
+
* serves pages from split storage — framework assets in Cache API,
|
|
6
|
+
* user content (pages, widgets, manifests) in IndexedDB.
|
|
7
|
+
*
|
|
8
|
+
* Consumer creates their own sw.ts, calls `createEmrouteSW()` with
|
|
9
|
+
* options, and the returned handler wires install/activate/fetch.
|
|
10
|
+
*/
|
|
11
|
+
/// <reference lib="webworker" />
|
|
12
|
+
import { Emroute } from "../../core/server/emroute.server.js";
|
|
13
|
+
import { CacheRuntime } from "../../runtime/cache.runtime.js";
|
|
14
|
+
import { IdbRuntime } from "../../runtime/idb.runtime.js";
|
|
15
|
+
import { Runtime, } from "../../runtime/abstract.runtime.js";
|
|
16
|
+
/**
|
|
17
|
+
* Composite Runtime: reads from Cache API first, falls back to IDB.
|
|
18
|
+
* Writes go to IDB (user content is mutable).
|
|
19
|
+
*/
|
|
20
|
+
class SwRuntime extends Runtime {
|
|
21
|
+
cache;
|
|
22
|
+
idb;
|
|
23
|
+
constructor(cache, idb) {
|
|
24
|
+
super();
|
|
25
|
+
this.cache = cache;
|
|
26
|
+
this.idb = idb;
|
|
27
|
+
}
|
|
28
|
+
handle(resource, init) {
|
|
29
|
+
const method = init?.method ?? 'GET';
|
|
30
|
+
// Writes go to IDB (user content)
|
|
31
|
+
if (method === 'PUT' || method === 'DELETE') {
|
|
32
|
+
return this.idb.handle(resource, init);
|
|
33
|
+
}
|
|
34
|
+
// Reads: Cache first, IDB fallback
|
|
35
|
+
return this.cache.handle(resource, init).then(async (r) => {
|
|
36
|
+
if (r.status !== 404)
|
|
37
|
+
return r;
|
|
38
|
+
return this.idb.handle(resource, init);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
query(resource, options) {
|
|
42
|
+
if (options?.as === 'text') {
|
|
43
|
+
return this.handle(resource, options).then(async (r) => {
|
|
44
|
+
if (r.status === 404) {
|
|
45
|
+
const path = typeof resource === 'string' ? resource
|
|
46
|
+
: resource instanceof URL ? resource.pathname
|
|
47
|
+
: new URL(resource.url).pathname;
|
|
48
|
+
throw new Error(`Not found: ${path}`);
|
|
49
|
+
}
|
|
50
|
+
return r.text();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return this.handle(resource, options);
|
|
54
|
+
}
|
|
55
|
+
async loadModule(path) {
|
|
56
|
+
// Try cache first, then IDB
|
|
57
|
+
try {
|
|
58
|
+
return await this.cache.loadModule(path);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return await this.idb.loadModule(path);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Wire emroute into the ServiceWorker lifecycle.
|
|
67
|
+
*
|
|
68
|
+
* Call this from your sw.ts:
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { createEmrouteSW } from '@emkodev/emroute/sw';
|
|
71
|
+
* createEmrouteSW({
|
|
72
|
+
* cacheName: 'my-app-v1',
|
|
73
|
+
* precache: ['/emroute.js', '/app.js', '/importmap.json', '/main.css'],
|
|
74
|
+
* content: ['/routes.manifest.json', '/routes/index.page.js'],
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function createEmrouteSW(options) {
|
|
79
|
+
const { cacheName, precache, content = [], dbName = 'emroute-content', origin = self.location.origin, } = options;
|
|
80
|
+
const cacheRuntime = new CacheRuntime(cacheName);
|
|
81
|
+
const idbRuntime = new IdbRuntime(dbName);
|
|
82
|
+
const swRuntime = new SwRuntime(cacheRuntime, idbRuntime);
|
|
83
|
+
let emroute = null;
|
|
84
|
+
async function getEmroute() {
|
|
85
|
+
if (emroute)
|
|
86
|
+
return emroute;
|
|
87
|
+
emroute = await Emroute.create({
|
|
88
|
+
spa: options.spa ?? 'only',
|
|
89
|
+
...(options.basePath ? { basePath: options.basePath } : {}),
|
|
90
|
+
...(options.title ? { title: options.title } : {}),
|
|
91
|
+
...(options.markdownRenderer ? { markdownRenderer: options.markdownRenderer } : {}),
|
|
92
|
+
...(options.extendContext ? { extendContext: options.extendContext } : {}),
|
|
93
|
+
}, swRuntime);
|
|
94
|
+
return emroute;
|
|
95
|
+
}
|
|
96
|
+
// ── Install: precache from network ──────────────────────────────────
|
|
97
|
+
self.addEventListener('install', (event) => {
|
|
98
|
+
event.waitUntil((async () => {
|
|
99
|
+
// Framework assets → Cache API
|
|
100
|
+
if (precache.length > 0) {
|
|
101
|
+
const cache = await caches.open(cacheName);
|
|
102
|
+
await Promise.all(precache.map(async (path) => {
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(`${origin}${path}`);
|
|
105
|
+
if (response.ok) {
|
|
106
|
+
await cache.put(new Request(`https://emroute-cache${path}`), response);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
console.error(`[emroute-sw] Failed to precache asset: ${path}`);
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
// User content → IDB
|
|
115
|
+
if (content.length > 0) {
|
|
116
|
+
await Promise.all(content.map(async (path) => {
|
|
117
|
+
try {
|
|
118
|
+
const response = await fetch(`${origin}${path}`);
|
|
119
|
+
if (response.ok) {
|
|
120
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
121
|
+
await idbRuntime.handle(path, {
|
|
122
|
+
method: 'PUT',
|
|
123
|
+
body: data,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
console.error(`[emroute-sw] Failed to precache content: ${path}`);
|
|
129
|
+
}
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
await self.skipWaiting();
|
|
133
|
+
})());
|
|
134
|
+
});
|
|
135
|
+
// ── Activate: claim clients, clean old caches ───────────────────────
|
|
136
|
+
self.addEventListener('activate', (event) => {
|
|
137
|
+
event.waitUntil((async () => {
|
|
138
|
+
// Delete old emroute caches
|
|
139
|
+
const keys = await caches.keys();
|
|
140
|
+
await Promise.all(keys
|
|
141
|
+
.filter((key) => key !== cacheName && key.startsWith('emroute'))
|
|
142
|
+
.map((key) => caches.delete(key)));
|
|
143
|
+
await self.clients.claim();
|
|
144
|
+
})());
|
|
145
|
+
});
|
|
146
|
+
// ── Fetch: serve from emroute or storage ────────────────────────────
|
|
147
|
+
self.addEventListener('fetch', (event) => {
|
|
148
|
+
const url = new URL(event.request.url);
|
|
149
|
+
// Only handle same-origin requests
|
|
150
|
+
if (url.origin !== self.location.origin)
|
|
151
|
+
return;
|
|
152
|
+
event.respondWith(handleFetch(event.request, url));
|
|
153
|
+
});
|
|
154
|
+
async function handleFetch(request, url) {
|
|
155
|
+
// Navigation requests → emroute server
|
|
156
|
+
if (request.mode === 'navigate') {
|
|
157
|
+
try {
|
|
158
|
+
const server = await getEmroute();
|
|
159
|
+
const response = await server.handleRequest(request);
|
|
160
|
+
if (response)
|
|
161
|
+
return response;
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
console.error('[emroute-sw] Navigation error:', e);
|
|
165
|
+
}
|
|
166
|
+
// Fall through to storage/network
|
|
167
|
+
}
|
|
168
|
+
// Static files → composite runtime (cache first, then IDB)
|
|
169
|
+
const cached = await swRuntime.handle(url.pathname);
|
|
170
|
+
if (cached.status !== 404)
|
|
171
|
+
return cached;
|
|
172
|
+
// Network fallback
|
|
173
|
+
try {
|
|
174
|
+
return await fetch(request);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return new Response('Offline', { status: 503 });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=emroute.sw.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emroute.sw.js","sourceRoot":"","sources":["../../../src/service-worker/emroute.sw.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Worker Module
|
|
3
|
+
*
|
|
4
|
+
* Entry point for `@emkodev/emroute/sw`.
|
|
5
|
+
*/
|
|
6
|
+
export { createEmrouteSW, type EmrouteSWOptions } from './emroute.sw.ts';
|
|
7
|
+
export { CacheRuntime } from '../../runtime/cache.runtime.ts';
|
|
8
|
+
export { IdbRuntime } from '../../runtime/idb.runtime.ts';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Worker Module
|
|
3
|
+
*
|
|
4
|
+
* Entry point for `@emkodev/emroute/sw`.
|
|
5
|
+
*/
|
|
6
|
+
export { createEmrouteSW } from "./emroute.sw.js";
|
|
7
|
+
export { CacheRuntime } from "../../runtime/cache.runtime.js";
|
|
8
|
+
export { IdbRuntime } from "../../runtime/idb.runtime.js";
|
|
9
|
+
//# sourceMappingURL=mod.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","sourceRoot":"","sources":["../../../src/service-worker/mod.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAyB,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emkodev/emroute",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0-beta.1",
|
|
4
4
|
"description": "File-based (but storage-agnostic) router with triple rendering (SPA, SSR HTML, SSR Markdown). Zero dependencies.",
|
|
5
5
|
"license": "BSD-3-Clause",
|
|
6
6
|
"author": "emko.dev",
|
|
@@ -96,6 +96,21 @@
|
|
|
96
96
|
"bun": "./runtime/fetch.runtime.ts",
|
|
97
97
|
"types": "./dist/runtime/fetch.runtime.d.ts",
|
|
98
98
|
"default": "./dist/runtime/fetch.runtime.js"
|
|
99
|
+
},
|
|
100
|
+
"./runtime/cache": {
|
|
101
|
+
"bun": "./runtime/cache.runtime.ts",
|
|
102
|
+
"types": "./dist/runtime/cache.runtime.d.ts",
|
|
103
|
+
"default": "./dist/runtime/cache.runtime.js"
|
|
104
|
+
},
|
|
105
|
+
"./runtime/idb": {
|
|
106
|
+
"bun": "./runtime/idb.runtime.ts",
|
|
107
|
+
"types": "./dist/runtime/idb.runtime.d.ts",
|
|
108
|
+
"default": "./dist/runtime/idb.runtime.js"
|
|
109
|
+
},
|
|
110
|
+
"./sw": {
|
|
111
|
+
"bun": "./src/service-worker/mod.ts",
|
|
112
|
+
"types": "./dist/src/service-worker/mod.d.ts",
|
|
113
|
+
"default": "./dist/src/service-worker/mod.js"
|
|
99
114
|
}
|
|
100
115
|
},
|
|
101
116
|
"devDependencies": {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Runtime
|
|
3
|
+
*
|
|
4
|
+
* Browser-compatible Runtime backed by the Cache API.
|
|
5
|
+
* Used inside ServiceWorkers to serve files offline.
|
|
6
|
+
*
|
|
7
|
+
* No bundling, no transpiling, no filesystem access.
|
|
8
|
+
* No directory scanning — manifests are pre-cached during SW install.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
CONTENT_TYPES,
|
|
13
|
+
type FetchParams,
|
|
14
|
+
type FetchReturn,
|
|
15
|
+
Runtime,
|
|
16
|
+
type RuntimeConfig,
|
|
17
|
+
} from './abstract.runtime.ts';
|
|
18
|
+
|
|
19
|
+
export class CacheRuntime extends Runtime {
|
|
20
|
+
private cache: Cache | null = null;
|
|
21
|
+
private readonly cacheName: string;
|
|
22
|
+
|
|
23
|
+
constructor(cacheName: string, config: RuntimeConfig = {}) {
|
|
24
|
+
super(config);
|
|
25
|
+
this.cacheName = cacheName;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private async getCache(): Promise<Cache> {
|
|
29
|
+
this.cache ??= await caches.open(this.cacheName);
|
|
30
|
+
return this.cache;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
handle(
|
|
34
|
+
resource: FetchParams[0],
|
|
35
|
+
init?: FetchParams[1],
|
|
36
|
+
): FetchReturn {
|
|
37
|
+
const path = this.parsePath(resource);
|
|
38
|
+
const method = init?.method ?? 'GET';
|
|
39
|
+
|
|
40
|
+
switch (method) {
|
|
41
|
+
case 'PUT':
|
|
42
|
+
return this.write(path, init?.body ?? null);
|
|
43
|
+
case 'DELETE':
|
|
44
|
+
return this.delete(path);
|
|
45
|
+
default:
|
|
46
|
+
return this.read(path);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
query(
|
|
51
|
+
resource: FetchParams[0],
|
|
52
|
+
options: FetchParams[1] & { as: 'text' },
|
|
53
|
+
): Promise<string>;
|
|
54
|
+
query(
|
|
55
|
+
resource: FetchParams[0],
|
|
56
|
+
options?: FetchParams[1],
|
|
57
|
+
): FetchReturn;
|
|
58
|
+
query(
|
|
59
|
+
resource: FetchParams[0],
|
|
60
|
+
options?: FetchParams[1] & { as?: 'text' },
|
|
61
|
+
): Promise<Response | string> {
|
|
62
|
+
if (options?.as === 'text') {
|
|
63
|
+
return this.read(this.parsePath(resource)).then(async (r) => {
|
|
64
|
+
if (r.status === 404) throw new Error(`Not found: ${this.parsePath(resource)}`);
|
|
65
|
+
return r.text();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return this.handle(resource, options);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override async loadModule(path: string): Promise<unknown> {
|
|
72
|
+
const response = await this.read(path);
|
|
73
|
+
if (response.status === 404) {
|
|
74
|
+
throw new Error(`Module not found in cache: ${path}`);
|
|
75
|
+
}
|
|
76
|
+
const js = await response.text();
|
|
77
|
+
const blob = new Blob([js], { type: 'application/javascript' });
|
|
78
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
79
|
+
try {
|
|
80
|
+
return await import(objectUrl);
|
|
81
|
+
} finally {
|
|
82
|
+
URL.revokeObjectURL(objectUrl);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Private ─────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
private async read(path: string): Promise<Response> {
|
|
89
|
+
const cache = await this.getCache();
|
|
90
|
+
const key = new Request(this.toFakeUrl(path));
|
|
91
|
+
const cached = await cache.match(key);
|
|
92
|
+
if (!cached) return new Response('Not Found', { status: 404 });
|
|
93
|
+
return cached;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async write(path: string, body: BodyInit | null): Promise<Response> {
|
|
97
|
+
const cache = await this.getCache();
|
|
98
|
+
const ext = path.slice(path.lastIndexOf('.')).toLowerCase();
|
|
99
|
+
const contentType = CONTENT_TYPES.get(ext) ?? 'application/octet-stream';
|
|
100
|
+
const response = new Response(body, {
|
|
101
|
+
status: 200,
|
|
102
|
+
headers: { 'Content-Type': contentType },
|
|
103
|
+
});
|
|
104
|
+
await cache.put(new Request(this.toFakeUrl(path)), response);
|
|
105
|
+
return new Response(null, { status: 204 });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private async delete(path: string): Promise<Response> {
|
|
109
|
+
const cache = await this.getCache();
|
|
110
|
+
await cache.delete(new Request(this.toFakeUrl(path)));
|
|
111
|
+
return new Response(null, { status: 204 });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private parsePath(resource: FetchParams[0]): string {
|
|
115
|
+
if (typeof resource === 'string') return resource;
|
|
116
|
+
if (resource instanceof URL) return resource.pathname;
|
|
117
|
+
return new URL(resource.url).pathname;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Cache API requires full URLs as keys.
|
|
122
|
+
* Use a synthetic origin so paths are consistent regardless of SW scope.
|
|
123
|
+
*/
|
|
124
|
+
private toFakeUrl(path: string): string {
|
|
125
|
+
return `https://emroute-cache${path}`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexedDB Runtime
|
|
3
|
+
*
|
|
4
|
+
* Browser-compatible Runtime backed by IndexedDB.
|
|
5
|
+
* Stores user content (pages, widgets, manifests) with full CRUD,
|
|
6
|
+
* directory listing, and persistent storage.
|
|
7
|
+
*
|
|
8
|
+
* Schema: single object store, key = path (string), value = Uint8Array.
|
|
9
|
+
* Content type inferred from file extension via CONTENT_TYPES map.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
CONTENT_TYPES,
|
|
14
|
+
type FetchParams,
|
|
15
|
+
type FetchReturn,
|
|
16
|
+
Runtime,
|
|
17
|
+
type RuntimeConfig,
|
|
18
|
+
} from './abstract.runtime.ts';
|
|
19
|
+
|
|
20
|
+
const STORE_NAME = 'files';
|
|
21
|
+
|
|
22
|
+
export class IdbRuntime extends Runtime {
|
|
23
|
+
private db: IDBDatabase | null = null;
|
|
24
|
+
private readonly dbName: string;
|
|
25
|
+
|
|
26
|
+
constructor(dbName: string, config: RuntimeConfig = {}) {
|
|
27
|
+
super(config);
|
|
28
|
+
this.dbName = dbName;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private open(): Promise<IDBDatabase> {
|
|
32
|
+
if (this.db) return Promise.resolve(this.db);
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const request = indexedDB.open(this.dbName, 1);
|
|
35
|
+
request.onupgradeneeded = () => {
|
|
36
|
+
request.result.createObjectStore(STORE_NAME);
|
|
37
|
+
};
|
|
38
|
+
request.onsuccess = () => {
|
|
39
|
+
this.db = request.result;
|
|
40
|
+
resolve(this.db);
|
|
41
|
+
};
|
|
42
|
+
request.onerror = () => reject(request.error);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
handle(
|
|
47
|
+
resource: FetchParams[0],
|
|
48
|
+
init?: FetchParams[1],
|
|
49
|
+
): FetchReturn {
|
|
50
|
+
const [pathname, method, body] = this.parse(resource, init);
|
|
51
|
+
|
|
52
|
+
switch (method) {
|
|
53
|
+
case 'PUT':
|
|
54
|
+
return this.write(pathname, body);
|
|
55
|
+
case 'DELETE':
|
|
56
|
+
return this.delete(pathname);
|
|
57
|
+
default:
|
|
58
|
+
return this.read(pathname);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
query(
|
|
63
|
+
resource: FetchParams[0],
|
|
64
|
+
options: FetchParams[1] & { as: 'text' },
|
|
65
|
+
): Promise<string>;
|
|
66
|
+
query(
|
|
67
|
+
resource: FetchParams[0],
|
|
68
|
+
options?: FetchParams[1],
|
|
69
|
+
): FetchReturn;
|
|
70
|
+
query(
|
|
71
|
+
resource: FetchParams[0],
|
|
72
|
+
options?: FetchParams[1] & { as?: 'text' },
|
|
73
|
+
): Promise<Response | string> {
|
|
74
|
+
if (options?.as === 'text') {
|
|
75
|
+
const pathname = this.parsePath(resource);
|
|
76
|
+
return this.get(pathname).then((data) => {
|
|
77
|
+
if (!data) throw new Error(`Not found: ${pathname}`);
|
|
78
|
+
return new TextDecoder().decode(data);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return this.handle(resource, options);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
override async loadModule(path: string): Promise<unknown> {
|
|
85
|
+
const data = await this.get(path);
|
|
86
|
+
if (!data) throw new Error(`Module not found in IDB: ${path}`);
|
|
87
|
+
const buf = data.buffer as ArrayBuffer;
|
|
88
|
+
const blob = new Blob([buf], { type: 'application/javascript' });
|
|
89
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
90
|
+
try {
|
|
91
|
+
return await import(objectUrl);
|
|
92
|
+
} finally {
|
|
93
|
+
URL.revokeObjectURL(objectUrl);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Private ─────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
private async read(path: string): Promise<Response> {
|
|
100
|
+
if (path.endsWith('/')) {
|
|
101
|
+
const children = await this.listChildren(path);
|
|
102
|
+
if (children.length === 0) return new Response('Not Found', { status: 404 });
|
|
103
|
+
return Response.json(children);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const data = await this.get(path);
|
|
107
|
+
if (!data) {
|
|
108
|
+
// Directory-style fallback: check if path has children
|
|
109
|
+
const children = await this.listChildren(path + '/');
|
|
110
|
+
if (children.length > 0) return Response.json(children);
|
|
111
|
+
return new Response('Not Found', { status: 404 });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const ext = path.slice(path.lastIndexOf('.')).toLowerCase();
|
|
115
|
+
return new Response(data.buffer as ArrayBuffer, {
|
|
116
|
+
status: 200,
|
|
117
|
+
headers: { 'Content-Type': CONTENT_TYPES.get(ext) ?? 'application/octet-stream' },
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async write(path: string, body: BodyInit | null): Promise<Response> {
|
|
122
|
+
const data = body
|
|
123
|
+
? new Uint8Array(await new Response(body).arrayBuffer())
|
|
124
|
+
: new Uint8Array();
|
|
125
|
+
await this.put(path, data);
|
|
126
|
+
return new Response(null, { status: 204 });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async delete(path: string): Promise<Response> {
|
|
130
|
+
const db = await this.open();
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
const tx = db.transaction(STORE_NAME, 'readwrite');
|
|
133
|
+
tx.objectStore(STORE_NAME).delete(path);
|
|
134
|
+
tx.oncomplete = () => resolve(new Response(null, { status: 204 }));
|
|
135
|
+
tx.onerror = () => reject(tx.error);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private async get(path: string): Promise<Uint8Array | undefined> {
|
|
140
|
+
const db = await this.open();
|
|
141
|
+
return new Promise((resolve, reject) => {
|
|
142
|
+
const tx = db.transaction(STORE_NAME, 'readonly');
|
|
143
|
+
const req = tx.objectStore(STORE_NAME).get(path);
|
|
144
|
+
req.onsuccess = () => resolve(req.result as Uint8Array | undefined);
|
|
145
|
+
req.onerror = () => reject(req.error);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private async put(path: string, data: Uint8Array): Promise<void> {
|
|
150
|
+
const db = await this.open();
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
const tx = db.transaction(STORE_NAME, 'readwrite');
|
|
153
|
+
tx.objectStore(STORE_NAME).put(data, path);
|
|
154
|
+
tx.oncomplete = () => resolve();
|
|
155
|
+
tx.onerror = () => reject(tx.error);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private async listChildren(prefix: string): Promise<string[]> {
|
|
160
|
+
const db = await this.open();
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const tx = db.transaction(STORE_NAME, 'readonly');
|
|
163
|
+
const store = tx.objectStore(STORE_NAME);
|
|
164
|
+
const range = IDBKeyRange.bound(prefix, prefix + '\uffff', false, false);
|
|
165
|
+
const req = store.getAllKeys(range);
|
|
166
|
+
req.onsuccess = () => {
|
|
167
|
+
const entries = new Set<string>();
|
|
168
|
+
for (const key of req.result as string[]) {
|
|
169
|
+
const rest = (key as string).slice(prefix.length);
|
|
170
|
+
const slashIdx = rest.indexOf('/');
|
|
171
|
+
if (slashIdx === -1) {
|
|
172
|
+
entries.add(rest);
|
|
173
|
+
} else {
|
|
174
|
+
entries.add(rest.slice(0, slashIdx + 1));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
resolve([...entries]);
|
|
178
|
+
};
|
|
179
|
+
req.onerror = () => reject(req.error);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private parsePath(resource: FetchParams[0]): string {
|
|
184
|
+
if (typeof resource === 'string') return resource;
|
|
185
|
+
if (resource instanceof URL) return resource.pathname;
|
|
186
|
+
return new URL(resource.url).pathname;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private parse(
|
|
190
|
+
resource: FetchParams[0],
|
|
191
|
+
init?: RequestInit,
|
|
192
|
+
): [string, string, BodyInit | null] {
|
|
193
|
+
const pathname = this.parsePath(resource);
|
|
194
|
+
if (typeof resource === 'string' || resource instanceof URL) {
|
|
195
|
+
return [pathname, init?.method ?? 'GET', init?.body ?? null];
|
|
196
|
+
}
|
|
197
|
+
return [
|
|
198
|
+
pathname,
|
|
199
|
+
init?.method ?? resource.method,
|
|
200
|
+
init?.body ?? resource.body,
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -39,7 +39,10 @@ export class ComponentElement<TParams, TData> extends HTMLElementBase {
|
|
|
39
39
|
/** App-level context provider set once during router initialization. */
|
|
40
40
|
private static extendContext: ContextProvider | undefined;
|
|
41
41
|
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Register (or clear) the context provider that enriches every widget's ComponentContext.
|
|
44
|
+
* @deprecated Use `bootEmrouteApp({ extendContext })` instead — it wires both pages and widgets.
|
|
45
|
+
*/
|
|
43
46
|
static setContextProvider(provider: ContextProvider | undefined): void {
|
|
44
47
|
ComponentElement.extendContext = provider;
|
|
45
48
|
}
|
package/src/index.ts
CHANGED
|
@@ -59,7 +59,6 @@ export type {
|
|
|
59
59
|
|
|
60
60
|
export { PageComponent } from '../core/component/page.component.ts';
|
|
61
61
|
export { WidgetComponent } from '../core/component/widget.component.ts';
|
|
62
|
-
export { WidgetRegistry } from '../core/widget/widget.registry.ts';
|
|
63
62
|
|
|
64
63
|
// Route config
|
|
65
64
|
export { type BasePath, DEFAULT_BASE_PATH } from '../core/server/emroute.server.ts';
|