@astroscope/wormhole 2.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1,154 +1,4 @@
1
- import { r as als, t as assignWormholeNames } from "./define-BO-pYQc1.js";
2
- import { RECOMMENDED_EXCLUDES, shouldExclude } from "@astroscope/node/excludes";
3
- import { wormholes } from "virtual:@astroscope/wormhole/registry";
4
- import { registerDocumentEmitter, registerIslandEmitter } from "@astroscope/node/islands";
5
- import { log } from "@astroscope/node/log";
6
- import { manifest } from "virtual:@astroscope/wormhole/manifest";
7
- //#region src/client-state.ts
8
- /**
9
- * Script generation for wormhole delivery, shipped through the `@astroscope/node`
10
- * islands pipeline (see islands-emitter.ts for the placement policy).
11
- */
12
- /** values may contain `<\/script>` — escape `<` so they cannot end the tag */
13
- function jsonForScript(value) {
14
- return JSON.stringify(value).replace(/</g, "\\u003c");
15
- }
16
- /**
17
- * Inline script writing entries onto `self.__wormholes__` and notifying listeners —
18
- * the counterpart of the browser proxy's entry protocol, safe in either order.
19
- */
20
- function createWormholeMergeScript(entries) {
21
- return `<script>{const s=self.__wormholes__??={},d=${jsonForScript(entries)};for(const n in d){const e=s[n]??={};e.v=d[n];e.l&&e.l.forEach(f=>f(e.v))}}<\/script>`;
22
- }
23
- //#endregion
24
- //#region src/manifest.ts
25
- /** null in dev and when the build produced no manifest (broken deploy → full delivery fallback) */
26
- function getWormholeManifest() {
27
- return manifest;
28
- }
29
- //#endregion
30
- //#region src/request-store.ts
31
- const requestWormholes = /* @__PURE__ */ new WeakMap();
32
- function setRequestWormholes(request, wormholes) {
33
- requestWormholes.set(request, wormholes);
34
- }
35
- function getRequestWormholes(request) {
36
- return requestWormholes.get(request);
37
- }
38
- //#endregion
39
- //#region src/islands-emitter.ts
40
- /**
41
- * Contributes each island's wormhole slice: an inline merge script with the values
42
- * of every open wormhole the island's full chunk closure (dynamic imports included)
43
- * can reach, emitted before the island tag — parsed strictly before the island
44
- * connects, even mid-stream. Names already written earlier in the document are
45
- * skipped; a `*` chunk (dynamic proxy access) degrades to all open wormholes.
46
- */
47
- const IS_DEV = !!import.meta.env?.DEV;
48
- let registered = false;
49
- let warnedMissingManifest = false;
50
- /** public url of a chunk → manifest chunk name, e.g. `…/_astro/Counter.abc.js` → `Counter.abc` */
51
- function urlToChunkName(url) {
52
- return url.slice(url.lastIndexOf("/") + 1).replace(/\.js$/, "");
53
- }
54
- /**
55
- * The document-level wormhole delivery, exported for tests: everything open as
56
- * `head` content in dev (or without a manifest), script-entry names as `end`
57
- * content in prod — running after the rewriter, names the islands emitter
58
- * already wrote are skipped race-free.
59
- */
60
- function createWormholeDocumentEmitter(dev = IS_DEV) {
61
- return (context) => {
62
- const request = getRequestWormholes(context.request);
63
- if (!request || request.values.size === 0) return null;
64
- if (dev) return { head: createWormholeMergeScript(Object.fromEntries(request.values)) };
65
- const manifest = getWormholeManifest();
66
- if (!manifest) {
67
- if (!warnedMissingManifest) {
68
- warnedMissingManifest = true;
69
- log.error("wormhole manifest not found — delivering all open wormholes with every page (is the integration configured?)");
70
- }
71
- return { head: createWormholeMergeScript(Object.fromEntries(request.values)) };
72
- }
73
- const names = manifest.scripts.includes("*") ? [...request.values.keys()] : manifest.scripts.filter((name) => request.values.has(name));
74
- if (names.length === 0) return null;
75
- return { end: () => {
76
- const fresh = names.filter((name) => !request.emitted.has(name));
77
- if (fresh.length === 0) return null;
78
- return createWormholeMergeScript(Object.fromEntries(fresh.map((name) => [name, request.values.get(name)])));
79
- } };
80
- };
81
- }
82
- function registerWormholeEmitters() {
83
- if (registered) return;
84
- registered = true;
85
- registerDocumentEmitter(createWormholeDocumentEmitter());
86
- registerIslandEmitter((island, context) => {
87
- if (!context) return null;
88
- const request = getRequestWormholes(context.request);
89
- const manifest = getWormholeManifest();
90
- if (!request || request.values.size === 0 || !manifest) return null;
91
- let all = false;
92
- const reachable = /* @__PURE__ */ new Set();
93
- for (const url of island.fullClosure) for (const name of manifest.chunks[urlToChunkName(url)] ?? []) if (name === "*") all = true;
94
- else reachable.add(name);
95
- const entries = {};
96
- let count = 0;
97
- for (const name of all ? request.values.keys() : reachable) {
98
- if (!request.values.has(name) || request.emitted.has(name)) continue;
99
- request.emitted.add(name);
100
- entries[name] = request.values.get(name);
101
- count++;
102
- }
103
- if (count === 0) return null;
104
- return { html: createWormholeMergeScript(entries) };
105
- });
106
- }
107
- //#endregion
108
- //#region src/middleware.ts
109
- /**
110
- * Create the wormhole middleware: resolves the per-request values, makes them
111
- * readable on the server (frontmatter, endpoints, SSR islands) for the duration of
112
- * the request, and delivers them to the client — sliced per island through the
113
- * `@astroscope/node` islands pipeline, at stream end for astro `<script>` consumers,
114
- * and in full in dev. The registry comes from `src/wormholes.ts` via the
115
- * integration, so values are simply keyed by name.
116
- *
117
- * @example
118
- * ```typescript
119
- * // src/middleware.ts
120
- * import { createWormholeMiddleware } from '@astroscope/wormhole/server';
121
- *
122
- * export const onRequest = createWormholeMiddleware({
123
- * values: (ctx) => ({
124
- * session: { loggedIn: ctx.locals.user !== undefined },
125
- * }),
126
- * });
127
- * ```
128
- */
129
- function createWormholeMiddleware(options) {
130
- assignWormholeNames(wormholes);
131
- registerWormholeEmitters();
132
- return async (ctx, next) => {
133
- if (shouldExclude(ctx, options.exclude ?? RECOMMENDED_EXCLUDES)) return next();
134
- const resolved = await options.values(ctx);
135
- const open = /* @__PURE__ */ new Map();
136
- const scope = new Map(als.getStore());
137
- for (const [name, data] of Object.entries(resolved)) {
138
- if (data === void 0) continue;
139
- const wormhole = wormholes[name];
140
- if (!wormhole) throw new Error(`wormhole "${name}" is not in the src/wormholes.ts registry`);
141
- open.set(name, data);
142
- scope.set(wormhole.key, data);
143
- }
144
- setRequestWormholes(ctx.request, {
145
- values: open,
146
- emitted: /* @__PURE__ */ new Set()
147
- });
148
- return als.run(scope, () => next());
149
- };
150
- }
151
- //#endregion
1
+ import { t as als } from "./als-CntRqWdO.js";
152
2
  //#region src/server.ts
153
3
  function openWormholes(...args) {
154
4
  const [entries, fn] = Array.isArray(args[0]) ? args : [[[args[0], args[1]]], args[2]];
@@ -157,6 +7,6 @@ function openWormholes(...args) {
157
7
  return als.run(ctx, fn);
158
8
  }
159
9
  //#endregion
160
- export { createWormholeMiddleware, openWormholes };
10
+ export { openWormholes };
161
11
 
162
12
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","names":["registry"],"sources":["../src/client-state.ts","../src/manifest.ts","../src/request-store.ts","../src/islands-emitter.ts","../src/middleware.ts","../src/server.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","/**\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 { type DocumentEmitter, registerDocumentEmitter, registerIslandEmitter } from '@astroscope/node/islands';\nimport { log } from '@astroscope/node/log';\nimport { createWormholeMergeScript } from './client-state.js';\nimport { getWormholeManifest } from './manifest.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/** public url of a chunk → manifest chunk name, e.g. `…/_astro/Counter.abc.js` → `Counter.abc` */\nfunction urlToChunkName(url: string): string {\n return url.slice(url.lastIndexOf('/') + 1).replace(/\\.js$/, '');\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 let all = false;\n const reachable = new Set<string>();\n\n for (const url of island.fullClosure) {\n for (const name of manifest.chunks[urlToChunkName(url)] ?? []) {\n if (name === '*') {\n all = true;\n } else {\n reachable.add(name);\n }\n }\n }\n\n const entries: Record<string, unknown> = {};\n let count = 0;\n\n for (const name of all ? request.values.keys() : reachable) {\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 { type ExcludePattern, RECOMMENDED_EXCLUDES, shouldExclude } from '@astroscope/node/excludes';\nimport type { APIContext, MiddlewareHandler } from 'astro';\nimport { wormholes as registry } from 'virtual:@astroscope/wormhole/registry';\nimport { als } from './als.js';\nimport { assignWormholeNames } from './define.js';\nimport { registerWormholeEmitters } from './islands-emitter.js';\nimport { setRequestWormholes } from './request-store.js';\nimport type { DeepReadonly, WormholeRegistry } from './types.js';\n\n/**\n * Per-request values keyed by registry name — omit a key (or pass undefined) to\n * leave that wormhole closed. Typed from the `WormholeRegistry` interface the\n * integration generates from `src/wormholes.ts`.\n */\nexport type WormholeValues = {\n [K in keyof WormholeRegistry]?: DeepReadonly<WormholeRegistry[K]> | undefined;\n};\n\nexport type WormholeMiddlewareOptions = {\n /** resolve the request's wormhole values */\n values: (context: APIContext) => WormholeValues | Promise<WormholeValues>;\n /**\n * Patterns to exclude from wormhole setup. Defaults to RECOMMENDED_EXCLUDES.\n */\n exclude?: ExcludePattern[] | ((context: APIContext) => boolean) | undefined;\n};\n\n/**\n * Create the wormhole middleware: resolves the per-request values, makes them\n * readable on the server (frontmatter, endpoints, SSR islands) for the duration of\n * the request, and delivers them to the client — sliced per island through the\n * `@astroscope/node` islands pipeline, at stream end for astro `<script>` consumers,\n * and in full in dev. The registry comes from `src/wormholes.ts` via the\n * integration, so values are simply keyed by name.\n *\n * @example\n * ```typescript\n * // src/middleware.ts\n * import { createWormholeMiddleware } from '@astroscope/wormhole/server';\n *\n * export const onRequest = createWormholeMiddleware({\n * values: (ctx) => ({\n * session: { loggedIn: ctx.locals.user !== undefined },\n * }),\n * });\n * ```\n */\nexport function createWormholeMiddleware(options: WormholeMiddlewareOptions): MiddlewareHandler {\n assignWormholeNames(registry);\n registerWormholeEmitters();\n\n return async (ctx, next) => {\n if (shouldExclude(ctx, options.exclude ?? RECOMMENDED_EXCLUDES)) {\n return next();\n }\n\n const resolved = await options.values(ctx);\n const open = new Map<string, unknown>();\n const scope = new Map<string, unknown>(als.getStore());\n\n for (const [name, data] of Object.entries(resolved as Record<string, unknown>)) {\n if (data === undefined) {\n continue;\n }\n\n const wormhole = registry[name];\n\n if (!wormhole) {\n throw new Error(`wormhole \"${name}\" is not in the src/wormholes.ts registry`);\n }\n\n open.set(name, data);\n scope.set(wormhole.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 // nothing to transform here\n return als.run(scope, () => next());\n };\n}\n","import { als } from './als.js';\nimport type { DeepReadonly, Wormhole } from './types.js';\n\nexport { createWormholeMiddleware } from './middleware.js';\nexport type { WormholeMiddlewareOptions, WormholeValues } from './middleware.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]>, DeepReadonly<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: DeepReadonly<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":"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"}
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
11
+ * change it is `set()` (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-v_ftArqH.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-v_ftArqH.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,7 +1,7 @@
1
1
  {
2
2
  "name": "@astroscope/wormhole",
3
- "version": "2.0.0",
4
- "description": "Share dynamic server data with React islands and client scripts — typed, streamed with the HTML, sliced per island",
3
+ "version": "3.0.1",
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",
@@ -15,6 +15,10 @@
15
15
  "types": "./dist/server.d.ts",
16
16
  "import": "./dist/server.js"
17
17
  },
18
+ "./middleware": {
19
+ "types": "./dist/middleware-entrypoint.d.ts",
20
+ "import": "./dist/middleware-entrypoint.js"
21
+ },
18
22
  "./react": {
19
23
  "types": "./dist/react/index.d.ts",
20
24
  "import": "./dist/react/index.js"
@@ -47,20 +51,22 @@
47
51
  },
48
52
  "homepage": "https://github.com/entwico/astroscope/tree/main/packages/wormhole#readme",
49
53
  "dependencies": {
50
- "@babel/parser": "^8.0.4"
54
+ "@babel/parser": "^8.0.7",
55
+ "@entwico/dash": "^1.3.0"
51
56
  },
52
57
  "devDependencies": {
53
- "@astroscope/node": "^2.0.0",
54
- "@types/node": "^26.3.0",
55
- "@types/react": "^19.2.18",
56
- "astro": "^7.2.6",
57
- "react": "^19.2.8",
58
- "tsdown": "^0.22.14",
58
+ "@astroscope/node": "^4.0.0",
59
+ "@types/node": "^26.6.4",
60
+ "@types/react": "^19.3.0",
61
+ "astro": "^7.3.7",
62
+ "react": "^19.3.0",
63
+ "tsdown": "^0.23.0",
59
64
  "typescript": "^6.0.3",
60
- "vite": "^8.2.2"
65
+ "vite": "^8.3.4",
66
+ "vitest": "^5.0.3"
61
67
  },
62
68
  "peerDependencies": {
63
- "@astroscope/node": "^2.0.0",
69
+ "@astroscope/node": "^4.0.0",
64
70
  "astro": "^7.0.0",
65
71
  "react": "^18.0.0 || ^19.0.0",
66
72
  "vite": "^8.0.0"
@@ -1 +0,0 @@
1
- {"version":3,"file":"define-BO-pYQc1.js","names":[],"sources":["../src/als.ts","../src/define.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\n// a single ALS holds an immutable map of all open wormholes (keyed by wormhole key);\n// keyed on globalThis so the vite-runner and native module instances share one context\nexport const als: AsyncLocalStorage<ReadonlyMap<string, unknown>> = ((globalThis as any)[\n Symbol.for('@astroscope/wormhole/als')\n] ??= new AsyncLocalStorage());\n","import { als } from './als.js';\nimport { wormholeKey } from './key.js';\nimport type { DeepReadonly, Wormhole } from './types.js';\n\n// name assignment must survive dual module instances (vite runner + native), so the\n// assigner rides the wormhole object itself under a Symbol.for key\nconst ASSIGN = Symbol.for('@astroscope/wormhole.assign');\n\n/**\n * Define a wormhole for the `src/wormholes.ts` registry. The wormhole's name is the\n * registry key — it is assigned when the registry is passed to\n * `createWormholeMiddleware()` (or `assignWormholeNames()`).\n *\n * **Security:** wormhole data is serialized into the HTML and sent to the browser.\n * Never store secrets (tokens, API keys, credentials) in a wormhole.\n */\nexport function defineWormhole<T>(): Wormhole<T> {\n let name: string | undefined;\n\n const requireName = (): string => {\n if (name === undefined) {\n throw new Error('wormhole is not registered — pass its registry to createWormholeMiddleware()');\n }\n\n return name;\n };\n\n const key = (): string => wormholeKey(requireName());\n\n // methods must not rely on `this` — consumers pass them around detached\n // (e.g. useSyncExternalStore(wormhole.subscribe, wormhole.get, wormhole.get))\n const wormhole: Wormhole<T> & { [ASSIGN](next: string): void } = {\n get name() {\n return requireName();\n },\n\n get key() {\n return key();\n },\n\n get(): DeepReadonly<T> {\n const value = als.getStore()?.get(key());\n\n if (value === undefined) {\n throw new Error(\n `wormhole \"${requireName()}\" is not open for this request — is it provided by the wormhole middleware?`,\n );\n }\n\n return value as DeepReadonly<T>;\n },\n\n set(): void {\n throw new Error(\n `wormhole \"${requireName()}\" set() cannot be called on the server — values are request-scoped and provided by the middleware`,\n );\n },\n\n subscribe(): () => void {\n // server values never change within a request — nothing to notify\n return () => {};\n },\n\n [ASSIGN](next: string): void {\n if (name !== undefined && name !== next) {\n throw new Error(`wormhole is registered under two names: \"${name}\" and \"${next}\"`);\n }\n\n name = next;\n },\n };\n\n return wormhole;\n}\n\n/**\n * Assign each wormhole its registry key as name. Called by `createWormholeMiddleware()`;\n * exposed for setups that read wormholes on the server without the middleware.\n */\nexport function assignWormholeNames(registry: Record<string, object>): void {\n for (const [name, wormhole] of Object.entries(registry)) {\n const assign = (wormhole as { [ASSIGN]?: (next: string) => void })[ASSIGN];\n\n if (typeof assign !== 'function') {\n throw new Error(\n `registry entry \"${name}\" is not a wormhole — the registry must contain defineWormhole() values only`,\n );\n }\n\n assign(name);\n }\n}\n"],"mappings":";;;AAIA,MAAa,MAAwD,WACnE,OAAO,IAAI,0BAA0B,OACjC,IAAI,kBAAkB;;;ACA5B,MAAM,SAAS,OAAO,IAAI,6BAA6B;;;;;;;;;AAUvD,SAAgB,iBAAiC;CAC/C,IAAI;CAEJ,MAAM,oBAA4B;EAChC,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,8EAA8E;EAGhG,OAAO;CACT;CAEA,MAAM,YAAoB,YAAY,YAAY,CAAC;CA6CnD,OAAO;EAxCL,IAAI,OAAO;GACT,OAAO,YAAY;EACrB;EAEA,IAAI,MAAM;GACR,OAAO,IAAI;EACb;EAEA,MAAuB;GACrB,MAAM,QAAQ,IAAI,SAAS,CAAC,EAAE,IAAI,IAAI,CAAC;GAEvC,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MACR,aAAa,YAAY,EAAE,4EAC7B;GAGF,OAAO;EACT;EAEA,MAAY;GACV,MAAM,IAAI,MACR,aAAa,YAAY,EAAE,kGAC7B;EACF;EAEA,YAAwB;GAEtB,aAAa,CAAC;EAChB;EAEA,CAAC,QAAQ,MAAoB;GAC3B,IAAI,SAAS,KAAA,KAAa,SAAS,MACjC,MAAM,IAAI,MAAM,4CAA4C,KAAK,SAAS,KAAK,EAAE;GAGnF,OAAO;EACT;CAGY;AAChB;;;;;AAMA,SAAgB,oBAAoB,UAAwC;CAC1E,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,QAAQ,GAAG;EACvD,MAAM,SAAU,SAAmD;EAEnE,IAAI,OAAO,WAAW,YACpB,MAAM,IAAI,MACR,mBAAmB,KAAK,6EAC1B;EAGF,OAAO,IAAI;CACb;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"scan-4GXufA9_.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');\n const plugins: ('typescript' | 'jsx')[] = [];\n\n if (isTs) {\n plugins.push('typescript');\n }\n\n if (!id.endsWith('.ts')) {\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;CACrD,MAAM,UAAoC,CAAC;CAE3C,IAAI,MACF,QAAQ,KAAK,YAAY;CAG3B,IAAI,CAAC,GAAG,SAAS,KAAK,GACpB,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"}
@@ -1,42 +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 the middleware's per-request values (server), 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** — every subscriber on the page
24
- * (islands and scripts alike) is notified. Throws on the server, where values are
25
- * request-scoped and provided by the middleware.
26
- */
27
- set(data: DeepReadonly<T>): void;
28
- subscribe(fn: (data: DeepReadonly<T>) => void): () => void;
29
- }
30
- /**
31
- * The project's wormhole names and value types. Empty by default — the integration
32
- * generates a type stub (via `injectTypes`) that merges the `src/wormholes.ts`
33
- * registry into this interface, which types the `wormholes` proxy.
34
- */
35
- interface WormholeRegistry {}
36
- /** maps a registry module's `{ name: Wormhole<T> }` shape to `{ name: T }` */
37
- type UnwrapWormholes<R> = { [K in keyof R]: R[K] extends Wormhole<infer T> ? T : never; };
38
- /** the shape of the `wormholes` proxy: one typed accessor per registry entry */
39
- type WormholeMap = { readonly [K in keyof WormholeRegistry]: Wormhole<WormholeRegistry[K]>; };
40
- //#endregion
41
- export { WormholeRegistry as a, WormholeMap as i, UnwrapWormholes as n, Wormhole as r, DeepReadonly as t };
42
- //# sourceMappingURL=types-okMd-V3s.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-okMd-V3s.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;KAMY,aAAa,KAAK,eAAc,6BACxC,IACA,+BACc,WAAW,IAAI,aAAa,EAAE,SAC1C;;;;;;;;;;UAaW,gBAAgB;WACtB;WACA;EACT,OAAO,aAAa;;;;;;EAMpB,IAAI,MAAM,aAAa;EACvB,UAAU,KAAK,MAAM,aAAa;;;;;;;UASnB;;KAGL,gBAAgB,QAAQ,WAAW,IAAI,EAAE,WAAW,eAAe,KAAK;;KAGxE,0BAA0B,WAAW,mBAAmB,SAAS,iBAAiB"}