everything-dev 2.0.0-rc.6 → 2.0.0-rc.8

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 (148) hide show
  1. package/README.md +2 -2
  2. package/dist/api/auth-middleware.cjs +14 -1
  3. package/dist/api/auth-middleware.cjs.map +1 -1
  4. package/dist/api/auth-middleware.d.cts +9 -0
  5. package/dist/api/auth-middleware.d.mts +9 -0
  6. package/dist/api/auth-middleware.mjs +14 -1
  7. package/dist/api/auth-middleware.mjs.map +1 -1
  8. package/dist/build.cjs +1 -1
  9. package/dist/build.mjs +1 -1
  10. package/dist/bundle-cache.cjs +1 -1
  11. package/dist/bundle-cache.cjs.map +1 -1
  12. package/dist/bundle-cache.mjs +1 -1
  13. package/dist/bundle-cache.mjs.map +1 -1
  14. package/dist/cli/init.cjs +22 -10
  15. package/dist/cli/init.cjs.map +1 -1
  16. package/dist/cli/init.mjs +22 -10
  17. package/dist/cli/init.mjs.map +1 -1
  18. package/dist/cli.cjs +6 -5
  19. package/dist/cli.cjs.map +1 -1
  20. package/dist/cli.mjs +5 -4
  21. package/dist/cli.mjs.map +1 -1
  22. package/dist/commands/deploy.cjs +111 -1
  23. package/dist/commands/deploy.cjs.map +1 -1
  24. package/dist/commands/deploy.mjs +112 -2
  25. package/dist/commands/deploy.mjs.map +1 -1
  26. package/dist/commands/keys.cjs +1 -1
  27. package/dist/commands/keys.cjs.map +1 -1
  28. package/dist/commands/keys.mjs +1 -1
  29. package/dist/commands/keys.mjs.map +1 -1
  30. package/dist/commands/smoke.cjs +41 -0
  31. package/dist/commands/smoke.cjs.map +1 -0
  32. package/dist/commands/smoke.mjs +41 -0
  33. package/dist/commands/smoke.mjs.map +1 -0
  34. package/dist/commands/upgrade.cjs +2 -2
  35. package/dist/commands/upgrade.cjs.map +1 -1
  36. package/dist/commands/upgrade.mjs +1 -1
  37. package/dist/commands/upgrade.mjs.map +1 -1
  38. package/dist/contract.cjs +27 -1
  39. package/dist/contract.cjs.map +1 -1
  40. package/dist/contract.d.cts +65 -1
  41. package/dist/contract.d.mts +65 -1
  42. package/dist/contract.meta.cjs +14 -2
  43. package/dist/contract.meta.cjs.map +1 -1
  44. package/dist/contract.meta.d.cts +24 -2
  45. package/dist/contract.meta.d.mts +24 -2
  46. package/dist/contract.meta.mjs +14 -2
  47. package/dist/contract.meta.mjs.map +1 -1
  48. package/dist/contract.mjs +26 -2
  49. package/dist/contract.mjs.map +1 -1
  50. package/dist/db/core.cjs +22 -4
  51. package/dist/db/core.cjs.map +1 -1
  52. package/dist/db/core.mjs +22 -4
  53. package/dist/db/core.mjs.map +1 -1
  54. package/dist/dev-log-pipeline.cjs +6 -0
  55. package/dist/dev-log-pipeline.cjs.map +1 -1
  56. package/dist/dev-log-pipeline.mjs +6 -0
  57. package/dist/dev-log-pipeline.mjs.map +1 -1
  58. package/dist/dev-program.cjs +20 -3
  59. package/dist/dev-program.cjs.map +1 -1
  60. package/dist/dev-program.mjs +20 -4
  61. package/dist/dev-program.mjs.map +1 -1
  62. package/dist/dev-session.cjs +1 -0
  63. package/dist/dev-session.cjs.map +1 -1
  64. package/dist/dev-session.mjs +1 -0
  65. package/dist/dev-session.mjs.map +1 -1
  66. package/dist/fastkv.cjs +21 -3
  67. package/dist/fastkv.cjs.map +1 -1
  68. package/dist/fastkv.d.cts +14 -1
  69. package/dist/fastkv.d.mts +14 -1
  70. package/dist/fastkv.mjs +21 -5
  71. package/dist/fastkv.mjs.map +1 -1
  72. package/dist/{mf.cjs → federation.cjs} +6 -13
  73. package/dist/federation.cjs.map +1 -0
  74. package/dist/{mf.d.cts → federation.d.cts} +2 -2
  75. package/dist/{mf.d.mts → federation.d.mts} +2 -2
  76. package/dist/{mf.mjs → federation.mjs} +6 -13
  77. package/dist/federation.mjs.map +1 -0
  78. package/dist/http-client.cjs +84 -57
  79. package/dist/http-client.cjs.map +1 -1
  80. package/dist/http-client.mjs +84 -58
  81. package/dist/http-client.mjs.map +1 -1
  82. package/dist/image-deploy.cjs +9 -0
  83. package/dist/image-deploy.cjs.map +1 -1
  84. package/dist/image-deploy.mjs +9 -1
  85. package/dist/image-deploy.mjs.map +1 -1
  86. package/dist/index.cjs +4 -0
  87. package/dist/index.d.cts +3 -3
  88. package/dist/index.d.mts +3 -3
  89. package/dist/index.mjs +3 -3
  90. package/dist/infra/planner.cjs +6 -2
  91. package/dist/infra/planner.cjs.map +1 -1
  92. package/dist/infra/planner.mjs +6 -2
  93. package/dist/infra/planner.mjs.map +1 -1
  94. package/dist/orchestrator.cjs +39 -35
  95. package/dist/orchestrator.cjs.map +1 -1
  96. package/dist/orchestrator.mjs +39 -35
  97. package/dist/orchestrator.mjs.map +1 -1
  98. package/dist/plugin.cjs +2 -0
  99. package/dist/plugin.cjs.map +1 -1
  100. package/dist/plugin.d.cts +24 -0
  101. package/dist/plugin.d.mts +24 -0
  102. package/dist/plugin.mjs +2 -0
  103. package/dist/plugin.mjs.map +1 -1
  104. package/dist/process-registry.cjs +1 -1
  105. package/dist/process-registry.mjs +1 -1
  106. package/dist/publish.cjs +3 -3
  107. package/dist/publish.cjs.map +1 -1
  108. package/dist/publish.mjs +4 -4
  109. package/dist/publish.mjs.map +1 -1
  110. package/dist/resolver.cjs +293 -0
  111. package/dist/resolver.cjs.map +1 -0
  112. package/dist/resolver.d.cts +99 -0
  113. package/dist/resolver.d.mts +99 -0
  114. package/dist/resolver.mjs +284 -0
  115. package/dist/resolver.mjs.map +1 -0
  116. package/dist/smoke.cjs +262 -0
  117. package/dist/smoke.cjs.map +1 -0
  118. package/dist/smoke.mjs +260 -0
  119. package/dist/smoke.mjs.map +1 -0
  120. package/dist/ui/auth.d.cts +217 -123
  121. package/dist/ui/auth.d.mts +217 -123
  122. package/dist/ui/router-client.cjs +2 -1
  123. package/dist/ui/router-client.cjs.map +1 -1
  124. package/dist/ui/router-client.mjs +2 -1
  125. package/dist/ui/router-client.mjs.map +1 -1
  126. package/dist/version-manifest-resolve.cjs +3 -3
  127. package/dist/version-manifest-resolve.cjs.map +1 -1
  128. package/dist/version-manifest-resolve.mjs +1 -1
  129. package/dist/version-manifest-resolve.mjs.map +1 -1
  130. package/dist/workspace.cjs +1 -1
  131. package/dist/workspace.mjs +1 -1
  132. package/package.json +18 -18
  133. package/skills/api-and-auth/SKILL.md +12 -9
  134. package/skills/api-and-auth/references/middleware.md +38 -0
  135. package/skills/cli-reference/SKILL.md +22 -6
  136. package/skills/talk-to-the-app/SKILL.md +32 -5
  137. package/dist/bundle-fs-resolve.cjs +0 -235
  138. package/dist/bundle-fs-resolve.cjs.map +0 -1
  139. package/dist/bundle-fs-resolve.d.cts +0 -82
  140. package/dist/bundle-fs-resolve.d.mts +0 -82
  141. package/dist/bundle-fs-resolve.mjs +0 -228
  142. package/dist/bundle-fs-resolve.mjs.map +0 -1
  143. package/dist/bundle-path.cjs +0 -48
  144. package/dist/bundle-path.cjs.map +0 -1
  145. package/dist/bundle-path.mjs +0 -45
  146. package/dist/bundle-path.mjs.map +0 -1
  147. package/dist/mf.cjs.map +0 -1
  148. package/dist/mf.mjs.map +0 -1
@@ -0,0 +1,99 @@
1
+ import { Cause, Context, Effect, Layer, Schema } from "effect";
2
+ //#region src/resolver.d.ts
3
+ /**
4
+ * Bundle resolution — the one seam every boot-time byte crosses (config
5
+ * manifest discovery, contract-type fetches, federation remote loading, SSR
6
+ * serving). Two tiers, one policy (ADR 0011/0021):
7
+ *
8
+ * - Self-contained runtime image: a staged namespace under `BOS_BUNDLE_DIR`
9
+ * resolves from disk; a miss falls through to the network so a partially-
10
+ * staged namespace boots over the wire instead of failing.
11
+ * - Registry tier: plain resilient network fetch — CDN is the single
12
+ * distribution source. The stale-if-error disk cache exists only behind an
13
+ * explicit `BOS_BUNDLE_CACHE_DIR` opt-in; cached bytes are a resilience
14
+ * artifact, never a deployment, so a post-reset boot can never satisfy
15
+ * itself with pre-reset bytes.
16
+ */
17
+ interface BundleNamespace {
18
+ readonly bundleDir: string;
19
+ readonly account: string;
20
+ readonly gateway: string;
21
+ }
22
+ declare const BundleReadError_base: Schema.Class<BundleReadError, Schema.TaggedStruct<"BundleReadError", {
23
+ readonly path: Schema.String;
24
+ readonly cause: Schema.optional<Schema.Unknown>;
25
+ }>, Cause.YieldableError>;
26
+ declare class BundleReadError extends BundleReadError_base {}
27
+ /**
28
+ * Maps a bundle URL onto the staged namespace layout. Bundle URLs carry their
29
+ * namespace in the path (`…/bundles/<account>/<gateway>/<workspace>/…`); the
30
+ * staged layout mirrors it under `<bundleDir>/<account>/<gateway>/…`. The
31
+ * namespace is taken from the URL itself — this answers "which staged
32
+ * directory do these bytes live in?" rather than "is this my namespace?" —
33
+ * and the result is contained within the account/gateway directory. Returns
34
+ * null for foreign shapes.
35
+ */
36
+ declare function bundleUrlToStagedPath(url: string, bundleDir: string): string | null;
37
+ /** Resolve a relative config-supplied path inside `dir`, or null on escape. */
38
+ declare function resolveContained(dir: string, relative: string): string | null;
39
+ /**
40
+ * Own-namespace path for a bundle URL: the shared URL→staged-path mapping,
41
+ * narrowed to the runtime identity — a URL whose encoded namespace differs
42
+ * from `namespace.account`/`namespace.gateway` must fall to the network,
43
+ * never serve a sibling namespace's staged bytes.
44
+ */
45
+ declare function bundleUrlToLocalPath(url: string, namespace: BundleNamespace): string | null;
46
+ declare const BundleResolver_base: Context.ServiceClass<BundleResolver, "everything-dev/resolver/BundleResolver", {
47
+ /** Resolve a fetch target from the staged namespace; null = miss or not our namespace — fall through to the network. */
48
+ readonly lookup: (input: string | URL | Request) => Effect.Effect<Response | null>;
49
+ }>;
50
+ declare class BundleResolver extends BundleResolver_base {
51
+ static layer(namespace: BundleNamespace | undefined): Layer.Layer<BundleResolver>;
52
+ }
53
+ interface BundleFetchHandle {
54
+ readonly uninstall: () => Promise<void>;
55
+ }
56
+ interface BundleFetchOptions {
57
+ /** Own-namespace FS resolution — requires a staged `BOS_BUNDLE_DIR`. */
58
+ namespace?: BundleNamespace;
59
+ /**
60
+ * Explicit opt-in stale-if-error cache (child tier): `/bundles/…` URLs
61
+ * write through to disk on success and serve last-known-good bytes when
62
+ * the origin fails. Never derived implicitly — a cache that activates
63
+ * itself can serve pre-reset bytes after a deploy.
64
+ */
65
+ cacheDir?: string;
66
+ /** Per-attempt deadline for idempotent GETs crossing the interceptor. */
67
+ timeoutMs?: number;
68
+ }
69
+ /**
70
+ * Installs the resolver as a global fetch interceptor — the one seam every
71
+ * boot-time consumer shares (config manifest discovery, contract-type
72
+ * fetches, orchestrator host loading, MF remoteEntry/identity probes) and
73
+ * the SSR serving path. One layered resolution policy, as a single Effect
74
+ * program on the managed runtime: staged disk → network → (explicitly
75
+ * opted-in) stale-if-error. Own-namespace URLs resolve from the staged
76
+ * directory and fall through to the network on a miss; a miss never touches
77
+ * the write-through cache — for the own namespace the staged disk IS the
78
+ * resilience artifact. Inert when neither a staged namespace nor an
79
+ * explicit cache is configured.
80
+ */
81
+ declare function installGlobalBundleFetch(options: BundleFetchOptions): BundleFetchHandle;
82
+ /**
83
+ * Boot-time installer for the CLI and the host server: derives the own
84
+ * namespace from `BOS_BUNDLE_DIR` plus the runtime identity
85
+ * (`BOS_ACCOUNT`/`BOS_GATEWAY` registry env taking precedence over the
86
+ * bos.config.json fields), and the stale-if-error cache from an explicit
87
+ * `BOS_BUNDLE_CACHE_DIR` — never implicit. Tier auto-detection (ADR 0021):
88
+ * an identity whose namespace is not actually staged under the bundle dir
89
+ * drops to the registry tier — plain resilient network fetch — instead of
90
+ * deterministically 404ing own-namespace URLs against a foreign image's
91
+ * staged bytes. Never throws — a missing or malformed identity leaves the
92
+ * default fetch in place.
93
+ */
94
+ declare function installBundleFetchFromEnv(input: {
95
+ configPath?: string | null;
96
+ }): BundleFetchHandle | null;
97
+ //#endregion
98
+ export { BundleFetchHandle, BundleFetchOptions, BundleNamespace, BundleReadError, BundleResolver, bundleUrlToLocalPath, bundleUrlToStagedPath, installBundleFetchFromEnv, installGlobalBundleFetch, resolveContained };
99
+ //# sourceMappingURL=resolver.d.cts.map
@@ -0,0 +1,99 @@
1
+ import { Cause, Context, Effect, Layer, Schema } from "effect";
2
+ //#region src/resolver.d.ts
3
+ /**
4
+ * Bundle resolution — the one seam every boot-time byte crosses (config
5
+ * manifest discovery, contract-type fetches, federation remote loading, SSR
6
+ * serving). Two tiers, one policy (ADR 0011/0021):
7
+ *
8
+ * - Self-contained runtime image: a staged namespace under `BOS_BUNDLE_DIR`
9
+ * resolves from disk; a miss falls through to the network so a partially-
10
+ * staged namespace boots over the wire instead of failing.
11
+ * - Registry tier: plain resilient network fetch — CDN is the single
12
+ * distribution source. The stale-if-error disk cache exists only behind an
13
+ * explicit `BOS_BUNDLE_CACHE_DIR` opt-in; cached bytes are a resilience
14
+ * artifact, never a deployment, so a post-reset boot can never satisfy
15
+ * itself with pre-reset bytes.
16
+ */
17
+ interface BundleNamespace {
18
+ readonly bundleDir: string;
19
+ readonly account: string;
20
+ readonly gateway: string;
21
+ }
22
+ declare const BundleReadError_base: Schema.Class<BundleReadError, Schema.TaggedStruct<"BundleReadError", {
23
+ readonly path: Schema.String;
24
+ readonly cause: Schema.optional<Schema.Unknown>;
25
+ }>, Cause.YieldableError>;
26
+ declare class BundleReadError extends BundleReadError_base {}
27
+ /**
28
+ * Maps a bundle URL onto the staged namespace layout. Bundle URLs carry their
29
+ * namespace in the path (`…/bundles/<account>/<gateway>/<workspace>/…`); the
30
+ * staged layout mirrors it under `<bundleDir>/<account>/<gateway>/…`. The
31
+ * namespace is taken from the URL itself — this answers "which staged
32
+ * directory do these bytes live in?" rather than "is this my namespace?" —
33
+ * and the result is contained within the account/gateway directory. Returns
34
+ * null for foreign shapes.
35
+ */
36
+ declare function bundleUrlToStagedPath(url: string, bundleDir: string): string | null;
37
+ /** Resolve a relative config-supplied path inside `dir`, or null on escape. */
38
+ declare function resolveContained(dir: string, relative: string): string | null;
39
+ /**
40
+ * Own-namespace path for a bundle URL: the shared URL→staged-path mapping,
41
+ * narrowed to the runtime identity — a URL whose encoded namespace differs
42
+ * from `namespace.account`/`namespace.gateway` must fall to the network,
43
+ * never serve a sibling namespace's staged bytes.
44
+ */
45
+ declare function bundleUrlToLocalPath(url: string, namespace: BundleNamespace): string | null;
46
+ declare const BundleResolver_base: Context.ServiceClass<BundleResolver, "everything-dev/resolver/BundleResolver", {
47
+ /** Resolve a fetch target from the staged namespace; null = miss or not our namespace — fall through to the network. */
48
+ readonly lookup: (input: string | URL | Request) => Effect.Effect<Response | null>;
49
+ }>;
50
+ declare class BundleResolver extends BundleResolver_base {
51
+ static layer(namespace: BundleNamespace | undefined): Layer.Layer<BundleResolver>;
52
+ }
53
+ interface BundleFetchHandle {
54
+ readonly uninstall: () => Promise<void>;
55
+ }
56
+ interface BundleFetchOptions {
57
+ /** Own-namespace FS resolution — requires a staged `BOS_BUNDLE_DIR`. */
58
+ namespace?: BundleNamespace;
59
+ /**
60
+ * Explicit opt-in stale-if-error cache (child tier): `/bundles/…` URLs
61
+ * write through to disk on success and serve last-known-good bytes when
62
+ * the origin fails. Never derived implicitly — a cache that activates
63
+ * itself can serve pre-reset bytes after a deploy.
64
+ */
65
+ cacheDir?: string;
66
+ /** Per-attempt deadline for idempotent GETs crossing the interceptor. */
67
+ timeoutMs?: number;
68
+ }
69
+ /**
70
+ * Installs the resolver as a global fetch interceptor — the one seam every
71
+ * boot-time consumer shares (config manifest discovery, contract-type
72
+ * fetches, orchestrator host loading, MF remoteEntry/identity probes) and
73
+ * the SSR serving path. One layered resolution policy, as a single Effect
74
+ * program on the managed runtime: staged disk → network → (explicitly
75
+ * opted-in) stale-if-error. Own-namespace URLs resolve from the staged
76
+ * directory and fall through to the network on a miss; a miss never touches
77
+ * the write-through cache — for the own namespace the staged disk IS the
78
+ * resilience artifact. Inert when neither a staged namespace nor an
79
+ * explicit cache is configured.
80
+ */
81
+ declare function installGlobalBundleFetch(options: BundleFetchOptions): BundleFetchHandle;
82
+ /**
83
+ * Boot-time installer for the CLI and the host server: derives the own
84
+ * namespace from `BOS_BUNDLE_DIR` plus the runtime identity
85
+ * (`BOS_ACCOUNT`/`BOS_GATEWAY` registry env taking precedence over the
86
+ * bos.config.json fields), and the stale-if-error cache from an explicit
87
+ * `BOS_BUNDLE_CACHE_DIR` — never implicit. Tier auto-detection (ADR 0021):
88
+ * an identity whose namespace is not actually staged under the bundle dir
89
+ * drops to the registry tier — plain resilient network fetch — instead of
90
+ * deterministically 404ing own-namespace URLs against a foreign image's
91
+ * staged bytes. Never throws — a missing or malformed identity leaves the
92
+ * default fetch in place.
93
+ */
94
+ declare function installBundleFetchFromEnv(input: {
95
+ configPath?: string | null;
96
+ }): BundleFetchHandle | null;
97
+ //#endregion
98
+ export { BundleFetchHandle, BundleFetchOptions, BundleNamespace, BundleReadError, BundleResolver, bundleUrlToLocalPath, bundleUrlToStagedPath, installBundleFetchFromEnv, installGlobalBundleFetch, resolveContained };
99
+ //# sourceMappingURL=resolver.d.mts.map
@@ -0,0 +1,284 @@
1
+ import { FetchNetworkError, HttpFetch } from "./http-client.mjs";
2
+ import { bundleCachePath, readBundleCache, writeBundleCache } from "./bundle-cache.mjs";
3
+ import { isAppDescriptorPath } from "./config.mjs";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import path from "node:path";
6
+ import { Cause, Context, Data, Duration, Effect, Layer, ManagedRuntime, Schedule, Schema } from "effect";
7
+ import { readFile } from "node:fs/promises";
8
+ import { cacheControlOf } from "every-plugin/build/artifact-names";
9
+
10
+ //#region src/resolver.ts
11
+ var BundleReadError = class extends Schema.TaggedError()("BundleReadError", {
12
+ path: Schema.String,
13
+ cause: Schema.optional(Schema.Unknown)
14
+ }) {};
15
+ const MIME_TYPES = {
16
+ ".js": "text/javascript",
17
+ ".mjs": "text/javascript",
18
+ ".cjs": "text/javascript",
19
+ ".css": "text/css",
20
+ ".json": "application/json",
21
+ ".map": "application/json",
22
+ ".svg": "image/svg+xml",
23
+ ".png": "image/png",
24
+ ".jpg": "image/jpeg",
25
+ ".jpeg": "image/jpeg",
26
+ ".gif": "image/gif",
27
+ ".webp": "image/webp",
28
+ ".avif": "image/avif",
29
+ ".ico": "image/x-icon",
30
+ ".html": "text/html",
31
+ ".htm": "text/html",
32
+ ".webmanifest": "application/manifest+json",
33
+ ".md": "text/markdown",
34
+ ".ts": "text/plain",
35
+ ".woff": "font/woff",
36
+ ".woff2": "font/woff2",
37
+ ".ttf": "font/ttf",
38
+ ".otf": "font/otf",
39
+ ".eot": "application/vnd.ms-opentype",
40
+ ".wasm": "application/wasm",
41
+ ".txt": "text/xml",
42
+ ".xml": "application/xml"
43
+ };
44
+ /**
45
+ * Maps a bundle URL onto the staged namespace layout. Bundle URLs carry their
46
+ * namespace in the path (`…/bundles/<account>/<gateway>/<workspace>/…`); the
47
+ * staged layout mirrors it under `<bundleDir>/<account>/<gateway>/…`. The
48
+ * namespace is taken from the URL itself — this answers "which staged
49
+ * directory do these bytes live in?" rather than "is this my namespace?" —
50
+ * and the result is contained within the account/gateway directory. Returns
51
+ * null for foreign shapes.
52
+ */
53
+ function bundleUrlToStagedPath(url, bundleDir) {
54
+ let parsed;
55
+ try {
56
+ parsed = new URL(url);
57
+ } catch {
58
+ return null;
59
+ }
60
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
61
+ const segments = parsed.pathname.split("/").filter(Boolean);
62
+ if (segments[0] !== "bundles" || segments.length < 3) return null;
63
+ let decoded;
64
+ try {
65
+ decoded = segments.slice(1).map((segment) => decodeURIComponent(segment));
66
+ } catch {
67
+ return null;
68
+ }
69
+ const [account, gateway, ...rest] = decoded;
70
+ if (!account || !gateway || rest.length === 0) return null;
71
+ const nsDir = path.resolve(bundleDir, account, gateway);
72
+ const filePath = path.resolve(nsDir, ...rest);
73
+ if (filePath === nsDir || !filePath.startsWith(nsDir + path.sep)) return null;
74
+ return filePath;
75
+ }
76
+ /** Resolve a relative config-supplied path inside `dir`, or null on escape. */
77
+ function resolveContained(dir, relative) {
78
+ const resolved = path.resolve(dir, ...relative.split("/").filter(Boolean));
79
+ if (resolved === dir || !resolved.startsWith(dir + path.sep)) return null;
80
+ return resolved;
81
+ }
82
+ /**
83
+ * Own-namespace path for a bundle URL: the shared URL→staged-path mapping,
84
+ * narrowed to the runtime identity — a URL whose encoded namespace differs
85
+ * from `namespace.account`/`namespace.gateway` must fall to the network,
86
+ * never serve a sibling namespace's staged bytes.
87
+ */
88
+ function bundleUrlToLocalPath(url, namespace) {
89
+ const filePath = bundleUrlToStagedPath(url, namespace.bundleDir);
90
+ if (!filePath) return null;
91
+ const nsDir = path.resolve(namespace.bundleDir, namespace.account, namespace.gateway);
92
+ if (filePath !== nsDir && !filePath.startsWith(nsDir + path.sep)) return null;
93
+ return filePath;
94
+ }
95
+ var BundleResolver = class BundleResolver extends Context.Service()("everything-dev/resolver/BundleResolver") {
96
+ static layer(namespace) {
97
+ if (!namespace) return Layer.succeed(BundleResolver, BundleResolver.of({ lookup: () => Effect.succeed(null) }));
98
+ const respond = Effect.fn("BundleResolver.respond")(function* (url) {
99
+ const filePath = bundleUrlToLocalPath(url, namespace);
100
+ if (!filePath) return null;
101
+ const bytes = yield* Effect.tryPromise({
102
+ try: () => readFile(filePath),
103
+ catch: (cause) => new BundleReadError({
104
+ path: filePath,
105
+ cause
106
+ })
107
+ }).pipe(Effect.catchTag("BundleReadError", () => Effect.succeed(null)));
108
+ if (bytes === null) return null;
109
+ const name = path.basename(filePath);
110
+ const contentType = MIME_TYPES[path.extname(name).toLowerCase()] ?? "application/octet-stream";
111
+ return new Response(new Uint8Array(bytes), { headers: {
112
+ "content-type": contentType,
113
+ "cache-control": cacheControlOf(name),
114
+ etag: `"${Buffer.from(bytes.subarray(0, 4096)).toString("base64").slice(0, 24)}"`
115
+ } });
116
+ });
117
+ return Layer.effect(BundleResolver, Effect.succeed(BundleResolver.of({ lookup: (input) => {
118
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
119
+ return respond(url);
120
+ } })));
121
+ }
122
+ };
123
+ const staleResponse = (bytes, url) => {
124
+ const name = url.split("?")[0]?.split("/").pop() ?? "";
125
+ const contentType = MIME_TYPES[path.extname(name).toLowerCase()] ?? "application/octet-stream";
126
+ return new Response(bytes.slice().buffer, {
127
+ status: 200,
128
+ headers: {
129
+ "content-type": contentType,
130
+ "x-bundle-cache": "stale"
131
+ }
132
+ });
133
+ };
134
+ const DEFAULT_ATTEMPT_TIMEOUT = "30 seconds";
135
+ const RETRY_BASE_DELAY = "250 millis";
136
+ const RETRY_CAP = "5 seconds";
137
+ const RETRY_TIMES = 4;
138
+ const isTransientStatus = (status) => status === 429 || status >= 500;
139
+ function requestMethod(input, init) {
140
+ if (init?.method) return init.method.toUpperCase();
141
+ if (typeof input === "string" || input instanceof URL) return "GET";
142
+ return input.method.toUpperCase();
143
+ }
144
+ const asError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
145
+ /** A 429/5xx response — carried as a failure so the Schedule can retry it,
146
+ * with the Response kept for the exhausted-retries fallthrough. */
147
+ var TransientBundleResponse = class extends Data.TaggedError("TransientBundleResponse") {};
148
+ /**
149
+ * One network attempt through the `HttpFetch` policy: the fetch runs under
150
+ * the fiber's interruption signal, and `Effect.timeout` interrupts at the
151
+ * deadline — aborting the in-flight request. The response is returned
152
+ * regardless of status; transient statuses are lifted by the caller.
153
+ */
154
+ const fetchAttempt = Effect.fn("resolver.attempt")(function* (http, input, init, timeout) {
155
+ return yield* http.attempt(input, init).pipe(Effect.timeoutOrElse({
156
+ duration: Duration.toMillis(timeout),
157
+ orElse: () => Effect.fail(new Cause.TimeoutError())
158
+ }), Effect.filterOrFail((response) => !isTransientStatus(response.status), (response) => new TransientBundleResponse({ response })));
159
+ });
160
+ /**
161
+ * The resilience policy for idempotent GETs crossing the interceptor
162
+ * (ADR 0021 amendment — the CDN-only image): a cold boot has no staged
163
+ * fallback, so one edge hiccup or a hung connection during the load window
164
+ * must not hard-fail the host or a plugin. Transient statuses and network
165
+ * errors (including the deadline's TimeoutError) retry on a capped
166
+ * exponential schedule before propagating; the last transient response is
167
+ * returned so an explicit stale-if-error cache still sees it. Non-GETs pass
168
+ * through untouched; a caller-initiated cancellation is never spent on the
169
+ * schedule.
170
+ */
171
+ const resilientFetch = Effect.fn("resolver.fetch")(function* (http, input, init, timeout) {
172
+ if (requestMethod(input, init) !== "GET") return yield* http.attempt(input, init).pipe(Effect.mapError((cause) => asError(cause.cause)));
173
+ return yield* fetchAttempt(http, input, init, timeout).pipe(Effect.retry({
174
+ schedule: Schedule.exponential(RETRY_BASE_DELAY).pipe(Schedule.upTo({
175
+ duration: RETRY_CAP,
176
+ times: RETRY_TIMES
177
+ })),
178
+ while: (error) => !((error instanceof FetchNetworkError || error instanceof Cause.TimeoutError) && init?.signal?.aborted)
179
+ }), Effect.catchTags({
180
+ TransientBundleResponse: (error) => Effect.succeed(error.response),
181
+ FetchNetworkError: (error) => Effect.fail(asError(error.cause))
182
+ }));
183
+ });
184
+ /**
185
+ * Installs the resolver as a global fetch interceptor — the one seam every
186
+ * boot-time consumer shares (config manifest discovery, contract-type
187
+ * fetches, orchestrator host loading, MF remoteEntry/identity probes) and
188
+ * the SSR serving path. One layered resolution policy, as a single Effect
189
+ * program on the managed runtime: staged disk → network → (explicitly
190
+ * opted-in) stale-if-error. Own-namespace URLs resolve from the staged
191
+ * directory and fall through to the network on a miss; a miss never touches
192
+ * the write-through cache — for the own namespace the staged disk IS the
193
+ * resilience artifact. Inert when neither a staged namespace nor an
194
+ * explicit cache is configured.
195
+ */
196
+ function installGlobalBundleFetch(options) {
197
+ const namespace = options.namespace;
198
+ const cacheDir = options.cacheDir;
199
+ const timeout = Duration.millis(options.timeoutMs ?? Duration.toMillis(DEFAULT_ATTEMPT_TIMEOUT));
200
+ const original = globalThis.fetch;
201
+ const runtime = ManagedRuntime.make(Layer.mergeAll(BundleResolver.layer(namespace), HttpFetch.layerWith({ fetchImpl: original })));
202
+ const program = Effect.fn("resolver.program")(function* (input, init) {
203
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
204
+ const http = yield* HttpFetch;
205
+ if (namespace && bundleUrlToLocalPath(url, namespace)) {
206
+ const local = yield* (yield* BundleResolver).lookup(input);
207
+ if (local !== null) return local;
208
+ return yield* resilientFetch(http, input, init, timeout);
209
+ }
210
+ if (cacheDir !== void 0 && bundleCachePath(url, { cacheDir })) {
211
+ const cachedRead = Effect.tryPromise(() => readBundleCache(url, { cacheDir }));
212
+ return yield* Effect.gen(function* () {
213
+ const response = yield* resilientFetch(http, input, init, timeout);
214
+ if (response.ok) yield* Effect.tryPromise(async () => {
215
+ const bytes = new Uint8Array(await response.clone().arrayBuffer());
216
+ await writeBundleCache(url, bytes, { cacheDir });
217
+ }).pipe(Effect.ignore);
218
+ else {
219
+ const cached = yield* cachedRead;
220
+ if (cached) return staleResponse(cached, url);
221
+ }
222
+ return response;
223
+ }).pipe(Effect.catch((error) => Effect.gen(function* () {
224
+ const cached = yield* cachedRead;
225
+ if (cached) return staleResponse(cached, url);
226
+ return yield* Effect.fail(error);
227
+ })));
228
+ }
229
+ return yield* resilientFetch(http, input, init, timeout);
230
+ });
231
+ const patched = ((input, init) => runtime.runPromise(program(input, init)));
232
+ const originalPreconnect = original.preconnect;
233
+ if (originalPreconnect) patched.preconnect = originalPreconnect.bind(original);
234
+ globalThis.fetch = patched;
235
+ return { uninstall: async () => {
236
+ if (globalThis.fetch === patched) globalThis.fetch = original;
237
+ await runtime.dispose();
238
+ } };
239
+ }
240
+ /**
241
+ * Boot-time installer for the CLI and the host server: derives the own
242
+ * namespace from `BOS_BUNDLE_DIR` plus the runtime identity
243
+ * (`BOS_ACCOUNT`/`BOS_GATEWAY` registry env taking precedence over the
244
+ * bos.config.json fields), and the stale-if-error cache from an explicit
245
+ * `BOS_BUNDLE_CACHE_DIR` — never implicit. Tier auto-detection (ADR 0021):
246
+ * an identity whose namespace is not actually staged under the bundle dir
247
+ * drops to the registry tier — plain resilient network fetch — instead of
248
+ * deterministically 404ing own-namespace URLs against a foreign image's
249
+ * staged bytes. Never throws — a missing or malformed identity leaves the
250
+ * default fetch in place.
251
+ */
252
+ function installBundleFetchFromEnv(input) {
253
+ const bundleDir = process.env.BOS_BUNDLE_DIR;
254
+ const cacheDir = process.env.BOS_BUNDLE_CACHE_DIR;
255
+ if (!bundleDir && cacheDir === void 0) return null;
256
+ let configAccount;
257
+ let configGateway;
258
+ const configPath = input.configPath;
259
+ if (configPath && !isAppDescriptorPath(configPath)) try {
260
+ const parsed = JSON.parse(readFileSync(configPath, "utf8"));
261
+ configAccount = parsed.account;
262
+ configGateway = parsed.domain;
263
+ } catch {}
264
+ const account = process.env.BOS_ACCOUNT ?? configAccount;
265
+ const gateway = process.env.BOS_GATEWAY ?? configGateway;
266
+ if (bundleDir && (!account || !gateway)) return null;
267
+ let namespace;
268
+ if (bundleDir && account && gateway) {
269
+ if (existsSync(path.join(bundleDir, account, gateway))) namespace = {
270
+ bundleDir,
271
+ account,
272
+ gateway
273
+ };
274
+ else console.warn(`[bundles] staged namespace ${account}/${gateway} not found under ${bundleDir} — registry tier (network fetch)`);
275
+ }
276
+ return installGlobalBundleFetch({
277
+ namespace,
278
+ cacheDir
279
+ });
280
+ }
281
+
282
+ //#endregion
283
+ export { BundleReadError, BundleResolver, bundleUrlToLocalPath, bundleUrlToStagedPath, installBundleFetchFromEnv, installGlobalBundleFetch, resolveContained };
284
+ //# sourceMappingURL=resolver.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.mjs","names":[],"sources":["../src/resolver.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport {\n Cause,\n Context,\n Data,\n Duration,\n Effect,\n Layer,\n ManagedRuntime,\n Schedule,\n Schema,\n} from \"effect\";\nimport { cacheControlOf } from \"every-plugin/build/artifact-names\";\nimport { bundleCachePath, readBundleCache, writeBundleCache } from \"./bundle-cache\";\nimport { isAppDescriptorPath } from \"./config\";\nimport { FetchNetworkError, HttpFetch, type HttpFetchShape } from \"./http-client\";\n\ntype FetchImpl = typeof globalThis.fetch;\n\n/**\n * Bundle resolution — the one seam every boot-time byte crosses (config\n * manifest discovery, contract-type fetches, federation remote loading, SSR\n * serving). Two tiers, one policy (ADR 0011/0021):\n *\n * - Self-contained runtime image: a staged namespace under `BOS_BUNDLE_DIR`\n * resolves from disk; a miss falls through to the network so a partially-\n * staged namespace boots over the wire instead of failing.\n * - Registry tier: plain resilient network fetch — CDN is the single\n * distribution source. The stale-if-error disk cache exists only behind an\n * explicit `BOS_BUNDLE_CACHE_DIR` opt-in; cached bytes are a resilience\n * artifact, never a deployment, so a post-reset boot can never satisfy\n * itself with pre-reset bytes.\n */\n\nexport interface BundleNamespace {\n readonly bundleDir: string;\n readonly account: string;\n readonly gateway: string;\n}\n\nexport class BundleReadError extends Schema.TaggedError<BundleReadError>()(\"BundleReadError\", {\n path: Schema.String,\n cause: Schema.optional(Schema.Unknown),\n}) {}\n\nconst MIME_TYPES: Record<string, string> = {\n \".js\": \"text/javascript\",\n \".mjs\": \"text/javascript\",\n \".cjs\": \"text/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".map\": \"application/json\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".ico\": \"image/x-icon\",\n \".html\": \"text/html\",\n \".htm\": \"text/html\",\n \".webmanifest\": \"application/manifest+json\",\n \".md\": \"text/markdown\",\n \".ts\": \"text/plain\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".otf\": \"font/otf\",\n \".eot\": \"application/vnd.ms-opentype\",\n \".wasm\": \"application/wasm\",\n \".txt\": \"text/xml\",\n \".xml\": \"application/xml\",\n};\n\n/**\n * Maps a bundle URL onto the staged namespace layout. Bundle URLs carry their\n * namespace in the path (`…/bundles/<account>/<gateway>/<workspace>/…`); the\n * staged layout mirrors it under `<bundleDir>/<account>/<gateway>/…`. The\n * namespace is taken from the URL itself — this answers \"which staged\n * directory do these bytes live in?\" rather than \"is this my namespace?\" —\n * and the result is contained within the account/gateway directory. Returns\n * null for foreign shapes.\n */\nexport function bundleUrlToStagedPath(url: string, bundleDir: string): string | null {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return null;\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return null;\n\n const segments = parsed.pathname.split(\"/\").filter(Boolean);\n if (segments[0] !== \"bundles\" || segments.length < 3) return null;\n\n let decoded: string[];\n try {\n decoded = segments.slice(1).map((segment) => decodeURIComponent(segment));\n } catch {\n return null;\n }\n const [account, gateway, ...rest] = decoded;\n if (!account || !gateway || rest.length === 0) return null;\n\n const nsDir = path.resolve(bundleDir, account, gateway);\n const filePath = path.resolve(nsDir, ...rest);\n if (filePath === nsDir || !filePath.startsWith(nsDir + path.sep)) return null;\n return filePath;\n}\n\n/** Resolve a relative config-supplied path inside `dir`, or null on escape. */\nexport function resolveContained(dir: string, relative: string): string | null {\n const resolved = path.resolve(dir, ...relative.split(\"/\").filter(Boolean));\n if (resolved === dir || !resolved.startsWith(dir + path.sep)) return null;\n return resolved;\n}\n\n/**\n * Own-namespace path for a bundle URL: the shared URL→staged-path mapping,\n * narrowed to the runtime identity — a URL whose encoded namespace differs\n * from `namespace.account`/`namespace.gateway` must fall to the network,\n * never serve a sibling namespace's staged bytes.\n */\nexport function bundleUrlToLocalPath(url: string, namespace: BundleNamespace): string | null {\n const filePath = bundleUrlToStagedPath(url, namespace.bundleDir);\n if (!filePath) return null;\n\n const nsDir = path.resolve(namespace.bundleDir, namespace.account, namespace.gateway);\n if (filePath !== nsDir && !filePath.startsWith(nsDir + path.sep)) return null;\n\n return filePath;\n}\n\nexport class BundleResolver extends Context.Service<\n BundleResolver,\n {\n /** Resolve a fetch target from the staged namespace; null = miss or not our namespace — fall through to the network. */\n readonly lookup: (input: string | URL | Request) => Effect.Effect<Response | null>;\n }\n>()(\"everything-dev/resolver/BundleResolver\") {\n static layer(namespace: BundleNamespace | undefined): Layer.Layer<BundleResolver> {\n if (!namespace) {\n return Layer.succeed(\n BundleResolver,\n BundleResolver.of({ lookup: () => Effect.succeed(null) }),\n );\n }\n const respond = Effect.fn(\"BundleResolver.respond\")(function* (\n url: string,\n ): Effect.fn.Return<Response | null, never> {\n const filePath = bundleUrlToLocalPath(url, namespace);\n if (!filePath) return null;\n const bytes = yield* Effect.tryPromise({\n try: () => readFile(filePath),\n catch: (cause) => new BundleReadError({ path: filePath, cause }),\n }).pipe(Effect.catchTag(\"BundleReadError\", () => Effect.succeed(null)));\n if (bytes === null) return null;\n const name = path.basename(filePath);\n const contentType =\n MIME_TYPES[path.extname(name).toLowerCase()] ?? \"application/octet-stream\";\n return new Response(new Uint8Array(bytes), {\n headers: {\n \"content-type\": contentType,\n \"cache-control\": cacheControlOf(name),\n etag: `\"${Buffer.from(bytes.subarray(0, 4096)).toString(\"base64\").slice(0, 24)}\"`,\n },\n });\n });\n\n return Layer.effect(\n BundleResolver,\n Effect.succeed(\n BundleResolver.of({\n lookup: (input) => {\n const url =\n typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n return respond(url);\n },\n }),\n ),\n );\n }\n}\n\nexport interface BundleFetchHandle {\n readonly uninstall: () => Promise<void>;\n}\n\nexport interface BundleFetchOptions {\n /** Own-namespace FS resolution — requires a staged `BOS_BUNDLE_DIR`. */\n namespace?: BundleNamespace;\n /**\n * Explicit opt-in stale-if-error cache (child tier): `/bundles/…` URLs\n * write through to disk on success and serve last-known-good bytes when\n * the origin fails. Never derived implicitly — a cache that activates\n * itself can serve pre-reset bytes after a deploy.\n */\n cacheDir?: string;\n /** Per-attempt deadline for idempotent GETs crossing the interceptor. */\n timeoutMs?: number;\n}\n\nconst staleResponse = (bytes: Uint8Array, url: string): Response => {\n const name = url.split(\"?\")[0]?.split(\"/\").pop() ?? \"\";\n const contentType = MIME_TYPES[path.extname(name).toLowerCase()] ?? \"application/octet-stream\";\n return new Response(bytes.slice().buffer, {\n status: 200,\n headers: { \"content-type\": contentType, \"x-bundle-cache\": \"stale\" },\n });\n};\n\nconst DEFAULT_ATTEMPT_TIMEOUT = \"30 seconds\";\nconst RETRY_BASE_DELAY = \"250 millis\";\nconst RETRY_CAP = \"5 seconds\";\nconst RETRY_TIMES = 4;\n\nconst isTransientStatus = (status: number): boolean => status === 429 || status >= 500;\n\nfunction requestMethod(input: Parameters<FetchImpl>[0], init?: Parameters<FetchImpl>[1]): string {\n if (init?.method) return init.method.toUpperCase();\n if (typeof input === \"string\" || input instanceof URL) return \"GET\";\n return input.method.toUpperCase();\n}\n\nconst asError = (cause: unknown): Error =>\n cause instanceof Error ? cause : new Error(String(cause));\n\n/** A 429/5xx response — carried as a failure so the Schedule can retry it,\n * with the Response kept for the exhausted-retries fallthrough. */\nclass TransientBundleResponse extends Data.TaggedError(\"TransientBundleResponse\")<{\n readonly response: Response;\n}> {}\n\n/**\n * One network attempt through the `HttpFetch` policy: the fetch runs under\n * the fiber's interruption signal, and `Effect.timeout` interrupts at the\n * deadline — aborting the in-flight request. The response is returned\n * regardless of status; transient statuses are lifted by the caller.\n */\nconst fetchAttempt = Effect.fn(\"resolver.attempt\")(function* (\n http: HttpFetchShape,\n input: Parameters<FetchImpl>[0],\n init: Parameters<FetchImpl>[1],\n timeout: Duration.Duration,\n): Effect.fn.Return<Response, FetchNetworkError | Cause.TimeoutError | TransientBundleResponse> {\n return yield* http.attempt(input, init).pipe(\n Effect.timeoutOrElse({\n duration: Duration.toMillis(timeout),\n orElse: () => Effect.fail(new Cause.TimeoutError()),\n }),\n Effect.filterOrFail(\n (response) => !isTransientStatus(response.status),\n (response) => new TransientBundleResponse({ response }),\n ),\n );\n});\n\n/**\n * The resilience policy for idempotent GETs crossing the interceptor\n * (ADR 0021 amendment — the CDN-only image): a cold boot has no staged\n * fallback, so one edge hiccup or a hung connection during the load window\n * must not hard-fail the host or a plugin. Transient statuses and network\n * errors (including the deadline's TimeoutError) retry on a capped\n * exponential schedule before propagating; the last transient response is\n * returned so an explicit stale-if-error cache still sees it. Non-GETs pass\n * through untouched; a caller-initiated cancellation is never spent on the\n * schedule.\n */\nconst resilientFetch = Effect.fn(\"resolver.fetch\")(function* (\n http: HttpFetchShape,\n input: Parameters<FetchImpl>[0],\n init: Parameters<FetchImpl>[1],\n timeout: Duration.Duration,\n): Effect.fn.Return<Response, Cause.TimeoutError | Error> {\n if (requestMethod(input, init) !== \"GET\") {\n return yield* http.attempt(input, init).pipe(Effect.mapError((cause) => asError(cause.cause)));\n }\n return yield* fetchAttempt(http, input, init, timeout).pipe(\n Effect.retry({\n schedule: Schedule.exponential(RETRY_BASE_DELAY).pipe(\n Schedule.upTo({ duration: RETRY_CAP, times: RETRY_TIMES }),\n ),\n // A caller-initiated cancellation is not transient — propagate it\n // immediately instead of spending the schedule on a canceled request.\n while: (error) =>\n !(\n (error instanceof FetchNetworkError || error instanceof Cause.TimeoutError) &&\n init?.signal?.aborted\n ),\n }),\n Effect.catchTags({\n TransientBundleResponse: (error) => Effect.succeed(error.response),\n FetchNetworkError: (error) => Effect.fail(asError(error.cause)),\n }),\n );\n});\n\n/**\n * Installs the resolver as a global fetch interceptor — the one seam every\n * boot-time consumer shares (config manifest discovery, contract-type\n * fetches, orchestrator host loading, MF remoteEntry/identity probes) and\n * the SSR serving path. One layered resolution policy, as a single Effect\n * program on the managed runtime: staged disk → network → (explicitly\n * opted-in) stale-if-error. Own-namespace URLs resolve from the staged\n * directory and fall through to the network on a miss; a miss never touches\n * the write-through cache — for the own namespace the staged disk IS the\n * resilience artifact. Inert when neither a staged namespace nor an\n * explicit cache is configured.\n */\nexport function installGlobalBundleFetch(options: BundleFetchOptions): BundleFetchHandle {\n const namespace = options.namespace;\n const cacheDir = options.cacheDir;\n const timeout = Duration.millis(options.timeoutMs ?? Duration.toMillis(DEFAULT_ATTEMPT_TIMEOUT));\n const original: FetchImpl = globalThis.fetch;\n\n const runtime = ManagedRuntime.make(\n Layer.mergeAll(BundleResolver.layer(namespace), HttpFetch.layerWith({ fetchImpl: original })),\n );\n\n const program = Effect.fn(\"resolver.program\")(function* (\n input: Parameters<FetchImpl>[0],\n init?: Parameters<FetchImpl>[1],\n ): Effect.fn.Return<Response, Cause.TimeoutError | Error, BundleResolver | HttpFetch> {\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const http = yield* HttpFetch;\n\n if (namespace && bundleUrlToLocalPath(url, namespace)) {\n const resolver = yield* BundleResolver;\n const local = yield* resolver.lookup(input);\n if (local !== null) return local;\n return yield* resilientFetch(http, input, init, timeout);\n }\n\n if (cacheDir !== undefined && bundleCachePath(url, { cacheDir })) {\n const cachedRead = Effect.tryPromise(() => readBundleCache(url, { cacheDir }));\n return yield* Effect.gen(function* () {\n const response = yield* resilientFetch(http, input, init, timeout);\n if (response.ok) {\n // write-through is best-effort\n yield* Effect.tryPromise(async () => {\n const bytes = new Uint8Array(await response.clone().arrayBuffer());\n await writeBundleCache(url, bytes, { cacheDir });\n }).pipe(Effect.ignore);\n } else {\n const cached = yield* cachedRead;\n if (cached) return staleResponse(cached, url);\n }\n return response;\n }).pipe(\n Effect.catch((error) =>\n Effect.gen(function* () {\n const cached = yield* cachedRead;\n if (cached) return staleResponse(cached, url);\n return yield* Effect.fail(error);\n }),\n ),\n );\n }\n\n return yield* resilientFetch(http, input, init, timeout);\n });\n\n interface FetchWithPreconnect {\n (input: Parameters<FetchImpl>[0], init?: Parameters<FetchImpl>[1]): Promise<Response>;\n preconnect?: (...args: unknown[]) => unknown;\n }\n\n const patched = ((input: Parameters<FetchImpl>[0], init?: Parameters<FetchImpl>[1]) =>\n runtime.runPromise(program(input, init))) as FetchWithPreconnect;\n const originalPreconnect = (original as FetchWithPreconnect).preconnect;\n if (originalPreconnect) {\n patched.preconnect = originalPreconnect.bind(original);\n }\n globalThis.fetch = patched as unknown as FetchImpl;\n return {\n uninstall: async () => {\n if (globalThis.fetch === patched) {\n globalThis.fetch = original;\n }\n await runtime.dispose();\n },\n };\n}\n\n/**\n * Boot-time installer for the CLI and the host server: derives the own\n * namespace from `BOS_BUNDLE_DIR` plus the runtime identity\n * (`BOS_ACCOUNT`/`BOS_GATEWAY` registry env taking precedence over the\n * bos.config.json fields), and the stale-if-error cache from an explicit\n * `BOS_BUNDLE_CACHE_DIR` — never implicit. Tier auto-detection (ADR 0021):\n * an identity whose namespace is not actually staged under the bundle dir\n * drops to the registry tier — plain resilient network fetch — instead of\n * deterministically 404ing own-namespace URLs against a foreign image's\n * staged bytes. Never throws — a missing or malformed identity leaves the\n * default fetch in place.\n */\nexport function installBundleFetchFromEnv(input: {\n configPath?: string | null;\n}): BundleFetchHandle | null {\n const bundleDir = process.env.BOS_BUNDLE_DIR;\n const cacheDir = process.env.BOS_BUNDLE_CACHE_DIR;\n if (!bundleDir && cacheDir === undefined) return null;\n\n let configAccount: string | undefined;\n let configGateway: string | undefined;\n const configPath = input.configPath;\n if (configPath && !isAppDescriptorPath(configPath)) {\n try {\n const parsed = JSON.parse(readFileSync(configPath, \"utf8\")) as {\n account?: string;\n domain?: string;\n };\n configAccount = parsed.account;\n configGateway = parsed.domain;\n } catch {\n // unreadable config — identity may still come from the registry env\n }\n }\n\n const account = process.env.BOS_ACCOUNT ?? configAccount;\n const gateway = process.env.BOS_GATEWAY ?? configGateway;\n if (bundleDir && (!account || !gateway)) return null;\n\n let namespace: BundleNamespace | undefined;\n if (bundleDir && account && gateway) {\n if (existsSync(path.join(bundleDir, account, gateway))) {\n namespace = { bundleDir, account, gateway };\n } else {\n console.warn(\n `[bundles] staged namespace ${account}/${gateway} not found under ${bundleDir} — registry tier (network fetch)`,\n );\n }\n }\n return installGlobalBundleFetch({ namespace, cacheDir });\n}\n"],"mappings":"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"}