@astroscope/wormhole 1.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -126
- package/dist/als-CntRqWdO.js +7 -0
- package/dist/als-CntRqWdO.js.map +1 -0
- package/dist/define-1UkPnsXV.js +83 -0
- package/dist/define-1UkPnsXV.js.map +1 -0
- package/dist/index.browser.d.ts +9 -0
- package/dist/index.browser.d.ts.map +1 -0
- package/dist/index.browser.js +48 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.d.ts +53 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +230 -28
- package/dist/index.js.map +1 -1
- package/dist/key-BqTy1gM3.js +9 -0
- package/dist/key-BqTy1gM3.js.map +1 -0
- package/dist/middleware-entrypoint.d.ts +4 -0
- package/dist/middleware-entrypoint.d.ts.map +1 -0
- package/dist/middleware-entrypoint.js +263 -0
- package/dist/middleware-entrypoint.js.map +1 -0
- package/dist/react/index.d.ts +3 -4
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/scan-CczEOWpD.js +79 -0
- package/dist/scan-CczEOWpD.js.map +1 -0
- package/dist/server.d.ts +11 -16
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +4 -8
- package/dist/server.js.map +1 -1
- package/dist/types-DiWa8aQg.d.ts +53 -0
- package/dist/types-DiWa8aQg.d.ts.map +1 -0
- package/package.json +25 -16
- package/dist/types-Q2gaO_N_.d.ts +0 -31
- package/dist/types-Q2gaO_N_.d.ts.map +0 -1
- package/src/astro/WormholeScript.astro +0 -29
- package/src/astro/index.ts +0 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-entrypoint.js","names":["registry"],"sources":["../src/client-state.ts","../src/manifest.ts","../src/reachable.ts","../src/request-store.ts","../src/islands-emitter.ts","../src/telemetry.ts","../src/middleware.ts","../src/middleware-entrypoint.ts"],"sourcesContent":["/**\n * Script generation for wormhole delivery, shipped through the `@astroscope/node`\n * islands pipeline (see islands-emitter.ts for the placement policy).\n */\n\n/** values may contain `</script>` — escape `<` so they cannot end the tag */\nexport function jsonForScript(value: unknown): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\n/**\n * Inline script writing entries onto `self.__wormholes__` and notifying listeners —\n * the counterpart of the browser proxy's entry protocol, safe in either order.\n */\nexport function createWormholeMergeScript(entries: Record<string, unknown>): string {\n return (\n `<script>{const s=self.__wormholes__??={},d=${jsonForScript(entries)};` +\n `for(const n in d){const e=s[n]??={};e.v=d[n];e.l&&e.l.forEach(f=>f(e.v))}}</script>`\n );\n}\n","import { manifest } from 'virtual:@astroscope/wormhole/manifest';\nimport type { WormholeManifest } from './extraction/types.js';\n\n/** null in dev and when the build produced no manifest (broken deploy → full delivery fallback) */\nexport function getWormholeManifest(): WormholeManifest | null {\n return manifest;\n}\n","import type { WormholeManifest } from './extraction/types.js';\n\n/** public url or dist-relative file name of a chunk → manifest chunk name, e.g. `…/_astro/Counter.abc.js` → `Counter.abc` */\nexport function chunkName(url: string): string {\n return url.slice(url.lastIndexOf('/') + 1).replace(/\\.js$/, '');\n}\n\n/**\n * The wormhole names the chunks of a closure can reach; `null` when one of them\n * reads dynamically (`*` — all open wormholes).\n */\nexport function namesInClosure(closure: Iterable<string>, manifest: WormholeManifest): string[] | null {\n const names = new Set<string>();\n\n for (const url of closure) {\n for (const name of manifest.chunks[chunkName(url)] ?? []) {\n if (name === '*') {\n return null;\n }\n\n names.add(name);\n }\n }\n\n return [...names];\n}\n","/**\n * Per-request wormhole values for the islands emitter and the stream-end script —\n * both run while the response streams, outside the middleware's ALS scope, so the\n * request object is the only stable handle (same pattern as the i18n locale).\n */\nexport type RequestWormholes = {\n /** wormhole name → serializable value, as resolved by the middleware */\n values: ReadonlyMap<string, unknown>;\n /** names already written into the document by the islands emitter */\n emitted: Set<string>;\n};\n\nconst requestWormholes = new WeakMap<Request, RequestWormholes>();\n\nexport function setRequestWormholes(request: Request, wormholes: RequestWormholes): void {\n requestWormholes.set(request, wormholes);\n}\n\nexport function getRequestWormholes(request: Request): RequestWormholes | undefined {\n return requestWormholes.get(request);\n}\n","import {\n type DocumentEmitter,\n type IslandInfo,\n registerDocumentEmitter,\n registerIslandEmitter,\n} from '@astroscope/node/islands';\nimport { log } from '@astroscope/node/log';\nimport { createWormholeMergeScript } from './client-state.js';\nimport type { WormholeManifest } from './extraction/types.js';\nimport { getWormholeManifest } from './manifest.js';\nimport { namesInClosure } from './reachable.js';\nimport { getRequestWormholes } from './request-store.js';\n\n/**\n * Contributes each island's wormhole slice: an inline merge script with the values\n * of every open wormhole the island's full chunk closure (dynamic imports included)\n * can reach, emitted before the island tag — parsed strictly before the island\n * connects, even mid-stream. Names already written earlier in the document are\n * skipped; a `*` chunk (dynamic proxy access) degrades to all open wormholes.\n */\n\n// vite defines import.meta.env when bundling the middleware; typed structurally\n// so the package compiles without vite's global client types\nconst IS_DEV = !!(import.meta as { env?: { DEV?: boolean } }).env?.DEV;\n\nlet registered = false;\nlet warnedMissingManifest = false;\n\n/** the wormhole names an island's full closure can reach; `null` when a chunk reads dynamically (all open) */\ntype Reachable = string[] | null;\n\n// islands arrive as the same object per component/renderer/directive and the\n// manifest is loaded once, so the intersection is computed once per island\nconst reachableByManifest = new WeakMap<WormholeManifest, WeakMap<IslandInfo, Reachable>>();\n\nfunction reachableFor(island: IslandInfo, manifest: WormholeManifest): Reachable {\n let byIsland = reachableByManifest.get(manifest);\n\n if (!byIsland) {\n byIsland = new WeakMap();\n reachableByManifest.set(manifest, byIsland);\n }\n\n const cached = byIsland.get(island);\n\n if (cached !== undefined) {\n return cached;\n }\n\n const reachable = namesInClosure(island.fullClosure, manifest);\n\n byIsland.set(island, reachable);\n\n return reachable;\n}\n\n/**\n * The document-level wormhole delivery, exported for tests: everything open as\n * `head` content in dev (or without a manifest), script-entry names as `end`\n * content in prod — running after the rewriter, names the islands emitter\n * already wrote are skipped race-free.\n */\nexport function createWormholeDocumentEmitter(dev: boolean = IS_DEV): DocumentEmitter {\n return (context) => {\n const request = getRequestWormholes(context.request);\n\n if (!request || request.values.size === 0) {\n return null;\n }\n\n if (dev) {\n return { head: createWormholeMergeScript(Object.fromEntries(request.values)) };\n }\n\n const manifest = getWormholeManifest();\n\n if (!manifest) {\n if (!warnedMissingManifest) {\n warnedMissingManifest = true;\n log.error(\n 'wormhole manifest not found — delivering all open wormholes with every page (is the integration configured?)',\n );\n }\n\n return { head: createWormholeMergeScript(Object.fromEntries(request.values)) };\n }\n\n const names = manifest.scripts.includes('*')\n ? [...request.values.keys()]\n : manifest.scripts.filter((name) => request.values.has(name));\n\n if (names.length === 0) {\n return null;\n }\n\n return {\n end: () => {\n const fresh = names.filter((name) => !request.emitted.has(name));\n\n if (fresh.length === 0) {\n return null;\n }\n\n return createWormholeMergeScript(Object.fromEntries(fresh.map((name) => [name, request.values.get(name)])));\n },\n };\n };\n}\n\nexport function registerWormholeEmitters(): void {\n if (registered) {\n return;\n }\n\n registered = true;\n\n registerDocumentEmitter(createWormholeDocumentEmitter());\n\n registerIslandEmitter((island, context) => {\n // no request context (prerendered build pass) — values are per-request, nothing to emit\n if (!context) {\n return null;\n }\n\n const request = getRequestWormholes(context.request);\n const manifest = getWormholeManifest();\n\n if (!request || request.values.size === 0 || !manifest) {\n return null;\n }\n\n const reachable = reachableFor(island, manifest);\n const entries: Record<string, unknown> = {};\n let count = 0;\n\n for (const name of reachable ?? request.values.keys()) {\n if (!request.values.has(name) || request.emitted.has(name)) {\n continue;\n }\n\n request.emitted.add(name);\n entries[name] = request.values.get(name);\n count++;\n }\n\n if (count === 0) {\n return null;\n }\n\n return { html: createWormholeMergeScript(entries) };\n });\n}\n","import { log } from '@astroscope/node/log';\nimport { DURATION_BUCKETS, createCounter, createHistogram, errorType, withSpan } from '@astroscope/node/telemetry';\nimport { type MaybePromise, maybeThen } from '@entwico/dash';\n\nconst SCOPE = '@astroscope/wormhole';\n\nconst handlerDuration = createHistogram('astro.wormhole.handler.duration', {\n scope: SCOPE,\n description: 'Wormhole handler execution per request',\n unit: 's',\n buckets: DURATION_BUCKETS.io,\n});\n\nconst handlerFailures = createCounter('astro.wormhole.handler.failures', {\n scope: SCOPE,\n description: 'Wormhole handlers that threw',\n unit: '{handler}',\n});\n\n/**\n * Runs one handler under its own span (child of the request span — the\n * handlers run in parallel, so the trace shows which one holds the page's\n * first byte), records its duration and counts and logs a failure. A\n * synchronous handler stays synchronous.\n */\nexport function measureHandler<T>(name: string, run: () => MaybePromise<T>): MaybePromise<T> {\n const attributes = { 'astro.wormhole.name': name };\n const started = performance.now();\n\n const fail = (error: unknown): never => {\n handlerFailures.add(1, { ...attributes, 'error.type': errorType(error) });\n log.error(\n error instanceof Error ? { err: error, wormhole: name } : { reason: error, wormhole: name },\n 'wormhole handler failed',\n );\n\n throw error;\n };\n\n return withSpan(`wormhole ${name}`, { scope: SCOPE, attributes }, () => {\n let result: MaybePromise<T>;\n\n try {\n result = run();\n } catch (error) {\n return fail(error);\n }\n\n return maybeThen(\n result,\n (value) => {\n handlerDuration.record((performance.now() - started) / 1000, attributes);\n\n return value;\n },\n fail,\n );\n });\n}\n","import { type ExcludePattern, RECOMMENDED_EXCLUDES, shouldExclude } from '@astroscope/node/excludes';\nimport { getRouteIslands } from '@astroscope/node/islands';\nimport { maybeAll, maybeThen } from '@entwico/dash';\nimport type { MiddlewareHandler } from 'astro';\nimport { wormholes as registry } from 'virtual:@astroscope/wormhole/registry';\nimport { als } from './als.js';\nimport { type WormholeSource, assignWormholeNames, getWormholeSource } from './define.js';\nimport { registerWormholeEmitters } from './islands-emitter.js';\nimport { getWormholeManifest } from './manifest.js';\nimport { namesInClosure } from './reachable.js';\nimport { setRequestWormholes } from './request-store.js';\nimport { measureHandler } from './telemetry.js';\nimport type { WormholeHandler } from './types.js';\n\nexport type WormholeMiddlewareOptions = {\n /** patterns to exclude from wormhole loading; defaults to RECOMMENDED_EXCLUDES */\n exclude?: ExcludePattern[] | undefined;\n};\n\n/**\n * The wormhole middleware, injected by the integration after the project's own\n * middleware: runs the handlers of the wormholes the request's route can read —\n * server and `<script>` reads from the manifest, island reads through the route's\n * islands from `@astroscope/node`, and eager wormholes — makes the values\n * readable on the server for the duration of the request, and hands them to the\n * islands pipeline for delivery. Routes the build could not attribute (dev, no\n * manifest, astro's own routes) load everything.\n */\nexport function createWormholeMiddleware(options: WormholeMiddlewareOptions = {}): MiddlewareHandler {\n assignWormholeNames(registry);\n registerWormholeEmitters();\n\n const sources = new Map<string, WormholeSource>();\n\n for (const [name, wormhole] of Object.entries(registry)) {\n const source = getWormholeSource(wormhole);\n\n if (!source) {\n throw new Error(`wormhole \"${name}\" has no handler — define it with defineWormhole({ handler: (ctx) => ... })`);\n }\n\n sources.set(name, source);\n }\n\n const everything = [...sources.keys()];\n const eager = everything.filter((name) => sources.get(name)!.eager);\n\n // what a route opens, resolved once per route: the name, the handler and the ALS key\n type Entry = { name: string; key: string; handler: WormholeHandler<unknown> };\n\n const entriesByRoute = new Map<string, Entry[]>();\n\n // null: the route's readers are unknown, load everything\n const routeNames = (pattern: string): string[] | null => {\n const manifest = getWormholeManifest();\n\n if (!manifest) {\n return null;\n }\n\n const server = manifest.routes[pattern];\n const islands = getRouteIslands(pattern);\n\n if (!server || !islands || server.includes('*')) {\n return null;\n }\n\n const names = new Set([...eager, ...server]);\n\n for (const island of islands) {\n const reachable = namesInClosure(island.fullClosure, manifest);\n\n if (!reachable) {\n return null;\n }\n\n reachable.forEach((name) => names.add(name));\n }\n\n return [...names].filter((name) => sources.has(name));\n };\n\n const entriesFor = (pattern: string): Entry[] => {\n let entries = entriesByRoute.get(pattern);\n\n if (!entries) {\n entries = (routeNames(pattern) ?? everything).map((name) => ({\n name,\n key: registry[name]!.key,\n handler: sources.get(name)!.handler,\n }));\n entriesByRoute.set(pattern, entries);\n }\n\n return entries;\n };\n\n return (ctx, next) => {\n if (shouldExclude(ctx, options.exclude ?? RECOMMENDED_EXCLUDES)) {\n return next();\n }\n\n const entries = entriesFor(ctx.routePattern);\n\n // synchronous handlers — the common case — open the scope without a promise\n // allocation or a microtask hop; async ones settle together\n return maybeThen(\n maybeAll(entries.map((entry) => measureHandler(entry.name, () => entry.handler(ctx)))),\n (loaded) => {\n const open = new Map<string, unknown>();\n const scope = new Map<string, unknown>(als.getStore());\n\n for (let i = 0; i < entries.length; i++) {\n const data = loaded[i];\n\n if (data === undefined) {\n continue;\n }\n\n open.set(entries[i]!.name, data);\n scope.set(entries[i]!.key, data);\n }\n\n // the islands emitter and the stream-end script run while the response streams,\n // outside this ALS scope — the request object carries the values to them\n setRequestWormholes(ctx.request, { values: open, emitted: new Set() });\n\n return als.run(scope, () => next());\n },\n );\n };\n}\n","import { exclude } from 'virtual:@astroscope/wormhole/config';\nimport { createWormholeMiddleware } from './middleware.js';\n\nexport const onRequest = createWormholeMiddleware({ exclude: exclude ?? undefined });\n"],"mappings":"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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { n as Wormhole
|
|
2
|
-
|
|
1
|
+
import { n as Wormhole } from "../types-DiWa8aQg.js";
|
|
2
|
+
import { ReadonlyDeep } from "@entwico/dash";
|
|
3
3
|
//#region src/react/index.d.ts
|
|
4
|
-
declare function useWormhole<T>(wormhole: Wormhole<T>):
|
|
4
|
+
export declare function useWormhole<T>(wormhole: Wormhole<T>): ReadonlyDeep<T>;
|
|
5
5
|
//#endregion
|
|
6
|
-
export { useWormhole };
|
|
7
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/react/index.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/react/index.ts"],"mappings":";;;wBAIgB,YAAY,GAAG,UAAU,SAAS,KAAK,aAAa"}
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/react/index.ts"],"sourcesContent":["import { useSyncExternalStore } from 'react';\nimport type {
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/react/index.ts"],"sourcesContent":["import type { ReadonlyDeep } from '@entwico/dash';\nimport { useSyncExternalStore } from 'react';\nimport type { Wormhole } from '../types.js';\n\nexport function useWormhole<T>(wormhole: Wormhole<T>): ReadonlyDeep<T> {\n return useSyncExternalStore(wormhole.subscribe, wormhole.get, wormhole.get);\n}\n"],"mappings":";;AAIA,SAAgB,YAAe,UAAwC;CACrE,OAAO,qBAAqB,SAAS,WAAW,SAAS,KAAK,SAAS,GAAG;AAC5E"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
//#region src/extraction/scan.ts
|
|
3
|
+
const PACKAGE_NAME = "@astroscope/wormhole";
|
|
4
|
+
const TYPE_ONLY_NODES = /* @__PURE__ */ new Set([
|
|
5
|
+
"TSTypeAnnotation",
|
|
6
|
+
"TSTypeParameterDeclaration",
|
|
7
|
+
"TSTypeParameterInstantiation",
|
|
8
|
+
"TSTypeQuery",
|
|
9
|
+
"TSTypeAliasDeclaration",
|
|
10
|
+
"TSInterfaceDeclaration"
|
|
11
|
+
]);
|
|
12
|
+
function isNode(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
14
|
+
}
|
|
15
|
+
function walk(node, visit, parent, key) {
|
|
16
|
+
if (TYPE_ONLY_NODES.has(node.type)) return;
|
|
17
|
+
visit(node, parent, key);
|
|
18
|
+
for (const [childKey, value] of Object.entries(node)) {
|
|
19
|
+
if (childKey === "loc") continue;
|
|
20
|
+
if (isNode(value)) walk(value, visit, node, childKey);
|
|
21
|
+
else if (Array.isArray(value)) {
|
|
22
|
+
for (const item of value) if (isNode(item)) walk(item, visit, node, childKey);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function isMember(node) {
|
|
27
|
+
return node?.type === "MemberExpression" || node?.type === "OptionalMemberExpression";
|
|
28
|
+
}
|
|
29
|
+
function scanWormholeAccess(code, id) {
|
|
30
|
+
const isTs = id.endsWith(".ts") || id.endsWith(".tsx") || id.endsWith(".astro");
|
|
31
|
+
const plugins = [];
|
|
32
|
+
if (isTs) plugins.push("typescript");
|
|
33
|
+
if (!id.endsWith(".ts") && !id.endsWith(".astro")) plugins.push("jsx");
|
|
34
|
+
let program;
|
|
35
|
+
try {
|
|
36
|
+
program = parse(code, {
|
|
37
|
+
sourceType: "module",
|
|
38
|
+
plugins
|
|
39
|
+
}).program;
|
|
40
|
+
} catch {
|
|
41
|
+
return {
|
|
42
|
+
names: /* @__PURE__ */ new Set(),
|
|
43
|
+
dynamic: true
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const locals = /* @__PURE__ */ new Set();
|
|
47
|
+
const scan = {
|
|
48
|
+
names: /* @__PURE__ */ new Set(),
|
|
49
|
+
dynamic: false
|
|
50
|
+
};
|
|
51
|
+
for (const statement of program["body"]) {
|
|
52
|
+
if (statement["source"]?.["value"] !== PACKAGE_NAME) continue;
|
|
53
|
+
if (statement.type === "ImportDeclaration") {
|
|
54
|
+
for (const specifier of statement["specifiers"]) if (specifier.type === "ImportSpecifier") {
|
|
55
|
+
const imported = specifier["imported"];
|
|
56
|
+
if ((imported.type === "Identifier" ? imported["name"] : imported["value"]) === "wormholes") locals.add(specifier["local"]["name"]);
|
|
57
|
+
} else if (specifier.type === "ImportNamespaceSpecifier") scan.dynamic = true;
|
|
58
|
+
} else scan.dynamic = true;
|
|
59
|
+
}
|
|
60
|
+
if (locals.size === 0 && !scan.dynamic) return null;
|
|
61
|
+
walk(program, (node, parent, key) => {
|
|
62
|
+
if (node.type !== "Identifier" || !locals.has(node["name"]) || !parent) return;
|
|
63
|
+
if (parent.type === "ImportSpecifier") return;
|
|
64
|
+
if (isMember(parent) && key === "object") {
|
|
65
|
+
const property = parent["property"];
|
|
66
|
+
if (!parent["computed"] && property.type === "Identifier") scan.names.add(property["name"]);
|
|
67
|
+
else if (parent["computed"] && property.type === "StringLiteral") scan.names.add(property["value"]);
|
|
68
|
+
else scan.dynamic = true;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if ((key === "property" || key === "key") && !parent["computed"]) return;
|
|
72
|
+
scan.dynamic = true;
|
|
73
|
+
}, null, "");
|
|
74
|
+
return scan;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { scanWormholeAccess };
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=scan-CczEOWpD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-CczEOWpD.js","names":[],"sources":["../src/extraction/scan.ts"],"sourcesContent":["import { parse } from '@babel/parser';\n\nconst PACKAGE_NAME = '@astroscope/wormhole';\n\n/**\n * Which wormhole names a module can reach through the `wormhole` proxy.\n *\n * `wormholes.cart` and `wormholes['cart']` yield names; anything the scan cannot\n * resolve statically — computed access, aliasing the proxy into a variable,\n * namespace imports, re-exports — sets `dynamic`, which the manifest records as\n * `*` (all open wormholes). The failure mode is therefore over-emission, never\n * missing data.\n */\nexport type WormholeScan = { names: Set<string>; dynamic: boolean };\n\n// type-only subtrees are erased at runtime — identifiers inside them are not accesses\nconst TYPE_ONLY_NODES = new Set([\n 'TSTypeAnnotation',\n 'TSTypeParameterDeclaration',\n 'TSTypeParameterInstantiation',\n 'TSTypeQuery',\n 'TSTypeAliasDeclaration',\n 'TSInterfaceDeclaration',\n]);\n\ntype Node = { type: string; [key: string]: unknown };\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && typeof (value as Node).type === 'string';\n}\n\nfunction walk(\n node: Node,\n visit: (node: Node, parent: Node | null, key: string) => void,\n parent: Node | null,\n key: string,\n): void {\n if (TYPE_ONLY_NODES.has(node.type)) {\n return;\n }\n\n visit(node, parent, key);\n\n for (const [childKey, value] of Object.entries(node)) {\n if (childKey === 'loc') {\n continue;\n }\n\n if (isNode(value)) {\n walk(value, visit, node, childKey);\n } else if (Array.isArray(value)) {\n for (const item of value) {\n if (isNode(item)) {\n walk(item, visit, node, childKey);\n }\n }\n }\n }\n}\n\nfunction isMember(node: Node | null): node is Node {\n return node?.type === 'MemberExpression' || node?.type === 'OptionalMemberExpression';\n}\n\nexport function scanWormholeAccess(code: string, id: string): WormholeScan | null {\n const isTs = id.endsWith('.ts') || id.endsWith('.tsx') || id.endsWith('.astro');\n const plugins: ('typescript' | 'jsx')[] = [];\n\n if (isTs) {\n plugins.push('typescript');\n }\n\n // compiled astro output is not jsx\n if (!id.endsWith('.ts') && !id.endsWith('.astro')) {\n plugins.push('jsx');\n }\n\n let program: Node;\n\n try {\n program = parse(code, { sourceType: 'module', plugins }).program as unknown as Node;\n } catch {\n // an unparseable module that mentions the package cannot be scanned — degrade soundly\n return { names: new Set(), dynamic: true };\n }\n\n const locals = new Set<string>();\n const scan: WormholeScan = { names: new Set(), dynamic: false };\n\n for (const statement of program['body'] as Node[]) {\n const source = (statement['source'] as Node | null)?.['value'];\n\n if (source !== PACKAGE_NAME) {\n continue;\n }\n\n if (statement.type === 'ImportDeclaration') {\n for (const specifier of statement['specifiers'] as Node[]) {\n if (specifier.type === 'ImportSpecifier') {\n const imported = specifier['imported'] as Node;\n const name = imported.type === 'Identifier' ? (imported['name'] as string) : (imported['value'] as string);\n\n if (name === 'wormholes') {\n locals.add((specifier['local'] as Node)['name'] as string);\n }\n } else if (specifier.type === 'ImportNamespaceSpecifier') {\n scan.dynamic = true;\n }\n }\n } else {\n // export { wormhole } from '...' / export * from '...' — accesses happen elsewhere\n scan.dynamic = true;\n }\n }\n\n if (locals.size === 0 && !scan.dynamic) {\n return null;\n }\n\n walk(\n program,\n (node, parent, key) => {\n if (node.type !== 'Identifier' || !locals.has(node['name'] as string) || !parent) {\n return;\n }\n\n if (parent.type === 'ImportSpecifier') {\n return;\n }\n\n if (isMember(parent) && key === 'object') {\n const property = parent['property'] as Node;\n\n if (!parent['computed'] && property.type === 'Identifier') {\n scan.names.add(property['name'] as string);\n } else if (parent['computed'] && property.type === 'StringLiteral') {\n scan.names.add(property['value'] as string);\n } else {\n scan.dynamic = true;\n }\n\n return;\n }\n\n // a same-named identifier in a non-computed name position cannot reference the proxy value\n if ((key === 'property' || key === 'key') && !(parent['computed'] as boolean)) {\n return;\n }\n\n // the proxy escapes (aliased, passed, re-exported) — its accesses are untrackable\n scan.dynamic = true;\n },\n null,\n '',\n );\n\n return scan;\n}\n"],"mappings":";;AAEA,MAAM,eAAe;AAcrB,MAAM,kCAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAID,SAAS,OAAO,OAA+B;CAC7C,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,OAAQ,MAAe,SAAS;AACxF;AAEA,SAAS,KACP,MACA,OACA,QACA,KACM;CACN,IAAI,gBAAgB,IAAI,KAAK,IAAI,GAC/B;CAGF,MAAM,MAAM,QAAQ,GAAG;CAEvB,KAAK,MAAM,CAAC,UAAU,UAAU,OAAO,QAAQ,IAAI,GAAG;EACpD,IAAI,aAAa,OACf;EAGF,IAAI,OAAO,KAAK,GACd,KAAK,OAAO,OAAO,MAAM,QAAQ;OAC5B,IAAI,MAAM,QAAQ,KAAK,GACvB;QAAA,MAAM,QAAQ,OACjB,IAAI,OAAO,IAAI,GACb,KAAK,MAAM,OAAO,MAAM,QAAQ;EAAA;CAIxC;AACF;AAEA,SAAS,SAAS,MAAiC;CACjD,OAAO,MAAM,SAAS,sBAAsB,MAAM,SAAS;AAC7D;AAEA,SAAgB,mBAAmB,MAAc,IAAiC;CAChF,MAAM,OAAO,GAAG,SAAS,KAAK,KAAK,GAAG,SAAS,MAAM,KAAK,GAAG,SAAS,QAAQ;CAC9E,MAAM,UAAoC,CAAC;CAE3C,IAAI,MACF,QAAQ,KAAK,YAAY;CAI3B,IAAI,CAAC,GAAG,SAAS,KAAK,KAAK,CAAC,GAAG,SAAS,QAAQ,GAC9C,QAAQ,KAAK,KAAK;CAGpB,IAAI;CAEJ,IAAI;EACF,UAAU,MAAM,MAAM;GAAE,YAAY;GAAU;EAAQ,CAAC,CAAC,CAAC;CAC3D,QAAQ;EAEN,OAAO;GAAE,uBAAO,IAAI,IAAI;GAAG,SAAS;EAAK;CAC3C;CAEA,MAAM,yBAAS,IAAI,IAAY;CAC/B,MAAM,OAAqB;EAAE,uBAAO,IAAI,IAAI;EAAG,SAAS;CAAM;CAE9D,KAAK,MAAM,aAAa,QAAQ,SAAmB;EAGjD,IAFgB,UAAU,SAAS,GAAmB,aAEvC,cACb;EAGF,IAAI,UAAU,SAAS,qBAChB;QAAA,MAAM,aAAa,UAAU,eAChC,IAAI,UAAU,SAAS,mBAAmB;IACxC,MAAM,WAAW,UAAU;IAG3B,KAFa,SAAS,SAAS,eAAgB,SAAS,UAAsB,SAAS,cAE1E,aACX,OAAO,IAAK,UAAU,QAAQ,CAAU,OAAiB;GAE7D,OAAO,IAAI,UAAU,SAAS,4BAC5B,KAAK,UAAU;EAAA,OAKnB,KAAK,UAAU;CAEnB;CAEA,IAAI,OAAO,SAAS,KAAK,CAAC,KAAK,SAC7B,OAAO;CAGT,KACE,UACC,MAAM,QAAQ,QAAQ;EACrB,IAAI,KAAK,SAAS,gBAAgB,CAAC,OAAO,IAAI,KAAK,OAAiB,KAAK,CAAC,QACxE;EAGF,IAAI,OAAO,SAAS,mBAClB;EAGF,IAAI,SAAS,MAAM,KAAK,QAAQ,UAAU;GACxC,MAAM,WAAW,OAAO;GAExB,IAAI,CAAC,OAAO,eAAe,SAAS,SAAS,cAC3C,KAAK,MAAM,IAAI,SAAS,OAAiB;QACpC,IAAI,OAAO,eAAe,SAAS,SAAS,iBACjD,KAAK,MAAM,IAAI,SAAS,QAAkB;QAE1C,KAAK,UAAU;GAGjB;EACF;EAGA,KAAK,QAAQ,cAAc,QAAQ,UAAU,CAAE,OAAO,aACpD;EAIF,KAAK,UAAU;CACjB,GACA,MACA,EACF;CAEA,OAAO;AACT"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
import { n as Wormhole
|
|
2
|
-
|
|
1
|
+
import { n as Wormhole } from "./types-DiWa8aQg.js";
|
|
2
|
+
import { ReadonlyDeep } from "@entwico/dash";
|
|
3
3
|
//#region src/server.d.ts
|
|
4
4
|
/** One [wormhole, data] pair per element — data is checked against its own wormhole's T. */
|
|
5
|
-
type WormholeEntries<Ts extends readonly unknown[]> = { [K in keyof Ts]: readonly [Wormhole<Ts[K]>,
|
|
5
|
+
type WormholeEntries<Ts extends readonly unknown[]> = { [K in keyof Ts]: readonly [Wormhole<Ts[K]>, ReadonlyDeep<Ts[K]>]; };
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Provide wormhole values to server code that runs outside the request pipeline —
|
|
8
|
+
* tests above all, or out-of-request rendering (emails, jobs). `wormhole.get()`
|
|
9
|
+
* returns `data` anywhere in the async execution rooted at `fn`. Inside the app,
|
|
10
|
+
* the middleware is the way; values opened here are never delivered to the client.
|
|
9
11
|
*/
|
|
10
|
-
declare function
|
|
12
|
+
export declare function openWormholes<T, R>(wormhole: Wormhole<T>, data: ReadonlyDeep<T>, fn: () => R): R;
|
|
11
13
|
/**
|
|
12
|
-
* Open several wormholes at once for the duration of `fn
|
|
13
|
-
*
|
|
14
|
-
* ```typescript
|
|
15
|
-
* return open([
|
|
16
|
-
* [cartStore, cart],
|
|
17
|
-
* [sessionStore, session],
|
|
18
|
-
* ], () => next());
|
|
19
|
-
* ```
|
|
14
|
+
* Open several wormholes at once for the duration of `fn` — pass an array of
|
|
15
|
+
* `[wormhole, data]` pairs, each checked against its own wormhole.
|
|
20
16
|
*/
|
|
21
|
-
declare function
|
|
17
|
+
export declare function openWormholes<Ts extends readonly unknown[], R>(entries: readonly [...WormholeEntries<Ts>], fn: () => R): R;
|
|
22
18
|
//#endregion
|
|
23
|
-
export { open };
|
|
24
19
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;;;KAKK,gBAAgB,oCAClB,WAAW,eAAe,SAAS,GAAG,KAAK,aAAa,GAAG;;;;;;;wBAS9C,cAAc,GAAG,GAAG,UAAU,SAAS,IAAI,MAAM,aAAa,IAAI,UAAU,IAAI;;;;;wBAKhF,cAAc,+BAA+B,GAC3D,sBAAsB,gBAAgB,MACtC,UAAU,IACT"}
|
package/dist/server.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as als } from "./als-CntRqWdO.js";
|
|
2
2
|
//#region src/server.ts
|
|
3
|
-
|
|
4
|
-
function open(...args) {
|
|
3
|
+
function openWormholes(...args) {
|
|
5
4
|
const [entries, fn] = Array.isArray(args[0]) ? args : [[[args[0], args[1]]], args[2]];
|
|
6
5
|
const ctx = new Map(als.getStore());
|
|
7
|
-
for (const [wormhole, data] of entries)
|
|
8
|
-
ctx.set(wormhole.key, data);
|
|
9
|
-
globalThis[wormhole.key] ??= () => als.getStore()?.get(wormhole.key);
|
|
10
|
-
}
|
|
6
|
+
for (const [wormhole, data] of entries) ctx.set(wormhole.key, data);
|
|
11
7
|
return als.run(ctx, fn);
|
|
12
8
|
}
|
|
13
9
|
//#endregion
|
|
14
|
-
export {
|
|
10
|
+
export { openWormholes };
|
|
15
11
|
|
|
16
12
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["import type { ReadonlyDeep } from '@entwico/dash';\nimport { als } from './als.js';\nimport type { Wormhole } from './types.js';\n\n/** One [wormhole, data] pair per element — data is checked against its own wormhole's T. */\ntype WormholeEntries<Ts extends readonly unknown[]> = {\n [K in keyof Ts]: readonly [Wormhole<Ts[K]>, ReadonlyDeep<Ts[K]>];\n};\n\n/**\n * Provide wormhole values to server code that runs outside the request pipeline —\n * tests above all, or out-of-request rendering (emails, jobs). `wormhole.get()`\n * returns `data` anywhere in the async execution rooted at `fn`. Inside the app,\n * the middleware is the way; values opened here are never delivered to the client.\n */\nexport function openWormholes<T, R>(wormhole: Wormhole<T>, data: ReadonlyDeep<T>, fn: () => R): R;\n/**\n * Open several wormholes at once for the duration of `fn` — pass an array of\n * `[wormhole, data]` pairs, each checked against its own wormhole.\n */\nexport function openWormholes<Ts extends readonly unknown[], R>(\n entries: readonly [...WormholeEntries<Ts>],\n fn: () => R,\n): R;\nexport function openWormholes(...args: unknown[]): unknown {\n const [entries, fn] = (Array.isArray(args[0]) ? args : [[[args[0], args[1]]], args[2]]) as [\n readonly (readonly [Wormhole<unknown>, unknown])[],\n () => unknown,\n ];\n\n const ctx = new Map(als.getStore());\n\n for (const [wormhole, data] of entries) {\n ctx.set(wormhole.key, data);\n }\n\n return als.run(ctx, fn);\n}\n"],"mappings":";;AAwBA,SAAgB,cAAc,GAAG,MAA0B;CACzD,MAAM,CAAC,SAAS,MAAO,MAAM,QAAQ,KAAK,EAAE,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC,GAAG,KAAK,EAAE;CAKrF,MAAM,MAAM,IAAI,IAAI,IAAI,SAAS,CAAC;CAElC,KAAK,MAAM,CAAC,UAAU,SAAS,SAC7B,IAAI,IAAI,SAAS,KAAK,IAAI;CAG5B,OAAO,IAAI,IAAI,KAAK,EAAE;AACxB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ReadonlyDeep } from "@entwico/dash";
|
|
2
|
+
import { APIContext } from "astro";
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A wormhole transfers state from server middleware to client-side components.
|
|
6
|
+
*
|
|
7
|
+
* **Security:** wormhole data is serialized into an inline `<script>` tag and sent to the browser.
|
|
8
|
+
* Never store secrets (tokens, API keys, credentials) in a wormhole.
|
|
9
|
+
*
|
|
10
|
+
* The stored value is exposed as deeply readonly (`ReadonlyDeep` of `@entwico/dash`) — the only way to change it is `set()`
|
|
11
|
+
* (client) or the middleware's per-request values (server), which keeps subscribers in sync.
|
|
12
|
+
*/
|
|
13
|
+
interface Wormhole<in out T> {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly key: string;
|
|
16
|
+
get(): ReadonlyDeep<T>;
|
|
17
|
+
/**
|
|
18
|
+
* Update the wormhole value on the **client only** — every subscriber on the page
|
|
19
|
+
* (islands and scripts alike) is notified. Throws on the server, where values are
|
|
20
|
+
* request-scoped and provided by the middleware.
|
|
21
|
+
*/
|
|
22
|
+
set(data: ReadonlyDeep<T>): void;
|
|
23
|
+
subscribe(fn: (data: ReadonlyDeep<T>) => void): () => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolves a wormhole's value for one request — called by the wormhole middleware
|
|
27
|
+
* for every request whose route can read the wormhole. Return `undefined` to
|
|
28
|
+
* leave it closed for the request.
|
|
29
|
+
*/
|
|
30
|
+
type WormholeHandler<T> = (context: APIContext) => T | undefined | Promise<T | undefined>;
|
|
31
|
+
/** what `defineWormhole()` takes — the actions-style definition object */
|
|
32
|
+
type WormholeDefinition<T> = {
|
|
33
|
+
handler: WormholeHandler<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Load on every request instead of only where the build found a reader — for
|
|
36
|
+
* wormholes read by components the build cannot attribute to a route (islands
|
|
37
|
+
* chosen dynamically at render time).
|
|
38
|
+
*/
|
|
39
|
+
eager?: boolean | undefined;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The project's wormhole names and value types. Empty by default — the integration
|
|
43
|
+
* generates a type stub (via `injectTypes`) that merges the `src/wormholes.ts`
|
|
44
|
+
* registry into this interface, which types the `wormholes` proxy.
|
|
45
|
+
*/
|
|
46
|
+
interface WormholeRegistry {}
|
|
47
|
+
/** maps a registry module's `{ name: Wormhole<T> }` shape to `{ name: T }` */
|
|
48
|
+
type UnwrapWormholes<R> = { [K in keyof R]: R[K] extends Wormhole<infer T> ? T : never; };
|
|
49
|
+
/** the shape of the `wormholes` proxy: one typed accessor per registry entry */
|
|
50
|
+
type WormholeMap = { readonly [K in keyof WormholeRegistry]: Wormhole<WormholeRegistry[K]>; };
|
|
51
|
+
//#endregion
|
|
52
|
+
export { WormholeMap as a, WormholeHandler as i, Wormhole as n, WormholeRegistry as o, WormholeDefinition as r, UnwrapWormholes as t };
|
|
53
|
+
//# sourceMappingURL=types-DiWa8aQg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types-DiWa8aQg.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;UAciB,gBAAgB;WACtB;WACA;EACT,OAAO,aAAa;;;;;;EAMpB,IAAI,MAAM,aAAa;EACvB,UAAU,KAAK,MAAM,aAAa;;;;;;;KAQxB,gBAAgB,MAAM,SAAS,eAAe,gBAAgB,QAAQ;;KAGtE,mBAAmB;EAC7B,SAAS,gBAAgB;;;;;;EAMzB;;;;;;;UASe;;KAGL,gBAAgB,QAAQ,WAAW,IAAI,EAAE,WAAW,eAAe,KAAK;;KAGxE,0BAA0B,WAAW,mBAAmB,SAAS,iBAAiB"}
|
package/package.json
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astroscope/wormhole",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Share dynamic server data with React islands and client scripts — typed,
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Share dynamic server data with React islands and client scripts — typed, loaded per route, streamed with the HTML, sliced per island",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
|
-
"
|
|
11
|
+
"browser": "./dist/index.browser.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
12
13
|
},
|
|
13
14
|
"./server": {
|
|
14
15
|
"types": "./dist/server.d.ts",
|
|
15
16
|
"import": "./dist/server.js"
|
|
16
17
|
},
|
|
18
|
+
"./middleware": {
|
|
19
|
+
"types": "./dist/middleware-entrypoint.d.ts",
|
|
20
|
+
"import": "./dist/middleware-entrypoint.js"
|
|
21
|
+
},
|
|
17
22
|
"./react": {
|
|
18
23
|
"types": "./dist/react/index.d.ts",
|
|
19
24
|
"import": "./dist/react/index.js"
|
|
20
|
-
},
|
|
21
|
-
"./astro": {
|
|
22
|
-
"import": "./src/astro/index.ts"
|
|
23
25
|
}
|
|
24
26
|
},
|
|
25
27
|
"files": [
|
|
26
|
-
"dist"
|
|
27
|
-
"src/astro"
|
|
28
|
+
"dist"
|
|
28
29
|
],
|
|
29
30
|
"sideEffects": false,
|
|
30
31
|
"publishConfig": {
|
|
@@ -49,17 +50,25 @@
|
|
|
49
50
|
"url": "https://github.com/entwico/astroscope/issues"
|
|
50
51
|
},
|
|
51
52
|
"homepage": "https://github.com/entwico/astroscope/tree/main/packages/wormhole#readme",
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@babel/parser": "^8.0.4",
|
|
55
|
+
"@entwico/dash": "^1.3.0"
|
|
56
|
+
},
|
|
52
57
|
"devDependencies": {
|
|
53
|
-
"@
|
|
54
|
-
"@types/
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
58
|
+
"@astroscope/node": "^3.0.0",
|
|
59
|
+
"@types/node": "^26.5.0",
|
|
60
|
+
"@types/react": "^19.2.18",
|
|
61
|
+
"astro": "^7.3.2",
|
|
62
|
+
"react": "^19.2.8",
|
|
63
|
+
"tsdown": "^0.23.0",
|
|
64
|
+
"typescript": "^6.0.3",
|
|
65
|
+
"vite": "^8.2.2"
|
|
59
66
|
},
|
|
60
67
|
"peerDependencies": {
|
|
68
|
+
"@astroscope/node": "^3.0.0",
|
|
61
69
|
"astro": "^7.0.0",
|
|
62
|
-
"react": "^18.0.0 || ^19.0.0"
|
|
70
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
71
|
+
"vite": "^8.0.0"
|
|
63
72
|
},
|
|
64
73
|
"peerDependenciesMeta": {
|
|
65
74
|
"react": {
|
|
@@ -67,7 +76,7 @@
|
|
|
67
76
|
}
|
|
68
77
|
},
|
|
69
78
|
"scripts": {
|
|
70
|
-
"build": "tsdown
|
|
79
|
+
"build": "tsdown",
|
|
71
80
|
"typecheck": "tsc --noEmit",
|
|
72
81
|
"lint": "eslint src --cache",
|
|
73
82
|
"lint:fix": "eslint src --fix"
|
package/dist/types-Q2gaO_N_.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
//#region src/types.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Recursively marks all properties of `T` as `readonly`.
|
|
4
|
-
*
|
|
5
|
-
* Wormhole data is JSON-serializable by design (it is inlined into a `<script>` tag),
|
|
6
|
-
* so only plain objects, arrays, and primitives need to be handled.
|
|
7
|
-
*/
|
|
8
|
-
type DeepReadonly<T> = T extends ((...args: never[]) => unknown) ? T : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
|
|
9
|
-
/**
|
|
10
|
-
* A wormhole transfers state from server middleware to client-side components.
|
|
11
|
-
*
|
|
12
|
-
* **Security:** wormhole data is serialized into an inline `<script>` tag and sent to the browser.
|
|
13
|
-
* Never store secrets (tokens, API keys, credentials) in a wormhole.
|
|
14
|
-
*
|
|
15
|
-
* The stored value is exposed as deeply readonly — the only way to change it is `set()`
|
|
16
|
-
* (client) or `open()` (server) with a new value, which keeps subscribers in sync.
|
|
17
|
-
*/
|
|
18
|
-
interface Wormhole<in out T> {
|
|
19
|
-
readonly name: string;
|
|
20
|
-
readonly key: string;
|
|
21
|
-
get(): DeepReadonly<T>;
|
|
22
|
-
/**
|
|
23
|
-
* Update the wormhole value on the **client only**.
|
|
24
|
-
* Throws on the server — use `open(wormhole, data, fn)` from `@astroscope/wormhole/server` instead.
|
|
25
|
-
*/
|
|
26
|
-
set(data: DeepReadonly<T>): void;
|
|
27
|
-
subscribe(fn: (data: DeepReadonly<T>) => void): () => void;
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
|
-
export { Wormhole as n, DeepReadonly as t };
|
|
31
|
-
//# sourceMappingURL=types-Q2gaO_N_.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-Q2gaO_N_.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;AAMA;;;;;KAAY,YAAA,MAAkB,CAAA,cAAc,IAAA,yBACxC,CAAA,GACA,CAAA,yCACyB,CAAA,GAAI,YAAA,CAAa,CAAA,CAAE,CAAA,OAC1C,CAAA;;;;;;;;;;UAaW,QAAA;EAAA,SACN,IAAA;EAAA,SACA,GAAA;EACT,GAAA,IAAO,YAAA,CAAa,CAAA;EAjBwB;;;;EAsB5C,GAAA,CAAI,IAAA,EAAM,YAAA,CAAa,CAAA;EACvB,SAAA,CAAU,EAAA,GAAK,IAAA,EAAM,YAAA,CAAa,CAAA;AAAA"}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
interface Props {
|
|
3
|
-
wormhole: { readonly name: string; readonly key: string; get(): unknown };
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
const { wormhole } = Astro.props;
|
|
7
|
-
|
|
8
|
-
let data: unknown;
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
data = wormhole.get();
|
|
12
|
-
} catch {
|
|
13
|
-
data = undefined;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const key = wormhole.key;
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
data !== undefined ? (
|
|
21
|
-
<script is:inline define:vars={{ data, key }}>
|
|
22
|
-
globalThis[key] = function(){return data;}
|
|
23
|
-
</script>
|
|
24
|
-
) : (
|
|
25
|
-
<script is:inline define:vars={{ wormholeName: wormhole.name }}>
|
|
26
|
-
console.error(`[wormhole] "${wormholeName}" has no data — is the middleware configured?`);
|
|
27
|
-
</script>
|
|
28
|
-
)
|
|
29
|
-
}
|
package/src/astro/index.ts
DELETED