@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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/core/component/widget.component.ts +1 -1
  3. package/core/pipeline/pipeline.ts +11 -1
  4. package/core/renderer/html.renderer.ts +16 -18
  5. package/core/renderer/md.renderer.ts +3 -5
  6. package/core/renderer/ssr.renderer.ts +21 -5
  7. package/core/server/emroute.server.ts +9 -62
  8. package/core/util/widget-resolve.util.ts +2 -2
  9. package/core/widget/widget.registry.ts +14 -28
  10. package/dist/core/component/widget.component.d.ts +1 -1
  11. package/dist/core/component/widget.component.js +1 -1
  12. package/dist/core/pipeline/pipeline.d.ts +1 -0
  13. package/dist/core/pipeline/pipeline.js +9 -1
  14. package/dist/core/pipeline/pipeline.js.map +1 -1
  15. package/dist/core/renderer/html.renderer.js +10 -12
  16. package/dist/core/renderer/html.renderer.js.map +1 -1
  17. package/dist/core/renderer/md.renderer.js +3 -5
  18. package/dist/core/renderer/md.renderer.js.map +1 -1
  19. package/dist/core/renderer/ssr.renderer.d.ts +6 -4
  20. package/dist/core/renderer/ssr.renderer.js +16 -3
  21. package/dist/core/renderer/ssr.renderer.js.map +1 -1
  22. package/dist/core/server/emroute.server.d.ts +2 -4
  23. package/dist/core/server/emroute.server.js +7 -52
  24. package/dist/core/server/emroute.server.js.map +1 -1
  25. package/dist/core/util/widget-resolve.util.d.ts +1 -3
  26. package/dist/core/util/widget-resolve.util.js +2 -2
  27. package/dist/core/util/widget-resolve.util.js.map +1 -1
  28. package/dist/core/widget/widget.registry.d.ts +5 -10
  29. package/dist/core/widget/widget.registry.js +15 -20
  30. package/dist/core/widget/widget.registry.js.map +1 -1
  31. package/dist/emroute.js +449 -95
  32. package/dist/emroute.js.map +17 -14
  33. package/dist/runtime/cache.runtime.d.ts +31 -0
  34. package/dist/runtime/cache.runtime.js +107 -0
  35. package/dist/runtime/cache.runtime.js.map +1 -0
  36. package/dist/runtime/idb.runtime.d.ts +31 -0
  37. package/dist/runtime/idb.runtime.js +178 -0
  38. package/dist/runtime/idb.runtime.js.map +1 -0
  39. package/dist/src/element/component.element.d.ts +4 -1
  40. package/dist/src/element/component.element.js +4 -1
  41. package/dist/src/element/component.element.js.map +1 -1
  42. package/dist/src/index.d.ts +0 -1
  43. package/dist/src/index.js +0 -1
  44. package/dist/src/index.js.map +1 -1
  45. package/dist/src/renderer/spa/emroute.app.d.ts +3 -0
  46. package/dist/src/renderer/spa/emroute.app.js +6 -4
  47. package/dist/src/renderer/spa/emroute.app.js.map +1 -1
  48. package/dist/src/renderer/spa/mod.d.ts +4 -3
  49. package/dist/src/renderer/spa/mod.js +5 -3
  50. package/dist/src/renderer/spa/mod.js.map +1 -1
  51. package/dist/src/service-worker/emroute.sw.d.ts +54 -0
  52. package/dist/src/service-worker/emroute.sw.js +181 -0
  53. package/dist/src/service-worker/emroute.sw.js.map +1 -0
  54. package/dist/src/service-worker/mod.d.ts +8 -0
  55. package/dist/src/service-worker/mod.js +9 -0
  56. package/dist/src/service-worker/mod.js.map +1 -0
  57. package/package.json +16 -1
  58. package/runtime/cache.runtime.ts +127 -0
  59. package/runtime/idb.runtime.ts +203 -0
  60. package/src/element/component.element.ts +4 -1
  61. package/src/index.ts +0 -1
  62. package/src/renderer/spa/emroute.app.ts +11 -4
  63. package/src/renderer/spa/mod.ts +6 -4
  64. package/src/service-worker/emroute.sw.ts +264 -0
  65. package/src/service-worker/mod.ts +9 -0
package/dist/emroute.js CHANGED
@@ -472,27 +472,6 @@ class ComponentElement extends HTMLElementBase {
472
472
  }
473
473
  }
474
474
 
475
- // dist/core/widget/widget.registry.js
476
- class WidgetRegistry {
477
- entries = new Map;
478
- add(widget, modulePath) {
479
- this.entries.set(widget.name, { widget, modulePath });
480
- }
481
- get(name) {
482
- return this.entries.get(name)?.widget;
483
- }
484
- getModulePath(name) {
485
- return this.entries.get(name)?.modulePath;
486
- }
487
- [Symbol.iterator]() {
488
- const entries = this.entries.values();
489
- return function* () {
490
- for (const entry of entries)
491
- yield entry.widget;
492
- }();
493
- }
494
- }
495
-
496
475
  // dist/core/router/route.trie.js
497
476
  class RouteTrie {
498
477
  tree;
@@ -715,6 +694,13 @@ class Pipeline {
715
694
  }
716
695
  return hierarchy;
717
696
  }
697
+ async findWidgetModulePath(name) {
698
+ const response = await this.runtime.query(WIDGETS_MANIFEST_PATH);
699
+ if (response.status === 404)
700
+ return;
701
+ const entries = await response.json();
702
+ return entries.find((e) => e.name === name)?.modulePath;
703
+ }
718
704
  async loadModule(modulePath) {
719
705
  const loader = this.moduleLoaders[modulePath];
720
706
  if (loader) {
@@ -806,7 +792,7 @@ async function resolveRecursively(content, parse, resolve, replace, depth = 0, l
806
792
  }));
807
793
  return replace(content, replacements);
808
794
  }
809
- function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider, logger = defaultLogger) {
795
+ function resolveWidgetTags(html, getWidget, routeInfo, loadFiles, contextProvider, logger = defaultLogger) {
810
796
  const tagPattern = /<widget-(?<name>[a-z][a-z0-9-]*)(?<attrs>\s[^>]*)?>(?<content>.*?)<\/widget-\k<name>>/gis;
811
797
  const wrappers = new Map;
812
798
  const ssrAttrPattern = new RegExp(`\\s${SSR_ATTR}(?:\\s|=|$)`);
@@ -820,7 +806,7 @@ function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider
820
806
  const resolve = async (match) => {
821
807
  const widgetName = match.groups.name;
822
808
  const attrsString = match.groups.attrs?.trim() ?? "";
823
- const widget = registry.get(widgetName);
809
+ const widget = await getWidget(widgetName);
824
810
  if (!widget)
825
811
  return match[0];
826
812
  const params = parseAttrsToParams(attrsString);
@@ -1017,15 +1003,40 @@ class PageComponent extends Component {
1017
1003
  }
1018
1004
  var page_component_default = new PageComponent;
1019
1005
 
1006
+ // dist/core/widget/widget.registry.js
1007
+ function extractWidgetExport(mod) {
1008
+ for (const value of Object.values(mod)) {
1009
+ if (!value)
1010
+ continue;
1011
+ if (typeof value === "object" && "getData" in value) {
1012
+ return value;
1013
+ }
1014
+ if (typeof value === "function" && value.prototype?.getData) {
1015
+ return new value;
1016
+ }
1017
+ }
1018
+ return null;
1019
+ }
1020
+
1020
1021
  // dist/core/renderer/ssr.renderer.js
1021
1022
  class SsrRenderer {
1022
1023
  pipeline;
1023
- widgets;
1024
1024
  logger;
1025
- constructor(pipeline, options = {}) {
1025
+ constructor(pipeline, _options = {}) {
1026
1026
  this.pipeline = pipeline;
1027
1027
  this.logger = pipeline.logger;
1028
- this.widgets = options.widgets ?? null;
1028
+ }
1029
+ async resolveWidget(name) {
1030
+ const path = await this.pipeline.findWidgetModulePath(name);
1031
+ if (!path)
1032
+ return;
1033
+ try {
1034
+ const mod = await this.pipeline.loadModule(path);
1035
+ return extractWidgetExport(mod) ?? undefined;
1036
+ } catch (e) {
1037
+ this.logger.error(`[${this.label}] Failed to load widget "${name}"`, e instanceof Error ? e : undefined);
1038
+ return;
1039
+ }
1029
1040
  }
1030
1041
  async render(url, signal) {
1031
1042
  const matched = await this.pipeline.match(url);
@@ -1190,18 +1201,16 @@ class SsrHtmlRenderer extends SsrRenderer {
1190
1201
  let content = rawContent;
1191
1202
  content = await this.expandMarkdown(content);
1192
1203
  content = this.attributeSlots(content, route.pattern);
1193
- if (this.widgets) {
1194
- content = await resolveWidgetTags(content, this.widgets, routeInfo, async (name) => {
1195
- const modulePath = this.widgets.getModulePath(name);
1196
- if (modulePath) {
1197
- const mod = await this.pipeline.loadModule(modulePath);
1198
- const inlined = this.pipeline.getModuleFiles(mod);
1199
- if (inlined)
1200
- return inlined;
1201
- }
1202
- return {};
1203
- }, this.pipeline.contextProvider, this.logger);
1204
- }
1204
+ content = await resolveWidgetTags(content, (name) => this.resolveWidget(name), routeInfo, async (name) => {
1205
+ const modulePath = await this.pipeline.findWidgetModulePath(name);
1206
+ if (modulePath) {
1207
+ const mod = await this.pipeline.loadModule(modulePath);
1208
+ const inlined = this.pipeline.getModuleFiles(mod);
1209
+ if (inlined)
1210
+ return inlined;
1211
+ }
1212
+ return {};
1213
+ }, this.pipeline.contextProvider, this.logger);
1205
1214
  return { content, ...title !== undefined ? { title } : {} };
1206
1215
  }
1207
1216
  renderContent(component, args) {
@@ -1313,9 +1322,7 @@ class SsrMdRenderer extends SsrRenderer {
1313
1322
  const { content: rawContent, title } = await this.loadRouteContent(routeInfo, route, isLeaf, signal);
1314
1323
  let content = rawContent;
1315
1324
  content = content.replaceAll(BARE_SLOT_BLOCK, routerSlotBlock(route.pattern));
1316
- if (this.widgets) {
1317
- content = await this.resolveWidgets(content, routeInfo);
1318
- }
1325
+ content = await this.resolveWidgets(content, routeInfo);
1319
1326
  return { content, ...title !== undefined ? { title } : {} };
1320
1327
  }
1321
1328
  renderContent(component, args) {
@@ -1339,13 +1346,13 @@ Path: \`${url.pathname}\``;
1339
1346
  if (block.parseError || !block.params) {
1340
1347
  return `> **Error** (\`${block.widgetName}\`): ${block.parseError}`;
1341
1348
  }
1342
- const widget = this.widgets.get(block.widgetName);
1349
+ const widget = await this.resolveWidget(block.widgetName);
1343
1350
  if (!widget) {
1344
1351
  return `> **Error**: Unknown widget \`${block.widgetName}\``;
1345
1352
  }
1346
1353
  try {
1347
1354
  let files;
1348
- const modulePath = this.widgets.getModulePath(block.widgetName);
1355
+ const modulePath = await this.pipeline.findWidgetModulePath(block.widgetName);
1349
1356
  if (modulePath) {
1350
1357
  const mod = await this.pipeline.loadModule(modulePath);
1351
1358
  files = this.pipeline.getModuleFiles(mod);
@@ -1430,28 +1437,13 @@ class Emroute {
1430
1437
  ...config.extendContext ? { contextProvider: config.extendContext } : {},
1431
1438
  ...config.moduleLoaders ? { moduleLoaders: config.moduleLoaders } : {}
1432
1439
  });
1433
- let widgets;
1434
- const widgetsResponse = await runtime.query(WIDGETS_MANIFEST_PATH);
1435
- if (widgetsResponse.status !== 404) {
1436
- const entries = await widgetsResponse.json();
1437
- widgets = await Emroute.importWidgets(entries, runtime);
1438
- }
1439
- if (config.widgets) {
1440
- if (!widgets)
1441
- widgets = new WidgetRegistry;
1442
- for (const w of config.widgets)
1443
- widgets.add(w);
1444
- }
1445
1440
  let ssrHtmlRenderer = null;
1446
1441
  let ssrMdRenderer = null;
1447
1442
  if (spa !== "only") {
1448
1443
  ssrHtmlRenderer = new SsrHtmlRenderer(pipeline, {
1449
- ...config.markdownRenderer ? { markdownRenderer: config.markdownRenderer } : {},
1450
- ...widgets ? { widgets } : {}
1451
- });
1452
- ssrMdRenderer = new SsrMdRenderer(pipeline, {
1453
- ...widgets ? { widgets } : {}
1444
+ ...config.markdownRenderer ? { markdownRenderer: config.markdownRenderer } : {}
1454
1445
  });
1446
+ ssrMdRenderer = new SsrMdRenderer(pipeline);
1455
1447
  }
1456
1448
  const title = config.title ?? "emroute";
1457
1449
  const shellBase = spa === "root" || spa === "only" ? appBase : htmlBase;
@@ -1531,38 +1523,14 @@ class Emroute {
1531
1523
  const bare = pathname === "/" ? "" : pathname.slice(1).replace(/\/$/, "");
1532
1524
  return Response.redirect(new URL(`${base}/${bare}`, url.origin), 302);
1533
1525
  }
1534
- static extractWidgetExport(mod) {
1535
- for (const value of Object.values(mod)) {
1536
- if (!value)
1537
- continue;
1538
- if (typeof value === "object" && "getData" in value) {
1539
- return value;
1540
- }
1541
- if (typeof value === "function" && value.prototype?.getData) {
1542
- return new value;
1543
- }
1544
- }
1545
- return null;
1546
- }
1547
- static async importWidgets(entries, runtime) {
1548
- const registry = new WidgetRegistry;
1549
- for (const entry of entries) {
1550
- try {
1551
- const runtimePath = entry.modulePath.startsWith("/") ? entry.modulePath : `/${entry.modulePath}`;
1552
- const mod = await runtime.loadModule(runtimePath);
1553
- const instance = Emroute.extractWidgetExport(mod);
1554
- if (!instance)
1555
- continue;
1556
- registry.add(instance, runtimePath);
1557
- } catch (e) {
1558
- console.error(`[emroute] Failed to load widget ${entry.modulePath}:`, e);
1559
- }
1560
- }
1561
- return registry;
1562
- }
1563
1526
  static async buildHtmlShell(runtime, title, basePath, spa) {
1564
1527
  const baseTag = basePath ? `
1565
1528
  <base href="${escapeHtml(basePath)}/">` : "";
1529
+ let manifestTag = "";
1530
+ if ((await runtime.query("/manifest.json")).status !== 404) {
1531
+ manifestTag = `
1532
+ <link rel="manifest" href="/manifest.json">`;
1533
+ }
1566
1534
  let cssTag = "";
1567
1535
  if ((await runtime.query("/main.css")).status !== 404) {
1568
1536
  cssTag = `
@@ -1591,7 +1559,7 @@ ${importMap}
1591
1559
  <meta charset="utf-8">
1592
1560
  <meta name="viewport" content="width=device-width, initial-scale=1">
1593
1561
  <title>${escapeHtml(title)}</title>
1594
- <style>@view-transition { navigation: auto; } router-slot { display: contents; }</style>${cssTag}
1562
+ <style>@view-transition { navigation: auto; } router-slot { display: contents; }</style>${manifestTag}${cssTag}
1595
1563
  </head>
1596
1564
  <body>
1597
1565
  <router-slot></router-slot>${importMapHtml}${scriptHtml}
@@ -2415,7 +2383,6 @@ async function bootEmrouteApp(options) {
2415
2383
  const elementsResponse = await runtime.handle(ELEMENTS_MANIFEST_PATH);
2416
2384
  const elementEntries = elementsResponse.ok ? await elementsResponse.json() : [];
2417
2385
  const moduleLoaders = buildLazyLoaders(routeTree, widgetEntries, elementEntries, runtime);
2418
- const widgets = new WidgetRegistry;
2419
2386
  for (const entry of widgetEntries) {
2420
2387
  ComponentElement.registerLazy(entry.name, moduleLoaders[entry.modulePath]);
2421
2388
  }
@@ -2432,12 +2399,15 @@ async function bootEmrouteApp(options) {
2432
2399
  });
2433
2400
  }
2434
2401
  }
2402
+ if (options?.extendContext) {
2403
+ ComponentElement.setContextProvider(options.extendContext);
2404
+ }
2435
2405
  const mdRenderer = MarkdownElement.getConfiguredRenderer();
2436
2406
  const server = await Emroute.create({
2437
2407
  routeTree,
2438
- widgets,
2439
2408
  moduleLoaders,
2440
- ...mdRenderer ? { markdownRenderer: mdRenderer } : {}
2409
+ ...mdRenderer ? { markdownRenderer: mdRenderer } : {},
2410
+ ...options?.extendContext ? { extendContext: options.extendContext } : {}
2441
2411
  }, runtime);
2442
2412
  return createEmrouteApp(server, options);
2443
2413
  }
@@ -3015,6 +2985,388 @@ function createOverlayService() {
3015
2985
  dismissAll
3016
2986
  };
3017
2987
  }
2988
+ // dist/runtime/cache.runtime.js
2989
+ var __rewriteRelativeImportExtension2 = function(path, preserveJsx) {
2990
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
2991
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
2992
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
2993
+ });
2994
+ }
2995
+ return path;
2996
+ };
2997
+
2998
+ class CacheRuntime extends Runtime {
2999
+ cache = null;
3000
+ cacheName;
3001
+ constructor(cacheName, config = {}) {
3002
+ super(config);
3003
+ this.cacheName = cacheName;
3004
+ }
3005
+ async getCache() {
3006
+ this.cache ??= await caches.open(this.cacheName);
3007
+ return this.cache;
3008
+ }
3009
+ handle(resource, init) {
3010
+ const path = this.parsePath(resource);
3011
+ const method = init?.method ?? "GET";
3012
+ switch (method) {
3013
+ case "PUT":
3014
+ return this.write(path, init?.body ?? null);
3015
+ case "DELETE":
3016
+ return this.delete(path);
3017
+ default:
3018
+ return this.read(path);
3019
+ }
3020
+ }
3021
+ query(resource, options) {
3022
+ if (options?.as === "text") {
3023
+ return this.read(this.parsePath(resource)).then(async (r) => {
3024
+ if (r.status === 404)
3025
+ throw new Error(`Not found: ${this.parsePath(resource)}`);
3026
+ return r.text();
3027
+ });
3028
+ }
3029
+ return this.handle(resource, options);
3030
+ }
3031
+ async loadModule(path) {
3032
+ const response = await this.read(path);
3033
+ if (response.status === 404) {
3034
+ throw new Error(`Module not found in cache: ${path}`);
3035
+ }
3036
+ const js = await response.text();
3037
+ const blob = new Blob([js], { type: "application/javascript" });
3038
+ const objectUrl = URL.createObjectURL(blob);
3039
+ try {
3040
+ return await import(__rewriteRelativeImportExtension2(objectUrl));
3041
+ } finally {
3042
+ URL.revokeObjectURL(objectUrl);
3043
+ }
3044
+ }
3045
+ async read(path) {
3046
+ const cache = await this.getCache();
3047
+ const key = new Request(this.toFakeUrl(path));
3048
+ const cached = await cache.match(key);
3049
+ if (!cached)
3050
+ return new Response("Not Found", { status: 404 });
3051
+ return cached;
3052
+ }
3053
+ async write(path, body) {
3054
+ const cache = await this.getCache();
3055
+ const ext = path.slice(path.lastIndexOf(".")).toLowerCase();
3056
+ const contentType = CONTENT_TYPES.get(ext) ?? "application/octet-stream";
3057
+ const response = new Response(body, {
3058
+ status: 200,
3059
+ headers: { "Content-Type": contentType }
3060
+ });
3061
+ await cache.put(new Request(this.toFakeUrl(path)), response);
3062
+ return new Response(null, { status: 204 });
3063
+ }
3064
+ async delete(path) {
3065
+ const cache = await this.getCache();
3066
+ await cache.delete(new Request(this.toFakeUrl(path)));
3067
+ return new Response(null, { status: 204 });
3068
+ }
3069
+ parsePath(resource) {
3070
+ if (typeof resource === "string")
3071
+ return resource;
3072
+ if (resource instanceof URL)
3073
+ return resource.pathname;
3074
+ return new URL(resource.url).pathname;
3075
+ }
3076
+ toFakeUrl(path) {
3077
+ return `https://emroute-cache${path}`;
3078
+ }
3079
+ }
3080
+
3081
+ // dist/runtime/idb.runtime.js
3082
+ var __rewriteRelativeImportExtension3 = function(path, preserveJsx) {
3083
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3084
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
3085
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
3086
+ });
3087
+ }
3088
+ return path;
3089
+ };
3090
+ var STORE_NAME = "files";
3091
+
3092
+ class IdbRuntime extends Runtime {
3093
+ db = null;
3094
+ dbName;
3095
+ constructor(dbName, config = {}) {
3096
+ super(config);
3097
+ this.dbName = dbName;
3098
+ }
3099
+ open() {
3100
+ if (this.db)
3101
+ return Promise.resolve(this.db);
3102
+ return new Promise((resolve, reject) => {
3103
+ const request = indexedDB.open(this.dbName, 1);
3104
+ request.onupgradeneeded = () => {
3105
+ request.result.createObjectStore(STORE_NAME);
3106
+ };
3107
+ request.onsuccess = () => {
3108
+ this.db = request.result;
3109
+ resolve(this.db);
3110
+ };
3111
+ request.onerror = () => reject(request.error);
3112
+ });
3113
+ }
3114
+ handle(resource, init) {
3115
+ const [pathname, method, body] = this.parse(resource, init);
3116
+ switch (method) {
3117
+ case "PUT":
3118
+ return this.write(pathname, body);
3119
+ case "DELETE":
3120
+ return this.delete(pathname);
3121
+ default:
3122
+ return this.read(pathname);
3123
+ }
3124
+ }
3125
+ query(resource, options) {
3126
+ if (options?.as === "text") {
3127
+ const pathname = this.parsePath(resource);
3128
+ return this.get(pathname).then((data) => {
3129
+ if (!data)
3130
+ throw new Error(`Not found: ${pathname}`);
3131
+ return new TextDecoder().decode(data);
3132
+ });
3133
+ }
3134
+ return this.handle(resource, options);
3135
+ }
3136
+ async loadModule(path) {
3137
+ const data = await this.get(path);
3138
+ if (!data)
3139
+ throw new Error(`Module not found in IDB: ${path}`);
3140
+ const buf = data.buffer;
3141
+ const blob = new Blob([buf], { type: "application/javascript" });
3142
+ const objectUrl = URL.createObjectURL(blob);
3143
+ try {
3144
+ return await import(__rewriteRelativeImportExtension3(objectUrl));
3145
+ } finally {
3146
+ URL.revokeObjectURL(objectUrl);
3147
+ }
3148
+ }
3149
+ async read(path) {
3150
+ if (path.endsWith("/")) {
3151
+ const children = await this.listChildren(path);
3152
+ if (children.length === 0)
3153
+ return new Response("Not Found", { status: 404 });
3154
+ return Response.json(children);
3155
+ }
3156
+ const data = await this.get(path);
3157
+ if (!data) {
3158
+ const children = await this.listChildren(path + "/");
3159
+ if (children.length > 0)
3160
+ return Response.json(children);
3161
+ return new Response("Not Found", { status: 404 });
3162
+ }
3163
+ const ext = path.slice(path.lastIndexOf(".")).toLowerCase();
3164
+ return new Response(data.buffer, {
3165
+ status: 200,
3166
+ headers: { "Content-Type": CONTENT_TYPES.get(ext) ?? "application/octet-stream" }
3167
+ });
3168
+ }
3169
+ async write(path, body) {
3170
+ const data = body ? new Uint8Array(await new Response(body).arrayBuffer()) : new Uint8Array;
3171
+ await this.put(path, data);
3172
+ return new Response(null, { status: 204 });
3173
+ }
3174
+ async delete(path) {
3175
+ const db = await this.open();
3176
+ return new Promise((resolve, reject) => {
3177
+ const tx = db.transaction(STORE_NAME, "readwrite");
3178
+ tx.objectStore(STORE_NAME).delete(path);
3179
+ tx.oncomplete = () => resolve(new Response(null, { status: 204 }));
3180
+ tx.onerror = () => reject(tx.error);
3181
+ });
3182
+ }
3183
+ async get(path) {
3184
+ const db = await this.open();
3185
+ return new Promise((resolve, reject) => {
3186
+ const tx = db.transaction(STORE_NAME, "readonly");
3187
+ const req = tx.objectStore(STORE_NAME).get(path);
3188
+ req.onsuccess = () => resolve(req.result);
3189
+ req.onerror = () => reject(req.error);
3190
+ });
3191
+ }
3192
+ async put(path, data) {
3193
+ const db = await this.open();
3194
+ return new Promise((resolve, reject) => {
3195
+ const tx = db.transaction(STORE_NAME, "readwrite");
3196
+ tx.objectStore(STORE_NAME).put(data, path);
3197
+ tx.oncomplete = () => resolve();
3198
+ tx.onerror = () => reject(tx.error);
3199
+ });
3200
+ }
3201
+ async listChildren(prefix) {
3202
+ const db = await this.open();
3203
+ return new Promise((resolve, reject) => {
3204
+ const tx = db.transaction(STORE_NAME, "readonly");
3205
+ const store = tx.objectStore(STORE_NAME);
3206
+ const range = IDBKeyRange.bound(prefix, prefix + "￿", false, false);
3207
+ const req = store.getAllKeys(range);
3208
+ req.onsuccess = () => {
3209
+ const entries = new Set;
3210
+ for (const key of req.result) {
3211
+ const rest = key.slice(prefix.length);
3212
+ const slashIdx = rest.indexOf("/");
3213
+ if (slashIdx === -1) {
3214
+ entries.add(rest);
3215
+ } else {
3216
+ entries.add(rest.slice(0, slashIdx + 1));
3217
+ }
3218
+ }
3219
+ resolve([...entries]);
3220
+ };
3221
+ req.onerror = () => reject(req.error);
3222
+ });
3223
+ }
3224
+ parsePath(resource) {
3225
+ if (typeof resource === "string")
3226
+ return resource;
3227
+ if (resource instanceof URL)
3228
+ return resource.pathname;
3229
+ return new URL(resource.url).pathname;
3230
+ }
3231
+ parse(resource, init) {
3232
+ const pathname = this.parsePath(resource);
3233
+ if (typeof resource === "string" || resource instanceof URL) {
3234
+ return [pathname, init?.method ?? "GET", init?.body ?? null];
3235
+ }
3236
+ return [
3237
+ pathname,
3238
+ init?.method ?? resource.method,
3239
+ init?.body ?? resource.body
3240
+ ];
3241
+ }
3242
+ }
3243
+
3244
+ // dist/src/service-worker/emroute.sw.js
3245
+ class SwRuntime extends Runtime {
3246
+ cache;
3247
+ idb;
3248
+ constructor(cache, idb) {
3249
+ super();
3250
+ this.cache = cache;
3251
+ this.idb = idb;
3252
+ }
3253
+ handle(resource, init) {
3254
+ const method = init?.method ?? "GET";
3255
+ if (method === "PUT" || method === "DELETE") {
3256
+ return this.idb.handle(resource, init);
3257
+ }
3258
+ return this.cache.handle(resource, init).then(async (r) => {
3259
+ if (r.status !== 404)
3260
+ return r;
3261
+ return this.idb.handle(resource, init);
3262
+ });
3263
+ }
3264
+ query(resource, options) {
3265
+ if (options?.as === "text") {
3266
+ return this.handle(resource, options).then(async (r) => {
3267
+ if (r.status === 404) {
3268
+ const path = typeof resource === "string" ? resource : resource instanceof URL ? resource.pathname : new URL(resource.url).pathname;
3269
+ throw new Error(`Not found: ${path}`);
3270
+ }
3271
+ return r.text();
3272
+ });
3273
+ }
3274
+ return this.handle(resource, options);
3275
+ }
3276
+ async loadModule(path) {
3277
+ try {
3278
+ return await this.cache.loadModule(path);
3279
+ } catch {
3280
+ return await this.idb.loadModule(path);
3281
+ }
3282
+ }
3283
+ }
3284
+ function createEmrouteSW(options) {
3285
+ const { cacheName, precache, content = [], dbName = "emroute-content", origin = self.location.origin } = options;
3286
+ const cacheRuntime = new CacheRuntime(cacheName);
3287
+ const idbRuntime = new IdbRuntime(dbName);
3288
+ const swRuntime = new SwRuntime(cacheRuntime, idbRuntime);
3289
+ let emroute = null;
3290
+ async function getEmroute() {
3291
+ if (emroute)
3292
+ return emroute;
3293
+ emroute = await Emroute.create({
3294
+ spa: options.spa ?? "only",
3295
+ ...options.basePath ? { basePath: options.basePath } : {},
3296
+ ...options.title ? { title: options.title } : {},
3297
+ ...options.markdownRenderer ? { markdownRenderer: options.markdownRenderer } : {},
3298
+ ...options.extendContext ? { extendContext: options.extendContext } : {}
3299
+ }, swRuntime);
3300
+ return emroute;
3301
+ }
3302
+ self.addEventListener("install", (event) => {
3303
+ event.waitUntil((async () => {
3304
+ if (precache.length > 0) {
3305
+ const cache = await caches.open(cacheName);
3306
+ await Promise.all(precache.map(async (path) => {
3307
+ try {
3308
+ const response = await fetch(`${origin}${path}`);
3309
+ if (response.ok) {
3310
+ await cache.put(new Request(`https://emroute-cache${path}`), response);
3311
+ }
3312
+ } catch {
3313
+ console.error(`[emroute-sw] Failed to precache asset: ${path}`);
3314
+ }
3315
+ }));
3316
+ }
3317
+ if (content.length > 0) {
3318
+ await Promise.all(content.map(async (path) => {
3319
+ try {
3320
+ const response = await fetch(`${origin}${path}`);
3321
+ if (response.ok) {
3322
+ const data = new Uint8Array(await response.arrayBuffer());
3323
+ await idbRuntime.handle(path, {
3324
+ method: "PUT",
3325
+ body: data
3326
+ });
3327
+ }
3328
+ } catch {
3329
+ console.error(`[emroute-sw] Failed to precache content: ${path}`);
3330
+ }
3331
+ }));
3332
+ }
3333
+ await self.skipWaiting();
3334
+ })());
3335
+ });
3336
+ self.addEventListener("activate", (event) => {
3337
+ event.waitUntil((async () => {
3338
+ const keys = await caches.keys();
3339
+ await Promise.all(keys.filter((key) => key !== cacheName && key.startsWith("emroute")).map((key) => caches.delete(key)));
3340
+ await self.clients.claim();
3341
+ })());
3342
+ });
3343
+ self.addEventListener("fetch", (event) => {
3344
+ const url = new URL(event.request.url);
3345
+ if (url.origin !== self.location.origin)
3346
+ return;
3347
+ event.respondWith(handleFetch(event.request, url));
3348
+ });
3349
+ async function handleFetch(request, url) {
3350
+ if (request.mode === "navigate") {
3351
+ try {
3352
+ const server = await getEmroute();
3353
+ const response = await server.handleRequest(request);
3354
+ if (response)
3355
+ return response;
3356
+ } catch (e) {
3357
+ console.error("[emroute-sw] Navigation error:", e);
3358
+ }
3359
+ }
3360
+ const cached = await swRuntime.handle(url.pathname);
3361
+ if (cached.status !== 404)
3362
+ return cached;
3363
+ try {
3364
+ return await fetch(request);
3365
+ } catch {
3366
+ return new Response("Offline", { status: 503 });
3367
+ }
3368
+ }
3369
+ }
3018
3370
  // dist/src/widget/page-title.widget.js
3019
3371
  class PageTitleWidget extends WidgetComponent {
3020
3372
  name = "page-title";
@@ -3093,23 +3445,25 @@ export {
3093
3445
  scopeWidgetCss,
3094
3446
  escapeHtml,
3095
3447
  createOverlayService,
3448
+ createEmrouteSW,
3096
3449
  createEmrouteApp,
3097
3450
  bootEmrouteApp,
3098
- WidgetRegistry,
3099
3451
  WidgetComponent,
3100
3452
  RouterSlot,
3101
3453
  RouteTrie,
3102
3454
  PageTitleWidget,
3103
3455
  PageComponent,
3104
3456
  MarkdownElement,
3457
+ IdbRuntime,
3105
3458
  FetchRuntime,
3106
3459
  EmrouteApp,
3107
3460
  Emroute,
3108
3461
  DEFAULT_BASE_PATH,
3109
3462
  ComponentElement,
3110
3463
  Component,
3464
+ CacheRuntime,
3111
3465
  BreadcrumbWidget
3112
3466
  };
3113
3467
 
3114
- //# debugId=AF103AF6F8C1794764756E2164756E21
3468
+ //# debugId=FB9DE7997217939864756E2164756E21
3115
3469
  //# sourceMappingURL=emroute.js.map