@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/README.md +32 -43
- package/dist/als-CntRqWdO.js +7 -0
- package/dist/als-CntRqWdO.js.map +1 -0
- package/dist/{define-BO-pYQc1.js → define-DAoqsUVA.js} +33 -11
- package/dist/define-DAoqsUVA.js.map +1 -0
- package/dist/index.browser.d.ts +6 -6
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +33 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -27
- package/dist/index.js.map +1 -1
- package/dist/middleware-entrypoint.d.ts +4 -0
- package/dist/middleware-entrypoint.d.ts.map +1 -0
- package/dist/middleware-entrypoint.js +269 -0
- package/dist/middleware-entrypoint.js.map +1 -0
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/{scan-4GXufA9_.js → scan-BDYo2M_0.js} +9 -7
- package/dist/scan-BDYo2M_0.js.map +1 -0
- package/dist/server.d.ts +5 -44
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -152
- package/dist/server.js.map +1 -1
- package/dist/types-v_ftArqH.d.ts +53 -0
- package/dist/types-v_ftArqH.d.ts.map +1 -0
- package/package.json +17 -11
- package/dist/define-BO-pYQc1.js.map +0 -1
- package/dist/scan-4GXufA9_.js.map +0 -1
- package/dist/types-okMd-V3s.d.ts +0 -42
- package/dist/types-okMd-V3s.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -6,9 +6,11 @@ Share dynamic server data with React islands and client scripts — typed, strea
|
|
|
6
6
|
|
|
7
7
|
Astro recommends [nanostores](https://docs.astro.build/en/recipes/sharing-state/) for sharing state between islands, but nanostores are client-only — there's no built-in way to hydrate them with server data during SSR.
|
|
8
8
|
|
|
9
|
-
`@astroscope/wormhole` bridges this gap: define your wormholes in a registry
|
|
9
|
+
`@astroscope/wormhole` bridges this gap: define your wormholes in a registry — each with the handler that resolves its value per request — and read them anywhere — Astro frontmatter, React islands, `<script>` blocks — through one typed `wormholes` proxy. Client code imports nothing of yours: the data streams into the HTML exactly where it is needed.
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Loading is scoped per route.** The build scans server and client code to see which wormholes each route can reach; at runtime only those handlers run for a request. A wormhole nothing on the route reads costs **nothing** — no database call, no bytes.
|
|
12
|
+
|
|
13
|
+
**Delivery is sliced per island.** Every island receives exactly the wormholes its code reads, embedded in the HTML right before it. A wormhole no client code reads ships **zero bytes**.
|
|
12
14
|
|
|
13
15
|
**Typical use cases:**
|
|
14
16
|
|
|
@@ -20,11 +22,11 @@ Astro recommends [nanostores](https://docs.astro.build/en/recipes/sharing-state/
|
|
|
20
22
|
|
|
21
23
|
## Important notes
|
|
22
24
|
|
|
23
|
-
- **No secrets in wormholes.** Wormhole data is serialized into inline `<script>` tags and sent to the browser. Never store tokens, API keys, credentials, or any sensitive data in a wormhole. (In dev,
|
|
25
|
+
- **No secrets in wormholes.** Wormhole data is serialized into inline `<script>` tags and sent to the browser. Never store tokens, API keys, credentials, or any sensitive data in a wormhole. (In dev, _all_ open wormholes ship with every page; production slicing is an optimization, not a security boundary.)
|
|
24
26
|
- **Requires `@astroscope/node`** — per-island delivery uses its islands pipeline in production.
|
|
25
|
-
- **The registry is server-only.** Client code must never import `src/wormholes.ts` — it accesses values through the `wormholes` proxy instead. (
|
|
26
|
-
- **`set()` is client-only.** Server values are request-scoped and come from the
|
|
27
|
-
- **Values are deeply readonly.** `get()`, `subscribe()` and `useWormhole()` expose the value as `
|
|
27
|
+
- **The registry is server-only.** It carries the handlers, and with them your server code. Client code must never import `src/wormholes.ts` — it accesses values through the `wormholes` proxy instead. (The client build fails on such an import, and the eslint plugin's `no-registry-import` rule flags it in island code.)
|
|
28
|
+
- **`set()` is client-only.** Server values are request-scoped and come from the handlers.
|
|
29
|
+
- **Values are deeply readonly.** `get()`, `subscribe()` and `useWormhole()` expose the value as `ReadonlyDeep<T>` — in-place mutation would silently bypass subscribers. The only way to update is `set()` with a new value: `wormholes.counter.set({ ...wormholes.counter.get(), count: 1 })`.
|
|
28
30
|
- **Prerendered pages get no wormhole data** — values are per-request by definition.
|
|
29
31
|
|
|
30
32
|
## Examples
|
|
@@ -39,6 +41,7 @@ npm install @astroscope/wormhole
|
|
|
39
41
|
|
|
40
42
|
```ts
|
|
41
43
|
// astro.config.ts
|
|
44
|
+
import { defineConfig } from 'astro/config';
|
|
42
45
|
import node from '@astroscope/node';
|
|
43
46
|
import wormhole from '@astroscope/wormhole';
|
|
44
47
|
|
|
@@ -49,13 +52,13 @@ export default defineConfig({
|
|
|
49
52
|
});
|
|
50
53
|
```
|
|
51
54
|
|
|
52
|
-
The integration registers the build-time scanner and generates a type stub from your registry, so `wormholes.<name>` is fully typed everywhere.
|
|
55
|
+
The integration registers the build-time scanner, injects the middleware after your own `src/middleware.ts` (so handlers see everything it put on `locals`), and generates a type stub from your registry, so `wormholes.<name>` is fully typed everywhere. `wormhole({ exclude })` takes the same patterns as other astroscope middlewares (defaults to `RECOMMENDED_EXCLUDES`).
|
|
53
56
|
|
|
54
57
|
## Usage
|
|
55
58
|
|
|
56
59
|
### 1. Define the registry
|
|
57
60
|
|
|
58
|
-
One file, one export named `wormholes` — the keys are the wormhole names (same pattern as Astro Actions' `src/actions/index.ts`):
|
|
61
|
+
One file, one export named `wormholes` — the keys are the wormhole names, each entry carries its handler (same pattern as Astro Actions' `src/actions/index.ts`):
|
|
59
62
|
|
|
60
63
|
```ts
|
|
61
64
|
// src/wormholes.ts
|
|
@@ -67,30 +70,18 @@ export type Session = {
|
|
|
67
70
|
};
|
|
68
71
|
|
|
69
72
|
export const wormholes = {
|
|
70
|
-
session: defineWormhole
|
|
71
|
-
|
|
72
|
-
};
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
`src/wormholes/index.ts` works too.
|
|
76
|
-
|
|
77
|
-
### 2. Resolve values in middleware
|
|
78
|
-
|
|
79
|
-
```ts
|
|
80
|
-
// src/middleware.ts
|
|
81
|
-
import { createWormholeMiddleware } from '@astroscope/wormhole/server';
|
|
82
|
-
|
|
83
|
-
export const onRequest = createWormholeMiddleware({
|
|
84
|
-
values: async (ctx) => ({
|
|
85
|
-
session: { user: 'Alice', role: 'admin' },
|
|
86
|
-
cart: await loadCart(ctx),
|
|
73
|
+
session: defineWormhole({
|
|
74
|
+
handler: (ctx): Session => ({ user: ctx.locals.user, role: ctx.locals.role }),
|
|
87
75
|
}),
|
|
88
|
-
|
|
76
|
+
cart: defineWormhole({
|
|
77
|
+
handler: async (ctx) => loadCart(ctx),
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
89
80
|
```
|
|
90
81
|
|
|
91
|
-
|
|
82
|
+
`src/wormholes/index.ts` works too. A handler receives the request's `APIContext` and runs only for routes the build found a reader on; return `undefined` to leave the wormhole closed for the request. Each request is isolated.
|
|
92
83
|
|
|
93
|
-
###
|
|
84
|
+
### 2. Read anywhere
|
|
94
85
|
|
|
95
86
|
#### Astro frontmatter (SSR)
|
|
96
87
|
|
|
@@ -137,7 +128,7 @@ export function UserBadge() {
|
|
|
137
128
|
</script>
|
|
138
129
|
```
|
|
139
130
|
|
|
140
|
-
###
|
|
131
|
+
### 3. Update from the client
|
|
141
132
|
|
|
142
133
|
Call `set()` to update the wormhole — every `useWormhole()` hook and `subscribe()` callback on the page reacts, across islands:
|
|
143
134
|
|
|
@@ -171,21 +162,17 @@ export function RoleToggle() {
|
|
|
171
162
|
|
|
172
163
|
The single access point, typed from your registry. `wormholes.cart` returns a `Wormhole<T>`:
|
|
173
164
|
|
|
174
|
-
| Method
|
|
175
|
-
|
|
|
176
|
-
| `wormholes.x.get()`
|
|
177
|
-
| `wormholes.x.set(data)`
|
|
178
|
-
| `wormholes.x.subscribe(fn)` | Listen for `set()` updates, returns an unsubscribe function
|
|
165
|
+
| Method | Description |
|
|
166
|
+
| --------------------------- | ------------------------------------------------------------------------------------------ |
|
|
167
|
+
| `wormholes.x.get()` | Read the current value as `ReadonlyDeep<T>` (server: request scope; client: streamed data) |
|
|
168
|
+
| `wormholes.x.set(data)` | Replace the value and notify all subscribers (client only) |
|
|
169
|
+
| `wormholes.x.subscribe(fn)` | Listen for `set()` updates, returns an unsubscribe function |
|
|
179
170
|
|
|
180
171
|
Accesses must be static (`wormholes.cart`, `wormholes['cart']`). A dynamic access (`wormholes[name]`) still works, but the build can no longer tell which wormholes that chunk needs and delivers all open ones to its islands.
|
|
181
172
|
|
|
182
|
-
### `defineWormhole
|
|
173
|
+
### `defineWormhole({ handler, eager? })` <sub>registry only</sub>
|
|
183
174
|
|
|
184
|
-
Creates a typed wormhole for the registry; the name comes from the registry key. Registry entries are full `Wormhole<T>` objects, so server code can also import `wormholes` from your own registry file directly.
|
|
185
|
-
|
|
186
|
-
### `createWormholeMiddleware({ values, exclude? })` <sub>server only</sub>
|
|
187
|
-
|
|
188
|
-
Creates the middleware: resolves `values(ctx)` per request, opens them for server-side `get()`, and delivers them to the client. `exclude` accepts the same patterns as other astroscope middlewares (defaults to `RECOMMENDED_EXCLUDES`).
|
|
175
|
+
Creates a typed wormhole for the registry; the value type is inferred from the handler (or given as `defineWormhole<T>(...)`), the name comes from the registry key. `eager: true` runs the handler on every request instead of only where the build found a reader — for wormholes read by islands the build cannot attribute to a route (components chosen dynamically at render time). Registry entries are full `Wormhole<T>` objects, so server code can also import `wormholes` from your own registry file directly.
|
|
189
176
|
|
|
190
177
|
### `openWormholes(wh, data, fn)` <sub>server only</sub>
|
|
191
178
|
|
|
@@ -195,13 +182,15 @@ Provides values to server code that runs outside the request pipeline — tests,
|
|
|
195
182
|
|
|
196
183
|
React hook that reads a wormhole and re-renders on `set()`. During SSR it reads the request scope, so server and client render identically.
|
|
197
184
|
|
|
198
|
-
### `
|
|
185
|
+
### `UnwrapWormholes<R>`
|
|
199
186
|
|
|
200
|
-
Exported helper
|
|
187
|
+
Exported helper type mapping a registry shape to its value types. The recursively-readonly value type is `ReadonlyDeep<T>` from `@entwico/dash`.
|
|
201
188
|
|
|
202
189
|
## How it works
|
|
203
190
|
|
|
204
|
-
|
|
191
|
+
Routes the build could not attribute (dev, astro's own routes) load everything. On the client, `set()` updates the shared data and notifies subscribers, keeping islands and scripts in sync.
|
|
192
|
+
|
|
193
|
+
Each handler runs under its own `wormhole <name>` span beneath the request span — they run in parallel, and the trace shows which one holds the page's first byte — and records `astro.wormhole.handler.duration` {`astro.wormhole.name`}. A throwing handler is counted on `astro.wormhole.handler.failures` {`astro.wormhole.name`, `error.type`}, logged with its name, and fails the request. All through the adapter's telemetry, so nothing runs without its SDK.
|
|
205
194
|
|
|
206
195
|
## License
|
|
207
196
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"als-CntRqWdO.js","names":[],"sources":["../src/als.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\n// eslint-disable-next-line unicorn/no-global-object-property-assignment -- `Symbol.for` slot on globalThis, shared by the vite-runner and native module instances\nexport const als: AsyncLocalStorage<ReadonlyMap<string, unknown>> = ((globalThis as any)[\n Symbol.for('@astroscope/wormhole/als')\n] ??= new AsyncLocalStorage());\n"],"mappings":";;AAKA,MAAa,MAAwD,WACnE,OAAO,IAAI,0BAA0B,OACjC,IAAI,kBAAkB"}
|
|
@@ -1,19 +1,37 @@
|
|
|
1
|
+
import { t as als } from "./als-CntRqWdO.js";
|
|
1
2
|
import { t as wormholeKey } from "./key-BqTy1gM3.js";
|
|
2
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
-
//#region src/als.ts
|
|
4
|
-
const als = globalThis[Symbol.for("@astroscope/wormhole/als")] ??= new AsyncLocalStorage();
|
|
5
|
-
//#endregion
|
|
6
3
|
//#region src/define.ts
|
|
7
4
|
const ASSIGN = Symbol.for("@astroscope/wormhole.assign");
|
|
5
|
+
const SOURCE = Symbol.for("@astroscope/wormhole.source");
|
|
8
6
|
/**
|
|
9
|
-
* Define a wormhole for the `src/wormholes.ts` registry. The
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Define a wormhole for the `src/wormholes.ts` registry, actions-style. The
|
|
8
|
+
* handler resolves the value per request; the middleware calls it only for routes
|
|
9
|
+
* the build found a reader on (frontmatter, islands, scripts) — or on every
|
|
10
|
+
* request with `eager: true`. The wormhole's name is the registry key, assigned
|
|
11
|
+
* when the registry is passed to `createWormholeMiddleware()` (or
|
|
12
|
+
* `assignWormholeNames()`).
|
|
12
13
|
*
|
|
13
14
|
* **Security:** wormhole data is serialized into the HTML and sent to the browser.
|
|
14
15
|
* Never store secrets (tokens, API keys, credentials) in a wormhole.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* export const wormholes = {
|
|
20
|
+
* cart: defineWormhole({ handler: (ctx) => ctx.locals.cart }),
|
|
21
|
+
* config: defineWormhole({ handler: () => loadConfig(), eager: true }),
|
|
22
|
+
* };
|
|
23
|
+
* ```
|
|
15
24
|
*/
|
|
16
|
-
function defineWormhole() {
|
|
25
|
+
function defineWormhole(definition) {
|
|
26
|
+
const wormhole = createWormhole();
|
|
27
|
+
wormhole[SOURCE] = {
|
|
28
|
+
handler: definition.handler,
|
|
29
|
+
eager: definition.eager ?? false
|
|
30
|
+
};
|
|
31
|
+
return wormhole;
|
|
32
|
+
}
|
|
33
|
+
/** the server-side wormhole object without a handler — the `wormholes` proxy stubs (internal) */
|
|
34
|
+
function createWormhole() {
|
|
17
35
|
let name;
|
|
18
36
|
const requireName = () => {
|
|
19
37
|
if (name === void 0) throw new Error("wormhole is not registered — pass its registry to createWormholeMiddleware()");
|
|
@@ -44,6 +62,10 @@ function defineWormhole() {
|
|
|
44
62
|
}
|
|
45
63
|
};
|
|
46
64
|
}
|
|
65
|
+
/** the definition a registry entry was created with — undefined for proxy stubs */
|
|
66
|
+
function getWormholeSource(wormhole) {
|
|
67
|
+
return wormhole[SOURCE];
|
|
68
|
+
}
|
|
47
69
|
/**
|
|
48
70
|
* Assign each wormhole its registry key as name. Called by `createWormholeMiddleware()`;
|
|
49
71
|
* exposed for setups that read wormholes on the server without the middleware.
|
|
@@ -51,11 +73,11 @@ function defineWormhole() {
|
|
|
51
73
|
function assignWormholeNames(registry) {
|
|
52
74
|
for (const [name, wormhole] of Object.entries(registry)) {
|
|
53
75
|
const assign = wormhole[ASSIGN];
|
|
54
|
-
if (typeof assign !== "function") throw new
|
|
76
|
+
if (typeof assign !== "function") throw new TypeError(`registry entry "${name}" is not a wormhole — the registry must contain defineWormhole() values only`);
|
|
55
77
|
assign(name);
|
|
56
78
|
}
|
|
57
79
|
}
|
|
58
80
|
//#endregion
|
|
59
|
-
export {
|
|
81
|
+
export { getWormholeSource as i, createWormhole as n, defineWormhole as r, assignWormholeNames as t };
|
|
60
82
|
|
|
61
|
-
//# sourceMappingURL=define-
|
|
83
|
+
//# sourceMappingURL=define-DAoqsUVA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-DAoqsUVA.js","names":[],"sources":["../src/define.ts"],"sourcesContent":["import type { ReadonlyDeep } from '@entwico/dash';\nimport { als } from './als.js';\nimport { wormholeKey } from './key.js';\nimport type { Wormhole, WormholeDefinition, WormholeHandler } from './types.js';\n\n// name assignment and the handler must survive dual module instances (vite runner +\n// native), so both ride the wormhole object itself under Symbol.for keys\nconst ASSIGN = Symbol.for('@astroscope/wormhole.assign');\nconst SOURCE = Symbol.for('@astroscope/wormhole.source');\n\n/** how the middleware opens a wormhole: its handler and whether every route loads it */\nexport type WormholeSource = { handler: WormholeHandler<unknown>; eager: boolean };\n\ntype Internal = Wormhole<unknown> & { [ASSIGN](next: string): void; [SOURCE]?: WormholeSource | undefined };\n\n/**\n * Define a wormhole for the `src/wormholes.ts` registry, actions-style. The\n * handler resolves the value per request; the middleware calls it only for routes\n * the build found a reader on (frontmatter, islands, scripts) — or on every\n * request with `eager: true`. The wormhole's name is the registry key, assigned\n * when the registry is passed to `createWormholeMiddleware()` (or\n * `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 *\n * @example\n * ```typescript\n * export const wormholes = {\n * cart: defineWormhole({ handler: (ctx) => ctx.locals.cart }),\n * config: defineWormhole({ handler: () => loadConfig(), eager: true }),\n * };\n * ```\n */\nexport function defineWormhole<T>(definition: WormholeDefinition<T>): Wormhole<T> {\n const wormhole = createWormhole() as Internal;\n\n wormhole[SOURCE] = { handler: definition.handler as WormholeHandler<unknown>, eager: definition.eager ?? false };\n\n return wormhole as Wormhole<T>;\n}\n\n/** the server-side wormhole object without a handler — the `wormholes` proxy stubs (internal) */\nexport function createWormhole<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(): ReadonlyDeep<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 ReadonlyDeep<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/** the definition a registry entry was created with — undefined for proxy stubs */\nexport function getWormholeSource<T>(wormhole: Wormhole<T>): WormholeSource | undefined {\n return (wormhole as unknown as Internal)[SOURCE];\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 TypeError(\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":";;;AAOA,MAAM,SAAS,OAAO,IAAI,6BAA6B;AACvD,MAAM,SAAS,OAAO,IAAI,6BAA6B;;;;;;;;;;;;;;;;;;;;AA0BvD,SAAgB,eAAkB,YAAgD;CAChF,MAAM,WAAW,eAAe;CAEhC,SAAS,UAAU;EAAE,SAAS,WAAW;EAAqC,OAAO,WAAW,SAAS;CAAM;CAE/G,OAAO;AACT;;AAGA,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;;AAGA,SAAgB,kBAAqB,UAAmD;CACtF,OAAQ,SAAiC;AAC3C;;;;;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,UACR,mBAAmB,KAAK,6EAC1B;EAGF,OAAO,IAAI;CACb;AACF"}
|
package/dist/index.browser.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as WormholeMap, i as WormholeHandler, n as Wormhole, o as WormholeRegistry, r as WormholeDefinition, t as UnwrapWormholes } from "./types-v_ftArqH.js";
|
|
2
2
|
//#region src/index.browser.d.ts
|
|
3
|
-
declare const wormholes: WormholeMap;
|
|
4
|
-
declare function defineWormhole(): never;
|
|
5
|
-
declare function assignWormholeNames(): never;
|
|
6
|
-
|
|
3
|
+
export declare const wormholes: WormholeMap;
|
|
4
|
+
export declare function defineWormhole(): never;
|
|
5
|
+
export declare function assignWormholeNames(): never;
|
|
6
|
+
export default function wormholeIntegration(): never;
|
|
7
7
|
//#endregion
|
|
8
|
-
export
|
|
8
|
+
export type { UnwrapWormholes, Wormhole, WormholeDefinition, WormholeHandler, WormholeMap, WormholeRegistry };
|
|
9
9
|
//# sourceMappingURL=index.browser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.d.ts","names":[],"sources":["../src/index.browser.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.browser.d.ts","names":[],"sources":["../src/index.browser.ts"],"mappings":";;qBA0Ea,WAAW;wBAMR;wBAMA;wBAIQ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","names":[],"sources":["../src/index.browser.ts"],"sourcesContent":["import { wormholeKey } from './key.js';\nimport type {
|
|
1
|
+
{"version":3,"file":"index.browser.js","names":[],"sources":["../src/index.browser.ts"],"sourcesContent":["import type { ReadonlyDeep } from '@entwico/dash';\nimport { wormholeKey } from './key.js';\nimport type { Wormhole, WormholeMap } from './types.js';\n\nexport type {\n UnwrapWormholes,\n Wormhole,\n WormholeDefinition,\n WormholeHandler,\n WormholeMap,\n WormholeRegistry,\n} from './types.js';\n\n/**\n * Browser side of the `wormholes` proxy. Values live on `self.__wormholes__` — written\n * by the inline merge scripts the server emits before each island (and at stream end\n * for script consumers), or by client-side `set()` calls. The entry protocol is\n * `{ v: value, l: listeners }`, shared with the emitted scripts, so writes land in\n * either order.\n */\n\ntype Entry = { v?: unknown; l?: ((value: unknown) => void)[] | undefined };\n\n// eslint-disable-next-line unicorn/no-global-object-property-assignment -- `self.__wormholes__` is the entry store shared with the emitted merge scripts\nconst store: Record<string, Entry> = ((globalThis as any).__wormholes__ ??= {});\n\nconst stubs = new Map<string, Wormhole<unknown>>();\n\nfunction stub(name: string): Wormhole<unknown> {\n let wormhole = stubs.get(name);\n\n if (wormhole) {\n return wormhole;\n }\n\n const entry = (): Entry => (store[name] ??= {});\n\n wormhole = {\n name,\n key: wormholeKey(name),\n\n get(): ReadonlyDeep<unknown> {\n const value = entry().v;\n\n if (value === undefined) {\n throw new Error(`wormhole \"${name}\" has no data — is it provided by the wormhole middleware?`);\n }\n\n return value;\n },\n\n set(data): void {\n const current = entry();\n\n current.v = data;\n current.l?.forEach((fn) => fn(data));\n },\n\n subscribe(fn): () => void {\n const current = entry();\n\n (current.l ??= []).push(fn);\n\n return () => {\n current.l = current.l?.filter((listener) => listener !== fn);\n };\n },\n };\n\n stubs.set(name, wormhole);\n\n return wormhole;\n}\n\nexport const wormholes: WormholeMap = new Proxy({} as WormholeMap, {\n get(_target, prop) {\n return typeof prop === 'string' ? stub(prop) : undefined;\n },\n});\n\nexport function defineWormhole(): never {\n throw new Error(\n 'defineWormhole() is server-only — do not import the wormhole registry in client code; access wormholes via the `wormholes` proxy instead',\n );\n}\n\nexport function assignWormholeNames(): never {\n throw new Error('assignWormholeNames() is server-only');\n}\n\nexport default function wormholeIntegration(): never {\n throw new Error('the @astroscope/wormhole integration is server-only');\n}\n"],"mappings":";;AAwBA,MAAM,QAAgC,WAAoB,kBAAkB,CAAC;AAE7E,MAAM,wBAAQ,IAAI,IAA+B;AAEjD,SAAS,KAAK,MAAiC;CAC7C,IAAI,WAAW,MAAM,IAAI,IAAI;CAE7B,IAAI,UACF,OAAO;CAGT,MAAM,cAAsB,MAAM,UAAU,CAAC;CAE7C,WAAW;EACT;EACA,KAAK,YAAY,IAAI;EAErB,MAA6B;GAC3B,MAAM,QAAQ,MAAM,CAAC,CAAC;GAEtB,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MAAM,aAAa,KAAK,2DAA2D;GAG/F,OAAO;EACT;EAEA,IAAI,MAAY;GACd,MAAM,UAAU,MAAM;GAEtB,QAAQ,IAAI;GACZ,QAAQ,GAAG,SAAS,OAAO,GAAG,IAAI,CAAC;EACrC;EAEA,UAAU,IAAgB;GACxB,MAAM,UAAU,MAAM;GAEtB,CAAC,QAAQ,MAAM,CAAC,EAAA,CAAG,KAAK,EAAE;GAE1B,aAAa;IACX,QAAQ,IAAI,QAAQ,GAAG,QAAQ,aAAa,aAAa,EAAE;GAC7D;EACF;CACF;CAEA,MAAM,IAAI,MAAM,QAAQ;CAExB,OAAO;AACT;AAEA,MAAa,YAAyB,IAAI,MAAM,CAAC,GAAkB,EACjE,IAAI,SAAS,MAAM;CACjB,OAAO,OAAO,SAAS,WAAW,KAAK,IAAI,IAAI,KAAA;AACjD,EACF,CAAC;AAED,SAAgB,iBAAwB;CACtC,MAAM,IAAI,MACR,0IACF;AACF;AAEA,SAAgB,sBAA6B;CAC3C,MAAM,IAAI,MAAM,sCAAsC;AACxD;AAEA,SAAwB,sBAA6B;CACnD,MAAM,IAAI,MAAM,qDAAqD;AACvE"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,33 +1,51 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as WormholeMap, i as WormholeHandler, n as Wormhole, o as WormholeRegistry, r as WormholeDefinition, t as UnwrapWormholes } from "./types-v_ftArqH.js";
|
|
2
|
+
import { ExcludePattern } from "@astroscope/node/excludes";
|
|
2
3
|
import { AstroIntegration } from "astro";
|
|
3
4
|
//#region src/integration.d.ts
|
|
5
|
+
type WormholeIntegrationOptions = {
|
|
6
|
+
/** patterns the middleware skips — no handler runs, nothing is delivered; defaults to RECOMMENDED_EXCLUDES */
|
|
7
|
+
exclude?: ExcludePattern[] | undefined;
|
|
8
|
+
};
|
|
4
9
|
/**
|
|
5
|
-
* Astro integration for wormholes with per-island
|
|
10
|
+
* Astro integration for wormholes with per-route loading and per-island delivery.
|
|
6
11
|
*
|
|
7
12
|
* Resolves the `src/wormholes.ts` registry and exposes it to the middleware as a
|
|
8
|
-
* virtual module,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* virtual module, injects the middleware after the project's own (so handlers see
|
|
14
|
+
* everything earlier middleware put on `locals`), registers the vite plugin that
|
|
15
|
+
* scans server and client code for `wormholes.<name>` accesses and maps them onto
|
|
16
|
+
* routes and chunks, and generates the registry type stub that types the
|
|
17
|
+
* `wormholes` proxy. Delivery happens at runtime: the middleware runs the handlers
|
|
18
|
+
* the route needs, and the islands emitter (via `@astroscope/node`) writes each
|
|
19
|
+
* island's slice before its tag.
|
|
13
20
|
*/
|
|
14
|
-
|
|
21
|
+
export default function wormholeIntegration(options?: WormholeIntegrationOptions): AstroIntegration;
|
|
15
22
|
//#endregion
|
|
16
23
|
//#region src/define.d.ts
|
|
17
24
|
/**
|
|
18
|
-
* Define a wormhole for the `src/wormholes.ts` registry. The
|
|
19
|
-
*
|
|
20
|
-
*
|
|
25
|
+
* Define a wormhole for the `src/wormholes.ts` registry, actions-style. The
|
|
26
|
+
* handler resolves the value per request; the middleware calls it only for routes
|
|
27
|
+
* the build found a reader on (frontmatter, islands, scripts) — or on every
|
|
28
|
+
* request with `eager: true`. The wormhole's name is the registry key, assigned
|
|
29
|
+
* when the registry is passed to `createWormholeMiddleware()` (or
|
|
30
|
+
* `assignWormholeNames()`).
|
|
21
31
|
*
|
|
22
32
|
* **Security:** wormhole data is serialized into the HTML and sent to the browser.
|
|
23
33
|
* Never store secrets (tokens, API keys, credentials) in a wormhole.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* export const wormholes = {
|
|
38
|
+
* cart: defineWormhole({ handler: (ctx) => ctx.locals.cart }),
|
|
39
|
+
* config: defineWormhole({ handler: () => loadConfig(), eager: true }),
|
|
40
|
+
* };
|
|
41
|
+
* ```
|
|
24
42
|
*/
|
|
25
|
-
declare function defineWormhole<T>(): Wormhole<T>;
|
|
43
|
+
export declare function defineWormhole<T>(definition: WormholeDefinition<T>): Wormhole<T>;
|
|
26
44
|
/**
|
|
27
45
|
* Assign each wormhole its registry key as name. Called by `createWormholeMiddleware()`;
|
|
28
46
|
* exposed for setups that read wormholes on the server without the middleware.
|
|
29
47
|
*/
|
|
30
|
-
declare function assignWormholeNames(registry: Record<string, object>): void;
|
|
48
|
+
export declare function assignWormholeNames(registry: Record<string, object>): void;
|
|
31
49
|
//#endregion
|
|
32
50
|
//#region src/index.d.ts
|
|
33
51
|
/**
|
|
@@ -37,7 +55,7 @@ declare function assignWormholeNames(registry: Record<string, object>): void;
|
|
|
37
55
|
* server-rendered islands alike. Typed via the registry type stub the integration
|
|
38
56
|
* generates from `src/wormholes.ts`.
|
|
39
57
|
*/
|
|
40
|
-
declare const wormholes: WormholeMap;
|
|
58
|
+
export declare const wormholes: WormholeMap;
|
|
41
59
|
//#endregion
|
|
42
|
-
export
|
|
60
|
+
export type { UnwrapWormholes, Wormhole, WormholeDefinition, WormholeHandler, WormholeIntegrationOptions, WormholeMap, WormholeRegistry };
|
|
43
61
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/integration.ts","../src/define.ts","../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/integration.ts","../src/define.ts","../src/index.ts"],"mappings":";;;;KAQY;;EAEV,UAAU;;;;;;;;;;;;;;wBAeY,oBAAoB,UAAS,6BAAkC;;;;;;;;;;;;;;;;;;;;;;wBCSvE,eAAe,GAAG,YAAY,mBAAmB,KAAK,SAAS;;;;;wBA6E/D,oBAAoB,UAAU;;;;;;;;;;qBC1EjC,WAAW"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import { n as defineWormhole, t as assignWormholeNames } from "./define-
|
|
1
|
+
import { n as createWormhole, r as defineWormhole, t as assignWormholeNames } from "./define-DAoqsUVA.js";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { collectRouteIslands, routeEntrypoints, stripQuery } from "@astroscope/node/islands";
|
|
6
|
+
import { serializeExcludePatterns } from "@astroscope/node/excludes";
|
|
5
7
|
//#region src/extraction/vite-plugin.ts
|
|
6
8
|
const WORMHOLE_VIRTUAL_MODULE_ID = "virtual:@astroscope/wormhole/manifest";
|
|
7
9
|
const REGISTRY_VIRTUAL_MODULE_ID = "virtual:@astroscope/wormhole/registry";
|
|
10
|
+
const CONFIG_VIRTUAL_MODULE_ID = "virtual:@astroscope/wormhole/config";
|
|
8
11
|
const RESOLVED_VIRTUAL_MODULE_ID = `\0${WORMHOLE_VIRTUAL_MODULE_ID}`;
|
|
9
12
|
const RESOLVED_REGISTRY_STUB_ID = `\0${REGISTRY_VIRTUAL_MODULE_ID}`;
|
|
13
|
+
const RESOLVED_CONFIG_VIRTUAL_MODULE_ID = `\0${CONFIG_VIRTUAL_MODULE_ID}`;
|
|
10
14
|
const MANIFEST_FILE_NAME = "wormhole-manifest.json";
|
|
11
15
|
const SCAN_EXTENSIONS = [
|
|
12
16
|
".ts",
|
|
@@ -14,13 +18,17 @@ const SCAN_EXTENSIONS = [
|
|
|
14
18
|
".js",
|
|
15
19
|
".jsx"
|
|
16
20
|
];
|
|
21
|
+
const SERVER_SCAN_EXTENSIONS = [...SCAN_EXTENSIONS, ".astro"];
|
|
17
22
|
/** chunk file name → manifest key, e.g. `_astro/Counter.abc.js` → `Counter.abc` */
|
|
18
23
|
function chunkFileNameToName(fileName) {
|
|
19
24
|
return fileName.slice(fileName.lastIndexOf("/") + 1).replace(/\.js$/, "");
|
|
20
25
|
}
|
|
21
26
|
function wormholeVitePlugin(options) {
|
|
22
27
|
let isBuild = false;
|
|
23
|
-
const
|
|
28
|
+
const clientScans = /* @__PURE__ */ new Map();
|
|
29
|
+
const serverScans = /* @__PURE__ */ new Map();
|
|
30
|
+
const routeNames = /* @__PURE__ */ new Map();
|
|
31
|
+
const patternsByFile = /* @__PURE__ */ new Map();
|
|
24
32
|
return {
|
|
25
33
|
name: "@astroscope/wormhole/extract",
|
|
26
34
|
configResolved(config) {
|
|
@@ -28,10 +36,13 @@ function wormholeVitePlugin(options) {
|
|
|
28
36
|
},
|
|
29
37
|
resolveId(id) {
|
|
30
38
|
if (id === "virtual:@astroscope/wormhole/manifest") return RESOLVED_VIRTUAL_MODULE_ID;
|
|
39
|
+
if (id === "virtual:@astroscope/wormhole/config") return RESOLVED_CONFIG_VIRTUAL_MODULE_ID;
|
|
31
40
|
if (id === "virtual:@astroscope/wormhole/registry") return options.registryPath ?? RESOLVED_REGISTRY_STUB_ID;
|
|
32
41
|
},
|
|
33
42
|
load(id) {
|
|
34
43
|
if (id === RESOLVED_REGISTRY_STUB_ID) return "export const wormholes = {};";
|
|
44
|
+
if (id === RESOLVED_CONFIG_VIRTUAL_MODULE_ID) return `export const exclude = ${options.exclude ? serializeExcludePatterns(options.exclude) : "null"};`;
|
|
45
|
+
if (id === options.registryPath && this.environment?.name === "client") throw new Error(`[@astroscope/wormhole] ${path.relative(process.cwd(), id)} is server-only — client code reads wormholes through the \`wormholes\` proxy from '@astroscope/wormhole'`);
|
|
35
46
|
if (id !== RESOLVED_VIRTUAL_MODULE_ID) return;
|
|
36
47
|
if (!isBuild) return "export const manifest = null;";
|
|
37
48
|
return `
|
|
@@ -60,13 +71,37 @@ export { manifest };
|
|
|
60
71
|
`;
|
|
61
72
|
},
|
|
62
73
|
async transform(code, id) {
|
|
63
|
-
if (!isBuild || this.environment
|
|
74
|
+
if (!isBuild || !this.environment) return;
|
|
64
75
|
if (id.includes("node_modules")) return;
|
|
65
|
-
|
|
76
|
+
const client = this.environment.name === "client";
|
|
77
|
+
const extensions = client ? SCAN_EXTENSIONS : SERVER_SCAN_EXTENSIONS;
|
|
78
|
+
const file = extensions.some((ext) => id.endsWith(ext)) ? id : stripQuery(id);
|
|
79
|
+
if (extensions.every((ext) => !file.endsWith(ext))) return;
|
|
66
80
|
if (!code.includes("@astroscope/wormhole")) return;
|
|
67
|
-
const { scanWormholeAccess } = await import("./scan-
|
|
68
|
-
const scan = scanWormholeAccess(code,
|
|
69
|
-
if (scan)
|
|
81
|
+
const { scanWormholeAccess } = await import("./scan-BDYo2M_0.js");
|
|
82
|
+
const scan = scanWormholeAccess(code, file);
|
|
83
|
+
if (scan) (client ? clientScans : serverScans).set(client ? id : stripQuery(id), scan);
|
|
84
|
+
},
|
|
85
|
+
async generateBundle() {
|
|
86
|
+
if (this.environment.name === "client") return;
|
|
87
|
+
const { routes, patternsByModule } = await collectRouteIslands(this, options.pages());
|
|
88
|
+
for (const pattern of routes.keys()) routeNames.set(pattern, routeNames.get(pattern) ?? /* @__PURE__ */ new Set());
|
|
89
|
+
for (const [id, patterns] of patternsByModule) {
|
|
90
|
+
const file = stripQuery(id);
|
|
91
|
+
let owners = patternsByFile.get(file);
|
|
92
|
+
if (!owners) {
|
|
93
|
+
owners = /* @__PURE__ */ new Set();
|
|
94
|
+
patternsByFile.set(file, owners);
|
|
95
|
+
}
|
|
96
|
+
patterns.forEach((pattern) => owners.add(pattern));
|
|
97
|
+
const scan = serverScans.get(file);
|
|
98
|
+
if (!scan) continue;
|
|
99
|
+
for (const pattern of patterns) {
|
|
100
|
+
const names = routeNames.get(pattern);
|
|
101
|
+
scan.names.forEach((name) => names.add(name));
|
|
102
|
+
if (scan.dynamic) names.add("*");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
70
105
|
},
|
|
71
106
|
writeBundle(outputOptions, bundle) {
|
|
72
107
|
if (this.environment.name !== "client" || !outputOptions.dir) return;
|
|
@@ -79,7 +114,7 @@ export { manifest };
|
|
|
79
114
|
const names = /* @__PURE__ */ new Set();
|
|
80
115
|
let dynamic = false;
|
|
81
116
|
for (const moduleId of chunk.moduleIds) {
|
|
82
|
-
const scan =
|
|
117
|
+
const scan = clientScans.get(moduleId);
|
|
83
118
|
scan?.names.forEach((name) => names.add(name));
|
|
84
119
|
dynamic ||= scan?.dynamic ?? false;
|
|
85
120
|
}
|
|
@@ -90,17 +125,30 @@ export { manifest };
|
|
|
90
125
|
if ((chunk.isEntry || chunk.isDynamicEntry) && chunk.facadeModuleId?.includes("astro&type=script")) scriptEntries.push(chunk.fileName);
|
|
91
126
|
}
|
|
92
127
|
const scriptNames = /* @__PURE__ */ new Set();
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
128
|
+
for (const entry of scriptEntries) {
|
|
129
|
+
const names = /* @__PURE__ */ new Set();
|
|
130
|
+
const visited = /* @__PURE__ */ new Set();
|
|
131
|
+
const walk = (fileName) => {
|
|
132
|
+
if (visited.has(fileName)) return;
|
|
133
|
+
visited.add(fileName);
|
|
134
|
+
chunkNamesByFileName.get(fileName)?.forEach((name) => names.add(name));
|
|
135
|
+
chunkEdges.get(fileName)?.forEach((imported) => walk(imported));
|
|
136
|
+
};
|
|
137
|
+
walk(entry);
|
|
138
|
+
names.forEach((name) => scriptNames.add(name));
|
|
139
|
+
const owner = bundle[entry]?.type === "chunk" ? stripQuery(bundle[entry].facadeModuleId ?? "") : "";
|
|
140
|
+
const owners = patternsByFile.get(owner) ?? routeNames.keys();
|
|
141
|
+
for (const pattern of owners) {
|
|
142
|
+
const route = routeNames.get(pattern);
|
|
143
|
+
names.forEach((name) => route?.add(name));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const routes = {};
|
|
147
|
+
for (const [pattern, names] of routeNames) routes[pattern] = [...names];
|
|
101
148
|
const manifest = {
|
|
102
149
|
chunks,
|
|
103
|
-
scripts: [...scriptNames]
|
|
150
|
+
scripts: [...scriptNames],
|
|
151
|
+
routes
|
|
104
152
|
};
|
|
105
153
|
const chunksDir = path.resolve(outputOptions.dir, "..", "server", "chunks");
|
|
106
154
|
if (fs.existsSync(chunksDir)) fs.writeFileSync(path.join(chunksDir, MANIFEST_FILE_NAME), JSON.stringify(manifest));
|
|
@@ -111,22 +159,38 @@ export { manifest };
|
|
|
111
159
|
//#endregion
|
|
112
160
|
//#region src/integration.ts
|
|
113
161
|
/**
|
|
114
|
-
* Astro integration for wormholes with per-island
|
|
162
|
+
* Astro integration for wormholes with per-route loading and per-island delivery.
|
|
115
163
|
*
|
|
116
164
|
* Resolves the `src/wormholes.ts` registry and exposes it to the middleware as a
|
|
117
|
-
* virtual module,
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
165
|
+
* virtual module, injects the middleware after the project's own (so handlers see
|
|
166
|
+
* everything earlier middleware put on `locals`), registers the vite plugin that
|
|
167
|
+
* scans server and client code for `wormholes.<name>` accesses and maps them onto
|
|
168
|
+
* routes and chunks, and generates the registry type stub that types the
|
|
169
|
+
* `wormholes` proxy. Delivery happens at runtime: the middleware runs the handlers
|
|
170
|
+
* the route needs, and the islands emitter (via `@astroscope/node`) writes each
|
|
171
|
+
* island's slice before its tag.
|
|
122
172
|
*/
|
|
123
|
-
function wormholeIntegration() {
|
|
173
|
+
function wormholeIntegration(options = {}) {
|
|
124
174
|
const resolveRegistryPath = (srcDir) => ["wormholes.ts", path.join("wormholes", "index.ts")].map((candidate) => path.join(fileURLToPath(srcDir), candidate)).find((candidate) => fs.existsSync(candidate)) ?? null;
|
|
175
|
+
let root = "";
|
|
176
|
+
let resolvedRoutes = [];
|
|
125
177
|
return {
|
|
126
178
|
name: "@astroscope/wormhole",
|
|
127
179
|
hooks: {
|
|
128
|
-
"astro:config:setup": ({ config, updateConfig }) => {
|
|
129
|
-
|
|
180
|
+
"astro:config:setup": ({ config, updateConfig, addMiddleware }) => {
|
|
181
|
+
root = fileURLToPath(config.root);
|
|
182
|
+
addMiddleware({
|
|
183
|
+
order: "post",
|
|
184
|
+
entrypoint: "@astroscope/wormhole/middleware"
|
|
185
|
+
});
|
|
186
|
+
updateConfig({ vite: { plugins: [wormholeVitePlugin({
|
|
187
|
+
registryPath: resolveRegistryPath(config.srcDir),
|
|
188
|
+
exclude: options.exclude ?? null,
|
|
189
|
+
pages: () => routeEntrypoints(root, resolvedRoutes)
|
|
190
|
+
})] } });
|
|
191
|
+
},
|
|
192
|
+
"astro:routes:resolved": ({ routes }) => {
|
|
193
|
+
resolvedRoutes = routes;
|
|
130
194
|
},
|
|
131
195
|
"astro:config:done": ({ config, injectTypes, logger }) => {
|
|
132
196
|
const registryPath = resolveRegistryPath(config.srcDir);
|
|
@@ -154,7 +218,7 @@ const stubs = /* @__PURE__ */ new Map();
|
|
|
154
218
|
function stub(name) {
|
|
155
219
|
let wormhole = stubs.get(name);
|
|
156
220
|
if (!wormhole) {
|
|
157
|
-
wormhole =
|
|
221
|
+
wormhole = createWormhole();
|
|
158
222
|
assignWormholeNames({ [name]: wormhole });
|
|
159
223
|
stubs.set(name, wormhole);
|
|
160
224
|
}
|