@browserwright/pi 0.0.1 → 0.10.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/core/exec-module.ts +61 -8
- package/package.json +1 -1
package/core/exec-module.ts
CHANGED
|
@@ -22,20 +22,73 @@ import { pathToFileURL } from "node:url";
|
|
|
22
22
|
import { isAbsolute, resolve } from "node:path";
|
|
23
23
|
import type { ModuleContext, ModuleProvider, ModuleRunner, ProviderOutcome, Role } from "./types.ts";
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Completed and in-flight loads, keyed by spec.
|
|
27
|
+
*
|
|
28
|
+
* This stores the PROMISE, not the resolved runner, on purpose. Two web_search
|
|
29
|
+
* calls fired in the same turn both miss a value cache and both import; under
|
|
30
|
+
* pi's jiti runtime that race is not merely wasteful but wrong — the second
|
|
31
|
+
* caller can observe a half-initialized module record and fail the load (see
|
|
32
|
+
* the mechanism note above loadRunner). A promise cache collapses the second
|
|
33
|
+
* caller onto the first's in-flight import, so everyone awaits the same
|
|
34
|
+
* import and sees the same resolved runner.
|
|
35
|
+
*/
|
|
36
|
+
const cache = new Map<string, Promise<ModuleRunner<unknown>>>();
|
|
26
37
|
|
|
27
|
-
|
|
38
|
+
/**
|
|
39
|
+
* How a module runner is loaded. Injectable for tests; production always uses
|
|
40
|
+
* the native dynamic import, which pi's jiti runtime routes through its own
|
|
41
|
+
* transpile-and-cache pipeline.
|
|
42
|
+
*
|
|
43
|
+
* Why the unwrap (measured, not hypothetical): jiti transpiles .ts modules to
|
|
44
|
+
* CommonJS and rewrites every static import into a top-level
|
|
45
|
+
* `await jitiImport(...)` inside an async wrapper. While the module body is
|
|
46
|
+
* suspended on those awaits, the module record is already in jiti's shared
|
|
47
|
+
* per-chain cache with `exports.default` still unset. A concurrent import of
|
|
48
|
+
* the same module then hits that half-initialized record, and jiti's
|
|
49
|
+
* interopDefault proxy surfaces the RAW exports object (`{ default: runner,
|
|
50
|
+
* explain }`) as the module's `.default` — which is not a function. Observable
|
|
51
|
+
* as an intermittent "does not default-export a runner function" on the first
|
|
52
|
+
* concurrent call, never again once cached. The promise cache above removes
|
|
53
|
+
* the race; this unwrap is belt-and-braces for any residual interop quirk
|
|
54
|
+
* (e.g. an environment forcing JITI_INTEROP_DEFAULT=false).
|
|
55
|
+
*/
|
|
56
|
+
export type ModuleImporter = (href: string) => Promise<{ default?: unknown }>;
|
|
57
|
+
|
|
58
|
+
const defaultImporter: ModuleImporter = (href) => import(href) as Promise<{ default?: unknown }>;
|
|
59
|
+
|
|
60
|
+
export async function loadRunner(
|
|
61
|
+
spec: string,
|
|
62
|
+
dir: string,
|
|
63
|
+
importer: ModuleImporter = defaultImporter,
|
|
64
|
+
): Promise<ModuleRunner<unknown>> {
|
|
28
65
|
const cached = cache.get(spec);
|
|
29
66
|
if (cached) return cached;
|
|
30
67
|
|
|
31
68
|
const path = isAbsolute(spec) ? spec : resolve(dir, spec);
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
69
|
+
const load = (async () => {
|
|
70
|
+
const imported = (await importer(pathToFileURL(path).href)) as { default?: unknown };
|
|
71
|
+
const direct = imported.default as unknown;
|
|
72
|
+
const runner =
|
|
73
|
+
typeof direct === "function"
|
|
74
|
+
? (direct as ModuleRunner<unknown>)
|
|
75
|
+
: ((direct as { default?: unknown } | null)?.default as unknown);
|
|
76
|
+
if (typeof runner !== "function") {
|
|
77
|
+
throw new Error(`${spec} does not default-export a runner function`);
|
|
78
|
+
}
|
|
79
|
+
return runner as ModuleRunner<unknown>;
|
|
80
|
+
})();
|
|
81
|
+
|
|
82
|
+
cache.set(spec, load);
|
|
83
|
+
try {
|
|
84
|
+
return await load;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// A failed load must not poison the cache forever: drop it so the next
|
|
87
|
+
// call re-imports. Both callers of a shared rejected promise reach here
|
|
88
|
+
// (delete is idempotent).
|
|
89
|
+
cache.delete(spec);
|
|
90
|
+
throw error;
|
|
36
91
|
}
|
|
37
|
-
cache.set(spec, runner as ModuleRunner<unknown>);
|
|
38
|
-
return runner as ModuleRunner<unknown>;
|
|
39
92
|
}
|
|
40
93
|
|
|
41
94
|
export async function execModule<T>(
|
package/package.json
CHANGED