praetom 0.2.3 → 0.3.0-rc.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.
Files changed (55) hide show
  1. package/dist/_active-channel.d.ts +28 -0
  2. package/dist/_active-channel.d.ts.map +1 -0
  3. package/dist/_active-channel.js +237 -0
  4. package/dist/_active-channel.js.map +1 -0
  5. package/dist/_esm-loader.d.ts +24 -26
  6. package/dist/_esm-loader.d.ts.map +1 -1
  7. package/dist/_esm-loader.js +49 -82
  8. package/dist/_esm-loader.js.map +1 -1
  9. package/dist/_internal.d.ts +67 -0
  10. package/dist/_internal.d.ts.map +1 -1
  11. package/dist/_internal.js +264 -14
  12. package/dist/_internal.js.map +1 -1
  13. package/dist/browser.d.ts +0 -4
  14. package/dist/browser.d.ts.map +1 -1
  15. package/dist/browser.js +0 -11
  16. package/dist/browser.js.map +1 -1
  17. package/dist/cli/cli.js +6 -1
  18. package/dist/cli/cli.js.map +1 -1
  19. package/dist/cli/commands/discover.d.ts +10 -1
  20. package/dist/cli/commands/discover.d.ts.map +1 -1
  21. package/dist/cli/commands/discover.js +40 -10
  22. package/dist/cli/commands/discover.js.map +1 -1
  23. package/dist/cli/commands/workspaces.d.ts +10 -0
  24. package/dist/cli/commands/workspaces.d.ts.map +1 -1
  25. package/dist/cli/commands/workspaces.js +46 -0
  26. package/dist/cli/commands/workspaces.js.map +1 -1
  27. package/dist/discover/ast-scan.d.ts +44 -0
  28. package/dist/discover/ast-scan.d.ts.map +1 -0
  29. package/dist/discover/ast-scan.js +331 -0
  30. package/dist/discover/ast-scan.js.map +1 -0
  31. package/dist/discover/ast-scan.test.d.ts +12 -0
  32. package/dist/discover/ast-scan.test.d.ts.map +1 -0
  33. package/dist/discover/ast-scan.test.js +188 -0
  34. package/dist/discover/ast-scan.test.js.map +1 -0
  35. package/dist/server.d.ts +23 -11
  36. package/dist/server.d.ts.map +1 -1
  37. package/dist/server.js +409 -81
  38. package/dist/server.js.map +1 -1
  39. package/dist/vite/index.d.ts +8 -0
  40. package/dist/vite/index.d.ts.map +1 -1
  41. package/dist/vite/index.js +32 -57
  42. package/dist/vite/index.js.map +1 -1
  43. package/dist/webpack/index.d.ts +6 -0
  44. package/dist/webpack/index.d.ts.map +1 -1
  45. package/dist/webpack/index.js +1 -0
  46. package/dist/webpack/index.js.map +1 -1
  47. package/dist/webpack/loader.d.ts +13 -8
  48. package/dist/webpack/loader.d.ts.map +1 -1
  49. package/dist/webpack/loader.js +59 -16
  50. package/dist/webpack/loader.js.map +1 -1
  51. package/dist/webpack/shared.d.ts +54 -2
  52. package/dist/webpack/shared.d.ts.map +1 -1
  53. package/dist/webpack/shared.js +200 -14
  54. package/dist/webpack/shared.js.map +1 -1
  55. package/package.json +15 -7
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Live active-feature-map channel.
3
+ *
4
+ * Default transport is **SSE** over fetch (`text/event-stream`). The
5
+ * server pushes a `snapshot` event on connect and on every server-side
6
+ * change to the active set; each event carries the full active list,
7
+ * which we hand to `setActiveMap` so existing late-binding wraps pick
8
+ * up the new state on their next call.
9
+ *
10
+ * Edge / serverless environments (Vercel Edge, AWS Lambda, Cloudflare
11
+ * Workers) don't keep long-lived connections — we fall back to 30s
12
+ * polling on `/api/runtime/active`. The fallback also fires
13
+ * permanently after `MAX_RECONNECT_FAILURES` consecutive SSE
14
+ * connection failures.
15
+ */
16
+ interface ChannelConfig {
17
+ configEndpoint: string;
18
+ ingestId: string;
19
+ warn: (msg: string) => void;
20
+ /** SDK version stamp; surfaces in the dashboard's live-instances panel. */
21
+ sdkVersion?: string;
22
+ /** Service identifier (heartbeat key); surfaces alongside SDK version. */
23
+ serviceName?: string | null;
24
+ }
25
+ export declare function startActiveChannel(cfg: ChannelConfig): void;
26
+ export declare function stopActiveChannel(): void;
27
+ export {};
28
+ //# sourceMappingURL=_active-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_active-channel.d.ts","sourceRoot":"","sources":["../src/_active-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAWH,UAAU,aAAa;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAYD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAU3D;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CASxC"}
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Live active-feature-map channel.
3
+ *
4
+ * Default transport is **SSE** over fetch (`text/event-stream`). The
5
+ * server pushes a `snapshot` event on connect and on every server-side
6
+ * change to the active set; each event carries the full active list,
7
+ * which we hand to `setActiveMap` so existing late-binding wraps pick
8
+ * up the new state on their next call.
9
+ *
10
+ * Edge / serverless environments (Vercel Edge, AWS Lambda, Cloudflare
11
+ * Workers) don't keep long-lived connections — we fall back to 30s
12
+ * polling on `/api/runtime/active`. The fallback also fires
13
+ * permanently after `MAX_RECONNECT_FAILURES` consecutive SSE
14
+ * connection failures.
15
+ */
16
+ import { setActiveMap } from "./_internal.js";
17
+ const POLL_INTERVAL_MS = 30_000;
18
+ const MAX_RECONNECT_FAILURES = 5;
19
+ const BACKOFF_BASE_MS = 1_000;
20
+ const BACKOFF_MAX_MS = 60_000;
21
+ let _abort = null;
22
+ let _pollTimer = null;
23
+ let _lastPollEtag = null;
24
+ let _reconnectFailures = 0;
25
+ export function startActiveChannel(cfg) {
26
+ stopActiveChannel(); // idempotent — register() can be called twice
27
+ if (shouldPollOnly()) {
28
+ cfg.warn("active-channel using polling (edge/serverless environment)");
29
+ startPolling(cfg);
30
+ return;
31
+ }
32
+ void connectSse(cfg);
33
+ }
34
+ export function stopActiveChannel() {
35
+ if (_abort) {
36
+ _abort.abort();
37
+ _abort = null;
38
+ }
39
+ if (_pollTimer) {
40
+ clearInterval(_pollTimer);
41
+ _pollTimer = null;
42
+ }
43
+ }
44
+ /**
45
+ * Detect runtime contexts where SSE is a bad fit. Short-lived per-
46
+ * request invocations can't keep a connection open across requests, so
47
+ * they fall back to per-boot polling.
48
+ */
49
+ function shouldPollOnly() {
50
+ const env = globalThis.process?.env ?? {};
51
+ if (typeof globalThis.EdgeRuntime !== "undefined") {
52
+ return true;
53
+ }
54
+ if (env.AWS_LAMBDA_FUNCTION_NAME)
55
+ return true;
56
+ if (env.VERCEL && env.VERCEL_REGION) {
57
+ // Vercel serverless functions; long-running services on Vercel
58
+ // (rare for Node, common for Hono/Cloudflare) won't set
59
+ // VERCEL_REGION at runtime.
60
+ return true;
61
+ }
62
+ return false;
63
+ }
64
+ async function connectSse(cfg) {
65
+ while (true) {
66
+ const abort = new AbortController();
67
+ _abort = abort;
68
+ const url = activeStreamUrl(cfg.configEndpoint);
69
+ try {
70
+ const res = await fetch(url, {
71
+ method: "GET",
72
+ headers: identityHeaders(cfg, "text/event-stream"),
73
+ signal: abort.signal,
74
+ });
75
+ if (!res.ok || !res.body) {
76
+ throw new Error(`HTTP ${res.status}`);
77
+ }
78
+ _reconnectFailures = 0;
79
+ await consumeSseStream(res.body, cfg.warn);
80
+ // Stream ended cleanly (server closed). Reconnect after a short
81
+ // pause unless we're shutting down.
82
+ if (abort.signal.aborted)
83
+ return;
84
+ }
85
+ catch (e) {
86
+ if (abort.signal.aborted)
87
+ return;
88
+ _reconnectFailures += 1;
89
+ cfg.warn(`active-channel SSE error (${_reconnectFailures}/${MAX_RECONNECT_FAILURES}): ${e.message}`);
90
+ if (_reconnectFailures >= MAX_RECONNECT_FAILURES) {
91
+ cfg.warn(`active-channel falling back to polling after ${MAX_RECONNECT_FAILURES} consecutive failures`);
92
+ startPolling(cfg);
93
+ return;
94
+ }
95
+ }
96
+ await sleep(backoffMs(_reconnectFailures), _abort?.signal);
97
+ }
98
+ }
99
+ async function consumeSseStream(body, warn) {
100
+ const reader = body.getReader();
101
+ const decoder = new TextDecoder();
102
+ let buffer = "";
103
+ try {
104
+ while (true) {
105
+ const { value, done } = await reader.read();
106
+ if (done)
107
+ return;
108
+ buffer += decoder.decode(value, { stream: true });
109
+ // SSE events are separated by blank lines (\n\n). Parse one at a time.
110
+ let idx;
111
+ while ((idx = buffer.indexOf("\n\n")) !== -1) {
112
+ const rawEvent = buffer.slice(0, idx);
113
+ buffer = buffer.slice(idx + 2);
114
+ const parsed = parseSseEvent(rawEvent);
115
+ if (!parsed)
116
+ continue;
117
+ if (parsed.event === "snapshot") {
118
+ try {
119
+ const payload = JSON.parse(parsed.data);
120
+ setActiveMap(payload.active);
121
+ }
122
+ catch (e) {
123
+ warn(`active-channel: invalid snapshot payload: ${e.message}`);
124
+ }
125
+ }
126
+ else if (parsed.event === "error") {
127
+ warn(`active-channel: server error event: ${parsed.data}`);
128
+ }
129
+ // 'heartbeat' (SSE comment) is dropped during parsing — see below.
130
+ }
131
+ }
132
+ }
133
+ finally {
134
+ try {
135
+ reader.releaseLock();
136
+ }
137
+ catch {
138
+ // already released
139
+ }
140
+ }
141
+ }
142
+ function parseSseEvent(raw) {
143
+ let event = "message";
144
+ const dataLines = [];
145
+ for (const line of raw.split("\n")) {
146
+ if (line.startsWith(":"))
147
+ continue; // SSE comment (heartbeats land here)
148
+ if (line.startsWith("event:")) {
149
+ event = line.slice(6).trim();
150
+ }
151
+ else if (line.startsWith("data:")) {
152
+ dataLines.push(line.slice(5).replace(/^ /, ""));
153
+ }
154
+ }
155
+ if (dataLines.length === 0 && event === "message")
156
+ return null;
157
+ return { event, data: dataLines.join("\n") };
158
+ }
159
+ function startPolling(cfg) {
160
+ // Fire immediately so initial state lands before the first interval.
161
+ void pollOnce(cfg);
162
+ _pollTimer = setInterval(() => {
163
+ void pollOnce(cfg);
164
+ }, POLL_INTERVAL_MS);
165
+ if (typeof _pollTimer.unref === "function") {
166
+ _pollTimer.unref();
167
+ }
168
+ }
169
+ async function pollOnce(cfg) {
170
+ const url = activeSnapshotUrl(cfg.configEndpoint);
171
+ try {
172
+ const headers = identityHeaders(cfg, "application/json");
173
+ if (_lastPollEtag)
174
+ headers["if-none-match"] = _lastPollEtag;
175
+ const res = await fetch(url, { headers });
176
+ if (res.status === 304)
177
+ return;
178
+ if (!res.ok) {
179
+ cfg.warn(`active-channel poll: HTTP ${res.status}`);
180
+ return;
181
+ }
182
+ _lastPollEtag = res.headers.get("etag");
183
+ const payload = (await res.json());
184
+ setActiveMap(payload.active);
185
+ }
186
+ catch (e) {
187
+ cfg.warn(`active-channel poll error: ${e.message}`);
188
+ }
189
+ }
190
+ /**
191
+ * Standard request identity headers — surface the SDK + Node version
192
+ * and the service name so the dashboard's live-instances panel can
193
+ * group connections meaningfully.
194
+ */
195
+ function identityHeaders(cfg, accept) {
196
+ const headers = {
197
+ authorization: `Bearer ${cfg.ingestId}`,
198
+ accept,
199
+ };
200
+ if (cfg.sdkVersion)
201
+ headers["x-praetom-sdk-version"] = cfg.sdkVersion;
202
+ const nodeVersion = globalThis
203
+ .process?.versions?.node;
204
+ if (nodeVersion)
205
+ headers["x-praetom-node-version"] = nodeVersion;
206
+ if (cfg.serviceName)
207
+ headers["x-praetom-service"] = cfg.serviceName;
208
+ return headers;
209
+ }
210
+ function activeStreamUrl(configEndpoint) {
211
+ // Replace `/runtime/config` → `/runtime/active/stream` so customers
212
+ // overriding the endpoint keep their override semantics.
213
+ return configEndpoint.replace(/\/config(?:\/?$|\?)/, "/active/stream$1");
214
+ }
215
+ function activeSnapshotUrl(configEndpoint) {
216
+ return configEndpoint.replace(/\/config(?:\/?$|\?)/, "/active$1");
217
+ }
218
+ function backoffMs(attempt) {
219
+ const exp = Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * 2 ** attempt);
220
+ // Add ±25% jitter to avoid thundering herd on widespread server restarts.
221
+ return Math.floor(exp * (0.75 + Math.random() * 0.5));
222
+ }
223
+ function sleep(ms, signal) {
224
+ return new Promise((resolve) => {
225
+ if (signal?.aborted)
226
+ return resolve();
227
+ const t = setTimeout(resolve, ms);
228
+ if (typeof t.unref === "function") {
229
+ t.unref();
230
+ }
231
+ signal?.addEventListener("abort", () => {
232
+ clearTimeout(t);
233
+ resolve();
234
+ });
235
+ });
236
+ }
237
+ //# sourceMappingURL=_active-channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_active-channel.js","sourceRoot":"","sources":["../src/_active-channel.ts"],"names":[],"mappings":"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"}
@@ -4,36 +4,34 @@
4
4
  * (Express/Fastify/Hono/Nest with `.mjs`/`.ts`), Bun, and pre-bundle
5
5
  * dev mode for several frameworks.
6
6
  *
7
- * Registered via `module.register('./dist/_esm-loader.js', import.meta.url,
8
- * { data: { featureMap: [...] } })` from server.ts's installEsmLoader().
7
+ * Registered via `module.register('./dist/_esm-loader.js',
8
+ * import.meta.url, { data: { eligibility: [...] } })` from server.ts's
9
+ * installEsmLoader().
9
10
  *
10
- * Strategy: regex-level source rewriting on files matching a feature
11
- * path. Detects common named-function export shapes, renames the
12
- * underlying function, and appends a wrapped re-export that calls
13
- * `__praetomWrapEntryPoint`. Falls back to no-op (with a `[praetom]`
14
- * warning) for shapes that don't match.
11
+ * Strategy: source rewriting on files matching an *eligibility* entry.
12
+ * The rewrite emits late-binding wraps (`__praetomLateBindWrap`) no
13
+ * slug is baked in. At call time, the wrap reads the shared
14
+ * `activeMap` on globalThis and decides whether to emit a span.
15
15
  *
16
- * Handles:
17
- * export async function NAME(...)
18
- * export function NAME(...)
19
- * export const NAME = async (...)
20
- * export const NAME = (...)
21
- *
22
- * Does NOT handle (yet):
23
- * export default ...
24
- * export { NAME }
25
- * export { NAME as default }
26
- * class exports
27
- *
28
- * The bundler plugins (Vite/Webpack) cover the cases this loader skips
29
- * — bundled apps go through the plugin, not this loader.
16
+ * The loader appends an inline `//# sourceMappingURL=data:` so stack
17
+ * traces stay aligned to the original lines after the rewrite.
30
18
  */
31
- interface FeatureMapEntry {
32
- slug: string;
33
- entrySymbol?: string;
34
- }
19
+ import type { MessagePort } from "node:worker_threads";
35
20
  interface InitData {
36
- featureMap: Array<[string, FeatureMapEntry]>;
21
+ eligibility: Array<[string, ReadonlyArray<{
22
+ exportName: string;
23
+ }>]>;
24
+ /**
25
+ * Optional MessagePort the main thread uses to push eligibility
26
+ * updates after boot. When provided, the loader listens for messages
27
+ * shaped `{ type: "eligibility", eligibility: [...] }` and swaps the
28
+ * in-memory map. When omitted, eligibility is frozen at boot
29
+ * (back-compat with pre-hot-pickup SDK builds).
30
+ *
31
+ * Already-loaded modules keep their boot-time wrap (or lack of wrap)
32
+ * — the loader only runs on `load()` for fresh imports.
33
+ */
34
+ port?: MessagePort;
37
35
  }
38
36
  interface LoadResult {
39
37
  format: string;
@@ -1 +1 @@
1
- {"version":3,"file":"_esm-loader.d.ts","sourceRoot":"","sources":["../src/_esm-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,QAAQ;IAChB,UAAU,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;CAC9C;AAED,UAAU,UAAU;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,eAAe,CAAC;IAChD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,KAAK,QAAQ,GAAG,CACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,OAAO,KACb,OAAO,CAAC,UAAU,CAAC,CAAC;AAKzB,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAG/C;AAED,wBAAsB,IAAI,CACxB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtD,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,UAAU,CAAC,CAoCrB"}
1
+ {"version":3,"file":"_esm-loader.d.ts","sourceRoot":"","sources":["../src/_esm-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AASvD,UAAU,QAAQ;IAChB,WAAW,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC,CAAC;IACpE;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAOD,UAAU,UAAU;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,eAAe,CAAC;IAChD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,KAAK,QAAQ,GAAG,CACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,OAAO,KACb,OAAO,CAAC,UAAU,CAAC,CAAC;AAKzB,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAiB/C;AAgBD,wBAAsB,IAAI,CACxB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtD,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,UAAU,CAAC,CA+CrB"}
@@ -4,35 +4,48 @@
4
4
  * (Express/Fastify/Hono/Nest with `.mjs`/`.ts`), Bun, and pre-bundle
5
5
  * dev mode for several frameworks.
6
6
  *
7
- * Registered via `module.register('./dist/_esm-loader.js', import.meta.url,
8
- * { data: { featureMap: [...] } })` from server.ts's installEsmLoader().
7
+ * Registered via `module.register('./dist/_esm-loader.js',
8
+ * import.meta.url, { data: { eligibility: [...] } })` from server.ts's
9
+ * installEsmLoader().
9
10
  *
10
- * Strategy: regex-level source rewriting on files matching a feature
11
- * path. Detects common named-function export shapes, renames the
12
- * underlying function, and appends a wrapped re-export that calls
13
- * `__praetomWrapEntryPoint`. Falls back to no-op (with a `[praetom]`
14
- * warning) for shapes that don't match.
11
+ * Strategy: source rewriting on files matching an *eligibility* entry.
12
+ * The rewrite emits late-binding wraps (`__praetomLateBindWrap`) no
13
+ * slug is baked in. At call time, the wrap reads the shared
14
+ * `activeMap` on globalThis and decides whether to emit a span.
15
15
  *
16
- * Handles:
17
- * export async function NAME(...)
18
- * export function NAME(...)
19
- * export const NAME = async (...)
20
- * export const NAME = (...)
21
- *
22
- * Does NOT handle (yet):
23
- * export default ...
24
- * export { NAME }
25
- * export { NAME as default }
26
- * class exports
27
- *
28
- * The bundler plugins (Vite/Webpack) cover the cases this loader skips
29
- * — bundled apps go through the plugin, not this loader.
16
+ * The loader appends an inline `//# sourceMappingURL=data:` so stack
17
+ * traces stay aligned to the original lines after the rewrite.
30
18
  */
31
- let _featureMap = new Map();
19
+ import { wrapModuleSourceForLateBinding } from "./webpack/shared";
20
+ let _eligibility = new Map();
32
21
  let _initialized = false;
33
22
  export function initialize(data) {
34
- _featureMap = new Map(data.featureMap);
23
+ applyEligibility(data.eligibility);
35
24
  _initialized = true;
25
+ // Subscribe to live updates from the main thread. Once the loader's
26
+ // map is replaced, every subsequent `load()` call wraps against the
27
+ // fresh eligibility set — same semantics as the CJS hook's runtime-
28
+ // re-read of mapByPath.
29
+ if (data.port) {
30
+ data.port.on("message", (msg) => {
31
+ if (!isEligibilityUpdate(msg))
32
+ return;
33
+ applyEligibility(msg.eligibility);
34
+ });
35
+ // Don't let the loader's port keep the process alive past its
36
+ // real work.
37
+ if (typeof data.port.unref === "function")
38
+ data.port.unref();
39
+ }
40
+ }
41
+ function applyEligibility(next) {
42
+ _eligibility = new Map(next.map(([path, exports]) => [path, { exports: [...exports] }]));
43
+ }
44
+ function isEligibilityUpdate(msg) {
45
+ if (!msg || typeof msg !== "object")
46
+ return false;
47
+ const m = msg;
48
+ return m.type === "eligibility" && Array.isArray(m.eligibility);
36
49
  }
37
50
  export async function load(url, context, nextLoad) {
38
51
  const result = await nextLoad(url, context);
@@ -49,7 +62,7 @@ export async function load(url, context, nextLoad) {
49
62
  catch {
50
63
  return result;
51
64
  }
52
- const match = matchFeature(filePath);
65
+ const match = matchEligibility(filePath);
53
66
  if (!match)
54
67
  return result;
55
68
  const source = typeof result.source === "string"
@@ -59,73 +72,27 @@ export async function load(url, context, nextLoad) {
59
72
  ? new Uint8Array(result.source)
60
73
  : result.source)
61
74
  : "";
62
- const wrapped = wrapModuleSource(source, match.slug, match.entrySymbol);
75
+ const wrapped = wrapModuleSourceForLateBinding(source, match.featurePath, match.file.exports.map((e) => e.exportName), filePath);
63
76
  if (!wrapped) {
64
77
  // eslint-disable-next-line no-console
65
78
  console.warn(`[praetom] could not wrap ${filePath} (export shape not recognized; falling back to no-op)`);
66
79
  return result;
67
80
  }
68
- return { ...result, source: wrapped };
81
+ const mapJson = wrapped.map.toString();
82
+ const inlined = Buffer.from(mapJson, "utf8").toString("base64");
83
+ const sourceWithMap = wrapped.code + `\n//# sourceMappingURL=data:application/json;base64,${inlined}\n`;
84
+ return { ...result, source: sourceWithMap };
69
85
  }
70
- function matchFeature(absPath) {
86
+ function matchEligibility(absPath) {
71
87
  const normalized = absPath.replace(/\\/g, "/");
72
- for (const [featurePath, info] of _featureMap) {
73
- if (normalized === featurePath ||
74
- normalized.endsWith(`/${featurePath}`)) {
75
- return info;
88
+ const direct = _eligibility.get(normalized);
89
+ if (direct)
90
+ return { featurePath: normalized, file: direct };
91
+ for (const [featurePath, file] of _eligibility) {
92
+ if (featurePath.includes("/") && normalized.endsWith(`/${featurePath}`)) {
93
+ return { featurePath, file };
76
94
  }
77
95
  }
78
96
  return null;
79
97
  }
80
- const PRAETOM_IMPORT_MARKER = "__praetom_wrap_import__";
81
- const HTTP_METHODS = new Set([
82
- "GET",
83
- "POST",
84
- "PUT",
85
- "PATCH",
86
- "DELETE",
87
- "OPTIONS",
88
- "HEAD",
89
- ]);
90
- function wrapModuleSource(source, slug, entrySymbol) {
91
- if (source.includes(PRAETOM_IMPORT_MARKER)) {
92
- // Already wrapped (shouldn't happen with nextLoad short-circuit but
93
- // belt + suspenders).
94
- return null;
95
- }
96
- // Pattern A: `export (async )?function NAME(` — rewrite the export
97
- // keyword off, rename the function to __praetom_orig_NAME, and
98
- // append an `export const NAME = __wrap(...)`.
99
- // Pattern B: `export const NAME = ` (function value) — same shape.
100
- const targets = new Set();
101
- let rewritten = source.replace(/^export\s+(async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/gm, (_match, asyncKw, name) => {
102
- targets.add(name);
103
- return `${asyncKw ?? ""}function __praetom_orig_${name}(`;
104
- });
105
- rewritten = rewritten.replace(/^export\s+const\s+([A-Za-z_$][\w$]*)\s*=/gm, (_match, name) => {
106
- // Only wrap if the entry name is the requested entrySymbol (when
107
- // specified) or an HTTP method (Next.js App Router convention).
108
- // Otherwise leave alone — `export const foo = "bar"` shouldn't
109
- // become `wrapEntryPoint(..., "bar")`.
110
- if (!shouldWrap(name, entrySymbol))
111
- return `export const ${name} =`;
112
- targets.add(name);
113
- return `const __praetom_orig_${name} =`;
114
- });
115
- if (targets.size === 0)
116
- return null;
117
- const filtered = [...targets].filter((n) => shouldWrap(n, entrySymbol));
118
- if (filtered.length === 0)
119
- return null;
120
- const importLine = `import { __praetomWrapEntryPoint as ${PRAETOM_IMPORT_MARKER} } from "praetom";\n`;
121
- const wraps = filtered
122
- .map((name) => `export const ${name} = ${PRAETOM_IMPORT_MARKER}(${JSON.stringify(slug)}, ${JSON.stringify(name)}, __praetom_orig_${name});`)
123
- .join("\n");
124
- return `${importLine}${rewritten}\n${wraps}\n`;
125
- }
126
- function shouldWrap(name, entrySymbol) {
127
- if (entrySymbol)
128
- return name === entrySymbol;
129
- return HTTP_METHODS.has(name) || name === "handler" || name === "default";
130
- }
131
98
  //# sourceMappingURL=_esm-loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_esm-loader.js","sourceRoot":"","sources":["../src/_esm-loader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"_esm-loader.js","sourceRoot":"","sources":["../src/_esm-loader.ts"],"names":[],"mappings":"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"}
@@ -47,6 +47,37 @@ export declare function currentFeatureContext(): FeatureContext | undefined;
47
47
  * bundler-plugin-injected code can call it; customers should never
48
48
  * import it directly.
49
49
  */
50
+ /**
51
+ * Late-binding wrap. Installed once at module-load time for every
52
+ * `(path, exportName)` pair the server declares as eligible. The wrap
53
+ * defers the slug decision to call time: each invocation reads the
54
+ * shared `activeMap` (mutated by the active-channel client when the
55
+ * dashboard adds/removes features) and either:
56
+ * - finds an entry for `path:exportName` → feature-wraps with that slug, or
57
+ * - finds nothing → passes straight through to the original.
58
+ *
59
+ * The passthrough path adds one `Map.get(string)` per call (~tens of
60
+ * ns). The active path is identical cost to a normal `wrapEntryPoint`.
61
+ *
62
+ * Preserves `.name`, `.length`, and `Function.prototype.toString` so
63
+ * frameworks that introspect function shape (Express middleware's
64
+ * length-based handler detection, NestJS decorators, etc.) keep
65
+ * working.
66
+ */
67
+ export declare function lateBindWrap(path: string, exportName: string, original: (...args: unknown[]) => unknown): (...args: unknown[]) => unknown;
68
+ /**
69
+ * Snapshot-mode update: replace the active map wholesale. Called by
70
+ * the active-channel client on every snapshot event (SSE or poll).
71
+ * Cheap — single Map mutation, no allocation per entry beyond the
72
+ * source iteration.
73
+ */
74
+ export declare function setActiveMap(entries: ReadonlyArray<{
75
+ path: string;
76
+ exportName: string;
77
+ slug: string;
78
+ }>): void;
79
+ /** Read-only access for diagnostic / test code. */
80
+ export declare function getActiveMapSize(): number;
50
81
  export declare function wrapEntryPoint(slug: string, entryName: string, original: (...args: unknown[]) => unknown): (...args: unknown[]) => unknown;
51
82
  /**
52
83
  * Public primitive — fire a point-in-time event tagged with the current
@@ -54,8 +85,44 @@ export declare function wrapEntryPoint(slug: string, entryName: string, original
54
85
  * latency splits, business events, or error context that the entry-
55
86
  * point wrap doesn't capture by itself.
56
87
  *
88
+ * ## Context propagation (Node.js)
89
+ *
90
+ * `emit()` discovers the current feature via Node's `AsyncLocalStorage`.
91
+ * That means it works **only inside the async causality chain started
92
+ * by a wrapped entry point**:
93
+ *
94
+ * - ✅ awaited code paths inside the handler (`await db.query(...)` →
95
+ * subsequent `emit()`)
96
+ * - ✅ `setTimeout` / `setImmediate` / `queueMicrotask` callbacks
97
+ * scheduled inside the handler — they inherit the ALS store
98
+ * - ✅ Promise chains that fork off the handler's call stack
99
+ * - ❌ `EventEmitter` listeners attached *outside* the handler and
100
+ * fired *from* it — listeners run in the caller's ALS scope, not
101
+ * the emitter's. Wrap listener attachment inside the handler (or
102
+ * pass the slug explicitly via a closure) to keep context.
103
+ * - ❌ Code executed from a **background worker / cron / queue
104
+ * consumer** that wasn't itself wrapped. There's no entry-point
105
+ * wrap, so there's no feature context. The `emit()` still records
106
+ * as a root span with no parent, but won't aggregate into any
107
+ * feature's signal.
108
+ * - ❌ Code that escapes the request via a detached `setImmediate`
109
+ * chain where you intentionally `als.exit()` (unusual; if you're
110
+ * doing this you already know).
111
+ *
112
+ * If you need feature context on a background path, wrap the background
113
+ * worker's entry point itself (the bundler plugin handles this when
114
+ * you point a feature contract at the worker's `path`).
115
+ *
57
116
  * Outside a feature context the span still emits, but with no parent —
58
117
  * use sparingly for top-level signals.
118
+ *
119
+ * ## Browser
120
+ *
121
+ * Browser builds don't use ALS (it doesn't exist in the browser). The
122
+ * current feature is read from a per-tab stack maintained by
123
+ * `__praetomWrapEntryPoint` — same shape, different mechanism. Same
124
+ * gotchas: `emit()` inside an event listener attached outside the wrap
125
+ * won't pick up the feature.
59
126
  */
60
127
  export declare function emit(eventName: string, payload?: Record<string, unknown>): void;
61
128
  export declare function flush(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"_internal.d.ts","sourceRoot":"","sources":["../src/_internal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA6BH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AA4BD,wBAAgB,IAAI,CAAC,IAAI,EAAE;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,IAAI,CAOP;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAiB3D;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,EAC3F,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ;AAqBD,wBAAgB,qBAAqB,IAAI,cAAc,GAAG,SAAS,CAElE;AAYD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GACxC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAsBjC;AA8CD;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAClB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CAgBN;AAqCD,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3C;AAED,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAM9C"}
1
+ {"version":3,"file":"_internal.d.ts","sourceRoot":"","sources":["../src/_internal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA4CH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAgDD,wBAAgB,IAAI,CAAC,IAAI,EAAE;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,IAAI,CAOP;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAiB3D;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG;IAAE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,EAC3F,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ;AAqBD,wBAAgB,qBAAqB,IAAI,cAAc,GAAG,SAAS,CAElE;AAYD;;;;;;;;;;;GAWG;AACH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GACxC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CA0BjC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GACzE,IAAI,CAKN;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AA6CD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GACxC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CA6BjC;AA8CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,IAAI,CAClB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CAgBN;AAqCD,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3C;AAED,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAM9C"}