@statewalker/webrun-modules 0.1.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +356 -0
  3. package/dist/index.d.ts +9 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +809 -0
  6. package/dist/resolution/node-builtins.d.ts +24 -0
  7. package/dist/resolution/node-builtins.d.ts.map +1 -0
  8. package/dist/resolution/resolve-entry.d.ts +9 -0
  9. package/dist/resolution/resolve-entry.d.ts.map +1 -0
  10. package/dist/server/new-module-server.d.ts +4 -0
  11. package/dist/server/new-module-server.d.ts.map +1 -0
  12. package/dist/server/specifiers.d.ts +11 -0
  13. package/dist/server/specifiers.d.ts.map +1 -0
  14. package/dist/sources/npm-registry-source.d.ts +29 -0
  15. package/dist/sources/npm-registry-source.d.ts.map +1 -0
  16. package/dist/sources/untar.d.ts +9 -0
  17. package/dist/sources/untar.d.ts.map +1 -0
  18. package/dist/transform/index.d.ts +15 -0
  19. package/dist/transform/index.d.ts.map +1 -0
  20. package/dist/transform/scan.d.ts +11 -0
  21. package/dist/transform/scan.d.ts.map +1 -0
  22. package/dist/transform/to-js.d.ts +10 -0
  23. package/dist/transform/to-js.d.ts.map +1 -0
  24. package/dist/transform/transform-cjs.d.ts +13 -0
  25. package/dist/transform/transform-cjs.d.ts.map +1 -0
  26. package/dist/transform/transform-esm.d.ts +9 -0
  27. package/dist/transform/transform-esm.d.ts.map +1 -0
  28. package/dist/types.d.ts +127 -0
  29. package/dist/types.d.ts.map +1 -0
  30. package/package.json +66 -0
  31. package/src/index.ts +26 -0
  32. package/src/resolution/node-builtins.ts +79 -0
  33. package/src/resolution/resolve-entry.ts +65 -0
  34. package/src/server/new-module-server.ts +416 -0
  35. package/src/server/specifiers.ts +26 -0
  36. package/src/sources/npm-registry-source.ts +86 -0
  37. package/src/sources/untar.ts +37 -0
  38. package/src/transform/index.ts +52 -0
  39. package/src/transform/scan.ts +32 -0
  40. package/src/transform/to-js.ts +18 -0
  41. package/src/transform/transform-cjs.ts +85 -0
  42. package/src/transform/transform-esm.ts +35 -0
  43. package/src/types.ts +143 -0
@@ -0,0 +1,24 @@
1
+ import type { ModuleTarget } from "../types.js";
2
+ /** Strip a `node:` prefix and any subpath, returning the core module name. */
3
+ export declare function nodeBuiltinName(spec: string): string | undefined;
4
+ export declare function isNodeBuiltin(spec: string): boolean;
5
+ /**
6
+ * Map a Node-builtin specifier under a given target:
7
+ * - `browser` → a `@jspm/core` browser-polyfill package ref (served locally).
8
+ * - `node` → `{ external }` (leave the `node:`-prefixed specifier in place).
9
+ *
10
+ * The subpath is `nodelibs/${name}` (not `nodelibs/browser/…`): `@jspm/core`'s
11
+ * `exports` map (`"./nodelibs/*"` → `default: "./nodelibs/browser/*.js"`) supplies
12
+ * the `browser` segment via the export condition. Hard-coding it here would make
13
+ * the wildcard expand to `nodelibs/browser/browser/${name}.js` — a path that
14
+ * doesn't exist.
15
+ */
16
+ export declare function resolveNodeBuiltin(spec: string, target: ModuleTarget): {
17
+ ref: {
18
+ pkg: string;
19
+ subpath: string;
20
+ };
21
+ } | {
22
+ external: string;
23
+ } | undefined;
24
+ //# sourceMappingURL=node-builtins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-builtins.d.ts","sourceRoot":"","sources":["../../src/resolution/node-builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgDhD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIhE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,YAAY,GACnB;IAAE,GAAG,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAK9E"}
@@ -0,0 +1,9 @@
1
+ import type { ModuleTarget, PackageManifest } from "../types.js";
2
+ /**
3
+ * Resolve a package + subpath to the concrete file path inside the package,
4
+ * honoring `package.json` `exports` conditions for the given `target`, with a
5
+ * `main`/`module`/`browser` legacy fallback. Returns a package-relative path
6
+ * (e.g. `"dist/index.js"`).
7
+ */
8
+ export declare function resolveEntry(manifest: PackageManifest, subpath: string | undefined, target: ModuleTarget): string;
9
+ //# sourceMappingURL=resolve-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-entry.d.ts","sourceRoot":"","sources":["../../src/resolution/resolve-entry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAsBjE;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,MAAM,EAAE,YAAY,GACnB,MAAM,CA+BR"}
@@ -0,0 +1,4 @@
1
+ import type { ModuleServer, ModuleServerOptions } from "../types.js";
2
+ /** Create a module/dependency server over an injected `FilesApi` cache. */
3
+ export declare function newModuleServer(options: ModuleServerOptions): ModuleServer;
4
+ //# sourceMappingURL=new-module-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"new-module-server.d.ts","sourceRoot":"","sources":["../../src/server/new-module-server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAGV,YAAY,EACZ,mBAAmB,EAGpB,MAAM,aAAa,CAAC;AA2BrB,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAsV1E"}
@@ -0,0 +1,11 @@
1
+ /** Split a bare specifier into package name + optional subpath (scope-aware). */
2
+ export declare function parseSpecifier(spec: string): {
3
+ pkg: string;
4
+ subpath?: string;
5
+ };
6
+ /**
7
+ * Relative URL from one canonical module id to another (both are slash paths with
8
+ * no leading slash, filename last). Keeps served imports mount-prefix-portable.
9
+ */
10
+ export declare function relativeUrl(fromId: string, toId: string): string;
11
+ //# sourceMappingURL=specifiers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specifiers.d.ts","sourceRoot":"","sources":["../../src/server/specifiers.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAU9E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQhE"}
@@ -0,0 +1,29 @@
1
+ import type { FilesApi } from "@statewalker/webrun-files";
2
+ import type { ModuleRef, PackageManifest, Source } from "../types.js";
3
+ export interface NpmRegistrySourceOptions {
4
+ /** Registry base URL. Defaults to the public npm registry. */
5
+ registryUrl?: string;
6
+ /** Injectable fetch (tests supply a fake; defaults to global `fetch`). */
7
+ fetch?: typeof fetch;
8
+ /** Factory for the in-memory package tree. Defaults to `new MemFilesApi()`. */
9
+ createFiles?: () => FilesApi;
10
+ }
11
+ /** Registry metadata shape (only the fields we read). */
12
+ interface RegistryMeta {
13
+ "dist-tags"?: Record<string, string>;
14
+ versions?: Record<string, PackageManifest & {
15
+ dist?: {
16
+ tarball?: string;
17
+ };
18
+ }>;
19
+ }
20
+ /**
21
+ * The default acquisition `Source`: resolves a version from npm registry
22
+ * metadata, downloads the tarball, untars it into a fresh `FilesApi`, and returns
23
+ * the package files + manifest. Fully isomorphic (`fetch` + pure-JS untar).
24
+ */
25
+ export declare function npmRegistrySource(opts?: NpmRegistrySourceOptions): Source;
26
+ /** Resolve a dist-tag, exact version, or semver range to a concrete version. */
27
+ export declare function resolveVersion(meta: RegistryMeta, spec: string | undefined, ref: ModuleRef): string;
28
+ export {};
29
+ //# sourceMappingURL=npm-registry-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-registry-source.d.ts","sourceRoot":"","sources":["../../src/sources/npm-registry-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAG1D,OAAO,KAAK,EAAiB,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIrF,MAAM,WAAW,wBAAwB;IACvC,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,QAAQ,CAAC;CAC9B;AAED,yDAAyD;AACzD,UAAU,YAAY;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG;QAAE,IAAI,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CAC9E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,wBAA6B,GAAG,MAAM,CAkC7E;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAC5B,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,GAAG,EAAE,SAAS,GACb,MAAM,CAYR"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Untar an npm `.tgz` (gzipped ustar) into a map of path → bytes, isomorphically
3
+ * (no `node:zlib`, no `Buffer`). npm tarballs prefix every entry with `package/`;
4
+ * that prefix is stripped so keys are relative to the package root.
5
+ */
6
+ export declare function untarTgz(tgz: Uint8Array): Map<string, Uint8Array>;
7
+ /** Parse a raw ustar archive (512-byte blocks). */
8
+ export declare function untar(buf: Uint8Array): Map<string, Uint8Array>;
9
+ //# sourceMappingURL=untar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"untar.d.ts","sourceRoot":"","sources":["../../src/sources/untar.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAEjE;AAQD,mDAAmD;AACnD,wBAAgB,KAAK,CAAC,GAAG,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAkB9D"}
@@ -0,0 +1,15 @@
1
+ import type { PackageManifest, SourceFormat, Transform } from "../types.js";
2
+ /** The default per-file transform: dispatch ESM/TS/JSX vs CJS by `file.format`. */
3
+ export declare function newDefaultTransform(): Transform;
4
+ /**
5
+ * Decide a file's `SourceFormat` from its extension, the package `type`, and
6
+ * (for ambiguous `.js`) a content sniff. Mirrors Node: `.mjs`=ESM, `.cjs`=CJS,
7
+ * `.js` follows `package.json#type`; with no `type`, a package `.js` is CJS
8
+ * (Node's default) unless real ESM syntax is present. Definitive CJS markers
9
+ * (`module.exports`/`exports.x`) win over a loose `import`/`export` word-match,
10
+ * so a CJS file with "export" in a string is not mis-read as ESM.
11
+ */
12
+ export declare function detectFormat(path: string, source: string, manifest?: PackageManifest): SourceFormat;
13
+ export { newCjsTransform } from "./transform-cjs.js";
14
+ export { newEsmTransform } from "./transform-esm.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transform/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAc,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIxF,mFAAmF;AACnF,wBAAgB,mBAAmB,IAAI,SAAS,CAQ/C;AAUD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,eAAe,GACzB,YAAY,CAad;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { parse as parseCjs } from "cjs-module-lexer";
2
+ import type { SourceFormat } from "../types.js";
3
+ /**
4
+ * List the import specifiers a file's transform will rewrite — the exact same set
5
+ * the ESM/CJS transforms discover — so the server can pre-resolve them (async)
6
+ * before running the synchronous rewrite. For TS/JSX the raw source is scanned;
7
+ * `es-module-lexer` tolerates types well enough to find every specifier.
8
+ */
9
+ export declare function scanSpecifiers(source: string, format: SourceFormat): Promise<string[]>;
10
+ export { parseCjs };
11
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/transform/scan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,IAAI,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQhD;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAY5F;AAGD,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { SourceFormat } from "../types.js";
2
+ /**
3
+ * Strip TS/JSX to plain JS (leave already-ESM/CJS JS untouched). Shared by the
4
+ * ESM transform and the specifier scanner so both see the *same* JS — raw TS/JSX
5
+ * cannot be lexed directly (`es-module-lexer` throws on JSX). JSX uses the
6
+ * automatic runtime, so scanning the output also surfaces the `react/jsx-runtime`
7
+ * import the runtime injects.
8
+ */
9
+ export declare function toJs(source: string, format: SourceFormat, path?: string): string;
10
+ //# sourceMappingURL=to-js.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-js.d.ts","sourceRoot":"","sources":["../../src/transform/to-js.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAOhF"}
@@ -0,0 +1,13 @@
1
+ import type { Transform } from "../types.js";
2
+ /**
3
+ * Transform a CommonJS file into served, browser-runnable ESM — no global runtime
4
+ * registry: each static `require` target is a static namespace import, so the ESM
5
+ * module graph itself provides load ordering and circular-dep handling. The CJS
6
+ * body runs synchronously against a synthetic `require` that maps specifiers to
7
+ * those namespaces; named exports (via `cjs-module-lexer`) are re-exported as
8
+ * eval-time snapshots (valid because the body executed synchronously above).
9
+ * Computed `require(expr)` hits the synthetic require's miss path (throws) — the
10
+ * declared `esbuild-wasm` fallback's job.
11
+ */
12
+ export declare function newCjsTransform(): Transform;
13
+ //# sourceMappingURL=transform-cjs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform-cjs.d.ts","sourceRoot":"","sources":["../../src/transform/transform-cjs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,aAAa,CAAC;AAgBzD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,IAAI,SAAS,CAyD3C"}
@@ -0,0 +1,9 @@
1
+ import type { Transform } from "../types.js";
2
+ /**
3
+ * The default per-file transform for ESM (and TS/JSX) sources: transpile to plain
4
+ * JS, then rewrite every static/dynamic-string import & re-export specifier in
5
+ * place via `rewrite`, leaving quotes and everything else byte-for-byte intact.
6
+ * Computed dynamic specifiers (no static string) are left untouched.
7
+ */
8
+ export declare function newEsmTransform(): Transform;
9
+ //# sourceMappingURL=transform-esm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform-esm.d.ts","sourceRoot":"","sources":["../../src/transform/transform-esm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,aAAa,CAAC;AAKzD;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,SAAS,CAsB3C"}
@@ -0,0 +1,127 @@
1
+ import type { FilesApi } from "@statewalker/webrun-files";
2
+ /**
3
+ * Public contract for `@statewalker/webrun-modules`. Types only — the resolver
4
+ * core, the default npm `Source`, and the transforms are implemented separately.
5
+ * Contract source: notes/2026/2026-07/2026-07-23/grill-module-webrun-modules.md
6
+ */
7
+ /** The build target — selects `exports` conditions and is part of the cache key. */
8
+ export type ModuleTarget = "browser" | "node";
9
+ /**
10
+ * A reference to resolve: either an npm package (with optional pinned version and
11
+ * subpath) or a URL (a local project script served by this server, or an already
12
+ * resolved local URL).
13
+ */
14
+ export type ModuleRef = {
15
+ pkg: string;
16
+ version?: string;
17
+ subpath?: string;
18
+ } | {
19
+ url: string;
20
+ };
21
+ /** The result of resolving a `ModuleRef`: a directly `import`-able local URL. */
22
+ export interface ResolvedModule {
23
+ /** Importable URL — `/{name}@{version}/{subpath}` for a package, carries `basePath`. */
24
+ url: string;
25
+ target: ModuleTarget;
26
+ }
27
+ /**
28
+ * The version-dedupe map produced by `prime` and reusable as a pin input. One
29
+ * artifact, two roles: a partial lockfile pins only the names it lists; `prime`
30
+ * writes the complete one back to the cache.
31
+ */
32
+ export type Lockfile = Record<string, string>;
33
+ /** A parsed `package.json` (only the fields resolution reads). */
34
+ export interface PackageManifest {
35
+ name: string;
36
+ version: string;
37
+ main?: string;
38
+ module?: string;
39
+ browser?: string | Record<string, string | false>;
40
+ exports?: unknown;
41
+ imports?: unknown;
42
+ dependencies?: Record<string, string>;
43
+ peerDependencies?: Record<string, string>;
44
+ [key: string]: unknown;
45
+ }
46
+ /** A package acquired by a `Source`: its untarred files plus parsed manifest. */
47
+ export interface LoadedPackage {
48
+ name: string;
49
+ version: string;
50
+ /** The untarred package tree — every file individually addressable. */
51
+ files: FilesApi;
52
+ manifest: PackageManifest;
53
+ }
54
+ /**
55
+ * Pluggable acquisition seam. The default impl fetches an npm registry tarball and
56
+ * untars it in memory; JSR and direct-URL are alternative impls.
57
+ */
58
+ export interface Source {
59
+ /** Whether this source handles the given reference (npm | jsr | url). */
60
+ matches(ref: ModuleRef): boolean;
61
+ /** Resolve a concrete version, fetch bytes, untar, parse the manifest. */
62
+ load(ref: ModuleRef): Promise<LoadedPackage>;
63
+ }
64
+ /** The source-file kinds the transform distinguishes. */
65
+ export type SourceFormat = "esm" | "cjs" | "ts" | "tsx";
66
+ export interface SourceFile {
67
+ path: string;
68
+ source: string;
69
+ format: SourceFormat;
70
+ }
71
+ /**
72
+ * Pluggable per-file transform seam. Turns one raw file into browser-runnable ESM,
73
+ * passing every discovered specifier through `rewrite` (which returns the
74
+ * same-origin, prefix-portable relative URL to emit).
75
+ */
76
+ export interface Transform {
77
+ transform(file: SourceFile, rewrite: (specifier: string) => string): Promise<string>;
78
+ }
79
+ export interface ModuleServerOptions {
80
+ /** Injected cache — NodeFilesApi / browser-OPFS / MemFilesApi. Never node:fs. */
81
+ cache: FilesApi;
82
+ /** Optional: local project files served under their own paths (authored scripts). */
83
+ project?: FilesApi;
84
+ /** Acquisition sources; defaults to `[npmRegistrySource()]`. */
85
+ sources?: Source[];
86
+ /** Per-file transform; defaults to the sucrase-based ESM + CJS-interop transform. */
87
+ transform?: Transform;
88
+ /** Selects `exports` conditions and cache key. Defaults to `"browser"`. */
89
+ target?: ModuleTarget;
90
+ /** Optional lockfile input for reproducible resolution; also written by `prime`. */
91
+ lock?: Lockfile;
92
+ /** Mount prefix, e.g. `"/deps/v1/"`. Defaults to `"/"`. */
93
+ basePath?: string;
94
+ /** Prefix (relative to `basePath`) for external package URLs, isolating npm
95
+ * deps under e.g. `"deps/"` while authored project files stay at `~/`.
96
+ * Defaults to `""` — packages served alongside project files. */
97
+ depsPath?: string;
98
+ }
99
+ export interface ModuleServer {
100
+ /** Resolve a single reference to an importable local URL (resolve-on-miss). */
101
+ resolve(ref: ModuleRef, importer?: string): Promise<ResolvedModule>;
102
+ /** Walk the entry's whole graph, warm the cache, write `lock`. */
103
+ prime(entry: ModuleRef): Promise<ResolvedModule>;
104
+ /** Prime an entry and return every reachable module URL (the exact set of
105
+ * scripts required to run it, `basePath`-prefixed, sorted). */
106
+ listResources(entry: ModuleRef): Promise<string[]>;
107
+ /** List every file of a package (raw, package-relative paths), loading it if
108
+ * needed — the full package contents, not just the reachable subset. */
109
+ listPackageFiles(ref: ModuleRef): Promise<string[]>;
110
+ /** Standard Web handler; serves the cache and matches under `basePath`. */
111
+ fetch(request: Request): Promise<Response>;
112
+ /** The resolved graph — also the persisted lockfile. */
113
+ readonly lock: Lockfile;
114
+ }
115
+ /** Thrown when a package / version / subpath cannot be resolved. */
116
+ export declare class ModuleResolveError extends Error {
117
+ readonly ref: ModuleRef;
118
+ readonly reason: string;
119
+ constructor(ref: ModuleRef, reason: string);
120
+ }
121
+ /** Thrown when a file cannot be transformed to runnable ESM. */
122
+ export declare class ModuleTransformError extends Error {
123
+ readonly path: string;
124
+ readonly reason: string;
125
+ constructor(path: string, reason: string);
126
+ }
127
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@statewalker/webrun-modules",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Isomorphic module/dependency server: resolve + download + transform npm and authored TS/JS into browser-runnable ESM served from a local FilesApi cache, with no runtime CDN dependency",
7
+ "homepage": "https://github.com/statewalker/webrun-wire",
8
+ "author": {
9
+ "name": "Mikhail Kotelnikov",
10
+ "email": "mikhail.kotelnikov@gmail.com"
11
+ },
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git@github.com:statewalker/webrun-wire.git"
16
+ },
17
+ "main": "./dist/index.js",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src"
29
+ ],
30
+ "dependencies": {
31
+ "@statewalker/webrun-files": "^0.7.0",
32
+ "@statewalker/webrun-files-mem": "^0.7.2",
33
+ "es-module-lexer": "^2.0.0",
34
+ "sucrase": "^3.35.1",
35
+ "@jspm/generator": "^2.6.1",
36
+ "cjs-module-lexer": "^1.4.1",
37
+ "resolve.exports": "^2.0.3",
38
+ "npm-package-arg": "^12.0.0",
39
+ "semver": "^7.6.3",
40
+ "@jspm/core": "^2.1.0",
41
+ "fflate": "^0.8.2"
42
+ },
43
+ "devDependencies": {
44
+ "@statewalker/webrun-files-node": "^0.7.0",
45
+ "@types/node": "^25.6.0",
46
+ "@types/semver": "^7.5.8",
47
+ "rimraf": "^6.1.3",
48
+ "rolldown": "^1.0.0-rc.16",
49
+ "tsx": "^4.21.0",
50
+ "typescript": "^6.0.3",
51
+ "vitest": "^4.1.4"
52
+ },
53
+ "sideEffects": false,
54
+ "publishConfig": {
55
+ "access": "public"
56
+ },
57
+ "scripts": {
58
+ "build": "rimraf dist && rolldown -c && tsc --emitDeclarationOnly --declaration",
59
+ "test": "vitest run",
60
+ "lint": "biome check src tests",
61
+ "example": "tsx examples/full-cycle.ts",
62
+ "example:full-cycle": "tsx examples/full-cycle.ts",
63
+ "example:server": "tsx examples/http-server.ts",
64
+ "example:site": "tsx examples/site-pipeline.ts"
65
+ }
66
+ }
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ export { newModuleServer } from "./server/new-module-server.js";
2
+ export { parseSpecifier, relativeUrl } from "./server/specifiers.js";
3
+ export type { NpmRegistrySourceOptions } from "./sources/npm-registry-source.js";
4
+ export { npmRegistrySource } from "./sources/npm-registry-source.js";
5
+ export { untarTgz } from "./sources/untar.js";
6
+ export {
7
+ detectFormat,
8
+ newCjsTransform,
9
+ newDefaultTransform,
10
+ newEsmTransform,
11
+ } from "./transform/index.js";
12
+ export type {
13
+ LoadedPackage,
14
+ Lockfile,
15
+ ModuleRef,
16
+ ModuleServer,
17
+ ModuleServerOptions,
18
+ ModuleTarget,
19
+ PackageManifest,
20
+ ResolvedModule,
21
+ Source,
22
+ SourceFile,
23
+ SourceFormat,
24
+ Transform,
25
+ } from "./types.js";
26
+ export { ModuleResolveError, ModuleTransformError } from "./types.js";
@@ -0,0 +1,79 @@
1
+ import type { ModuleTarget } from "../types.js";
2
+
3
+ /** Node core module names (static list — an isomorphic lib can't read `node:module`). */
4
+ const NODE_BUILTINS = new Set([
5
+ "assert",
6
+ "async_hooks",
7
+ "buffer",
8
+ "child_process",
9
+ "cluster",
10
+ "console",
11
+ "constants",
12
+ "crypto",
13
+ "dgram",
14
+ "diagnostics_channel",
15
+ "dns",
16
+ "domain",
17
+ "events",
18
+ "fs",
19
+ "http",
20
+ "http2",
21
+ "https",
22
+ "inspector",
23
+ "module",
24
+ "net",
25
+ "os",
26
+ "path",
27
+ "perf_hooks",
28
+ "process",
29
+ "punycode",
30
+ "querystring",
31
+ "readline",
32
+ "repl",
33
+ "stream",
34
+ "string_decoder",
35
+ "sys",
36
+ "timers",
37
+ "tls",
38
+ "trace_events",
39
+ "tty",
40
+ "url",
41
+ "util",
42
+ "v8",
43
+ "vm",
44
+ "wasi",
45
+ "worker_threads",
46
+ "zlib",
47
+ ]);
48
+
49
+ /** Strip a `node:` prefix and any subpath, returning the core module name. */
50
+ export function nodeBuiltinName(spec: string): string | undefined {
51
+ const bare = spec.replace(/^node:/, "");
52
+ const root = bare.split("/")[0];
53
+ return NODE_BUILTINS.has(root) ? bare : undefined;
54
+ }
55
+
56
+ export function isNodeBuiltin(spec: string): boolean {
57
+ return nodeBuiltinName(spec) !== undefined;
58
+ }
59
+
60
+ /**
61
+ * Map a Node-builtin specifier under a given target:
62
+ * - `browser` → a `@jspm/core` browser-polyfill package ref (served locally).
63
+ * - `node` → `{ external }` (leave the `node:`-prefixed specifier in place).
64
+ *
65
+ * The subpath is `nodelibs/${name}` (not `nodelibs/browser/…`): `@jspm/core`'s
66
+ * `exports` map (`"./nodelibs/*"` → `default: "./nodelibs/browser/*.js"`) supplies
67
+ * the `browser` segment via the export condition. Hard-coding it here would make
68
+ * the wildcard expand to `nodelibs/browser/browser/${name}.js` — a path that
69
+ * doesn't exist.
70
+ */
71
+ export function resolveNodeBuiltin(
72
+ spec: string,
73
+ target: ModuleTarget,
74
+ ): { ref: { pkg: string; subpath: string } } | { external: string } | undefined {
75
+ const name = nodeBuiltinName(spec);
76
+ if (name === undefined) return undefined;
77
+ if (target === "node") return { external: `node:${name}` };
78
+ return { ref: { pkg: "@jspm/core", subpath: `nodelibs/${name}` } };
79
+ }
@@ -0,0 +1,65 @@
1
+ import { legacy, resolve as resolveExports } from "resolve.exports";
2
+ import type { ModuleTarget, PackageManifest } from "../types.js";
3
+
4
+ /** Strip a leading `./` or `/` so the result is a package-relative file path. */
5
+ function norm(p: string): string {
6
+ return p.replace(/^\.?\//, "");
7
+ }
8
+
9
+ type ResolveOpts = { browser: boolean; conditions: string[]; require?: boolean };
10
+
11
+ /** Call resolve.exports, swallowing its "no known conditions" throw. */
12
+ function tryResolve(
13
+ manifest: PackageManifest,
14
+ entry: string,
15
+ opts: ResolveOpts,
16
+ ): string[] | undefined {
17
+ try {
18
+ return resolveExports(manifest, entry, opts) as string[] | undefined;
19
+ } catch {
20
+ return undefined;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Resolve a package + subpath to the concrete file path inside the package,
26
+ * honoring `package.json` `exports` conditions for the given `target`, with a
27
+ * `main`/`module`/`browser` legacy fallback. Returns a package-relative path
28
+ * (e.g. `"dist/index.js"`).
29
+ */
30
+ export function resolveEntry(
31
+ manifest: PackageManifest,
32
+ subpath: string | undefined,
33
+ target: ModuleTarget,
34
+ ): string {
35
+ const browser = target === "browser";
36
+ const clean = (subpath ?? "").replace(/^\.?\//, "");
37
+ const entry = clean ? `./${clean}` : ".";
38
+
39
+ if (manifest.exports !== undefined) {
40
+ const conditions = browser ? ["browser"] : ["node"];
41
+ // resolve.exports *throws* when no condition matches — try ESM (import) then
42
+ // CJS-only (require) exports maps, swallowing the no-match throw.
43
+ const out =
44
+ tryResolve(manifest, entry, { browser, conditions }) ??
45
+ tryResolve(manifest, entry, { browser, conditions, require: true });
46
+ if (out?.length) return norm(out[0]);
47
+ // exports present but subpath not exported: fall through to the raw path.
48
+ }
49
+
50
+ if (entry === ".") {
51
+ const leg = legacy(manifest, {
52
+ browser,
53
+ fields: browser ? ["browser", "module", "main"] : ["module", "main"],
54
+ });
55
+ if (typeof leg === "string") return norm(leg);
56
+ // `exports` present but neither `.` nor a legacy main resolves: the package has
57
+ // no root entry (e.g. `@jspm/core`). Fabricating `index.js` here yields a dead
58
+ // URL that 404s; fail loudly instead (Node throws ERR_PACKAGE_PATH_NOT_EXPORTED).
59
+ if (manifest.exports !== undefined) {
60
+ throw new Error(`${manifest.name}: package has no root entry ("." is not exported)`);
61
+ }
62
+ }
63
+
64
+ return clean || "index.js";
65
+ }