From d2f95668878bf89d0aef11b3f9241c6981eee7b4 Mon Sep 17 00:00:00 2001 From: Nick Chomey Date: Wed, 29 Jan 2025 19:25:20 -0600 Subject: [PATCH 1/8] deno, node and deno-sw hello world examples --- examples/typescript/deno-sw/README.md | 9 + examples/typescript/deno-sw/build.ts | 23 + examples/typescript/deno-sw/deno.json | 7 + examples/typescript/deno-sw/deno.lock | 169 ++ examples/typescript/deno-sw/deno.ts | 36 + examples/typescript/deno-sw/hello-world.js | 39 + examples/typescript/deno-sw/service-worker.ts | 75 + examples/typescript/deno-sw/shared-router.ts | 61 + .../typescript/deno-sw/static/datastar.js | 47 + .../typescript/deno-sw/static/datastar.js.map | 7 + examples/typescript/deno-sw/static/rocket.png | Bin 0 -> 47652 bytes .../deno-sw/static/service-worker.js | 2403 +++++++++++++++++ .../typescript/deno-sw/static/tailwind.js | 926 +++++++ examples/typescript/deno/deno.ts | 40 + .../typescript/deno/public/hello-world.html | 43 + .../typescript/node}/node.js | 2 +- .../typescript/node/public/hello-world.html | 43 + sdk/typescript/examples/deno.ts | 43 - 18 files changed, 3929 insertions(+), 44 deletions(-) create mode 100644 examples/typescript/deno-sw/README.md create mode 100644 examples/typescript/deno-sw/build.ts create mode 100644 examples/typescript/deno-sw/deno.json create mode 100644 examples/typescript/deno-sw/deno.lock create mode 100644 examples/typescript/deno-sw/deno.ts create mode 100644 examples/typescript/deno-sw/hello-world.js create mode 100644 examples/typescript/deno-sw/service-worker.ts create mode 100644 examples/typescript/deno-sw/shared-router.ts create mode 100644 examples/typescript/deno-sw/static/datastar.js create mode 100644 examples/typescript/deno-sw/static/datastar.js.map create mode 100644 examples/typescript/deno-sw/static/rocket.png create mode 100644 examples/typescript/deno-sw/static/service-worker.js create mode 100644 examples/typescript/deno-sw/static/tailwind.js create mode 100644 examples/typescript/deno/deno.ts create mode 100644 examples/typescript/deno/public/hello-world.html rename {sdk/typescript/examples => examples/typescript/node}/node.js (92%) create mode 100644 examples/typescript/node/public/hello-world.html delete mode 100644 sdk/typescript/examples/deno.ts diff --git a/examples/typescript/deno-sw/README.md b/examples/typescript/deno-sw/README.md new file mode 100644 index 000000000..8b67bc89c --- /dev/null +++ b/examples/typescript/deno-sw/README.md @@ -0,0 +1,9 @@ +This is a sample app that can serve the same responses from both the Deno backend and the Service Worker while offline. + +To test it, start the server with `deno run -A deno.ts` and load the site at `http://localhost:8000`. + +The service worker will install and static assets will be cached. You can then either turn on offline mode in your browser's dev tools Network tab, or turn off the Deno server (there will be a slight delay in the response as it tries to fetch from the network first). + +You can also reload the page while offline and it will load from the service worker. + +There's many other things that can be done from a service worker - including different caching and network strategies. Google Workbox is a good resource - both as an easy-to-use library as well as just reference material for all things PWA. \ No newline at end of file diff --git a/examples/typescript/deno-sw/build.ts b/examples/typescript/deno-sw/build.ts new file mode 100644 index 000000000..43bd78210 --- /dev/null +++ b/examples/typescript/deno-sw/build.ts @@ -0,0 +1,23 @@ +import * as esbuild from "https://deno.land/x/esbuild/mod.js"; +import { denoPlugins } from "jsr:@luca/esbuild-deno-loader"; + +// Build with configuration based on MINIFY env var +await esbuild.build({ + plugins: [...denoPlugins()], + write: true, + entryPoints: ["./service-worker.ts"], + outfile: "./static/service-worker.js", + bundle: true, + minify: false, + format: "esm", + legalComments: "none", + platform: "browser", + conditions: ["worker", "browser"], + // Add TypeScript resolution + resolveExtensions: [".ts", ".js", ".mjs"], + loader: { + ".ts": "ts", + }, +}); + +esbuild.stop(); diff --git a/examples/typescript/deno-sw/deno.json b/examples/typescript/deno-sw/deno.json new file mode 100644 index 000000000..136f1464d --- /dev/null +++ b/examples/typescript/deno-sw/deno.json @@ -0,0 +1,7 @@ +{ + "imports": { + "@hono/hono": "jsr:@hono/hono@^4.6.19", + "@luca/esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.9.0" + }, + "nodeModulesDir": "auto" +} diff --git a/examples/typescript/deno-sw/deno.lock b/examples/typescript/deno-sw/deno.lock new file mode 100644 index 000000000..2378b192d --- /dev/null +++ b/examples/typescript/deno-sw/deno.lock @@ -0,0 +1,169 @@ +{ + "version": "4", + "specifiers": { + "jsr:@hono/hono@*": "4.6.19", + "jsr:@hono/hono@^4.6.19": "4.6.19", + "jsr:@luca/esbuild-deno-loader@*": "0.9.0", + "jsr:@luca/esbuild-deno-loader@0.9": "0.9.0", + "jsr:@std/assert@~0.213.1": "0.213.1", + "jsr:@std/encoding@0.213": "0.213.1", + "jsr:@std/json@~0.213.1": "0.213.1", + "jsr:@std/jsonc@0.213": "0.213.1", + "jsr:@std/path@0.213": "0.213.1", + "npm:esbuild@0.20": "0.20.2", + "npm:type-fest@*": "4.33.0" + }, + "jsr": { + "@hono/hono@4.6.19": { + "integrity": "5ba1bd0ef74449c0a647f029e29896776c30fb64aefbcef8724af4ce4846791b" + }, + "@luca/esbuild-deno-loader@0.9.0": { + "integrity": "288bbcede5c8a6f97e635f8fa4df779b13440ee0c0506d9e478fb6537789dc93", + "dependencies": [ + "jsr:@std/encoding", + "jsr:@std/jsonc", + "jsr:@std/path", + "npm:esbuild" + ] + }, + "@std/assert@0.213.1": { + "integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe" + }, + "@std/encoding@0.213.1": { + "integrity": "fcbb6928713dde941a18ca5db88ca1544d0755ec8fb20fe61e2dc8144b390c62" + }, + "@std/json@0.213.1": { + "integrity": "f572b1de605d07c4a5602445dac54bfc51b1fb87a3710a17aed2608bfca54e68" + }, + "@std/jsonc@0.213.1": { + "integrity": "5578f21aa583b7eb7317eed077ffcde47b294f1056bdbb9aacec407758637bfe", + "dependencies": [ + "jsr:@std/assert", + "jsr:@std/json" + ] + }, + "@std/path@0.213.1": { + "integrity": "f187bf278a172752e02fcbacf6bd78a335ed320d080a7ed3a5a59c3e88abc673", + "dependencies": [ + "jsr:@std/assert" + ] + } + }, + "npm": { + "@esbuild/aix-ppc64@0.20.2": { + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==" + }, + "@esbuild/android-arm64@0.20.2": { + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==" + }, + "@esbuild/android-arm@0.20.2": { + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==" + }, + "@esbuild/android-x64@0.20.2": { + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==" + }, + "@esbuild/darwin-arm64@0.20.2": { + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==" + }, + "@esbuild/darwin-x64@0.20.2": { + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==" + }, + "@esbuild/freebsd-arm64@0.20.2": { + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==" + }, + "@esbuild/freebsd-x64@0.20.2": { + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==" + }, + "@esbuild/linux-arm64@0.20.2": { + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==" + }, + "@esbuild/linux-arm@0.20.2": { + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==" + }, + "@esbuild/linux-ia32@0.20.2": { + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==" + }, + "@esbuild/linux-loong64@0.20.2": { + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==" + }, + "@esbuild/linux-mips64el@0.20.2": { + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==" + }, + "@esbuild/linux-ppc64@0.20.2": { + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==" + }, + "@esbuild/linux-riscv64@0.20.2": { + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==" + }, + "@esbuild/linux-s390x@0.20.2": { + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==" + }, + "@esbuild/linux-x64@0.20.2": { + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==" + }, + "@esbuild/netbsd-x64@0.20.2": { + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==" + }, + "@esbuild/openbsd-x64@0.20.2": { + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==" + }, + "@esbuild/sunos-x64@0.20.2": { + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==" + }, + "@esbuild/win32-arm64@0.20.2": { + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==" + }, + "@esbuild/win32-ia32@0.20.2": { + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==" + }, + "@esbuild/win32-x64@0.20.2": { + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==" + }, + "esbuild@0.20.2": { + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dependencies": [ + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-x64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" + ] + }, + "type-fest@4.33.0": { + "integrity": "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==" + } + }, + "redirects": { + "https://deno.land/x/esbuild/mod.js": "https://deno.land/x/esbuild@v0.24.2/mod.js" + }, + "remote": { + "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", + "https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4", + "https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d", + "https://deno.land/x/esbuild@v0.24.2/mod.js": "8d1e46a6494585235b0514d37743ee48a4f6f0b8e00fca9d0a2e371914b1df0e" + }, + "workspace": { + "dependencies": [ + "jsr:@hono/hono@^4.6.19", + "jsr:@luca/esbuild-deno-loader@0.9" + ] + } +} diff --git a/examples/typescript/deno-sw/deno.ts b/examples/typescript/deno-sw/deno.ts new file mode 100644 index 000000000..0caedc004 --- /dev/null +++ b/examples/typescript/deno-sw/deno.ts @@ -0,0 +1,36 @@ +import { Hono } from "jsr:@hono/hono"; +import { serveStatic } from "jsr:@hono/hono/deno"; +import { createRouter } from "./shared-router.ts"; + +// Rebuild service worker first +const buildProcess = new Deno.Command(Deno.execPath(), { + args: ["run", "-A", "--unstable-sloppy-imports", "build.js"], +}); +await buildProcess.output(); + +const app = new Hono(); + +// Middleware to log incoming requests +app.use("*", async (c, next) => { + console.log(`Incoming request: ${c.req.method} ${c.req.url}`); + await next(); +}); + + +// Serve static files from public directory after router +app.use("/static/*", serveStatic({ root: "./" })); + +// Serve the service worker from the root path +app.use("/service-worker.js", serveStatic({ path: "./static/service-worker.js" })); + +// Mount the shared router at the root path +app.route("/", createRouter()); + + +// Serve the fallback static file +// app.use("/", serveStatic({ path: "./public/hello-world.html" })); + +Deno.serve({ + port: 8000, + hostname: "127.0.0.1", +}, app.fetch); diff --git a/examples/typescript/deno-sw/hello-world.js b/examples/typescript/deno-sw/hello-world.js new file mode 100644 index 000000000..e14d48428 --- /dev/null +++ b/examples/typescript/deno-sw/hello-world.js @@ -0,0 +1,39 @@ +export function getHelloWorldHtml() { + return ` + + + Datastar SDK Demo + + + + + +
+
+

Datastar SDK Demo

+ Rocket +
+

SSE events will be streamed from the backend to the frontend.

+
+ + +
+ +
+
+
Hello, world!
+
+ +`; +} \ No newline at end of file diff --git a/examples/typescript/deno-sw/service-worker.ts b/examples/typescript/deno-sw/service-worker.ts new file mode 100644 index 000000000..e38b47124 --- /dev/null +++ b/examples/typescript/deno-sw/service-worker.ts @@ -0,0 +1,75 @@ +import { createRouter } from "./shared-router.ts"; + +declare const self: ServiceWorkerGlobalScope; + +const CACHE_NAME = 'datastar-cache-v1'; +const CORE_ASSETS = [ + '/', + '/static/tailwind.js', + '/static/datastar.js', + '/static/datastar.js.map', + '/static/rocket.png', + '/service-worker.js' // Ensure the service worker script is cached +]; + +const router = createRouter(); + +self.addEventListener('install', (event) => { + console.log('SW Install'); + event.waitUntil( + (async () => { + const cache = await caches.open(CACHE_NAME); + await Promise.all( + CORE_ASSETS.map(url => fetch(url).then(response => cache.put(url, response))) + ); + await self.skipWaiting(); + })() + ); +}); + +self.addEventListener('activate', (event) => { + console.log('SW Activate'); + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener('fetch', (event) => { + console.log('SW Fetch:', event.request.url); + + event.respondWith( + (async () => { + const cache = await caches.open(CACHE_NAME); + const url = new URL(event.request.url); + + // Try cache first for core assets + if (CORE_ASSETS.includes(url.pathname)) { + const cachedResponse = await cache.match(event.request); + if (cachedResponse) { + return cachedResponse; + } + } + + // If offline, use router for dynamic routes + if (!self.navigator.onLine) { // Use self.navigator instead of window.navigator + console.log('Browser is offline, using router fallback'); + return await router.fetch(event.request); + } + + // Try network + try { + const networkResponse = await fetch(event.request); + + // Cache successful GET requests for core assets + if (networkResponse.ok && + event.request.method === 'GET' && + CORE_ASSETS.includes(url.pathname)) { + await cache.put(event.request, networkResponse.clone()); + } + + return networkResponse; + } catch (error) { + console.log('Network request failed, using router fallback'); + return await router.fetch(event.request); + } + })() + ); +}); diff --git a/examples/typescript/deno-sw/shared-router.ts b/examples/typescript/deno-sw/shared-router.ts new file mode 100644 index 000000000..28a2203b4 --- /dev/null +++ b/examples/typescript/deno-sw/shared-router.ts @@ -0,0 +1,61 @@ +import { Hono } from "jsr:@hono/hono"; +import { ServerSentEventGenerator } from "../../../sdk/typescript/src/web/serverSentEventGenerator.ts"; +import { getHelloWorldHtml } from "./hello-world.js"; + +interface Store { + delay: number; +} + + + + + + + +export function createRouter() { + const app = new Hono(); + console.log(`Router created`); + // Middleware to log incoming requests + app.use("*", async (c, next) => { + console.log(`Incoming request - router: ${c.req.method} ${c.req.url}`); + await next(); + }); + + // Homepage route + app.get("/", async (c) => { + console.log("Handling / route"); + return c.html(getHelloWorldHtml()); + }); + + + // Hello world SSE route + app.get("/hello-world", async (c) => { + + + const reader = await ServerSentEventGenerator.readSignals(c.req); + + if (!reader.success) { + return c.text(`Error while reading signals: ${reader.error}`, 400); + } + + return ServerSentEventGenerator.stream(async (stream) => { + const message = "Hello, world!"; + + for (let i = 0; i < message.length; i++) { + stream.mergeFragments( + `
${message.substring(0, i + 1)}
`, + ); + await new Promise(resolve => setTimeout(resolve, reader.signals.delay)); + } + }); + }); + + // Catch-all route - just return 404 for unmatched routes + app.all("*", (c) => { + console.log(`Route not found: ${c.req.url}`); + return c.text(`Path not found: ${c.req.url}`, 404); + }); + + return app; +} + diff --git a/examples/typescript/deno-sw/static/datastar.js b/examples/typescript/deno-sw/static/datastar.js new file mode 100644 index 000000000..457bd2660 --- /dev/null +++ b/examples/typescript/deno-sw/static/datastar.js @@ -0,0 +1,47 @@ +// Datastar v1.0.0-beta.2 +var je = /🖕JS_DS🚀/.source, pe = je.slice(0, 5), ke = je.slice(4), L = "datastar"; var Be = "Datastar-Request", Ge = "1.0.0-beta.1", me = 300; var Ke = "type module", ge = !1, Je = !1, ze = !0, O = { Morph: "morph", Inner: "inner", Outer: "outer", Prepend: "prepend", Append: "append", Before: "before", After: "after", UpsertAttributes: "upsertAttributes" }, Xe = O.Morph, I = { MergeFragments: "datastar-merge-fragments", MergeSignals: "datastar-merge-signals", RemoveFragments: "datastar-remove-fragments", RemoveSignals: "datastar-remove-signals", ExecuteScript: "datastar-execute-script" }; var p = (r => (r[r.Attribute = 1] = "Attribute", r[r.Watcher = 2] = "Watcher", r[r.Action = 3] = "Action", r))(p || {}); var xn = "computed", Ye = { type: 1, name: xn, keyReq: 1, valReq: 1, onLoad: ({ key: t, signals: e, genRX: n }) => { let r = n(); e.setComputed(t, r); } }; var $ = t => t.trim() === "true", U = t => t.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (e, n) => (n ? "-" : "") + e.toLowerCase()), Ze = t => t.replace(/(?:^\w|[A-Z]|\b\w)/g, (e, n) => n === 0 ? e.toLowerCase() : e.toUpperCase()).replace(/\s+/g, ""), he = t => new Function(`return Object.assign({}, ${t})`)(), j = t => t.startsWith("$") ? t.slice(1) : t; var Qe = { type: 1, name: "signals", removeOnLoad: !0, onLoad: t => { let { key: e, value: n, genRX: r, signals: i, mods: s } = t, o = s.has("ifmissing"); if (e !== "" && !o) { let a = n === "" ? n : r()(); i.setValue(e, a); } else { let a = he(t.value); t.value = JSON.stringify(a); let u = r()(); i.merge(u, o); } } }; var et = { type: 1, name: "star", keyReq: 2, valReq: 2, onLoad: () => { alert("YOU ARE PROBABLY OVERCOMPLICATING IT"); } }; var ie = class { #e = 0; #t; constructor(e = L) { this.#t = e; } with(e) { if (typeof e == "string") for (let n of e.split("")) this.with(n.charCodeAt(0)); else this.#e = (this.#e << 5) - this.#e + e; return this; } reset() { return this.#e = 0, this; } get value() { return this.#t + Math.abs(this.#e).toString(36); } }; function tt(t) { if (t.id) return t.id; let e = new ie, n = t; for (; n.parentNode;) { if (n.id) { e.with(n.id); break; } if (n === n.ownerDocument.documentElement) e.with(n.tagName); else { for (let r = 1, i = t; i.previousElementSibling; i = i.previousElementSibling, r++)e.with(r); n = n.parentNode; } n = n.parentNode; } return e.value; } function nt(t, e) { let n = new MutationObserver(r => { for (let i of r) for (let s of i.removedNodes) if (s === t) { n.disconnect(), e(); return; } }); n.observe(t.parentNode, { childList: !0 }); } var Pn = `${window.location.origin}/errors`; function De(t, e, n = {}) { + let r = new Error; e = e[0].toUpperCase() + e.slice(1), r.name = `${L} ${t} error`; let i = U(e).replaceAll("-", "_"), s = new URLSearchParams({ metadata: JSON.stringify(n) }).toString(), o = JSON.stringify(n, null, 2); return r.message = `${e} +More info: ${Pn}/${t}/${i}?${s} +Context: ${o}`, r; +} function P(t, e, n = {}) { return De("internal", e, Object.assign({ from: t }, n)); } function V(t, e, n = {}) { let r = { plugin: { name: e.plugin.name, type: p[e.plugin.type] } }; return De("init", t, Object.assign(r, n)); } function h(t, e, n = {}) { let r = { plugin: { name: e.plugin.name, type: p[e.plugin.type] }, element: { id: e.el.id, tag: e.el.tagName }, expression: { rawKey: e.rawKey, key: e.key, value: e.value, validSignals: e.signals.paths(), fnContent: e.fnContent } }; return De("runtime", t, Object.assign(r, n)); } var G = "preact-signals", Nn = Symbol.for("preact-signals"), F = 1, X = 2, se = 4, Z = 8, ye = 16, Y = 32; function Oe() { ve++; } function Ve() { if (ve > 1) { ve--; return; } let t, e = !1; for (; oe !== void 0;) { let n = oe; for (oe = void 0, Le++; n !== void 0;) { let r = n._nextBatchedEffect; if (n._nextBatchedEffect = void 0, n._flags &= ~X, !(n._flags & Z) && it(n)) try { n._callback(); } catch (i) { e || (t = i, e = !0); } n = r; } } if (Le = 0, ve--, e) throw P(G, "BatchError, error", { error: t }); } var v; var oe, ve = 0, Le = 0, be = 0; function rt(t) { if (v === void 0) return; let e = t._node; if (e === void 0 || e._target !== v) return e = { _version: 0, _source: t, _prevSource: v._sources, _nextSource: void 0, _target: v, _prevTarget: void 0, _nextTarget: void 0, _rollbackNode: e }, v._sources !== void 0 && (v._sources._nextSource = e), v._sources = e, t._node = e, v._flags & Y && t._subscribe(e), e; if (e._version === -1) return e._version = 0, e._nextSource !== void 0 && (e._nextSource._prevSource = e._prevSource, e._prevSource !== void 0 && (e._prevSource._nextSource = e._nextSource), e._prevSource = v._sources, e._nextSource = void 0, v._sources._nextSource = e, v._sources = e), e; } function R(t) { this._value = t, this._version = 0, this._node = void 0, this._targets = void 0; } R.prototype.brand = Nn; R.prototype._refresh = () => !0; R.prototype._subscribe = function (t) { this._targets !== t && t._prevTarget === void 0 && (t._nextTarget = this._targets, this._targets !== void 0 && (this._targets._prevTarget = t), this._targets = t); }; R.prototype._unsubscribe = function (t) { if (this._targets !== void 0) { let e = t._prevTarget, n = t._nextTarget; e !== void 0 && (e._nextTarget = n, t._prevTarget = void 0), n !== void 0 && (n._prevTarget = e, t._nextTarget = void 0), t === this._targets && (this._targets = n); } }; R.prototype.subscribe = function (t) { return Se(() => { let e = this.value, n = v; v = void 0; try { t(e); } finally { v = n; } }); }; R.prototype.valueOf = function () { return this.value; }; R.prototype.toString = function () { return `${this.value}`; }; R.prototype.toJSON = function () { return this.value; }; R.prototype.peek = function () { let t = v; v = void 0; try { return this.value; } finally { v = t; } }; Object.defineProperty(R.prototype, "value", { get() { let t = rt(this); return t !== void 0 && (t._version = this._version), this._value; }, set(t) { if (t !== this._value) { if (Le > 100) throw P(G, "SignalCycleDetected"); this._value = t, this._version++, be++, Oe(); try { for (let e = this._targets; e !== void 0; e = e._nextTarget)e._target._notify(); } finally { Ve(); } } } }); function it(t) { for (let e = t._sources; e !== void 0; e = e._nextSource)if (e._source._version !== e._version || !e._source._refresh() || e._source._version !== e._version) return !0; return !1; } function ot(t) { for (let e = t._sources; e !== void 0; e = e._nextSource) { let n = e._source._node; if (n !== void 0 && (e._rollbackNode = n), e._source._node = e, e._version = -1, e._nextSource === void 0) { t._sources = e; break; } } } function st(t) { let e = t._sources, n; for (; e !== void 0;) { let r = e._prevSource; e._version === -1 ? (e._source._unsubscribe(e), r !== void 0 && (r._nextSource = e._nextSource), e._nextSource !== void 0 && (e._nextSource._prevSource = r)) : n = e, e._source._node = e._rollbackNode, e._rollbackNode !== void 0 && (e._rollbackNode = void 0), e = r; } t._sources = n; } function K(t) { R.call(this, void 0), this._fn = t, this._sources = void 0, this._globalVersion = be - 1, this._flags = se; } K.prototype = new R; K.prototype._refresh = function () { if (this._flags &= ~X, this._flags & F) return !1; if ((this._flags & (se | Y)) === Y || (this._flags &= ~se, this._globalVersion === be)) return !0; if (this._globalVersion = be, this._flags |= F, this._version > 0 && !it(this)) return this._flags &= ~F, !0; let t = v; try { ot(this), v = this; let e = this._fn(); (this._flags & ye || this._value !== e || this._version === 0) && (this._value = e, this._flags &= ~ye, this._version++); } catch (e) { this._value = e, this._flags |= ye, this._version++; } return v = t, st(this), this._flags &= ~F, !0; }; K.prototype._subscribe = function (t) { if (this._targets === void 0) { this._flags |= se | Y; for (let e = this._sources; e !== void 0; e = e._nextSource)e._source._subscribe(e); } R.prototype._subscribe.call(this, t); }; K.prototype._unsubscribe = function (t) { if (this._targets !== void 0 && (R.prototype._unsubscribe.call(this, t), this._targets === void 0)) { this._flags &= ~Y; for (let e = this._sources; e !== void 0; e = e._nextSource)e._source._unsubscribe(e); } }; K.prototype._notify = function () { if (!(this._flags & X)) { this._flags |= se | X; for (let t = this._targets; t !== void 0; t = t._nextTarget)t._target._notify(); } }; Object.defineProperty(K.prototype, "value", { get() { if (this._flags & F) throw P(G, "SignalCycleDetected"); let t = rt(this); if (this._refresh(), t !== void 0 && (t._version = this._version), this._flags & ye) throw P(G, "GetComputedError", { value: this._value }); return this._value; } }); function at(t) { return new K(t); } function lt(t) { let e = t._cleanup; if (t._cleanup = void 0, typeof e == "function") { Oe(); let n = v; v = void 0; try { e(); } catch (r) { throw t._flags &= ~F, t._flags |= Z, Fe(t), P(G, "CleanupEffectError", { error: r }); } finally { v = n, Ve(); } } } function Fe(t) { for (let e = t._sources; e !== void 0; e = e._nextSource)e._source._unsubscribe(e); t._fn = void 0, t._sources = void 0, lt(t); } function Mn(t) { if (v !== this) throw P(G, "EndEffectError"); st(this), v = t, this._flags &= ~F, this._flags & Z && Fe(this), Ve(); } function ae(t) { this._fn = t, this._cleanup = void 0, this._sources = void 0, this._nextBatchedEffect = void 0, this._flags = Y; } ae.prototype._callback = function () { let t = this._start(); try { if (this._flags & Z || this._fn === void 0) return; let e = this._fn(); typeof e == "function" && (this._cleanup = e); } finally { t(); } }; ae.prototype._start = function () { if (this._flags & F) throw P(G, "SignalCycleDetected"); this._flags |= F, this._flags &= ~Z, lt(this), ot(this), Oe(); let t = v; return v = this, Mn.bind(this, t); }; ae.prototype._notify = function () { this._flags & X || (this._flags |= X, this._nextBatchedEffect = oe, oe = this); }; ae.prototype._dispose = function () { this._flags |= Z, this._flags & F || Fe(this); }; function Se(t) { let e = new ae(t); try { e._callback(); } catch (n) { throw e._dispose(), n; } return e._dispose.bind(e); } var ut = "namespacedSignals"; function ct(t, e = !1) { let n = {}; for (let r in t) if (Object.hasOwn(t, r)) { if (e && r.startsWith("_")) continue; let i = t[r]; i instanceof R ? n[r] = i.value : n[r] = ct(i); } return n; } function ft(t, e, n = !1) { for (let r in e) if (Object.hasOwn(e, r)) { if (r.match(/\_\_+/)) throw P(ut, "InvalidSignalKey", { key: r }); let i = e[r]; if (i instanceof Object && !Array.isArray(i)) t[r] || (t[r] = {}), ft(t[r], i, n); else { if (Object.hasOwn(t, r)) { if (n) continue; let o = t[r]; if (o instanceof R) { o.value = i; continue; } } t[r] = new R(i); } } } function dt(t, e) { for (let n in t) if (Object.hasOwn(t, n)) { let r = t[n]; r instanceof R ? e(n, r) : dt(r, (i, s) => { e(`${n}.${i}`, s); }); } } function Cn(t, ...e) { let n = {}; for (let r of e) { let i = r.split("."), s = t, o = n; for (let l = 0; l < i.length - 1; l++) { let u = i[l]; if (!s[u]) return {}; o[u] || (o[u] = {}), s = s[u], o = o[u]; } let a = i[i.length - 1]; o[a] = s[a]; } return n; } var Ee = class { #e = {}; exists(e) { return !!this.signal(e); } signal(e) { let n = e.split("."), r = this.#e; for (let o = 0; o < n.length - 1; o++) { let a = n[o]; if (!r[a]) return null; r = r[a]; } let i = n[n.length - 1], s = r[i]; if (!s) throw P(ut, "SignalNotFound", { path: e }); return s; } setSignal(e, n) { let r = e.split("."), i = this.#e; for (let o = 0; o < r.length - 1; o++) { let a = r[o]; i[a] || (i[a] = {}), i = i[a]; } let s = r[r.length - 1]; i[s] = n; } setComputed(e, n) { let r = at(() => n()); this.setSignal(e, r); } value(e) { return this.signal(e)?.value; } setValue(e, n) { let r = this.upsertIfMissing(e, n); r.value = n; } upsertIfMissing(e, n) { let r = e.split("."), i = this.#e; for (let l = 0; l < r.length - 1; l++) { let u = r[l]; i[u] || (i[u] = {}), i = i[u]; } let s = r[r.length - 1], o = i[s]; if (o instanceof R) return o; let a = new R(n); return i[s] = a, a; } remove(...e) { for (let n of e) { let r = n.split("."), i = this.#e; for (let o = 0; o < r.length - 1; o++) { let a = r[o]; if (!i[a]) return; i = i[a]; } let s = r[r.length - 1]; delete i[s]; } } merge(e, n = !1) { ft(this.#e, e, n); } subset(...e) { return Cn(this.values(), ...e); } walk(e) { dt(this.#e, e); } paths() { let e = new Array; return this.walk(n => e.push(n)), e; } values(e = !1) { return ct(this.#e, e); } JSON(e = !0, n = !1) { let r = this.values(n); return e ? JSON.stringify(r, null, 2) : JSON.stringify(r); } toString() { return this.JSON(); } }; var Te = class { + #e = new Ee; #t = []; #r = {}; #s = []; #n = new Map; get signals() { return this.#e; } get version() { return Ge; } load(...e) { for (let n of e) { let r = this, i = { get signals() { return r.#e; }, effect: o => Se(o), actions: this.#r, apply: this.apply.bind(this), cleanup: this.#i.bind(this), plugin: n }, s; switch (n.type) { case 2: { let o = n; this.#s.push(o), s = o.onGlobalInit; break; } case 3: { this.#r[n.name] = n; break; } case 1: { let o = n; this.#t.push(o), s = o.onGlobalInit; break; } default: throw V("InvalidPluginType", i); }s && s(i); } this.#t.sort((n, r) => { let i = r.name.length - n.name.length; return i !== 0 ? i : n.name.localeCompare(r.name); }); } apply(e) { this.#o(e, n => { this.#i(n); for (let r of Object.keys(n.dataset)) { let i = this.#t.find(f => r.startsWith(f.name)); if (!i) continue; n.id.length || (n.id = tt(n)); let [s, ...o] = r.slice(i.name.length).split(/\_\_+/), a = s.length > 0; if (a) { let f = s.slice(1); s = s.startsWith("-") ? f : s[0].toLowerCase() + f; } let l = `${n.dataset[r]}` || "", u = l.length > 0, c = this, d = { get signals() { return c.#e; }, effect: f => Se(f), apply: this.apply.bind(this), cleanup: this.#i.bind(this), actions: this.#r, genRX: () => this.#a(d, ...i.argNames || []), plugin: i, el: n, rawKey: r, key: s, value: l, mods: new Map }, y = i.keyReq || 0; if (a) { if (y === 2) throw h(`${i.name}KeyNotAllowed`, d); } else if (y === 1) throw h(`${i.name}KeyRequired`, d); let S = i.valReq || 0; if (u) { if (S === 2) throw h(`${i.name}ValueNotAllowed`, d); } else if (S === 1) throw h(`${i.name}ValueRequired`, d); if (y === 3 || S === 3) { if (a && u) throw h(`${i.name}KeyAndValueProvided`, d); if (!a && !u) throw h(`${i.name}KeyOrValueRequired`, d); } for (let f of o) { let [E, ...w] = f.split("."); d.mods.set(Ze(E), new Set(w.map(C => C.toLowerCase()))); } let m = i.onLoad(d); m && (this.#n.has(n) || this.#n.set(n, { id: n.id, fns: [] }), this.#n.get(n)?.fns.push(m)), i?.removeOnLoad && delete n.dataset[r]; } }); } #a(e, ...n) { + let r = e.value.split(/;|\n/).map(f => f.trim()).filter(f => f !== ""), i = r.length - 1; r[i].startsWith("return") || (r[i] = `return (${r[i]});`); let o = r.join(`; +`).trim(), a = new Map, l = new RegExp(`(?:${pe})(.*?)(?:${ke})`, "gm"); for (let f of o.matchAll(l)) { let E = f[1], w = new ie("dsEscaped").with(E).value; a.set(w, E), o = o.replace(pe + E + ke, w); } let u = /@(\w*)\(/gm, c = o.matchAll(u), d = new Set; for (let f of c) d.add(f[1]); let y = new RegExp(`@(${Object.keys(this.#r).join("|")})\\(`, "gm"); o = o.replaceAll(y, "ctx.actions.$1.fn(ctx,"); let S = e.signals.paths(); if (S.length) { let f = new RegExp(`\\$(${S.join("|")})(\\W|$)`, "gm"); o = o.replaceAll(f, "ctx.signals.signal('$1').value$2"); } for (let [f, E] of a) o = o.replace(f, E); let m = `return (()=> { +${o} +})()`; e.fnContent = m; try { let f = new Function("ctx", ...n, m); return (...E) => { try { return f(e, ...E); } catch (w) { throw h("ExecuteExpression", e, { error: w.message }); } }; } catch (f) { throw h("GenerateExpression", e, { error: f.message }); } + } #o(e, n) { if (!e || !(e instanceof HTMLElement || e instanceof SVGElement)) return null; let r = e.dataset; if ("starIgnore" in r) return null; "starIgnore__self" in r || n(e); let i = e.firstElementChild; for (; i;)this.#o(i, n), i = i.nextElementSibling; } #i(e) { let n = this.#n.get(e); if (n) { for (let r of n.fns) r(); this.#n.delete(e); } } +}; var pt = new Te; pt.load(et, Qe, Ye); var Ae = pt; async function In(t, e) { let n = t.getReader(), r; for (; !(r = await n.read()).done;)e(r.value); } function kn(t) { let e, n, r, i = !1; return function (o) { e === void 0 ? (e = o, n = 0, r = -1) : e = Ln(e, o); let a = e.length, l = 0; for (; n < a;) { i && (e[n] === 10 && (l = ++n), i = !1); let u = -1; for (; n < a && u === -1; ++n)switch (e[n]) { case 58: r === -1 && (r = n - l); break; case 13: i = !0; case 10: u = n; break; }if (u === -1) break; t(e.subarray(l, u), r), l = n, r = -1; } l === a ? e = void 0 : l !== 0 && (e = e.subarray(l), n -= l); }; } function Dn(t, e, n) { + let r = mt(), i = new TextDecoder; return function (o, a) { + if (o.length === 0) n?.(r), r = mt(); else if (a > 0) { + let l = i.decode(o.subarray(0, a)), u = a + (o[a + 1] === 32 ? 2 : 1), c = i.decode(o.subarray(u)); switch (l) { + case "data": r.data = r.data ? `${r.data} +${c}` : c; break; case "event": r.event = c; break; case "id": t(r.id = c); break; case "retry": { let d = Number.parseInt(c, 10); Number.isNaN(d) || e(r.retry = d); break; } + } + } + }; +} function Ln(t, e) { let n = new Uint8Array(t.length + e.length); return n.set(t), n.set(e, t.length), n; } function mt() { return { data: "", event: "", id: "", retry: void 0 }; } var On = "text/event-stream", gt = "last-event-id"; function ht(t, e, { signal: n, headers: r, onopen: i, onmessage: s, onclose: o, onerror: a, openWhenHidden: l, fetch: u, retryInterval: c = 1e3, retryScaler: d = 2, retryMaxWaitMs: y = 3e4, retryMaxCount: S = 10, ...m }) { return new Promise((f, E) => { let w = 0, C = { ...r }; C.accept || (C.accept = On); let A; function T() { A.abort(), document.hidden || D(); } l || document.addEventListener("visibilitychange", T); let b = 0; function x() { document.removeEventListener("visibilitychange", T), window.clearTimeout(b), A.abort(); } n?.addEventListener("abort", () => { x(), f(); }); let N = u ?? window.fetch, g = i ?? function () { }; async function D() { A = new AbortController; try { let M = await N(e, { ...m, headers: C, signal: A.signal }); await g(M), await In(M.body, kn(Dn(_ => { _ ? C[gt] = _ : delete C[gt]; }, _ => { c = _; }, s))), o?.(), x(), f(); } catch (M) { if (!A.signal.aborted) try { let _ = a?.(M) ?? c; window.clearTimeout(b), b = window.setTimeout(D, _), c *= d, c = Math.min(c, y), w++, w >= S ? (x(), E(h("SseMaxRetries", t, { retryMaxCount: S }))) : console.error(`Datastar failed to reach ${m.method}: ${e.toString()} retry in ${_}ms`); } catch (_) { x(), E(_); } } } D(); }); } var Q = `${L}-sse`, He = `${L}-settling`, J = `${L}-swapping`, _e = "started", we = "finished", yt = "error"; function H(t, e) { document.addEventListener(Q, n => { if (n.detail.type !== t) return; let { argsRaw: r } = n.detail; e(r); }); } function Re(t, e) { document.dispatchEvent(new CustomEvent(Q, { detail: { type: t, argsRaw: e } })); } var vt = t => `${t}`.includes("text/event-stream"), q = async (t, e, n, r) => { + let { el: { id: i }, el: s, signals: o } = t, { headers: a, contentType: l, includeLocal: u, selector: c, openWhenHidden: d, retryInterval: y, retryScaler: S, retryMaxWaitMs: m, retryMaxCount: f, abort: E } = Object.assign({ headers: {}, contentType: "json", includeLocal: !1, selector: null, openWhenHidden: !1, retryInterval: 1e3, retryScaler: 2, retryMaxWaitMs: 3e4, retryMaxCount: 10, abort: void 0 }, r), w = e.toLowerCase(), C = () => { }; try { + if (Re(_e, { elId: i }), !n?.length) throw h("SseNoUrlProvided", t, { action: w }); let A = {}; A[Be] = !0, l === "json" && (A["Content-Type"] = "application/json"); let T = Object.assign({}, A, a), b = { + method: e, headers: T, openWhenHidden: d, retryInterval: y, retryScaler: S, retryMaxWaitMs: m, retryMaxCount: f, signal: E, onopen: async g => { if (g.status >= 400) { let D = g.status.toString(); Re(yt, { status: D }); } }, onmessage: g => { + if (!g.event.startsWith(L)) return; let D = g.event, M = {}, _ = g.data.split(` +`); for (let re of _) { let fe = re.indexOf(" "), Ue = re.slice(0, fe), de = M[Ue]; de || (de = [], M[Ue] = de); let Rn = re.slice(fe + 1).trim(); de.push(Rn); } let W = {}; for (let [re, fe] of Object.entries(M)) W[re] = fe.join(` +`); Re(D, W); + }, onerror: g => { if (vt(g)) throw h("InvalidContentType", t, { url: n }); g && console.error(g.message); } + }, x = new URL(n, window.location.origin), N = new URLSearchParams(x.search); if (l === "json") { let g = o.JSON(!1, !u); e === "GET" ? N.set(L, g) : b.body = g; } else if (l === "form") { let g = c ? document.querySelector(c) : s.closest("form"); if (g === null) throw c ? h("SseFormNotFound", t, { action: w, selector: c }) : h("SseClosestFormNotFound", t, { action: w }); if (s !== g) { let M = _ => _.preventDefault(); g.addEventListener("submit", M), C = () => g.removeEventListener("submit", M); } if (!g.checkValidity()) { g.reportValidity(), C(); return; } let D = new FormData(g); if (e === "GET") { let M = new URLSearchParams(D); for (let [_, W] of M) N.set(_, W); } else b.body = D; } else throw h("SseInvalidContentType", t, { action: w, contentType: l }); x.search = N.toString(); try { await ht(t, x.toString(), b); } catch (g) { if (!vt(g)) throw h("SseFetchFailed", t, { method: e, url: n, error: g }); } + } finally { Re(we, { elId: i }), C(); } +}; var bt = { type: 3, name: "delete", fn: async (t, e, n) => q(t, "DELETE", e, { ...n }) }; var St = { type: 3, name: "get", fn: async (t, e, n) => q(t, "GET", e, { ...n }) }; var Et = { type: 3, name: "patch", fn: async (t, e, n) => q(t, "PATCH", e, { ...n }) }; var Tt = { type: 3, name: "post", fn: async (t, e, n) => q(t, "POST", e, { ...n }) }; var At = { type: 3, name: "put", fn: async (t, e, n) => q(t, "PUT", e, { ...n }) }; var _t = { type: 1, name: "indicator", keyReq: 3, valReq: 3, onLoad: ({ value: t, signals: e, el: n, key: r }) => { let i = r || j(t), s = e.upsertIfMissing(i, !1), o = a => { let { type: l, argsRaw: { elId: u } } = a.detail; if (u === n.id) switch (l) { case _e: s.value = !0; break; case we: s.value = !1; break; } }; return document.addEventListener(Q, o), () => { document.removeEventListener(Q, o); }; } }; var wt = { + type: 2, name: I.ExecuteScript, onGlobalInit: async t => { + H(I.ExecuteScript, ({ autoRemove: e = `${ze}`, attributes: n = Ke, script: r }) => { + let i = $(e); if (!r?.length) throw V("NoScriptProvided", t); let s = document.createElement("script"); for (let o of n.split(` +`)) { let a = o.indexOf(" "), l = a ? o.slice(0, a) : o, u = a ? o.slice(a) : ""; s.setAttribute(l.trim(), u.trim()); } s.text = r, document.head.appendChild(s), i && s.remove(); + }); + } +}; var le = document, ee = !!le.startViewTransition; var te = "idiomorph", Pe = new WeakSet; function Nt(t, e, n = {}) { t instanceof Document && (t = t.documentElement); let r; typeof e == "string" ? r = Wn(e) : r = e; let i = $n(r), s = Fn(t, i, n); return Mt(t, i, s); } function Mt(t, e, n) { if (n.head.block) { let r = t.querySelector("head"), i = e.querySelector("head"); if (r && i) { let s = It(i, r, n); Promise.all(s).then(() => { Mt(t, e, Object.assign(n, { head: { block: !1, ignore: !0 } })); }); return; } } if (n.morphStyle === "innerHTML") return Ct(e, t, n), t.children; if (n.morphStyle === "outerHTML" || n.morphStyle == null) { let r = jn(e, t, n); if (!r) throw P(te, "NoBestMatchFound", { old: t, new: e }); let i = r?.previousSibling, s = r?.nextSibling, o = Ne(t, r, n); return r ? Un(i, o, s) : []; } throw P(te, "InvalidMorphStyle", { style: n.morphStyle }); } function Ne(t, e, n) { if (!(n.ignoreActive && t === document.activeElement)) if (e == null) { if (n.callbacks.beforeNodeRemoved(t) === !1) return; t.remove(), n.callbacks.afterNodeRemoved(t); return; } else { if (Me(t, e)) return n.callbacks.beforeNodeMorphed(t, e) === !1 ? void 0 : (t instanceof HTMLHeadElement && n.head.ignore || (e instanceof HTMLHeadElement && t instanceof HTMLHeadElement && n.head.style !== O.Morph ? It(e, t, n) : (Vn(e, t), Ct(e, t, n))), n.callbacks.afterNodeMorphed(t, e), t); if (n.callbacks.beforeNodeRemoved(t) === !1 || n.callbacks.beforeNodeAdded(e) === !1) return; if (!t.parentElement) throw P(te, "NoParentElementFound", { oldNode: t }); return t.parentElement.replaceChild(e, t), n.callbacks.afterNodeAdded(e), n.callbacks.afterNodeRemoved(t), e; } } function Ct(t, e, n) { let r = t.firstChild, i = e.firstChild, s; for (; r;) { if (s = r, r = s.nextSibling, i == null) { if (n.callbacks.beforeNodeAdded(s) === !1) return; e.appendChild(s), n.callbacks.afterNodeAdded(s), z(n, s); continue; } if (kt(s, i, n)) { Ne(i, s, n), i = i.nextSibling, z(n, s); continue; } let o = Hn(t, e, s, i, n); if (o) { i = Rt(i, o, n), Ne(o, s, n), z(n, s); continue; } let a = qn(t, s, i, n); if (a) { i = Rt(i, a, n), Ne(a, s, n), z(n, s); continue; } if (n.callbacks.beforeNodeAdded(s) === !1) return; e.insertBefore(s, i), n.callbacks.afterNodeAdded(s), z(n, s); } for (; i !== null;) { let o = i; i = i.nextSibling, Dt(o, n); } } function Vn(t, e) { let n = t.nodeType; if (n === 1) { for (let r of t.attributes) e.getAttribute(r.name) !== r.value && e.setAttribute(r.name, r.value); for (let r of e.attributes) t.hasAttribute(r.name) || e.removeAttribute(r.name); } if ((n === Node.COMMENT_NODE || n === Node.TEXT_NODE) && e.nodeValue !== t.nodeValue && (e.nodeValue = t.nodeValue), t instanceof HTMLInputElement && e instanceof HTMLInputElement && t.type !== "file") e.value = t.value || "", xe(t, e, "value"), xe(t, e, "checked"), xe(t, e, "disabled"); else if (t instanceof HTMLOptionElement) xe(t, e, "selected"); else if (t instanceof HTMLTextAreaElement && e instanceof HTMLTextAreaElement) { let r = t.value, i = e.value; r !== i && (e.value = r), e.firstChild && e.firstChild.nodeValue !== r && (e.firstChild.nodeValue = r); } } function xe(t, e, n) { let r = t.getAttribute(n), i = e.getAttribute(n); r !== i && (r ? e.setAttribute(n, r) : e.removeAttribute(n)); } function It(t, e, n) { let r = [], i = [], s = [], o = [], a = n.head.style, l = new Map; for (let c of t.children) l.set(c.outerHTML, c); for (let c of e.children) { let d = l.has(c.outerHTML), y = n.head.shouldReAppend(c), S = n.head.shouldPreserve(c); d || S ? y ? i.push(c) : (l.delete(c.outerHTML), s.push(c)) : a === O.Append ? y && (i.push(c), o.push(c)) : n.head.shouldRemove(c) !== !1 && i.push(c); } o.push(...l.values()); let u = []; for (let c of o) { let d = document.createRange().createContextualFragment(c.outerHTML).firstChild; if (!d) throw P(te, "NewElementCouldNotBeCreated", { newNode: c }); if (n.callbacks.beforeNodeAdded(d)) { if (d.hasAttribute("href") || d.hasAttribute("src")) { let y, S = new Promise(m => { y = m; }); d.addEventListener("load", () => { y(void 0); }), u.push(S); } e.appendChild(d), n.callbacks.afterNodeAdded(d), r.push(d); } } for (let c of i) n.callbacks.beforeNodeRemoved(c) !== !1 && (e.removeChild(c), n.callbacks.afterNodeRemoved(c)); return n.head.afterHeadMorphed(e, { added: r, kept: s, removed: i }), u; } function B() { } function Fn(t, e, n) { return { target: t, newContent: e, config: n, morphStyle: n.morphStyle, ignoreActive: n.ignoreActive, idMap: Jn(t, e), deadIds: new Set, callbacks: Object.assign({ beforeNodeAdded: B, afterNodeAdded: B, beforeNodeMorphed: B, afterNodeMorphed: B, beforeNodeRemoved: B, afterNodeRemoved: B }, n.callbacks), head: Object.assign({ style: "merge", shouldPreserve: r => r.getAttribute("im-preserve") === "true", shouldReAppend: r => r.getAttribute("im-re-append") === "true", shouldRemove: B, afterHeadMorphed: B }, n.head) }; } function kt(t, e, n) { return !t || !e ? !1 : t.nodeType === e.nodeType && t.tagName === e.tagName ? t?.id?.length && t.id === e.id ? !0 : ue(n, t, e) > 0 : !1; } function Me(t, e) { return !t || !e ? !1 : t.nodeType === e.nodeType && t.tagName === e.tagName; } function Rt(t, e, n) { for (; t !== e;) { let r = t; if (t = t?.nextSibling, !r) throw P(te, "NoTemporaryNodeFound", { startInclusive: t, endExclusive: e }); Dt(r, n); } return z(n, e), e.nextSibling; } function Hn(t, e, n, r, i) { let s = ue(i, n, e), o = null; if (s > 0) { o = r; let a = 0; for (; o != null;) { if (kt(n, o, i)) return o; if (a += ue(i, o, t), a > s) return null; o = o.nextSibling; } } return o; } function qn(t, e, n, r) { let i = n, s = e.nextSibling, o = 0; for (; i && s;) { if (ue(r, i, t) > 0) return null; if (Me(e, i)) return i; if (Me(s, i) && (o++, s = s.nextSibling, o >= 2)) return null; i = i.nextSibling; } return i; } var xt = new DOMParser; function Wn(t) { let e = t.replace(/]*>|>)([\s\S]*?)<\/svg>/gim, ""); if (e.match(/<\/html>/) || e.match(/<\/head>/) || e.match(/<\/body>/)) { let i = xt.parseFromString(t, "text/html"); if (e.match(/<\/html>/)) return Pe.add(i), i; let s = i.firstChild; return s ? (Pe.add(s), s) : null; } let r = xt.parseFromString(``, "text/html").body.querySelector("template")?.content; if (!r) throw P(te, "NoContentFound", { newContent: t }); return Pe.add(r), r; } function $n(t) { if (t == null) return document.createElement("div"); if (Pe.has(t)) return t; if (t instanceof Node) { let n = document.createElement("div"); return n.append(t), n; } let e = document.createElement("div"); for (let n of [...t]) e.append(n); return e; } function Un(t, e, n) { let r = [], i = []; for (; t;)r.push(t), t = t.previousSibling; for (; r.length > 0;) { let s = r.pop(); i.push(s), e?.parentElement?.insertBefore(s, e); } for (i.push(e); n;)r.push(n), i.push(n), n = n.nextSibling; for (; r.length;)e?.parentElement?.insertBefore(r.pop(), e.nextSibling); return i; } function jn(t, e, n) { let r = t.firstChild, i = r, s = 0; for (; r;) { let o = Bn(r, e, n); o > s && (i = r, s = o), r = r.nextSibling; } return i; } function Bn(t, e, n) { return Me(t, e) ? .5 + ue(n, t, e) : 0; } function Dt(t, e) { z(e, t), e.callbacks.beforeNodeRemoved(t) !== !1 && (t.remove(), e.callbacks.afterNodeRemoved(t)); } function Gn(t, e) { return !t.deadIds.has(e); } function Kn(t, e, n) { return t.idMap.get(n)?.has(e) || !1; } function z(t, e) { let n = t.idMap.get(e); if (n) for (let r of n) t.deadIds.add(r); } function ue(t, e, n) { let r = t.idMap.get(e); if (!r) return 0; let i = 0; for (let s of r) Gn(t, s) && Kn(t, s, n) && ++i; return i; } function Pt(t, e) { let n = t.parentElement, r = t.querySelectorAll("[id]"); for (let i of r) { let s = i; for (; s !== n && s;) { let o = e.get(s); o == null && (o = new Set, e.set(s, o)), o.add(i.id), s = s.parentElement; } } } function Jn(t, e) { let n = new Map; return Pt(t, n), Pt(e, n), n; } var Ot = { type: 2, name: I.MergeFragments, onGlobalInit: async t => { let e = document.createElement("template"); H(I.MergeFragments, ({ fragments: n = "
", selector: r = "", mergeMode: i = Xe, settleDuration: s = `${me}`, useViewTransition: o = `${ge}` }) => { let a = Number.parseInt(s), l = $(o); e.innerHTML = n.trim(); let u = [...e.content.children]; for (let c of u) { if (!(c instanceof Element)) throw V("NoFragmentsFound", t); let d = r || `#${c.getAttribute("id")}`, y = [...document.querySelectorAll(d) || []]; if (!y.length) throw V("NoTargetsFound", t, { selectorOrID: d }); ee && l ? le.startViewTransition(() => Lt(t, i, a, c, y)) : Lt(t, i, a, c, y); } }); } }; function Lt(t, e, n, r, i) { for (let s of i) { s.classList.add(J); let o = s.outerHTML, a = s; switch (e) { case O.Morph: { let c = Nt(a, r, { callbacks: { beforeNodeRemoved: (d, y) => (t.cleanup(d), !0) } }); if (!c?.length) throw V("MorphFailed", t); a = c[0]; break; } case O.Inner: a.innerHTML = r.innerHTML; break; case O.Outer: a.replaceWith(r); break; case O.Prepend: a.prepend(r); break; case O.Append: a.append(r); break; case O.Before: a.before(r); break; case O.After: a.after(r); break; case O.UpsertAttributes: for (let c of r.getAttributeNames()) { let d = r.getAttribute(c); a.setAttribute(c, d); } break; default: throw V("InvalidMergeMode", t, { mergeMode: e }); }t.cleanup(a); let l = a.classList; l.add(J), t.apply(document.body), setTimeout(() => { s.classList.remove(J), l.remove(J); }, n); let u = a.outerHTML; o !== u && (l.add(He), setTimeout(() => { l.remove(He); }, n)); } } var Vt = { type: 2, name: I.MergeSignals, onGlobalInit: async t => { H(I.MergeSignals, ({ signals: e = "{}", onlyIfMissing: n = `${Je}` }) => { let { signals: r } = t, i = $(n); r.merge(he(e), i), t.apply(document.body); }); } }; var Ft = { type: 2, name: I.RemoveFragments, onGlobalInit: async t => { H(I.RemoveFragments, ({ selector: e, settleDuration: n = `${me}`, useViewTransition: r = `${ge}` }) => { if (!e.length) throw V("NoSelectorProvided", t); let i = Number.parseInt(n), s = $(r), o = document.querySelectorAll(e), a = () => { for (let l of o) l.classList.add(J); setTimeout(() => { for (let l of o) l.remove(); }, i); }; ee && s ? le.startViewTransition(() => a()) : a(); }); } }; var Ht = { + type: 2, name: I.RemoveSignals, onGlobalInit: async t => { + H(I.RemoveSignals, ({ paths: e = "" }) => { + let n = e.split(` +`).map(r => r.trim()); if (!n?.length) throw V("NoPathsProvided", t); t.signals.remove(...n), t.apply(document.body); + }); + } +}; var qt = { type: 3, name: "clipboard", fn: (t, e) => { if (!navigator.clipboard) throw h("ClipboardNotAvailable", t); navigator.clipboard.writeText(e); } }; var Wt = { type: 1, name: "customValidity", keyReq: 2, valReq: 1, onLoad: t => { let { el: e, genRX: n, effect: r } = t; if (!(e instanceof HTMLInputElement)) throw h("CustomValidityInvalidElement", t); let i = n(); return r(() => { let s = i(); if (typeof s != "string") throw h("CustomValidityInvalidExpression", t, { result: s }); e.setCustomValidity(s); }); } }; var $t = "once", Ut = "half", jt = "full", Bt = { type: 1, name: "intersects", keyReq: 2, mods: new Set([$t, Ut, jt]), onLoad: ({ el: t, rawKey: e, mods: n, genRX: r }) => { let i = { threshold: 0 }; n.has(jt) ? i.threshold = 1 : n.has(Ut) && (i.threshold = .5); let s = r(), o = new IntersectionObserver(a => { for (let l of a) l.isIntersecting && (s(), n.has($t) && (o.disconnect(), delete t.dataset[e])); }, i); return o.observe(t), () => o.disconnect(); } }; var Gt = "session", Kt = { type: 1, name: "persist", mods: new Set([Gt]), onLoad: ({ key: t, value: e, signals: n, effect: r, mods: i }) => { t === "" && (t = L); let s = i.has(Gt) ? sessionStorage : localStorage, o = e.split(/\s+/).filter(u => u !== ""); o = o.map(u => j(u)); let a = () => { let u = s.getItem(t) || "{}", c = JSON.parse(u); n.merge(c); }, l = () => { let u; o.length ? u = n.subset(...o) : u = n.values(), s.setItem(t, JSON.stringify(u)); }; return a(), r(() => { l(); }); } }; var Jt = { type: 1, name: "replaceUrl", keyReq: 2, valReq: 1, onLoad: ({ effect: t, genRX: e }) => { let n = e(); return t(() => { let r = n(), i = window.location.href, s = new URL(r, i).toString(); window.history.replaceState({}, "", s); }); } }; var Ce = "smooth", qe = "instant", We = "auto", zt = "hstart", Xt = "hcenter", Yt = "hend", Zt = "hnearest", Qt = "vstart", en = "vcenter", tn = "vend", nn = "vnearest", zn = "focus", Ie = "center", rn = "start", on = "end", sn = "nearest", an = { type: 1, name: "scrollIntoView", keyReq: 2, valReq: 2, mods: new Set([Ce, qe, We, zt, Xt, Yt, Zt, Qt, en, tn, nn, zn]), onLoad: t => { let { el: e, mods: n, rawKey: r } = t; e.tabIndex || e.setAttribute("tabindex", "0"); let i = { behavior: Ce, block: Ie, inline: Ie }; if (n.has(Ce) && (i.behavior = Ce), n.has(qe) && (i.behavior = qe), n.has(We) && (i.behavior = We), n.has(zt) && (i.inline = rn), n.has(Xt) && (i.inline = Ie), n.has(Yt) && (i.inline = on), n.has(Zt) && (i.inline = sn), n.has(Qt) && (i.block = rn), n.has(en) && (i.block = Ie), n.has(tn) && (i.block = on), n.has(nn) && (i.block = sn), !(e instanceof HTMLElement || e instanceof SVGElement)) throw h("ScrollIntoViewInvalidElement", t); return e.tabIndex || e.setAttribute("tabindex", "0"), e.scrollIntoView(i), n.has("focus") && e.focus(), delete e.dataset[r], () => { }; } }; var ln = "none", un = "display", cn = { type: 1, name: "show", keyReq: 2, valReq: 1, onLoad: ({ el: { style: t }, genRX: e, effect: n }) => { let r = e(); return n(async () => { r() ? t.display === ln && t.removeProperty(un) : t.setProperty(un, ln); }); } }; var fn = "view-transition", dn = { type: 1, name: "viewTransition", keyReq: 2, valReq: 1, onGlobalInit() { let t = !1; for (let e of document.head.childNodes) e instanceof HTMLMetaElement && e.name === fn && (t = !0); if (!t) { let e = document.createElement("meta"); e.name = fn, e.content = "same-origin", document.head.appendChild(e); } }, onLoad: ({ effect: t, el: e, genRX: n }) => { if (!ee) { console.error("Browser does not support view transitions"); return; } let r = n(); return t(() => { let i = r(); if (!i?.length) return; let s = e.style; s.viewTransitionName = i; }); } }; var pn = { type: 1, name: "attr", valReq: 1, onLoad: ({ el: t, genRX: e, key: n, effect: r }) => { let i = e(); return n === "" ? r(async () => { let s = i(); for (let [o, a] of Object.entries(s)) t.setAttribute(o, a); }) : (n = U(n), r(async () => { let s = !1; try { s = i(); } catch { } let o; typeof s == "string" ? o = s : o = JSON.stringify(s), !o || o === "false" || o === "null" || o === "undefined" ? t.removeAttribute(n) : t.setAttribute(n, o); })); } }; var Xn = /^data:(?[^;]+);base64,(?.*)$/, mn = ["change", "input", "keydown"], gn = { type: 1, name: "bind", keyReq: 3, valReq: 3, onLoad: t => { let { el: e, value: n, key: r, signals: i, effect: s } = t, o = r || j(n), a = () => { }, l = () => { }, u = e.tagName.toLowerCase(), c = "", d = u.includes("input"), y = e.getAttribute("type"), S = u.includes("checkbox") || d && y === "checkbox"; S && (c = !1), d && y === "number" && (c = 0); let f = u.includes("select"), E = u.includes("radio") || d && y === "radio", w = d && y === "file"; E && (e.getAttribute("name")?.length || e.setAttribute("name", o)), i.upsertIfMissing(o, c), a = () => { let A = "value" in e, T = i.value(o), b = `${T}`; if (S || E) { let x = e; S ? x.checked = !!T || T === "true" : E && (x.checked = b === x.value); } else if (!w) if (f) { let x = e; if (x.multiple) for (let N of x.options) { if (N?.disabled) return; Array.isArray(T) || typeof T == "string" ? N.selected = T.includes(N.value) : typeof T == "number" ? N.selected = T === Number(N.value) : N.selected = T; } else x.value = b; } else A ? e.value = b : e.setAttribute("value", b); }, l = async () => { if (w) { let b = [...e?.files || []], x = [], N = [], g = []; await Promise.all(b.map(D => new Promise(M => { let _ = new FileReader; _.onload = () => { if (typeof _.result != "string") throw h("InvalidFileResultType", t, { resultType: typeof _.result }); let W = _.result.match(Xn); if (!W?.groups) throw h("InvalidDataUri", t, { result: _.result }); x.push(W.groups.contents), N.push(W.groups.mime), g.push(D.name); }, _.onloadend = () => M(void 0), _.readAsDataURL(D); }))), i.setValue(o, x), i.setValue(`${o}Mimes`, N), i.setValue(`${o}Names`, g); return; } let A = i.value(o), T = e || e; if (typeof A == "number") { let b = Number(T.value || T.getAttribute("value")); i.setValue(o, b); } else if (typeof A == "string") { let b = T.value || T.getAttribute("value") || ""; i.setValue(o, b); } else if (typeof A == "boolean") if (S) { let b = T.checked || T.getAttribute("checked") === "true"; i.setValue(o, b); } else { let b = !!(T.value || T.getAttribute("value")); i.setValue(o, b); } else if (!(typeof A > "u")) if (Array.isArray(A)) if (f) { let N = [...e.selectedOptions].filter(g => g.selected).map(g => g.value); i.setValue(o, N); } else { let b = JSON.stringify(T.value.split(",")); i.setValue(o, b); } else throw h("BindUnsupportedSignalType", t, { signalType: typeof A }); }; for (let A of mn) e.addEventListener(A, l); let C = s(() => a()); return () => { C(); for (let A of mn) e.removeEventListener(A, l); }; } }; var hn = { type: 1, name: "class", valReq: 1, onLoad: ({ key: t, el: e, genRX: n, effect: r }) => { let i = e.classList, s = n(); return r(() => { if (t === "") { let o = s(); for (let [a, l] of Object.entries(o)) { let u = a.split(/\s+/); l ? i.add(...u) : i.remove(...u); } } else { let o = s(), a = U(t); o ? i.add(a) : i.remove(a); } }); } }; function ce(t) { if (!t || t.size <= 0) return 0; for (let e of t) { if (e.endsWith("ms")) return Number(e.replace("ms", "")); if (e.endsWith("s")) return Number(e.replace("s", "")) * 1e3; try { return Number.parseFloat(e); } catch { } } return 0; } function ne(t, e, n = !1) { return t ? t.has(e.toLowerCase()) : n; } function yn(t, e, n = !1) { return function (...i) { n ? t(...i) : setTimeout(() => { t(...i); }, e); }; } function vn(t, e, n = !1, r = !0) { let i = -1, s = () => i && clearTimeout(i); return function (...a) { s(), n && !i && t(...a), i = setTimeout(() => { r && t(...a), s(); }, e); }; } function bn(t, e, n = !0, r = !1) { let i = !1; return function (...o) { i || (n && t(...o), i = !0, setTimeout(() => { i = !1, r && t(...o); }, e)); }; } var $e = new Map, Yn = "evt", Sn = { type: 1, name: "on", keyReq: 1, valReq: 1, argNames: [Yn], onLoad: ({ el: t, key: e, genRX: n, mods: r, signals: i, effect: s }) => { let o = n(), a = t; r.has("window") && (a = window); let l = m => { m && ((r.has("prevent") || e === "submit") && m.preventDefault(), r.has("stop") && m.stopPropagation()), o(m); }, u = r.get("delay"); if (u) { let m = ce(u), f = ne(u, "leading", !1); l = yn(l, m, f); } let c = r.get("debounce"); if (c) { let m = ce(c), f = ne(c, "leading", !1), E = !ne(c, "notrail", !1); l = vn(l, m, f, E); } let d = r.get("throttle"); if (d) { let m = ce(d), f = !ne(d, "noleading", !1), E = ne(d, "trail", !1); l = bn(l, m, f, E); } let y = { capture: !0, passive: !1, once: !1 }; r.has("capture") || (y.capture = !1), r.has("passive") && (y.passive = !0), r.has("once") && (y.once = !0); let S = U(e).toLowerCase(); switch (S) { case "load": return l(), delete t.dataset.onLoad, () => { }; case "interval": { let m = 1e3; u && (m = ce(u), l()); let f = setInterval(l, m); return () => { clearInterval(f); }; } case "raf": { let m, f = () => { l(), m = requestAnimationFrame(f); }; return m = requestAnimationFrame(f), () => { m && cancelAnimationFrame(m); }; } case "signals-change": return nt(t, () => { $e.delete(t.id); }), s(() => { let m = r.has("remote"), f = i.JSON(!1, m); ($e.get(t.id) || "") !== f && ($e.set(t.id, f), l()); }); default: { if (r.has("outside")) { a = document; let f = l; l = w => { let C = w?.target; t.contains(C) || f(w); }; } return a.addEventListener(S, l, y), () => { a.removeEventListener(S, l); }; } } } }; var En = { type: 1, name: "ref", keyReq: 3, valReq: 3, onLoad: ({ el: t, key: e, value: n, signals: r }) => { let i = e || j(n); return r.setValue(i, t), () => r.setValue(i, null); } }; var Tn = { type: 1, name: "text", keyReq: 2, valReq: 1, onLoad: t => { let { el: e, genRX: n, effect: r } = t, i = n(); return e instanceof HTMLElement || h("TextInvalidElement", t), r(() => { let s = i(t); e.textContent = `${s}`; }); } }; var { round: Zn, max: Qn, min: er } = Math, An = { type: 3, name: "fit", fn: (t, e, n, r, i, s, o = !1, a = !1) => { let l = (e - n) / (r - n) * (s - i) + i; return a && (l = Zn(l)), o && (l = Qn(i, er(s, l))), l; } }; var _n = { type: 3, name: "setAll", fn: ({ signals: t }, e, n) => { t.walk((r, i) => { r.startsWith(e) && (i.value = n); }); } }; var wn = { type: 3, name: "toggleAll", fn: ({ signals: t }, e) => { t.walk((n, r) => { n.startsWith(e) && (r.value = !r.value); }); } }; Ae.load(pn, gn, hn, Sn, En, cn, Tn, _t, St, Tt, At, Et, bt, Ot, Vt, Ft, Ht, wt, qt, Wt, Bt, Kt, Jt, an, dn, An, _n, wn); Ae.apply(document.body); var bs = Ae; export { bs as Datastar }; +//# sourceMappingURL=datastar.js.map \ No newline at end of file diff --git a/examples/typescript/deno-sw/static/datastar.js.map b/examples/typescript/deno-sw/static/datastar.js.map new file mode 100644 index 000000000..725a6b1e7 --- /dev/null +++ b/examples/typescript/deno-sw/static/datastar.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../library/src/engine/consts.ts", "../library/src/engine/types.ts", "../library/src/plugins/official/core/attributes/computed.ts", "../library/src/utils/text.ts", "../library/src/plugins/official/core/attributes/signals.ts", "../library/src/plugins/official/core/attributes/star.ts", "../library/src/utils/dom.ts", "../library/src/engine/errors.ts", "../library/src/vendored/preact-core.ts", "../library/src/engine/signals.ts", "../library/src/engine/engine.ts", "../library/src/engine/index.ts", "../library/src/vendored/fetch-event-source.ts", "../library/src/plugins/official/backend/shared.ts", "../library/src/plugins/official/backend/actions/sse.ts", "../library/src/plugins/official/backend/actions/delete.ts", "../library/src/plugins/official/backend/actions/get.ts", "../library/src/plugins/official/backend/actions/patch.ts", "../library/src/plugins/official/backend/actions/post.ts", "../library/src/plugins/official/backend/actions/put.ts", "../library/src/plugins/official/backend/attributes/indicator.ts", "../library/src/plugins/official/backend/watchers/executeScript.ts", "../library/src/utils/view-transtions.ts", "../library/src/vendored/idiomorph.ts", "../library/src/plugins/official/backend/watchers/mergeFragments.ts", "../library/src/plugins/official/backend/watchers/mergeSignals.ts", "../library/src/plugins/official/backend/watchers/removeFragments.ts", "../library/src/plugins/official/backend/watchers/removeSignals.ts", "../library/src/plugins/official/browser/actions/clipboard.ts", "../library/src/plugins/official/browser/attributes/customValidity.ts", "../library/src/plugins/official/browser/attributes/intersects.ts", "../library/src/plugins/official/browser/attributes/persist.ts", "../library/src/plugins/official/browser/attributes/replaceUrl.ts", "../library/src/plugins/official/browser/attributes/scrollIntoView.ts", "../library/src/plugins/official/browser/attributes/show.ts", "../library/src/plugins/official/browser/attributes/viewTransition.ts", "../library/src/plugins/official/dom/attributes/attr.ts", "../library/src/plugins/official/dom/attributes/bind.ts", "../library/src/plugins/official/dom/attributes/class.ts", "../library/src/utils/tags.ts", "../library/src/utils/timing.ts", "../library/src/plugins/official/dom/attributes/on.ts", "../library/src/plugins/official/dom/attributes/ref.ts", "../library/src/plugins/official/dom/attributes/text.ts", "../library/src/plugins/official/logic/actions/fit.ts", "../library/src/plugins/official/logic/actions/setAll.ts", "../library/src/plugins/official/logic/actions/toggleAll.ts", "../library/src/bundles/datastar.ts"], + "sourcesContent": ["// This is auto-generated by Datastar. DO NOT EDIT.\nconst lol = /\uD83D\uDD95JS_DS\uD83D\uDE80/.source\nexport const DSP = lol.slice(0, 5)\nexport const DSS = lol.slice(4)\n\nexport const DATASTAR = \"datastar\";\nexport const DATASTAR_EVENT = \"datastar-event\";\nexport const DATASTAR_REQUEST = \"Datastar-Request\";\nexport const VERSION = \"1.0.0-beta.1\";\n\n// #region Defaults\n\n// #region Default durations\n\n// The default duration for settling during fragment merges. Allows for CSS transitions to complete.\nexport const DefaultFragmentsSettleDurationMs = 300;\n// The default duration for retrying SSE on connection reset. This is part of the underlying retry mechanism of SSE.\nexport const DefaultSseRetryDurationMs = 1000;\n\n// #endregion\n\n\n// #region Default strings\n\n// The default attributes for + + + + +
+
+

+ Datastar SDK Demo +

+ Rocket +
+

+ SSE events will be streamed from the backend to the frontend. +

+
+ + +
+ +
+
+
Hello, world!
+
+ + + \ No newline at end of file diff --git a/sdk/typescript/examples/node.js b/examples/typescript/node/node.js similarity index 92% rename from sdk/typescript/examples/node.js rename to examples/typescript/node/node.js index 6dc10105a..fe5f7f879 100644 --- a/sdk/typescript/examples/node.js +++ b/examples/typescript/node/node.js @@ -1,6 +1,6 @@ import { createServer } from "node:http"; // for this to work the esm build needs to be generated, see ../README.md -import { ServerSentEventGenerator } from "../npm/esm/node/serverSentEventGenerator.js"; +import { ServerSentEventGenerator } from "../../../sdk/typescript/npm/esm/node/serverSentEventGenerator.js"; const hostname = "127.0.0.1"; const port = 3000; diff --git a/examples/typescript/node/public/hello-world.html b/examples/typescript/node/public/hello-world.html new file mode 100644 index 000000000..6f403b8dd --- /dev/null +++ b/examples/typescript/node/public/hello-world.html @@ -0,0 +1,43 @@ + + + + + + + Datastar SDK Demo + + + + + +
+
+

+ Datastar SDK Demo +

+ Rocket +
+

+ SSE events will be streamed from the backend to the frontend. +

+
+ + +
+ +
+
+
Hello, world!
+
+ + + \ No newline at end of file diff --git a/sdk/typescript/examples/deno.ts b/sdk/typescript/examples/deno.ts deleted file mode 100644 index 4eb91e45d..000000000 --- a/sdk/typescript/examples/deno.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { serve } from "https://deno.land/std@0.140.0/http/server.ts"; -import { ServerSentEventGenerator } from "../src/web/serverSentEventGenerator.ts"; - -serve(async (req: Request) => { - const url = new URL(req.url); - - if (url.pathname === "/") { - return new Response( - `
Hello
`, - { - headers: { "Content-Type": "text/html" }, - }, - ); - } else if (url.pathname.includes("/merge")) { - const reader = await ServerSentEventGenerator.readSignals(req); - - if (!reader.success) { - console.error("Error while reading signals", reader.error); - - return new Response(`Error while reading signals`, { - headers: { "Content-Type": "text/html" }, - }); - } - - if (!("foo" in reader.signals)) { - console.error("The foo signal is not present"); - - return new Response("The foo signal is not present", { - headers: { "Content-Type": "text/html" }, - }); - } - - return ServerSentEventGenerator.stream((stream) => { - stream.mergeFragments( - `
Hello ${reader.signals.foo}
`, - ); - }); - } - - return new Response(`Path not found: ${req.url}`, { - headers: { "Content-Type": "text/html" }, - }); -}); From 537f64547bf242c7246d1447ac75217ce3a3e96a Mon Sep 17 00:00:00 2001 From: Nick Chomey Date: Wed, 29 Jan 2025 19:33:40 -0600 Subject: [PATCH 2/8] change to hono/tiny, tweak Readme --- examples/typescript/deno-sw/README.md | 6 +- examples/typescript/deno-sw/deno.ts | 2 +- examples/typescript/deno-sw/shared-router.ts | 2 +- .../deno-sw/static/service-worker.js | 692 +----------------- 4 files changed, 38 insertions(+), 664 deletions(-) diff --git a/examples/typescript/deno-sw/README.md b/examples/typescript/deno-sw/README.md index 8b67bc89c..2a08dbd8c 100644 --- a/examples/typescript/deno-sw/README.md +++ b/examples/typescript/deno-sw/README.md @@ -2,8 +2,6 @@ This is a sample app that can serve the same responses from both the Deno backen To test it, start the server with `deno run -A deno.ts` and load the site at `http://localhost:8000`. -The service worker will install and static assets will be cached. You can then either turn on offline mode in your browser's dev tools Network tab, or turn off the Deno server (there will be a slight delay in the response as it tries to fetch from the network first). +The service worker will install and static assets will be cached. You can then either turn on offline mode in your browser's dev tools Network tab, or turn off the Deno server (there will be a slight delay in the response as it tries to fetch from the network first). Reload the page while offline and it will load from the service worker! And the SSE response when you click Start will be rendered from the SW as well. -You can also reload the page while offline and it will load from the service worker. - -There's many other things that can be done from a service worker - including different caching and network strategies. Google Workbox is a good resource - both as an easy-to-use library as well as just reference material for all things PWA. \ No newline at end of file +There's many other things that can be done from a service worker - including different caching and network strategies. [Google Workbox](https://developer.chrome.com/docs/workbox) is a good resource - both as an easy-to-use library as well as just reference material for all things PWA. \ No newline at end of file diff --git a/examples/typescript/deno-sw/deno.ts b/examples/typescript/deno-sw/deno.ts index 0caedc004..ea76816c5 100644 --- a/examples/typescript/deno-sw/deno.ts +++ b/examples/typescript/deno-sw/deno.ts @@ -1,4 +1,4 @@ -import { Hono } from "jsr:@hono/hono"; +import { Hono } from "jsr:@hono/hono/tiny"; import { serveStatic } from "jsr:@hono/hono/deno"; import { createRouter } from "./shared-router.ts"; diff --git a/examples/typescript/deno-sw/shared-router.ts b/examples/typescript/deno-sw/shared-router.ts index 28a2203b4..5208349ce 100644 --- a/examples/typescript/deno-sw/shared-router.ts +++ b/examples/typescript/deno-sw/shared-router.ts @@ -1,4 +1,4 @@ -import { Hono } from "jsr:@hono/hono"; +import { Hono } from "jsr:@hono/hono/tiny"; import { ServerSentEventGenerator } from "../../../sdk/typescript/src/web/serverSentEventGenerator.ts"; import { getHelloWorldHtml } from "./hello-world.js"; diff --git a/examples/typescript/deno-sw/static/service-worker.js b/examples/typescript/deno-sw/static/service-worker.js index 824d09052..96f270e3d 100644 --- a/examples/typescript/deno-sw/static/service-worker.js +++ b/examples/typescript/deno-sw/static/service-worker.js @@ -64,57 +64,6 @@ var handleParsingNestedValues = (form, key, value) => { }; // https://jsr.io/@hono/hono/4.6.19/src/utils/url.ts -var splitPath = (path) => { - const paths = path.split("/"); - if (paths[0] === "") { - paths.shift(); - } - return paths; -}; -var splitRoutingPath = (routePath) => { - const { groups, path } = extractGroupsFromPath(routePath); - const paths = splitPath(path); - return replaceGroupMarks(paths, groups); -}; -var extractGroupsFromPath = (path) => { - const groups = []; - path = path.replace(/\{[^}]+\}/g, (match, index) => { - const mark = `@${index}`; - groups.push([mark, match]); - return mark; - }); - return { groups, path }; -}; -var replaceGroupMarks = (paths, groups) => { - for (let i = groups.length - 1; i >= 0; i--) { - const [mark] = groups[i]; - for (let j = paths.length - 1; j >= 0; j--) { - if (paths[j].includes(mark)) { - paths[j] = paths[j].replace(mark, groups[i][1]); - break; - } - } - } - return paths; -}; -var patternCache = {}; -var getPattern = (label) => { - if (label === "*") { - return "*"; - } - const match = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/); - if (match) { - if (!patternCache[label]) { - if (match[2]) { - patternCache[label] = [label, match[1], new RegExp("^" + match[2] + "$")]; - } else { - patternCache[label] = [label, match[1], true]; - } - } - return patternCache[label]; - } - return null; -}; var tryDecode = (str, decoder) => { try { return decoder(str); @@ -171,33 +120,6 @@ var mergePath = (...paths) => { } return p; }; -var checkOptionalParameter = (path) => { - if (!path.match(/\:.+\?$/)) { - return null; - } - const segments = path.split("/"); - const results = []; - let basePath = ""; - segments.forEach((segment) => { - if (segment !== "" && !/\:/.test(segment)) { - basePath += "/" + segment; - } else if (/\:/.test(segment)) { - if (/\?/.test(segment)) { - if (results.length === 0 && basePath === "") { - results.push("/"); - } else { - results.push(basePath); - } - const optionalSegment = segment.replace("?", ""); - basePath += "/" + optionalSegment; - results.push(basePath); - } else { - basePath += "/" + segment; - } - } - }); - return results.filter((v, i, a) => a.indexOf(v) === i); -}; var _decodeURI = (value) => { if (!/[%+]/.test(value)) { return value; @@ -1105,7 +1027,6 @@ var compose = (middleware, onError, onNotFound) => { var METHOD_NAME_ALL = "ALL"; var METHOD_NAME_ALL_LOWERCASE = "all"; var METHODS = ["get", "post", "put", "delete", "options", "patch"]; -var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is already built."; var UnsupportedPathError = class extends Error { }; @@ -1467,599 +1388,54 @@ var Hono = class _Hono { }; }; -// https://jsr.io/@hono/hono/4.6.19/src/router/reg-exp-router/node.ts -var LABEL_REG_EXP_STR = "[^/]+"; -var ONLY_WILDCARD_REG_EXP_STR = ".*"; -var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)"; -var PATH_ERROR = Symbol(); -var regExpMetaChars = new Set(".\\+*[^]$()"); -function compareKey(a, b) { - if (a.length === 1) { - return b.length === 1 ? a < b ? -1 : 1 : -1; - } - if (b.length === 1) { - return 1; - } - if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) { - return 1; - } else if (b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR) { - return -1; - } - if (a === LABEL_REG_EXP_STR) { - return 1; - } else if (b === LABEL_REG_EXP_STR) { - return -1; - } - return a.length === b.length ? a < b ? -1 : 1 : b.length - a.length; -} -var Node = class _Node { - #index; - #varIndex; - #children = /* @__PURE__ */ Object.create(null); - insert(tokens, index, paramMap, context, pathErrorCheckOnly) { - if (tokens.length === 0) { - if (this.#index !== void 0) { - throw PATH_ERROR; - } - if (pathErrorCheckOnly) { - return; - } - this.#index = index; - return; - } - const [token, ...restTokens] = tokens; - const pattern = token === "*" ? restTokens.length === 0 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/); - let node; - if (pattern) { - const name = pattern[1]; - let regexpStr = pattern[2] || LABEL_REG_EXP_STR; - if (name && pattern[2]) { - regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:"); - if (/\((?!\?:)/.test(regexpStr)) { - throw PATH_ERROR; - } - } - node = this.#children[regexpStr]; - if (!node) { - if (Object.keys(this.#children).some( - (k) => k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR - )) { - throw PATH_ERROR; - } - if (pathErrorCheckOnly) { - return; - } - node = this.#children[regexpStr] = new _Node(); - if (name !== "") { - node.#varIndex = context.varIndex++; - } - } - if (!pathErrorCheckOnly && name !== "") { - paramMap.push([name, node.#varIndex]); - } - } else { - node = this.#children[token]; - if (!node) { - if (Object.keys(this.#children).some( - (k) => k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR - )) { - throw PATH_ERROR; - } - if (pathErrorCheckOnly) { - return; - } - node = this.#children[token] = new _Node(); - } - } - node.insert(restTokens, index, paramMap, context, pathErrorCheckOnly); - } - buildRegExpStr() { - const childKeys = Object.keys(this.#children).sort(compareKey); - const strList = childKeys.map((k) => { - const c = this.#children[k]; - return (typeof c.#varIndex === "number" ? `(${k})@${c.#varIndex}` : regExpMetaChars.has(k) ? `\\${k}` : k) + c.buildRegExpStr(); - }); - if (typeof this.#index === "number") { - strList.unshift(`#${this.#index}`); - } - if (strList.length === 0) { - return ""; - } - if (strList.length === 1) { - return strList[0]; - } - return "(?:" + strList.join("|") + ")"; - } -}; - -// https://jsr.io/@hono/hono/4.6.19/src/router/reg-exp-router/trie.ts -var Trie = class { - #context = { varIndex: 0 }; - #root = new Node(); - insert(path, index, pathErrorCheckOnly) { - const paramAssoc = []; - const groups = []; - for (let i = 0; ; ) { - let replaced = false; - path = path.replace(/\{[^}]+\}/g, (m) => { - const mark = `@\\${i}`; - groups[i] = [mark, m]; - i++; - replaced = true; - return mark; - }); - if (!replaced) { - break; - } - } - const tokens = path.match(/(?::[^\/]+)|(?:\/\*$)|./g) || []; - for (let i = groups.length - 1; i >= 0; i--) { - const [mark] = groups[i]; - for (let j = tokens.length - 1; j >= 0; j--) { - if (tokens[j].indexOf(mark) !== -1) { - tokens[j] = tokens[j].replace(mark, groups[i][1]); - break; - } - } - } - this.#root.insert(tokens, index, paramAssoc, this.#context, pathErrorCheckOnly); - return paramAssoc; - } - buildRegExp() { - let regexp = this.#root.buildRegExpStr(); - if (regexp === "") { - return [/^$/, [], []]; - } - let captureIndex = 0; - const indexReplacementMap = []; - const paramReplacementMap = []; - regexp = regexp.replace(/#(\d+)|@(\d+)|\.\*\$/g, (_, handlerIndex, paramIndex) => { - if (handlerIndex !== void 0) { - indexReplacementMap[++captureIndex] = Number(handlerIndex); - return "$()"; - } - if (paramIndex !== void 0) { - paramReplacementMap[Number(paramIndex)] = ++captureIndex; - return ""; - } - return ""; - }); - return [new RegExp(`^${regexp}`), indexReplacementMap, paramReplacementMap]; - } -}; - -// https://jsr.io/@hono/hono/4.6.19/src/router/reg-exp-router/router.ts -var emptyParam = []; -var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)]; -var wildcardRegExpCache = /* @__PURE__ */ Object.create(null); -function buildWildcardRegExp(path) { - return wildcardRegExpCache[path] ??= new RegExp( - path === "*" ? "" : `^${path.replace( - /\/\*$|([.\\+*[^\]$()])/g, - (_, metaChar) => metaChar ? `\\${metaChar}` : "(?:|/.*)" - )}$` - ); -} -function clearWildcardRegExpCache() { - wildcardRegExpCache = /* @__PURE__ */ Object.create(null); -} -function buildMatcherFromPreprocessedRoutes(routes) { - const trie = new Trie(); - const handlerData = []; - if (routes.length === 0) { - return nullMatcher; - } - const routesWithStaticPathFlag = routes.map( - (route) => [!/\*|\/:/.test(route[0]), ...route] - ).sort( - ([isStaticA, pathA], [isStaticB, pathB]) => isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length - ); - const staticMap = /* @__PURE__ */ Object.create(null); - for (let i = 0, j = -1, len = routesWithStaticPathFlag.length; i < len; i++) { - const [pathErrorCheckOnly, path, handlers] = routesWithStaticPathFlag[i]; - if (pathErrorCheckOnly) { - staticMap[path] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam]; - } else { - j++; - } - let paramAssoc; - try { - paramAssoc = trie.insert(path, j, pathErrorCheckOnly); - } catch (e) { - throw e === PATH_ERROR ? new UnsupportedPathError(path) : e; - } - if (pathErrorCheckOnly) { - continue; - } - handlerData[j] = handlers.map(([h, paramCount]) => { - const paramIndexMap = /* @__PURE__ */ Object.create(null); - paramCount -= 1; - for (; paramCount >= 0; paramCount--) { - const [key, value] = paramAssoc[paramCount]; - paramIndexMap[key] = value; - } - return [h, paramIndexMap]; - }); - } - const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp(); - for (let i = 0, len = handlerData.length; i < len; i++) { - for (let j = 0, len2 = handlerData[i].length; j < len2; j++) { - const map = handlerData[i][j]?.[1]; - if (!map) { - continue; - } - const keys = Object.keys(map); - for (let k = 0, len3 = keys.length; k < len3; k++) { - map[keys[k]] = paramReplacementMap[map[keys[k]]]; - } - } - } - const handlerMap = []; - for (const i in indexReplacementMap) { - handlerMap[i] = handlerData[indexReplacementMap[i]]; - } - return [regexp, handlerMap, staticMap]; -} -function findMiddleware(middleware, path) { - if (!middleware) { - return void 0; - } - for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) { - if (buildWildcardRegExp(k).test(path)) { - return [...middleware[k]]; - } - } - return void 0; -} -var RegExpRouter = class { - name = "RegExpRouter"; - #middleware; - #routes; - constructor() { - this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) }; - this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) }; - } - add(method, path, handler) { - const middleware = this.#middleware; - const routes = this.#routes; - if (!middleware || !routes) { - throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT); - } - if (!middleware[method]) { - ; - [middleware, routes].forEach((handlerMap) => { - handlerMap[method] = /* @__PURE__ */ Object.create(null); - Object.keys(handlerMap[METHOD_NAME_ALL]).forEach((p) => { - handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]]; - }); - }); - } - if (path === "/*") { - path = "*"; - } - const paramCount = (path.match(/\/:/g) || []).length; - if (/\*$/.test(path)) { - const re = buildWildcardRegExp(path); - if (method === METHOD_NAME_ALL) { - Object.keys(middleware).forEach((m) => { - middleware[m][path] ||= findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || []; - }); - } else { - middleware[method][path] ||= findMiddleware(middleware[method], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || []; - } - Object.keys(middleware).forEach((m) => { - if (method === METHOD_NAME_ALL || method === m) { - Object.keys(middleware[m]).forEach((p) => { - re.test(p) && middleware[m][p].push([handler, paramCount]); - }); - } - }); - Object.keys(routes).forEach((m) => { - if (method === METHOD_NAME_ALL || method === m) { - Object.keys(routes[m]).forEach( - (p) => re.test(p) && routes[m][p].push([handler, paramCount]) - ); - } - }); - return; - } - const paths = checkOptionalParameter(path) || [path]; - for (let i = 0, len = paths.length; i < len; i++) { - const path2 = paths[i]; - Object.keys(routes).forEach((m) => { - if (method === METHOD_NAME_ALL || method === m) { - routes[m][path2] ||= [ - ...findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || [] - ]; - routes[m][path2].push([handler, paramCount - len + i + 1]); - } - }); - } - } - match(method, path) { - clearWildcardRegExpCache(); - const matchers = this.#buildAllMatchers(); - this.match = (method2, path2) => { - const matcher = matchers[method2] || matchers[METHOD_NAME_ALL]; - const staticMatch = matcher[2][path2]; - if (staticMatch) { - return staticMatch; - } - const match = path2.match(matcher[0]); - if (!match) { - return [[], emptyParam]; - } - const index = match.indexOf("", 1); - return [matcher[1][index], match]; - }; - return this.match(method, path); - } - #buildAllMatchers() { - const matchers = /* @__PURE__ */ Object.create(null); - Object.keys(this.#routes).concat(Object.keys(this.#middleware)).forEach((method) => { - matchers[method] ||= this.#buildMatcher(method); - }); - this.#middleware = this.#routes = void 0; - return matchers; - } - #buildMatcher(method) { - const routes = []; - let hasOwnRoute = method === METHOD_NAME_ALL; - [this.#middleware, this.#routes].forEach((r) => { - const ownRoute = r[method] ? Object.keys(r[method]).map((path) => [path, r[method][path]]) : []; - if (ownRoute.length !== 0) { - hasOwnRoute ||= true; - routes.push(...ownRoute); - } else if (method !== METHOD_NAME_ALL) { - routes.push( - ...Object.keys(r[METHOD_NAME_ALL]).map((path) => [path, r[METHOD_NAME_ALL][path]]) - ); - } - }); - if (!hasOwnRoute) { - return null; - } else { - return buildMatcherFromPreprocessedRoutes(routes); - } - } -}; - -// https://jsr.io/@hono/hono/4.6.19/src/router/smart-router/router.ts -var SmartRouter = class { - name = "SmartRouter"; - #routers = []; +// https://jsr.io/@hono/hono/4.6.19/src/router/pattern-router/router.ts +var emptyParams = /* @__PURE__ */ Object.create(null); +var PatternRouter = class { + name = "PatternRouter"; #routes = []; - constructor(init) { - this.#routers = init.routers; - } add(method, path, handler) { - if (!this.#routes) { - throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT); - } - this.#routes.push([method, path, handler]); - } - match(method, path) { - if (!this.#routes) { - throw new Error("Fatal error"); - } - const routers = this.#routers; - const routes = this.#routes; - const len = routers.length; - let i = 0; - let res; - for (; i < len; i++) { - const router2 = routers[i]; - try { - for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) { - router2.add(...routes[i2]); - } - res = router2.match(method, path); - } catch (e) { - if (e instanceof UnsupportedPathError) { - continue; - } - throw e; + const endsWithWildcard = path.at(-1) === "*"; + if (endsWithWildcard) { + path = path.slice(0, -2); + } + if (path.at(-1) === "?") { + path = path.slice(0, -1); + this.add(method, path.replace(/\/[^/]+$/, ""), handler); + } + const parts = (path.match(/\/?(:\w+(?:{(?:(?:{[\d,]+})|[^}])+})?)|\/?[^\/\?]+/g) || []).map( + (part) => { + const match = part.match(/^\/:([^{]+)(?:{(.*)})?/); + return match ? `/(?<${match[1]}>${match[2] || "[^/]+"})` : part === "/*" ? "/[^/]+" : part.replace(/[.\\+*[^\]$()]/g, "\\$&"); } - this.match = router2.match.bind(router2); - this.#routers = [router2]; - this.#routes = void 0; - break; - } - if (i === len) { - throw new Error("Fatal error"); - } - this.name = `SmartRouter + ${this.activeRouter.name}`; - return res; - } - get activeRouter() { - if (this.#routes || this.#routers.length !== 1) { - throw new Error("No active router has been determined yet."); + ); + let re; + try { + re = new RegExp(`^${parts.join("")}${endsWithWildcard ? "" : "/?$"}`); + } catch { + throw new UnsupportedPathError(); } - return this.#routers[0]; + this.#routes.push([re, method, handler]); } -}; - -// https://jsr.io/@hono/hono/4.6.19/src/router/trie-router/node.ts -var emptyParams = /* @__PURE__ */ Object.create(null); -var Node2 = class _Node { - #methods; - #children; - #patterns; - #order = 0; - #params = emptyParams; - constructor(method, handler, children) { - this.#children = children || /* @__PURE__ */ Object.create(null); - this.#methods = []; - if (method && handler) { - const m = /* @__PURE__ */ Object.create(null); - m[method] = { handler, possibleKeys: [], score: 0 }; - this.#methods = [m]; - } - this.#patterns = []; - } - insert(method, path, handler) { - this.#order = ++this.#order; - let curNode = this; - const parts = splitRoutingPath(path); - const possibleKeys = []; - for (let i = 0, len = parts.length; i < len; i++) { - const p = parts[i]; - if (Object.keys(curNode.#children).includes(p)) { - curNode = curNode.#children[p]; - const pattern2 = getPattern(p); - if (pattern2) { - possibleKeys.push(pattern2[1]); - } - continue; - } - curNode.#children[p] = new _Node(); - const pattern = getPattern(p); - if (pattern) { - curNode.#patterns.push(pattern); - possibleKeys.push(pattern[1]); - } - curNode = curNode.#children[p]; - } - const m = /* @__PURE__ */ Object.create(null); - const handlerSet = { - handler, - possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i), - score: this.#order - }; - m[method] = handlerSet; - curNode.#methods.push(m); - return curNode; - } - #getHandlerSets(node, method, nodeParams, params) { - const handlerSets = []; - for (let i = 0, len = node.#methods.length; i < len; i++) { - const m = node.#methods[i]; - const handlerSet = m[method] || m[METHOD_NAME_ALL]; - const processedSet = {}; - if (handlerSet !== void 0) { - handlerSet.params = /* @__PURE__ */ Object.create(null); - handlerSets.push(handlerSet); - if (nodeParams !== emptyParams || params && params !== emptyParams) { - for (let i2 = 0, len2 = handlerSet.possibleKeys.length; i2 < len2; i2++) { - const key = handlerSet.possibleKeys[i2]; - const processed = processedSet[handlerSet.score]; - handlerSet.params[key] = params?.[key] && !processed ? params[key] : nodeParams[key] ?? params?.[key]; - processedSet[handlerSet.score] = true; - } - } - } - } - return handlerSets; - } - search(method, path) { - const handlerSets = []; - this.#params = emptyParams; - const curNode = this; - let curNodes = [curNode]; - const parts = splitPath(path); - for (let i = 0, len = parts.length; i < len; i++) { - const part = parts[i]; - const isLast = i === len - 1; - const tempNodes = []; - for (let j = 0, len2 = curNodes.length; j < len2; j++) { - const node = curNodes[j]; - const nextNode = node.#children[part]; - if (nextNode) { - nextNode.#params = node.#params; - if (isLast) { - if (nextNode.#children["*"]) { - handlerSets.push( - ...this.#getHandlerSets(nextNode.#children["*"], method, node.#params) - ); - } - handlerSets.push(...this.#getHandlerSets(nextNode, method, node.#params)); - } else { - tempNodes.push(nextNode); - } - } - for (let k = 0, len3 = node.#patterns.length; k < len3; k++) { - const pattern = node.#patterns[k]; - const params = node.#params === emptyParams ? {} : { ...node.#params }; - if (pattern === "*") { - const astNode = node.#children["*"]; - if (astNode) { - handlerSets.push(...this.#getHandlerSets(astNode, method, node.#params)); - tempNodes.push(astNode); - } - continue; - } - if (part === "") { - continue; - } - const [key, name, matcher] = pattern; - const child = node.#children[key]; - const restPathString = parts.slice(i).join("/"); - if (matcher instanceof RegExp && matcher.test(restPathString)) { - params[name] = restPathString; - handlerSets.push(...this.#getHandlerSets(child, method, node.#params, params)); - continue; - } - if (matcher === true || matcher.test(part)) { - params[name] = part; - if (isLast) { - handlerSets.push(...this.#getHandlerSets(child, method, params, node.#params)); - if (child.#children["*"]) { - handlerSets.push( - ...this.#getHandlerSets(child.#children["*"], method, params, node.#params) - ); - } - } else { - child.#params = params; - tempNodes.push(child); - } - } + match(method, path) { + const handlers = []; + for (let i = 0, len = this.#routes.length; i < len; i++) { + const [pattern, routeMethod, handler] = this.#routes[i]; + if (routeMethod === method || routeMethod === METHOD_NAME_ALL) { + const match = pattern.exec(path); + if (match) { + handlers.push([handler, match.groups || emptyParams]); } } - curNodes = tempNodes; } - if (handlerSets.length > 1) { - handlerSets.sort((a, b) => { - return a.score - b.score; - }); - } - return [handlerSets.map(({ handler, params }) => [handler, params])]; + return [handlers]; } }; -// https://jsr.io/@hono/hono/4.6.19/src/router/trie-router/router.ts -var TrieRouter = class { - name = "TrieRouter"; - #node; - constructor() { - this.#node = new Node2(); - } - add(method, path, handler) { - const results = checkOptionalParameter(path); - if (results) { - for (let i = 0, len = results.length; i < len; i++) { - this.#node.insert(method, results[i], handler); - } - return; - } - this.#node.insert(method, path, handler); - } - match(method, path) { - return this.#node.search(method, path); - } -}; - -// https://jsr.io/@hono/hono/4.6.19/src/hono.ts +// https://jsr.io/@hono/hono/4.6.19/src/preset/tiny.ts var Hono2 = class extends Hono { - /** - * Creates an instance of the Hono class. - * - * @param options - Optional configuration options for the Hono instance. - */ constructor(options = {}) { super(options); - this.router = options.router ?? new SmartRouter({ - routers: [new RegExpRouter(), new TrieRouter()] - }); + this.router = new PatternRouter(); } }; From fc3290184570a51328d241f7a0463e4fd7859a9f Mon Sep 17 00:00:00 2001 From: Nick Chomey Date: Wed, 29 Jan 2025 19:37:01 -0600 Subject: [PATCH 3/8] add auto creation of hello-world.html --- build/run.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/build/run.go b/build/run.go index 82d6a3347..713a671b5 100644 --- a/build/run.go +++ b/build/run.go @@ -143,15 +143,17 @@ func writeOutConsts(version string) error { "sdk/java/core/src/main/java/starfederation/datastar/Consts.java": javaConsts, "sdk/java/core/src/main/java/starfederation/datastar/enums/EventType.java": javaEventType, "sdk/java/core/src/main/java/starfederation/datastar/enums/FragmentMergeMode.java": javaFragmentMergeMode, - "sdk/python/src/datastar_py/consts.py": pythonConsts, - "sdk/typescript/src/consts.ts": typescriptConsts, - "sdk/rust/src/consts.rs": rustConsts, - "sdk/zig/src/consts.zig": zigConsts, - "examples/dotnet/HelloWorld/wwwroot/hello-world.html": helloWorldExample, - "examples/go/hello-world/hello-world.html": helloWorldExample, - "examples/php/hello-world/public/hello-world.html": helloWorldExamplePHP, - "examples/zig/httpz/hello-world/src/hello-world.html": helloWorldExample, - "examples/zig/tokamak/hello-world/hello-world.html": helloWorldExample, + "sdk/python/src/datastar_py/consts.py": pythonConsts, + "sdk/typescript/src/consts.ts": typescriptConsts, + "sdk/rust/src/consts.rs": rustConsts, + "sdk/zig/src/consts.zig": zigConsts, + "examples/dotnet/HelloWorld/wwwroot/hello-world.html": helloWorldExample, + "examples/go/hello-world/hello-world.html": helloWorldExample, + "examples/php/hello-world/public/hello-world.html": helloWorldExamplePHP, + "examples/zig/httpz/hello-world/src/hello-world.html": helloWorldExample, + "examples/zig/tokamak/hello-world/hello-world.html": helloWorldExample, + "examples/typescript/deno/public/hello-world.html": helloWorldExample, + "examples/typescript/node/public/hello-world.html": helloWorldExample, } for path, tmplFn := range templates { From 90cb7d1a8ab481b79156f7d033a62b10d17bbd2e Mon Sep 17 00:00:00 2001 From: Nick Chomey Date: Wed, 29 Jan 2025 20:13:27 -0600 Subject: [PATCH 4/8] tidying up --- examples/typescript/deno-sw/README.md | 2 ++ examples/typescript/deno-sw/deno.ts | 15 ++------------- examples/typescript/deno-sw/service-worker.ts | 5 +++-- examples/typescript/deno-sw/shared-router.ts | 19 ++----------------- 4 files changed, 9 insertions(+), 32 deletions(-) diff --git a/examples/typescript/deno-sw/README.md b/examples/typescript/deno-sw/README.md index 2a08dbd8c..bae3b131f 100644 --- a/examples/typescript/deno-sw/README.md +++ b/examples/typescript/deno-sw/README.md @@ -4,4 +4,6 @@ To test it, start the server with `deno run -A deno.ts` and load the site at `ht The service worker will install and static assets will be cached. You can then either turn on offline mode in your browser's dev tools Network tab, or turn off the Deno server (there will be a slight delay in the response as it tries to fetch from the network first). Reload the page while offline and it will load from the service worker! And the SSE response when you click Start will be rendered from the SW as well. +If you want to expand upon it, it would be best to modify the `shared-router.ts` file, which is what both versions ultimately use (other than for loading static assets, which require different mechanisms in each environment). + There's many other things that can be done from a service worker - including different caching and network strategies. [Google Workbox](https://developer.chrome.com/docs/workbox) is a good resource - both as an easy-to-use library as well as just reference material for all things PWA. \ No newline at end of file diff --git a/examples/typescript/deno-sw/deno.ts b/examples/typescript/deno-sw/deno.ts index ea76816c5..906e20adc 100644 --- a/examples/typescript/deno-sw/deno.ts +++ b/examples/typescript/deno-sw/deno.ts @@ -10,26 +10,15 @@ await buildProcess.output(); const app = new Hono(); -// Middleware to log incoming requests -app.use("*", async (c, next) => { - console.log(`Incoming request: ${c.req.method} ${c.req.url}`); - await next(); -}); - - -// Serve static files from public directory after router +// Serve static files app.use("/static/*", serveStatic({ root: "./" })); -// Serve the service worker from the root path +// Serve the service worker from the root path - this is need so that it has scope for the entire application rather than just /static app.use("/service-worker.js", serveStatic({ path: "./static/service-worker.js" })); // Mount the shared router at the root path app.route("/", createRouter()); - -// Serve the fallback static file -// app.use("/", serveStatic({ path: "./public/hello-world.html" })); - Deno.serve({ port: 8000, hostname: "127.0.0.1", diff --git a/examples/typescript/deno-sw/service-worker.ts b/examples/typescript/deno-sw/service-worker.ts index e38b47124..d21b8b374 100644 --- a/examples/typescript/deno-sw/service-worker.ts +++ b/examples/typescript/deno-sw/service-worker.ts @@ -9,7 +9,7 @@ const CORE_ASSETS = [ '/static/datastar.js', '/static/datastar.js.map', '/static/rocket.png', - '/service-worker.js' // Ensure the service worker script is cached + '/service-worker.js' ]; const router = createRouter(); @@ -49,7 +49,7 @@ self.addEventListener('fetch', (event) => { } // If offline, use router for dynamic routes - if (!self.navigator.onLine) { // Use self.navigator instead of window.navigator + if (!self.navigator.onLine) { console.log('Browser is offline, using router fallback'); return await router.fetch(event.request); } @@ -67,6 +67,7 @@ self.addEventListener('fetch', (event) => { return networkResponse; } catch (error) { + // Fall back to router for dynamic routes console.log('Network request failed, using router fallback'); return await router.fetch(event.request); } diff --git a/examples/typescript/deno-sw/shared-router.ts b/examples/typescript/deno-sw/shared-router.ts index 5208349ce..6c5c9b458 100644 --- a/examples/typescript/deno-sw/shared-router.ts +++ b/examples/typescript/deno-sw/shared-router.ts @@ -6,32 +6,17 @@ interface Store { delay: number; } - - - - - - export function createRouter() { const app = new Hono(); - console.log(`Router created`); - // Middleware to log incoming requests - app.use("*", async (c, next) => { - console.log(`Incoming request - router: ${c.req.method} ${c.req.url}`); - await next(); - }); - + // Homepage route app.get("/", async (c) => { console.log("Handling / route"); return c.html(getHelloWorldHtml()); }); - - // Hello world SSE route + // Hello, world! route app.get("/hello-world", async (c) => { - - const reader = await ServerSentEventGenerator.readSignals(c.req); if (!reader.success) { From 303c42b5560b6cd49e8616c9576525b9f34fb7cb Mon Sep 17 00:00:00 2001 From: Nick Chomey Date: Wed, 5 Feb 2025 09:10:10 -0600 Subject: [PATCH 5/8] add deno tasks, remove build from deno.ts, add pre-built sw.js to the static assets, update Readme --- examples/typescript/deno-sw/README.md | 7 +-- examples/typescript/deno-sw/deno.json | 5 ++ examples/typescript/deno-sw/deno.lock | 3 + examples/typescript/deno-sw/deno.ts | 6 -- examples/typescript/deno-sw/shared-router.ts | 9 +-- .../deno-sw/static/package-lock.json | 55 +++++++++++++++++++ .../deno-sw/static/service-worker.js | 12 +--- 7 files changed, 74 insertions(+), 23 deletions(-) create mode 100644 examples/typescript/deno-sw/static/package-lock.json diff --git a/examples/typescript/deno-sw/README.md b/examples/typescript/deno-sw/README.md index bae3b131f..b609c8a18 100644 --- a/examples/typescript/deno-sw/README.md +++ b/examples/typescript/deno-sw/README.md @@ -1,9 +1,8 @@ This is a sample app that can serve the same responses from both the Deno backend and the Service Worker while offline. -To test it, start the server with `deno run -A deno.ts` and load the site at `http://localhost:8000`. +To test it, start the server with `deno run start` and load the site at `http://localhost:8000`. -The service worker will install and static assets will be cached. You can then either turn on offline mode in your browser's dev tools Network tab, or turn off the Deno server (there will be a slight delay in the response as it tries to fetch from the network first). Reload the page while offline and it will load from the service worker! And the SSE response when you click Start will be rendered from the SW as well. +The service worker will install in the browser and static assets will be cached. You can then either turn on offline mode in your browser's dev tools Network tab, or turn off the Deno server (there will be a slight delay in the response as it tries to fetch from the network first). Reload the page while offline and it will load from the service worker! And the SSE response when you click Start will be rendered from the SW as well. -If you want to expand upon it, it would be best to modify the `shared-router.ts` file, which is what both versions ultimately use (other than for loading static assets, which require different mechanisms in each environment). +If you want to expand upon it, it would be best to modify the `shared-router.ts` file, which is what both versions ultimately use (other than for loading static assets, which require different mechanisms in each environment). You can use `deno run dev` to rebuild the service worker (if your OS is capable of using esbuild) and start the server. -There's many other things that can be done from a service worker - including different caching and network strategies. [Google Workbox](https://developer.chrome.com/docs/workbox) is a good resource - both as an easy-to-use library as well as just reference material for all things PWA. \ No newline at end of file diff --git a/examples/typescript/deno-sw/deno.json b/examples/typescript/deno-sw/deno.json index 136f1464d..8977d9fff 100644 --- a/examples/typescript/deno-sw/deno.json +++ b/examples/typescript/deno-sw/deno.json @@ -3,5 +3,10 @@ "@hono/hono": "jsr:@hono/hono@^4.6.19", "@luca/esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.9.0" }, + "tasks": { + "start": "deno run -A deno.ts", + "build": "deno run -A build.ts", + "dev": "deno task build && deno run -A --watch deno.ts" + }, "nodeModulesDir": "auto" } diff --git a/examples/typescript/deno-sw/deno.lock b/examples/typescript/deno-sw/deno.lock index 2378b192d..6e2cfbabb 100644 --- a/examples/typescript/deno-sw/deno.lock +++ b/examples/typescript/deno-sw/deno.lock @@ -152,9 +152,12 @@ } }, "redirects": { + "https://deno.land/std/fs/ensure_dir.ts": "https://deno.land/std@0.224.0/fs/ensure_dir.ts", "https://deno.land/x/esbuild/mod.js": "https://deno.land/x/esbuild@v0.24.2/mod.js" }, "remote": { + "https://deno.land/std@0.224.0/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd", + "https://deno.land/std@0.224.0/fs/ensure_dir.ts": "51a6279016c65d2985f8803c848e2888e206d1b510686a509fa7cc34ce59d29f", "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", "https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4", "https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d", diff --git a/examples/typescript/deno-sw/deno.ts b/examples/typescript/deno-sw/deno.ts index 906e20adc..2abda90a2 100644 --- a/examples/typescript/deno-sw/deno.ts +++ b/examples/typescript/deno-sw/deno.ts @@ -2,12 +2,6 @@ import { Hono } from "jsr:@hono/hono/tiny"; import { serveStatic } from "jsr:@hono/hono/deno"; import { createRouter } from "./shared-router.ts"; -// Rebuild service worker first -const buildProcess = new Deno.Command(Deno.execPath(), { - args: ["run", "-A", "--unstable-sloppy-imports", "build.js"], -}); -await buildProcess.output(); - const app = new Hono(); // Serve static files diff --git a/examples/typescript/deno-sw/shared-router.ts b/examples/typescript/deno-sw/shared-router.ts index 6c5c9b458..d2036aff0 100644 --- a/examples/typescript/deno-sw/shared-router.ts +++ b/examples/typescript/deno-sw/shared-router.ts @@ -2,8 +2,9 @@ import { Hono } from "jsr:@hono/hono/tiny"; import { ServerSentEventGenerator } from "../../../sdk/typescript/src/web/serverSentEventGenerator.ts"; import { getHelloWorldHtml } from "./hello-world.js"; +// Make Store properties explicitly typed interface Store { - delay: number; + delay: number | null; } export function createRouter() { @@ -11,13 +12,12 @@ export function createRouter() { // Homepage route app.get("/", async (c) => { - console.log("Handling / route"); return c.html(getHelloWorldHtml()); }); // Hello, world! route app.get("/hello-world", async (c) => { - const reader = await ServerSentEventGenerator.readSignals(c.req); + const reader = await ServerSentEventGenerator.readSignals(c.req.raw); if (!reader.success) { return c.text(`Error while reading signals: ${reader.error}`, 400); @@ -25,12 +25,13 @@ export function createRouter() { return ServerSentEventGenerator.stream(async (stream) => { const message = "Hello, world!"; + const delay = typeof reader.signals.delay === 'number' ? reader.signals.delay : 400; // Default delay for (let i = 0; i < message.length; i++) { stream.mergeFragments( `
${message.substring(0, i + 1)}
`, ); - await new Promise(resolve => setTimeout(resolve, reader.signals.delay)); + await new Promise(resolve => setTimeout(resolve, delay)); } }); }); diff --git a/examples/typescript/deno-sw/static/package-lock.json b/examples/typescript/deno-sw/static/package-lock.json new file mode 100644 index 000000000..98ed9014b --- /dev/null +++ b/examples/typescript/deno-sw/static/package-lock.json @@ -0,0 +1,55 @@ +{ + "name": "service-worker", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "service-worker", + "version": "1.0.0", + "dependencies": { + "type-fest": "*" + }, + "devDependencies": { + "@types/node": "^20.9.0", + "picocolors": "^1.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.17.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.17.tgz", + "integrity": "sha512-/WndGO4kIfMicEQLTi/mDANUu/iVUhT7KboZPdEqqHQ4aTS+3qT3U5gIqWDFV+XouorjfgGqvKILJeHhuQgFYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/type-fest": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.33.0.tgz", + "integrity": "sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/typescript/deno-sw/static/service-worker.js b/examples/typescript/deno-sw/static/service-worker.js index 96f270e3d..3d62edbea 100644 --- a/examples/typescript/deno-sw/static/service-worker.js +++ b/examples/typescript/deno-sw/static/service-worker.js @@ -1689,27 +1689,22 @@ function getHelloWorldHtml() { // shared-router.ts function createRouter() { const app = new Hono2(); - console.log(`Router created`); - app.use("*", async (c, next) => { - console.log(`Incoming request - router: ${c.req.method} ${c.req.url}`); - await next(); - }); app.get("/", async (c) => { - console.log("Handling / route"); return c.html(getHelloWorldHtml()); }); app.get("/hello-world", async (c) => { - const reader = await ServerSentEventGenerator2.readSignals(c.req); + const reader = await ServerSentEventGenerator2.readSignals(c.req.raw); if (!reader.success) { return c.text(`Error while reading signals: ${reader.error}`, 400); } return ServerSentEventGenerator2.stream(async (stream) => { const message = "Hello, world!"; + const delay = typeof reader.signals.delay === "number" ? reader.signals.delay : 400; for (let i = 0; i < message.length; i++) { stream.mergeFragments( `
${message.substring(0, i + 1)}
` ); - await new Promise((resolve) => setTimeout(resolve, reader.signals.delay)); + await new Promise((resolve) => setTimeout(resolve, delay)); } }); }); @@ -1729,7 +1724,6 @@ var CORE_ASSETS = [ "/static/datastar.js.map", "/static/rocket.png", "/service-worker.js" - // Ensure the service worker script is cached ]; var router = createRouter(); self.addEventListener("install", (event) => { From e17df5ceae35d053b671ab117961866976ed6c55 Mon Sep 17 00:00:00 2001 From: Nick Chomey Date: Wed, 5 Feb 2025 10:13:14 -0600 Subject: [PATCH 6/8] switch to using remote assets --- examples/typescript/deno-sw/hello-world.js | 6 +-- examples/typescript/deno-sw/service-worker.ts | 43 +++++++++++-------- .../deno-sw/static/service-worker.js | 39 ++++++++++------- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/examples/typescript/deno-sw/hello-world.js b/examples/typescript/deno-sw/hello-world.js index e14d48428..849b70c4a 100644 --- a/examples/typescript/deno-sw/hello-world.js +++ b/examples/typescript/deno-sw/hello-world.js @@ -15,14 +15,14 @@ export function getHelloWorldHtml() { }); } - - + +

Datastar SDK Demo

- Rocket + Rocket

SSE events will be streamed from the backend to the frontend.

diff --git a/examples/typescript/deno-sw/service-worker.ts b/examples/typescript/deno-sw/service-worker.ts index d21b8b374..e599d31b8 100644 --- a/examples/typescript/deno-sw/service-worker.ts +++ b/examples/typescript/deno-sw/service-worker.ts @@ -2,14 +2,10 @@ import { createRouter } from "./shared-router.ts"; declare const self: ServiceWorkerGlobalScope; -const CACHE_NAME = 'datastar-cache-v1'; +const CACHE_NAME = 'datastar-cache'; const CORE_ASSETS = [ '/', - '/static/tailwind.js', - '/static/datastar.js', - '/static/datastar.js.map', - '/static/rocket.png', - '/service-worker.js' + 'https://data-star.dev/static/images/rocket.png', // need to cache this because it doesnt have cache header ]; const router = createRouter(); @@ -19,9 +15,16 @@ self.addEventListener('install', (event) => { event.waitUntil( (async () => { const cache = await caches.open(CACHE_NAME); - await Promise.all( - CORE_ASSETS.map(url => fetch(url).then(response => cache.put(url, response))) - ); + for (const url of CORE_ASSETS) { + try { + const fetchOptions = url.includes('rocket') ? { mode: 'no-cors' as RequestMode } : undefined; + const response = await fetch(url, fetchOptions); + await cache.put(url, response); + console.log('Cached:', url); + } catch (error) { + console.error('Failed to cache:', url, error); + } + } await self.skipWaiting(); })() ); @@ -33,22 +36,21 @@ self.addEventListener('activate', (event) => { }); self.addEventListener('fetch', (event) => { - console.log('SW Fetch:', event.request.url); - event.respondWith( (async () => { const cache = await caches.open(CACHE_NAME); const url = new URL(event.request.url); - + // Try cache first for core assets - if (CORE_ASSETS.includes(url.pathname)) { + if (CORE_ASSETS.includes(url.toString()) || CORE_ASSETS.includes(url.pathname)) { const cachedResponse = await cache.match(event.request); if (cachedResponse) { + console.log('SW Cache Hit:', event.request.url); return cachedResponse; } } - // If offline, use router for dynamic routes + // If browser is set to offline, use router for dynamic routes if (!self.navigator.onLine) { console.log('Browser is offline, using router fallback'); return await router.fetch(event.request); @@ -56,13 +58,20 @@ self.addEventListener('fetch', (event) => { // Try network try { - const networkResponse = await fetch(event.request); + console.log('SW Fetch:', event.request.url); + + const fetchOptions = event.request.url.includes('rocket') ? { mode: 'no-cors' as RequestMode } : undefined; + + const networkResponse = await fetch(event.request, fetchOptions); // Cache successful GET requests for core assets if (networkResponse.ok && - event.request.method === 'GET' && - CORE_ASSETS.includes(url.pathname)) { + event.request.method === 'GET' + && (CORE_ASSETS.includes(url.toString()) || CORE_ASSETS.includes(url.pathname)) + ) + { await cache.put(event.request, networkResponse.clone()); + console.log('SW Cache Put:', event.request.url); } return networkResponse; diff --git a/examples/typescript/deno-sw/static/service-worker.js b/examples/typescript/deno-sw/static/service-worker.js index 3d62edbea..19326848b 100644 --- a/examples/typescript/deno-sw/static/service-worker.js +++ b/examples/typescript/deno-sw/static/service-worker.js @@ -1663,14 +1663,14 @@ function getHelloWorldHtml() { }); } <\/script> - - +
diff --git a/examples/typescript/deno/deno.ts b/examples/typescript/deno/deno.ts index 3ce730a27..0349d66da 100644 --- a/examples/typescript/deno/deno.ts +++ b/examples/typescript/deno/deno.ts @@ -31,6 +31,7 @@ Deno.serve({ ); await new Promise(resolve => setTimeout(resolve, reader.signals.delay)); } + stream.close(); }); }