@smthrs/platform-bun 1.0.0-rc.7 → 1.0.0-rc.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,9 @@ package is that object for a process running on Bun.
22
22
 
23
23
  ## Install
24
24
 
25
- Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
25
+ ```bash
26
+ bun add @smthrs/platform-bun@1.0.0-rc.9 effect
27
+ ```
26
28
 
27
29
  `@smthrs/platform-node` and `effect` are required peers, as `package.json`
28
30
  declares them, with `effect` in the range `^4.0.4`. Package managers that
@@ -30,14 +32,9 @@ resolve required peers install both automatically. `@effect/platform-bun`,
30
32
  `@effect/platform-node` and `@effect/platform-node-shared` are bundled
31
33
  privately, built from `effect@4.0.4`.
32
34
 
33
- The filesystem slot spawns the `smithers-jj-export` native helper. From a
34
- source checkout, build it with:
35
-
36
- ```sh
37
- cargo build --locked --release -p smithers-ffi --bin smithers-jj-export
38
- ```
39
-
40
- Elsewhere, set `SMITHERS_WORKSPACE_JJ_EXPORT_BINARY` to its absolute path, or
35
+ The filesystem slot spawns the `smithers-jj-export` native helper, which
36
+ `@smthrs/platform-node` ships for macOS, Linux, and Windows. To use another
37
+ helper, set `SMITHERS_WORKSPACE_JJ_EXPORT_BINARY` to its absolute path, or
41
38
  build the layer with `BunFileSystem.layerWith({ executable })`. `PATH` is never
42
39
  searched. Windows is unsupported.
43
40
 
@@ -61,12 +58,13 @@ The program names Bun nowhere. It asks for a service tag, and the layer decides
61
58
  what fills it, so swapping `BunHost.layer` for `NodeHost.layer` from
62
59
  `@smthrs/platform-node` does not change a character of it.
63
60
 
64
- The complete host bundles require jj 0.39.0 or newer. Each bundle builds its jj
65
- layer with one version probe; construction can fail with `JjError`, including
66
- `not_installed` or `unsupported_version`. The version probe and repository commands use the
67
- selected process runner. Contained bundles record and retire the probe in the
68
- host process ledger. Probe results are cached per resolved absolute executable
69
- path and runner, with a separate cache for each spawner instance.
61
+ Every host bundle builds without jj. The `Jj` slot needs jj 0.39.0 or newer: its
62
+ first operation runs one version probe and fails with `JjError` `not_installed`
63
+ or `unsupported_version` when jj is missing or older. The version probe and
64
+ repository commands use the selected process runner. Contained bundles record
65
+ and retire the probe in the host process ledger. Probe results are cached per
66
+ resolved absolute executable path and runner, with a separate cache for each
67
+ spawner instance.
70
68
 
71
69
  ## Modules
72
70
 
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import * as BunChildProcessSpawner from "../vendor/cjs/bun/BunChildProcessSpawner.js";
25
25
  import * as BunCrypto from "../vendor/cjs/bun/BunCrypto.js";
26
- import type { Jj, JjError } from "@smthrs/jj";
26
+ import type { Jj } from "@smthrs/jj";
27
27
  import type { HostServiceIds } from "@smthrs/kernel/HostServices";
28
28
  import type * as KernelProcessConfinement from "@smthrs/kernel/ProcessConfinement";
29
29
  import type * as ProcessLedger from "@smthrs/kernel/ProcessLedger";
@@ -69,7 +69,8 @@ export type BunHost = FileSystem | Path.Path | ChildProcessSpawner | Jj | HttpCl
69
69
  *
70
70
  * A code is the contract a caller branches on; the message beside it is for a
71
71
  * person. `invalid_repository_root` is the root handed to {@link layerAt} or
72
- * {@link layerContainedAt} not being absolute, the empty string included.
72
+ * {@link layerContainedAt} not being absolute or containing NUL, the empty
73
+ * string included.
73
74
  *
74
75
  * @category models
75
76
  * @since 1.0.0-rc.0
@@ -147,30 +148,30 @@ export declare const layerHttpClient: Layer.Layer<HttpClient>;
147
148
  * Provides all five Bun Host services, including the runtime-independent Path
148
149
  * service.
149
150
  *
150
- * Fails at construction with {@link JjError} carrying `not_installed` or
151
- * `unsupported_version`: the `Jj` slot is merged eagerly and probes
152
- * `jj --version` while the layer is built, before the program body runs and
153
- * whether or not the program asks for `Jj`. Requires jj 0.39.0 or later.
151
+ * Builds on a host without jj. The `Jj` slot probes `jj --version` on its
152
+ * first operation and fails that operation with `JjError` carrying
153
+ * `not_installed` or `unsupported_version`; a program that never calls `Jj`
154
+ * runs without jj. `Jj` requires jj 0.39.0 or later.
154
155
  *
155
156
  * @category layers
156
157
  * @since 1.0.0-rc.0
157
158
  * @slop
158
159
  */
159
- export declare const layer: Layer.Layer<BunHost | Crypto.Crypto, JjError>;
160
+ export declare const layer: Layer.Layer<BunHost | Crypto.Crypto>;
160
161
  /**
161
162
  * Provides all five Bun Host services bound to one absolute repository root.
162
163
  *
163
164
  * Throws {@link BunHostError} with code `invalid_repository_root` when `root`
164
- * is not absolute; the empty string counts. The check runs when the factory is
165
- * called, before any layer exists.
165
+ * is not absolute or contains NUL; the empty string counts. The check runs
166
+ * when the factory is called, before any layer exists.
166
167
  *
167
- * Fails at construction with {@link JjError} exactly as {@link layer} does:
168
- * the `Jj` slot probes `jj --version` while the layer is built.
168
+ * Builds without jj exactly as {@link layer} does: the `Jj` slot probes
169
+ * `jj --version` on its first operation.
169
170
  *
170
171
  * @category layers
171
172
  * @since 1.0.0-rc.0
172
173
  */
173
- export declare const layerAt: (root: string) => Layer.Layer<BunHost | Crypto.Crypto, JjError>;
174
+ export declare const layerAt: (root: string) => Layer.Layer<BunHost | Crypto.Crypto>;
174
175
  /**
175
176
  * Provides the Bun host with process containment turned on.
176
177
  *
@@ -182,24 +183,23 @@ export declare const layerAt: (root: string) => Layer.Layer<BunHost | Crypto.Cry
182
183
  * it makes, `process.kill` and `taskkill`, are Node's, and Bun implements
183
184
  * them unchanged.
184
185
  *
185
- * Fails at construction with {@link JjError} exactly as {@link layer} does;
186
- * here the `jj --version` probe runs through the contained spawner, so its
186
+ * Builds without jj exactly as {@link layer} does; here the first
187
+ * operation's `jj --version` probe runs through the contained spawner, so its
187
188
  * record enters and leaves the ledger like any other child.
188
189
  *
189
190
  * @category layers
190
191
  * @since 1.0.0-rc.0
191
192
  */
192
- export declare const layerContained: (options?: ContainedOptions) => Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger>;
193
+ export declare const layerContained: (options?: ContainedOptions) => Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger>;
193
194
  /**
194
195
  * Provides the contained Bun host bound to one absolute repository root.
195
196
  *
196
197
  * Refuses a root exactly as {@link layerAt} does: {@link BunHostError} with
197
198
  * code `invalid_repository_root`, thrown when the factory is called.
198
- * Fails at construction with {@link JjError} exactly as {@link layerContained}
199
- * does.
199
+ * Builds without jj exactly as {@link layerContained} does.
200
200
  *
201
201
  * @category layers
202
202
  * @since 1.0.0-rc.0
203
203
  */
204
- export declare const layerContainedAt: (root: string, options?: ContainedOptions) => Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger>;
204
+ export declare const layerContainedAt: (root: string, options?: ContainedOptions) => Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger>;
205
205
  //# sourceMappingURL=BunHost.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BunHost.d.ts","sourceRoot":"","sources":["../../src/BunHost.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BunHost.d.ts","sourceRoot":"","sources":["../../src/BunHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,sBAAsB,MAAM,6CAA6C,CAAA;AACrF,OAAO,KAAK,SAAS,MAAM,gCAAgC,CAAA;AAE3D,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,YAAY,CAAA;AAEpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,KAAK,KAAK,wBAAwB,MAAM,mCAAmC,CAAA;AAClF,OAAO,KAAK,KAAK,aAAa,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,gBAAgB,MAAM,wCAAwC,CAAA;AAE1E,OAAO,KAAK,YAAY,MAAM,oCAAoC,CAAA;AAElE,OAAO,KAAK,aAAa,MAAM,qCAAqC,CAAA;AACpE,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,KAAK,IAAI,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AAC7E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAA;AAEnD;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,CAAA;AAE1G;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,GACf,UAAU,GACV,IAAI,CAAC,IAAI,GACT,mBAAmB,GACnB,EAAE,GACF,UAAU,GACV,wBAAwB,CAAC,kBAAkB,CAAA;AAE/C;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG,yBAAyB,CAAA;;;;;AAExD;;;;;;;;;;;GAWG;AACH,qBAAa,YAAa,SAAQ,iBAGhC;IACA,SAAkB,IAAI,kBAAiB;CACxC;AA4DD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAMvF,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,cAAc,GAAG,aAAa,CAAC,OAAO,CAAA;AAWnF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,CAAuC,CAAA;AAE3F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAMtD,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CASzE,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,gBAAgB,KACzB,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,aAAa,CAczE,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAC3B,MAAM,MAAM,EACZ,UAAU,gBAAgB,KACzB,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,aAAa,CAYzE,CAAA"}
@@ -79,8 +79,9 @@ const preview = (root, budget) => {
79
79
  return `"${excerpt}"${suffix}`;
80
80
  };
81
81
  const absoluteRoot = (factory, root) => {
82
- if ((0, import_node_path.isAbsolute)(root)) return root;
83
- const prefix = `BunHost.${factory} requires an absolute repository root, got `;
82
+ const requirement = !(0, import_node_path.isAbsolute)(root) ? "an absolute repository root" : root.includes("\0") ? "a repository root without NUL" : void 0;
83
+ if (requirement === void 0) return root;
84
+ const prefix = `BunHost.${factory} requires ${requirement}, got `;
84
85
  throw new BunHostError({
85
86
  code: "invalid_repository_root",
86
87
  message: `${prefix}${preview(root, messageLimit - prefix.length)}`
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/BunHost.ts"],
4
- "sourcesContent": ["/**\n * Aggregate Bun Host bundle.\n *\n * Runtime-specific dependencies stay inside this package; callers get the same\n * closed five-service Host surface every other bundle provides, plus native\n * `ProcessConfinement` for guarded commands.\n *\n * There is no Bun shell module: running a command is Effect's\n * `ChildProcessSpawner`, and `@effect/platform-bun`'s implementation is\n * `@effect/platform-node-shared`'s re-exported: the same code on both\n * runtimes, so no runtime detection is needed here.\n *\n * Outgoing HTTP uses the shared EgressHttpClient on both runtimes. Web\n * requests pin approved addresses; ordinary requests retain the shared pool.\n * Redirects remain visible to the capability kernel, and proxy routing follows\n * the host environment.\n *\n * The filesystem slot is `@smthrs/platform-node`'s `AtomicFileSystem`, byte for\n * byte the layer `NodeHost` uses, so a guarded path operation is\n * descriptor-relative and no-follow on both runtimes.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as BunChildProcessSpawner from \"@effect/platform-bun/BunChildProcessSpawner\"\nimport * as BunCrypto from \"@effect/platform-bun/BunCrypto\"\nimport * as BunPath from \"@effect/platform-bun/BunPath\"\nimport type { Jj, JjError } from \"@smthrs/jj\"\nimport * as BunJj from \"@smthrs/jj/bun/BunJj\"\nimport type { HostServiceIds } from \"@smthrs/kernel/HostServices\"\nimport type * as KernelProcessConfinement from \"@smthrs/kernel/ProcessConfinement\"\nimport type * as ProcessLedger from \"@smthrs/kernel/ProcessLedger\"\nimport * as AtomicFileSystem from \"@smthrs/platform-node/AtomicFileSystem\"\nimport * as EgressHttpClient from \"@smthrs/platform-node/EgressHttpClient\"\nimport * as HostLiveness from \"@smthrs/platform-node/HostLiveness\"\nimport * as ProcessConfinement from \"@smthrs/platform-node/ProcessConfinement\"\nimport * as ProcessReaper from \"@smthrs/platform-node/ProcessReaper\"\nimport type * as Crypto from \"effect/Crypto\"\nimport type { FileSystem } from \"effect/FileSystem\"\nimport type { HttpClient } from \"effect/http/HttpClient\"\nimport * as Layer from \"effect/Layer\"\nimport type * as Path from \"effect/Path\"\nimport type { ChildProcessSpawner } from \"effect/process/ChildProcessSpawner\"\nimport * as Schema from \"effect/Schema\"\nimport { isAbsolute } from \"node:path\"\nimport * as BunFileSystem from \"./BunFileSystem.ts\"\n\n/**\n * Bun platform modules for selectively providing individual services.\n *\n * `AtomicFileSystem` is here for the same reason `NodeHost` re-exports it: it\n * owns the only configuration escape hatch the filesystem slot has, and a Bun\n * program with a different packaged helper path needs to reach\n * `AtomicFileSystem.layerWith` without hand-composing the other four tags.\n *\n * `BunJj` is deliberately absent: it belongs to `@smthrs/jj` and is imported\n * from there, never re-exported here. So is `@effect/platform-bun/BunHttpClient`:\n * its `layer` follows redirects to origins the capability kernel never\n * authorized. The network slot on its own is {@link layerHttpClient}.\n *\n * @category re-exports\n * @since 1.0.0-rc.0\n * @slop\n */\nexport { AtomicFileSystem, BunChildProcessSpawner, BunCrypto, BunFileSystem, HostLiveness, ProcessReaper }\n\n/**\n * The complete closed Host service union provided by Bun.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport type BunHost =\n | FileSystem\n | Path.Path\n | ChildProcessSpawner\n | Jj\n | HttpClient\n | KernelProcessConfinement.ProcessConfinement\n\n/**\n * The stable codes a `BunHost` factory refuses with.\n *\n * A code is the contract a caller branches on; the message beside it is for a\n * person. `invalid_repository_root` is the root handed to {@link layerAt} or\n * {@link layerContainedAt} not being absolute, the empty string included.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type BunHostErrorCode = \"invalid_repository_root\"\n\n/**\n * The refusal a `BunHost` factory throws before it builds a layer.\n *\n * It is thrown rather than failed because a factory is called while a program\n * composes its layers, where there is no fiber to fail yet and a wrong root is\n * a composition mistake, not a runtime outcome. It is this package's class,\n * not the `Jj` adapter's, because a Bun caller composes `BunHost` and should\n * learn nothing about the adapter behind the `Jj` slot from an error message.\n *\n * @category errors\n * @since 1.0.0-rc.0\n */\nexport class BunHostError extends Schema.TaggedError<BunHostError>()(\"@smthrs/platform-bun/BunHostError\", {\n code: Schema.Literal(\"invalid_repository_root\"),\n message: Schema.String\n}) {\n override readonly name = \"BunHostError\"\n}\n\n/** The longest message a factory refuses with, in UTF-16 code units. */\nconst messageLimit = 255\n\n/** How many code points of a refused root the message repeats at most. */\nconst previewLimit = 64\n\n/**\n * A bounded, quoted excerpt of a refused root that fits in `budget` code\n * units of the message.\n *\n * Counted in code points rather than UTF-16 units so a cut never splits a\n * surrogate pair, and passed through `JSON.stringify` so a newline in the root\n * cannot break the log line the message lands on. Each code point is encoded\n * on its own and admitted only while the quoted excerpt plus the length\n * suffix still fit, so a control character that widens to a `\\u0001` escape\n * shortens the excerpt instead of lengthening the message, and a cut never\n * lands inside an escape.\n */\nconst preview = (root: string, budget: number): string => {\n const points = Array.from(root)\n const whole = JSON.stringify(root)\n if (points.length <= previewLimit && whole.length <= budget) return whole\n const suffix = `... (${points.length} characters)`\n const room = budget - suffix.length - 2\n let excerpt = \"\"\n for (const point of points.slice(0, previewLimit)) {\n const encoded = JSON.stringify(point).slice(1, -1)\n if (excerpt.length + encoded.length > room) break\n excerpt += encoded\n }\n return `\"${excerpt}\"${suffix}`\n}\n\n/**\n * Refuses a root the `Jj` adapter underneath would refuse, here and in this\n * package's name.\n *\n * `BunJj.layerAt` and `BunJj.layerSpawnerAt` are `NodeJj`'s, and those throw a\n * bare `TypeError` that names `NodeJj`, carries no code, and echoes the whole\n * string. The predicate here is the same `isAbsolute` the adapter applies, so\n * a root that passes this check cannot reach that throw.\n */\nconst absoluteRoot = (factory: \"layerAt\" | \"layerContainedAt\", root: string): string => {\n if (isAbsolute(root)) return root\n const prefix = `BunHost.${factory} requires an absolute repository root, got `\n throw new BunHostError({\n code: \"invalid_repository_root\",\n message: `${prefix}${preview(root, messageLimit - prefix.length)}`\n })\n}\n\n/**\n * Stable implementation identities keyed by the closed Host service slots.\n *\n * Each value names the module behind that slot in the raw host bundle.\n * Contained POSIX factories replace the spawner with {@link ProcessReaper.layerSpawner}.\n * These are identity\n * tokens rather than import specifiers: the filesystem entry is\n * `@smthrs/platform-node/AtomicFileSystem` because that is the implementation,\n * even though a consumer reaches it through `@smthrs/platform-bun`.\n *\n * Nothing digests them yet. `@smthrs/plan`'s step key carries a `layers`\n * component these values are meant to feed, but no planner derives it from a\n * host bundle today, so changing one invalidates no cached step.\n *\n * The keys are written as literals rather than `HostServiceIds` positions so\n * that reordering the closed list cannot silently pair a slot with another\n * slot's implementation.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const implementationIds: Readonly<Record<(typeof HostServiceIds)[number], string>> = {\n \"effect/FileSystem\": \"@smthrs/platform-node/AtomicFileSystem\",\n \"effect/Path\": \"@effect/platform-bun/BunPath\",\n \"effect/process/ChildProcessSpawner\": \"@effect/platform-bun/BunChildProcessSpawner\",\n \"@smthrs/jj/Jj\": \"@smthrs/jj/bun/BunJj\",\n \"effect/HttpClient\": \"@smthrs/platform-node/EgressHttpClient\"\n}\n\n/**\n * What a caller may configure about containment.\n *\n * `ContainedSpawner.Options.platform` is deliberately not part of it. It\n * decides one thing, whether a command that names no `detached` option gets a\n * process group of its own, and this native host detaches by `process.platform`\n * whatever a record claims. A\n * caller-supplied `\"win32\"` on a POSIX host would therefore record\n * `pgid: null` for a child that really does lead a group, and\n * {@link ProcessReaper.reap} would retire that record as `no-group` and leave\n * the orphan running forever: a durable lie rather than a compile error.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type ContainedOptions = ProcessReaper.SpawnerOptions & ProcessReaper.Options\n\n/** The reaper half of {@link ContainedOptions}. */\nconst reaping = (options?: ContainedOptions): ProcessReaper.Options => ({\n ownerPid: options?.ownerPid,\n system: options?.system\n})\n\n/** The two services `BunChildProcessSpawner` resolves paths and files with. */\nconst platform = Layer.mergeAll(BunFileSystem.layer, BunPath.layer, BunCrypto.layer)\n\n/**\n * The network slot on its own: the shared egress client, which never follows a\n * redirect. A `3xx` comes back to the caller with its `location` intact and the\n * second origin uncontacted; following a redirect is `@smthrs/kernel`'s guarded\n * `HttpClient.layer`, which rechecks every hop.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerHttpClient: Layer.Layer<HttpClient> = EgressHttpClient.layer(process.env)\n\n/**\n * Provides all five Bun Host services, including the runtime-independent Path\n * service.\n *\n * Fails at construction with {@link JjError} carrying `not_installed` or\n * `unsupported_version`: the `Jj` slot is merged eagerly and probes\n * `jj --version` while the layer is built, before the program body runs and\n * whether or not the program asks for `Jj`. Requires jj 0.39.0 or later.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layer: Layer.Layer<BunHost | Crypto.Crypto, JjError> = Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n Layer.provide(BunChildProcessSpawner.layer, platform),\n BunJj.layer,\n layerHttpClient\n)\n\n/**\n * Provides all five Bun Host services bound to one absolute repository root.\n *\n * Throws {@link BunHostError} with code `invalid_repository_root` when `root`\n * is not absolute; the empty string counts. The check runs when the factory is\n * called, before any layer exists.\n *\n * Fails at construction with {@link JjError} exactly as {@link layer} does:\n * the `Jj` slot probes `jj --version` while the layer is built.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerAt = (root: string): Layer.Layer<BunHost | Crypto.Crypto, JjError> => {\n const repositoryRoot = absoluteRoot(\"layerAt\", root)\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n Layer.provide(BunChildProcessSpawner.layer, platform),\n BunJj.layerAt(repositoryRoot),\n layerHttpClient\n )\n}\n\n/**\n * Provides the Bun host with process containment turned on.\n *\n * Bun uses the same prepared native process lifecycle as Node on POSIX, while\n * preserving its underlying runtime spawner on Windows. `ContainedSpawner` gives\n * every child an escalation deadline and a ledger entry, and\n * `ProcessReaper` sweeps the entries a crashed incarnation of this host left\n * behind. The reaper module lives in `@smthrs/platform-node` because the calls\n * it makes, `process.kill` and `taskkill`, are Node's, and Bun implements\n * them unchanged.\n *\n * Fails at construction with {@link JjError} exactly as {@link layer} does;\n * here the `jj --version` probe runs through the contained spawner, so its\n * record enters and leaves the ledger like any other child.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerContained = (\n options?: ContainedOptions\n): Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger> => {\n const spawner = Layer.provide(\n ProcessReaper.layerSpawner({ graceMs: options?.graceMs }),\n Layer.provide(BunChildProcessSpawner.layer, platform)\n )\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n layerHttpClient,\n // jj goes through the CONTAINED spawner here, not around it, exactly as in\n // `NodeHost.layerContained`: a jj child that starts its own process leads\n // no recorded group, is in no ledger, and outlives the host that ran it.\n Layer.provideMerge(BunJj.layerSpawner, spawner)\n ).pipe(Layer.provideMerge(ProcessReaper.layer(reaping(options))))\n}\n\n/**\n * Provides the contained Bun host bound to one absolute repository root.\n *\n * Refuses a root exactly as {@link layerAt} does: {@link BunHostError} with\n * code `invalid_repository_root`, thrown when the factory is called.\n * Fails at construction with {@link JjError} exactly as {@link layerContained}\n * does.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerContainedAt = (\n root: string,\n options?: ContainedOptions\n): Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger> => {\n const repositoryRoot = absoluteRoot(\"layerContainedAt\", root)\n const spawner = Layer.provide(\n ProcessReaper.layerSpawner({ graceMs: options?.graceMs }),\n Layer.provide(BunChildProcessSpawner.layer, platform)\n )\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n layerHttpClient,\n Layer.provideMerge(BunJj.layerSpawnerAt(repositoryRoot), spawner)\n ).pipe(Layer.provideMerge(ProcessReaper.layer(reaping(options))))\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Aggregate Bun Host bundle.\n *\n * Runtime-specific dependencies stay inside this package; callers get the same\n * closed five-service Host surface every other bundle provides, plus native\n * `ProcessConfinement` for guarded commands.\n *\n * There is no Bun shell module: running a command is Effect's\n * `ChildProcessSpawner`, and `@effect/platform-bun`'s implementation is\n * `@effect/platform-node-shared`'s re-exported: the same code on both\n * runtimes, so no runtime detection is needed here.\n *\n * Outgoing HTTP uses the shared EgressHttpClient on both runtimes. Web\n * requests pin approved addresses; ordinary requests retain the shared pool.\n * Redirects remain visible to the capability kernel, and proxy routing follows\n * the host environment.\n *\n * The filesystem slot is `@smthrs/platform-node`'s `AtomicFileSystem`, byte for\n * byte the layer `NodeHost` uses, so a guarded path operation is\n * descriptor-relative and no-follow on both runtimes.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as BunChildProcessSpawner from \"@effect/platform-bun/BunChildProcessSpawner\"\nimport * as BunCrypto from \"@effect/platform-bun/BunCrypto\"\nimport * as BunPath from \"@effect/platform-bun/BunPath\"\nimport type { Jj } from \"@smthrs/jj\"\nimport * as BunJj from \"@smthrs/jj/bun/BunJj\"\nimport type { HostServiceIds } from \"@smthrs/kernel/HostServices\"\nimport type * as KernelProcessConfinement from \"@smthrs/kernel/ProcessConfinement\"\nimport type * as ProcessLedger from \"@smthrs/kernel/ProcessLedger\"\nimport * as AtomicFileSystem from \"@smthrs/platform-node/AtomicFileSystem\"\nimport * as EgressHttpClient from \"@smthrs/platform-node/EgressHttpClient\"\nimport * as HostLiveness from \"@smthrs/platform-node/HostLiveness\"\nimport * as ProcessConfinement from \"@smthrs/platform-node/ProcessConfinement\"\nimport * as ProcessReaper from \"@smthrs/platform-node/ProcessReaper\"\nimport type * as Crypto from \"effect/Crypto\"\nimport type { FileSystem } from \"effect/FileSystem\"\nimport type { HttpClient } from \"effect/http/HttpClient\"\nimport * as Layer from \"effect/Layer\"\nimport type * as Path from \"effect/Path\"\nimport type { ChildProcessSpawner } from \"effect/process/ChildProcessSpawner\"\nimport * as Schema from \"effect/Schema\"\nimport { isAbsolute } from \"node:path\"\nimport * as BunFileSystem from \"./BunFileSystem.ts\"\n\n/**\n * Bun platform modules for selectively providing individual services.\n *\n * `AtomicFileSystem` is here for the same reason `NodeHost` re-exports it: it\n * owns the only configuration escape hatch the filesystem slot has, and a Bun\n * program with a different packaged helper path needs to reach\n * `AtomicFileSystem.layerWith` without hand-composing the other four tags.\n *\n * `BunJj` is deliberately absent: it belongs to `@smthrs/jj` and is imported\n * from there, never re-exported here. So is `@effect/platform-bun/BunHttpClient`:\n * its `layer` follows redirects to origins the capability kernel never\n * authorized. The network slot on its own is {@link layerHttpClient}.\n *\n * @category re-exports\n * @since 1.0.0-rc.0\n * @slop\n */\nexport { AtomicFileSystem, BunChildProcessSpawner, BunCrypto, BunFileSystem, HostLiveness, ProcessReaper }\n\n/**\n * The complete closed Host service union provided by Bun.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport type BunHost =\n | FileSystem\n | Path.Path\n | ChildProcessSpawner\n | Jj\n | HttpClient\n | KernelProcessConfinement.ProcessConfinement\n\n/**\n * The stable codes a `BunHost` factory refuses with.\n *\n * A code is the contract a caller branches on; the message beside it is for a\n * person. `invalid_repository_root` is the root handed to {@link layerAt} or\n * {@link layerContainedAt} not being absolute or containing NUL, the empty\n * string included.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type BunHostErrorCode = \"invalid_repository_root\"\n\n/**\n * The refusal a `BunHost` factory throws before it builds a layer.\n *\n * It is thrown rather than failed because a factory is called while a program\n * composes its layers, where there is no fiber to fail yet and a wrong root is\n * a composition mistake, not a runtime outcome. It is this package's class,\n * not the `Jj` adapter's, because a Bun caller composes `BunHost` and should\n * learn nothing about the adapter behind the `Jj` slot from an error message.\n *\n * @category errors\n * @since 1.0.0-rc.0\n */\nexport class BunHostError extends Schema.TaggedError<BunHostError>()(\"@smthrs/platform-bun/BunHostError\", {\n code: Schema.Literal(\"invalid_repository_root\"),\n message: Schema.String\n}) {\n override readonly name = \"BunHostError\"\n}\n\n/** The longest message a factory refuses with, in UTF-16 code units. */\nconst messageLimit = 255\n\n/** How many code points of a refused root the message repeats at most. */\nconst previewLimit = 64\n\n/**\n * A bounded, quoted excerpt of a refused root that fits in `budget` code\n * units of the message.\n *\n * Counted in code points rather than UTF-16 units so a cut never splits a\n * surrogate pair, and passed through `JSON.stringify` so a newline in the root\n * cannot break the log line the message lands on. Each code point is encoded\n * on its own and admitted only while the quoted excerpt plus the length\n * suffix still fit, so a control character that widens to a `\\u0001` escape\n * shortens the excerpt instead of lengthening the message, and a cut never\n * lands inside an escape.\n */\nconst preview = (root: string, budget: number): string => {\n const points = Array.from(root)\n const whole = JSON.stringify(root)\n if (points.length <= previewLimit && whole.length <= budget) return whole\n const suffix = `... (${points.length} characters)`\n const room = budget - suffix.length - 2\n let excerpt = \"\"\n for (const point of points.slice(0, previewLimit)) {\n const encoded = JSON.stringify(point).slice(1, -1)\n if (excerpt.length + encoded.length > room) break\n excerpt += encoded\n }\n return `\"${excerpt}\"${suffix}`\n}\n\n/**\n * Refuses a root the `Jj` adapter underneath would refuse, here and in this\n * package's name.\n *\n * `BunJj.layerAt` and `BunJj.layerSpawnerAt` are `NodeJj`'s, and those throw a\n * bare `TypeError` that names `NodeJj`, carries no code, and echoes the whole\n * string. The predicate includes the same `isAbsolute` the adapter applies, so\n * a root that passes this check cannot reach that throw. An absolute root\n * containing NUL is refused too: no filesystem or process API can represent it.\n */\nconst absoluteRoot = (factory: \"layerAt\" | \"layerContainedAt\", root: string): string => {\n // An absolute root with NUL names that defect, not absoluteness it already has.\n const requirement = !isAbsolute(root)\n ? \"an absolute repository root\"\n : root.includes(\"\\0\")\n ? \"a repository root without NUL\"\n : undefined\n if (requirement === undefined) return root\n const prefix = `BunHost.${factory} requires ${requirement}, got `\n throw new BunHostError({\n code: \"invalid_repository_root\",\n message: `${prefix}${preview(root, messageLimit - prefix.length)}`\n })\n}\n\n/**\n * Stable implementation identities keyed by the closed Host service slots.\n *\n * Each value names the module behind that slot in the raw host bundle.\n * Contained POSIX factories replace the spawner with {@link ProcessReaper.layerSpawner}.\n * These are identity\n * tokens rather than import specifiers: the filesystem entry is\n * `@smthrs/platform-node/AtomicFileSystem` because that is the implementation,\n * even though a consumer reaches it through `@smthrs/platform-bun`.\n *\n * Nothing digests them yet. `@smthrs/plan`'s step key carries a `layers`\n * component these values are meant to feed, but no planner derives it from a\n * host bundle today, so changing one invalidates no cached step.\n *\n * The keys are written as literals rather than `HostServiceIds` positions so\n * that reordering the closed list cannot silently pair a slot with another\n * slot's implementation.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const implementationIds: Readonly<Record<(typeof HostServiceIds)[number], string>> = {\n \"effect/FileSystem\": \"@smthrs/platform-node/AtomicFileSystem\",\n \"effect/Path\": \"@effect/platform-bun/BunPath\",\n \"effect/process/ChildProcessSpawner\": \"@effect/platform-bun/BunChildProcessSpawner\",\n \"@smthrs/jj/Jj\": \"@smthrs/jj/bun/BunJj\",\n \"effect/HttpClient\": \"@smthrs/platform-node/EgressHttpClient\"\n}\n\n/**\n * What a caller may configure about containment.\n *\n * `ContainedSpawner.Options.platform` is deliberately not part of it. It\n * decides one thing, whether a command that names no `detached` option gets a\n * process group of its own, and this native host detaches by `process.platform`\n * whatever a record claims. A\n * caller-supplied `\"win32\"` on a POSIX host would therefore record\n * `pgid: null` for a child that really does lead a group, and\n * {@link ProcessReaper.reap} would retire that record as `no-group` and leave\n * the orphan running forever: a durable lie rather than a compile error.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type ContainedOptions = ProcessReaper.SpawnerOptions & ProcessReaper.Options\n\n/** The reaper half of {@link ContainedOptions}. */\nconst reaping = (options?: ContainedOptions): ProcessReaper.Options => ({\n ownerPid: options?.ownerPid,\n system: options?.system\n})\n\n/** The two services `BunChildProcessSpawner` resolves paths and files with. */\nconst platform = Layer.mergeAll(BunFileSystem.layer, BunPath.layer, BunCrypto.layer)\n\n/**\n * The network slot on its own: the shared egress client, which never follows a\n * redirect. A `3xx` comes back to the caller with its `location` intact and the\n * second origin uncontacted; following a redirect is `@smthrs/kernel`'s guarded\n * `HttpClient.layer`, which rechecks every hop.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerHttpClient: Layer.Layer<HttpClient> = EgressHttpClient.layer(process.env)\n\n/**\n * Provides all five Bun Host services, including the runtime-independent Path\n * service.\n *\n * Builds on a host without jj. The `Jj` slot probes `jj --version` on its\n * first operation and fails that operation with `JjError` carrying\n * `not_installed` or `unsupported_version`; a program that never calls `Jj`\n * runs without jj. `Jj` requires jj 0.39.0 or later.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layer: Layer.Layer<BunHost | Crypto.Crypto> = Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n Layer.provide(BunChildProcessSpawner.layer, platform),\n BunJj.layer,\n layerHttpClient\n)\n\n/**\n * Provides all five Bun Host services bound to one absolute repository root.\n *\n * Throws {@link BunHostError} with code `invalid_repository_root` when `root`\n * is not absolute or contains NUL; the empty string counts. The check runs\n * when the factory is called, before any layer exists.\n *\n * Builds without jj exactly as {@link layer} does: the `Jj` slot probes\n * `jj --version` on its first operation.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerAt = (root: string): Layer.Layer<BunHost | Crypto.Crypto> => {\n const repositoryRoot = absoluteRoot(\"layerAt\", root)\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n Layer.provide(BunChildProcessSpawner.layer, platform),\n BunJj.layerAt(repositoryRoot),\n layerHttpClient\n )\n}\n\n/**\n * Provides the Bun host with process containment turned on.\n *\n * Bun uses the same prepared native process lifecycle as Node on POSIX, while\n * preserving its underlying runtime spawner on Windows. `ContainedSpawner` gives\n * every child an escalation deadline and a ledger entry, and\n * `ProcessReaper` sweeps the entries a crashed incarnation of this host left\n * behind. The reaper module lives in `@smthrs/platform-node` because the calls\n * it makes, `process.kill` and `taskkill`, are Node's, and Bun implements\n * them unchanged.\n *\n * Builds without jj exactly as {@link layer} does; here the first\n * operation's `jj --version` probe runs through the contained spawner, so its\n * record enters and leaves the ledger like any other child.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerContained = (\n options?: ContainedOptions\n): Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger> => {\n const spawner = Layer.provide(\n ProcessReaper.layerSpawner({ graceMs: options?.graceMs }),\n Layer.provide(BunChildProcessSpawner.layer, platform)\n )\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n layerHttpClient,\n // jj goes through the CONTAINED spawner here, not around it, exactly as in\n // `NodeHost.layerContained`: a jj child that starts its own process leads\n // no recorded group, is in no ledger, and outlives the host that ran it.\n Layer.provideMerge(BunJj.layerSpawner, spawner)\n ).pipe(Layer.provideMerge(ProcessReaper.layer(reaping(options))))\n}\n\n/**\n * Provides the contained Bun host bound to one absolute repository root.\n *\n * Refuses a root exactly as {@link layerAt} does: {@link BunHostError} with\n * code `invalid_repository_root`, thrown when the factory is called.\n * Builds without jj exactly as {@link layerContained} does.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerContainedAt = (\n root: string,\n options?: ContainedOptions\n): Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger> => {\n const repositoryRoot = absoluteRoot(\"layerContainedAt\", root)\n const spawner = Layer.provide(\n ProcessReaper.layerSpawner({ graceMs: options?.graceMs }),\n Layer.provide(BunChildProcessSpawner.layer, platform)\n )\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n layerHttpClient,\n Layer.provideMerge(BunJj.layerSpawnerAt(repositoryRoot), spawner)\n ).pipe(Layer.provideMerge(ProcessReaper.layer(reaping(options))))\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import * as BunChildProcessSpawner from "../vendor/bun/BunChildProcessSpawner.js";
25
25
  import * as BunCrypto from "../vendor/bun/BunCrypto.js";
26
- import type { Jj, JjError } from "@smthrs/jj";
26
+ import type { Jj } from "@smthrs/jj";
27
27
  import type { HostServiceIds } from "@smthrs/kernel/HostServices";
28
28
  import type * as KernelProcessConfinement from "@smthrs/kernel/ProcessConfinement";
29
29
  import type * as ProcessLedger from "@smthrs/kernel/ProcessLedger";
@@ -69,7 +69,8 @@ export type BunHost = FileSystem | Path.Path | ChildProcessSpawner | Jj | HttpCl
69
69
  *
70
70
  * A code is the contract a caller branches on; the message beside it is for a
71
71
  * person. `invalid_repository_root` is the root handed to {@link layerAt} or
72
- * {@link layerContainedAt} not being absolute, the empty string included.
72
+ * {@link layerContainedAt} not being absolute or containing NUL, the empty
73
+ * string included.
73
74
  *
74
75
  * @category models
75
76
  * @since 1.0.0-rc.0
@@ -147,30 +148,30 @@ export declare const layerHttpClient: Layer.Layer<HttpClient>;
147
148
  * Provides all five Bun Host services, including the runtime-independent Path
148
149
  * service.
149
150
  *
150
- * Fails at construction with {@link JjError} carrying `not_installed` or
151
- * `unsupported_version`: the `Jj` slot is merged eagerly and probes
152
- * `jj --version` while the layer is built, before the program body runs and
153
- * whether or not the program asks for `Jj`. Requires jj 0.39.0 or later.
151
+ * Builds on a host without jj. The `Jj` slot probes `jj --version` on its
152
+ * first operation and fails that operation with `JjError` carrying
153
+ * `not_installed` or `unsupported_version`; a program that never calls `Jj`
154
+ * runs without jj. `Jj` requires jj 0.39.0 or later.
154
155
  *
155
156
  * @category layers
156
157
  * @since 1.0.0-rc.0
157
158
  * @slop
158
159
  */
159
- export declare const layer: Layer.Layer<BunHost | Crypto.Crypto, JjError>;
160
+ export declare const layer: Layer.Layer<BunHost | Crypto.Crypto>;
160
161
  /**
161
162
  * Provides all five Bun Host services bound to one absolute repository root.
162
163
  *
163
164
  * Throws {@link BunHostError} with code `invalid_repository_root` when `root`
164
- * is not absolute; the empty string counts. The check runs when the factory is
165
- * called, before any layer exists.
165
+ * is not absolute or contains NUL; the empty string counts. The check runs
166
+ * when the factory is called, before any layer exists.
166
167
  *
167
- * Fails at construction with {@link JjError} exactly as {@link layer} does:
168
- * the `Jj` slot probes `jj --version` while the layer is built.
168
+ * Builds without jj exactly as {@link layer} does: the `Jj` slot probes
169
+ * `jj --version` on its first operation.
169
170
  *
170
171
  * @category layers
171
172
  * @since 1.0.0-rc.0
172
173
  */
173
- export declare const layerAt: (root: string) => Layer.Layer<BunHost | Crypto.Crypto, JjError>;
174
+ export declare const layerAt: (root: string) => Layer.Layer<BunHost | Crypto.Crypto>;
174
175
  /**
175
176
  * Provides the Bun host with process containment turned on.
176
177
  *
@@ -182,24 +183,23 @@ export declare const layerAt: (root: string) => Layer.Layer<BunHost | Crypto.Cry
182
183
  * it makes, `process.kill` and `taskkill`, are Node's, and Bun implements
183
184
  * them unchanged.
184
185
  *
185
- * Fails at construction with {@link JjError} exactly as {@link layer} does;
186
- * here the `jj --version` probe runs through the contained spawner, so its
186
+ * Builds without jj exactly as {@link layer} does; here the first
187
+ * operation's `jj --version` probe runs through the contained spawner, so its
187
188
  * record enters and leaves the ledger like any other child.
188
189
  *
189
190
  * @category layers
190
191
  * @since 1.0.0-rc.0
191
192
  */
192
- export declare const layerContained: (options?: ContainedOptions) => Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger>;
193
+ export declare const layerContained: (options?: ContainedOptions) => Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger>;
193
194
  /**
194
195
  * Provides the contained Bun host bound to one absolute repository root.
195
196
  *
196
197
  * Refuses a root exactly as {@link layerAt} does: {@link BunHostError} with
197
198
  * code `invalid_repository_root`, thrown when the factory is called.
198
- * Fails at construction with {@link JjError} exactly as {@link layerContained}
199
- * does.
199
+ * Builds without jj exactly as {@link layerContained} does.
200
200
  *
201
201
  * @category layers
202
202
  * @since 1.0.0-rc.0
203
203
  */
204
- export declare const layerContainedAt: (root: string, options?: ContainedOptions) => Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger>;
204
+ export declare const layerContainedAt: (root: string, options?: ContainedOptions) => Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger>;
205
205
  //# sourceMappingURL=BunHost.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BunHost.d.ts","sourceRoot":"","sources":["../../src/BunHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,sBAAsB,MAAM,6CAA6C,CAAA;AACrF,OAAO,KAAK,SAAS,MAAM,gCAAgC,CAAA;AAE3D,OAAO,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAE7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,KAAK,KAAK,wBAAwB,MAAM,mCAAmC,CAAA;AAClF,OAAO,KAAK,KAAK,aAAa,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,gBAAgB,MAAM,wCAAwC,CAAA;AAE1E,OAAO,KAAK,YAAY,MAAM,oCAAoC,CAAA;AAElE,OAAO,KAAK,aAAa,MAAM,qCAAqC,CAAA;AACpE,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,KAAK,IAAI,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AAC7E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAA;AAEnD;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,CAAA;AAE1G;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,GACf,UAAU,GACV,IAAI,CAAC,IAAI,GACT,mBAAmB,GACnB,EAAE,GACF,UAAU,GACV,wBAAwB,CAAC,kBAAkB,CAAA;AAE/C;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,yBAAyB,CAAA;;;;;AAExD;;;;;;;;;;;GAWG;AACH,qBAAa,YAAa,SAAQ,iBAGhC;IACA,SAAkB,IAAI,kBAAiB;CACxC;AAqDD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAMvF,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,cAAc,GAAG,aAAa,CAAC,OAAO,CAAA;AAWnF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,CAAuC,CAAA;AAE3F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAM/D,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CASlF,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,gBAAgB,KACzB,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,aAAa,CAc3E,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAC3B,MAAM,MAAM,EACZ,UAAU,gBAAgB,KACzB,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,aAAa,CAY3E,CAAA"}
1
+ {"version":3,"file":"BunHost.d.ts","sourceRoot":"","sources":["../../src/BunHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,sBAAsB,MAAM,6CAA6C,CAAA;AACrF,OAAO,KAAK,SAAS,MAAM,gCAAgC,CAAA;AAE3D,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,YAAY,CAAA;AAEpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,KAAK,KAAK,wBAAwB,MAAM,mCAAmC,CAAA;AAClF,OAAO,KAAK,KAAK,aAAa,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,gBAAgB,MAAM,wCAAwC,CAAA;AAE1E,OAAO,KAAK,YAAY,MAAM,oCAAoC,CAAA;AAElE,OAAO,KAAK,aAAa,MAAM,qCAAqC,CAAA;AACpE,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,KAAK,IAAI,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AAC7E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAA;AAEnD;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,CAAA;AAE1G;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,GACf,UAAU,GACV,IAAI,CAAC,IAAI,GACT,mBAAmB,GACnB,EAAE,GACF,UAAU,GACV,wBAAwB,CAAC,kBAAkB,CAAA;AAE/C;;;;;;;;;;GAUG;AACH,MAAM,MAAM,gBAAgB,GAAG,yBAAyB,CAAA;;;;;AAExD;;;;;;;;;;;GAWG;AACH,qBAAa,YAAa,SAAQ,iBAGhC;IACA,SAAkB,IAAI,kBAAiB;CACxC;AA4DD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAMvF,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,cAAc,GAAG,aAAa,CAAC,OAAO,CAAA;AAWnF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,CAAuC,CAAA;AAE3F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAMtD,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CASzE,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,gBAAgB,KACzB,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,aAAa,CAczE,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAC3B,MAAM,MAAM,EACZ,UAAU,gBAAgB,KACzB,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,aAAa,CAYzE,CAAA"}
@@ -108,13 +108,20 @@ const preview = (root, budget) => {
108
108
  *
109
109
  * `BunJj.layerAt` and `BunJj.layerSpawnerAt` are `NodeJj`'s, and those throw a
110
110
  * bare `TypeError` that names `NodeJj`, carries no code, and echoes the whole
111
- * string. The predicate here is the same `isAbsolute` the adapter applies, so
112
- * a root that passes this check cannot reach that throw.
111
+ * string. The predicate includes the same `isAbsolute` the adapter applies, so
112
+ * a root that passes this check cannot reach that throw. An absolute root
113
+ * containing NUL is refused too: no filesystem or process API can represent it.
113
114
  */
114
115
  const absoluteRoot = (factory, root) => {
115
- if (isAbsolute(root))
116
+ // An absolute root with NUL names that defect, not absoluteness it already has.
117
+ const requirement = !isAbsolute(root)
118
+ ? "an absolute repository root"
119
+ : root.includes("\0")
120
+ ? "a repository root without NUL"
121
+ : undefined;
122
+ if (requirement === undefined)
116
123
  return root;
117
- const prefix = `BunHost.${factory} requires an absolute repository root, got `;
124
+ const prefix = `BunHost.${factory} requires ${requirement}, got `;
118
125
  throw new BunHostError({
119
126
  code: "invalid_repository_root",
120
127
  message: `${prefix}${preview(root, messageLimit - prefix.length)}`
@@ -170,10 +177,10 @@ export const layerHttpClient = EgressHttpClient.layer(process.env);
170
177
  * Provides all five Bun Host services, including the runtime-independent Path
171
178
  * service.
172
179
  *
173
- * Fails at construction with {@link JjError} carrying `not_installed` or
174
- * `unsupported_version`: the `Jj` slot is merged eagerly and probes
175
- * `jj --version` while the layer is built, before the program body runs and
176
- * whether or not the program asks for `Jj`. Requires jj 0.39.0 or later.
180
+ * Builds on a host without jj. The `Jj` slot probes `jj --version` on its
181
+ * first operation and fails that operation with `JjError` carrying
182
+ * `not_installed` or `unsupported_version`; a program that never calls `Jj`
183
+ * runs without jj. `Jj` requires jj 0.39.0 or later.
177
184
  *
178
185
  * @category layers
179
186
  * @since 1.0.0-rc.0
@@ -184,11 +191,11 @@ export const layer = Layer.mergeAll(ProcessConfinement.layer(), platform, Layer.
184
191
  * Provides all five Bun Host services bound to one absolute repository root.
185
192
  *
186
193
  * Throws {@link BunHostError} with code `invalid_repository_root` when `root`
187
- * is not absolute; the empty string counts. The check runs when the factory is
188
- * called, before any layer exists.
194
+ * is not absolute or contains NUL; the empty string counts. The check runs
195
+ * when the factory is called, before any layer exists.
189
196
  *
190
- * Fails at construction with {@link JjError} exactly as {@link layer} does:
191
- * the `Jj` slot probes `jj --version` while the layer is built.
197
+ * Builds without jj exactly as {@link layer} does: the `Jj` slot probes
198
+ * `jj --version` on its first operation.
192
199
  *
193
200
  * @category layers
194
201
  * @since 1.0.0-rc.0
@@ -208,8 +215,8 @@ export const layerAt = (root) => {
208
215
  * it makes, `process.kill` and `taskkill`, are Node's, and Bun implements
209
216
  * them unchanged.
210
217
  *
211
- * Fails at construction with {@link JjError} exactly as {@link layer} does;
212
- * here the `jj --version` probe runs through the contained spawner, so its
218
+ * Builds without jj exactly as {@link layer} does; here the first
219
+ * operation's `jj --version` probe runs through the contained spawner, so its
213
220
  * record enters and leaves the ledger like any other child.
214
221
  *
215
222
  * @category layers
@@ -228,8 +235,7 @@ export const layerContained = (options) => {
228
235
  *
229
236
  * Refuses a root exactly as {@link layerAt} does: {@link BunHostError} with
230
237
  * code `invalid_repository_root`, thrown when the factory is called.
231
- * Fails at construction with {@link JjError} exactly as {@link layerContained}
232
- * does.
238
+ * Builds without jj exactly as {@link layerContained} does.
233
239
  *
234
240
  * @category layers
235
241
  * @since 1.0.0-rc.0
@@ -1 +1 @@
1
- {"version":3,"file":"BunHost.js","sourceRoot":"","sources":["../../src/BunHost.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Aggregate Bun Host bundle.\n *\n * Runtime-specific dependencies stay inside this package; callers get the same\n * closed five-service Host surface every other bundle provides, plus native\n * `ProcessConfinement` for guarded commands.\n *\n * There is no Bun shell module: running a command is Effect's\n * `ChildProcessSpawner`, and `@effect/platform-bun`'s implementation is\n * `@effect/platform-node-shared`'s re-exported: the same code on both\n * runtimes, so no runtime detection is needed here.\n *\n * Outgoing HTTP uses the shared EgressHttpClient on both runtimes. Web\n * requests pin approved addresses; ordinary requests retain the shared pool.\n * Redirects remain visible to the capability kernel, and proxy routing follows\n * the host environment.\n *\n * The filesystem slot is `@smthrs/platform-node`'s `AtomicFileSystem`, byte for\n * byte the layer `NodeHost` uses, so a guarded path operation is\n * descriptor-relative and no-follow on both runtimes.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as BunChildProcessSpawner from \"@effect/platform-bun/BunChildProcessSpawner\"\nimport * as BunCrypto from \"@effect/platform-bun/BunCrypto\"\nimport * as BunPath from \"@effect/platform-bun/BunPath\"\nimport type { Jj, JjError } from \"@smthrs/jj\"\nimport * as BunJj from \"@smthrs/jj/bun/BunJj\"\nimport type { HostServiceIds } from \"@smthrs/kernel/HostServices\"\nimport type * as KernelProcessConfinement from \"@smthrs/kernel/ProcessConfinement\"\nimport type * as ProcessLedger from \"@smthrs/kernel/ProcessLedger\"\nimport * as AtomicFileSystem from \"@smthrs/platform-node/AtomicFileSystem\"\nimport * as EgressHttpClient from \"@smthrs/platform-node/EgressHttpClient\"\nimport * as HostLiveness from \"@smthrs/platform-node/HostLiveness\"\nimport * as ProcessConfinement from \"@smthrs/platform-node/ProcessConfinement\"\nimport * as ProcessReaper from \"@smthrs/platform-node/ProcessReaper\"\nimport type * as Crypto from \"effect/Crypto\"\nimport type { FileSystem } from \"effect/FileSystem\"\nimport type { HttpClient } from \"effect/http/HttpClient\"\nimport * as Layer from \"effect/Layer\"\nimport type * as Path from \"effect/Path\"\nimport type { ChildProcessSpawner } from \"effect/process/ChildProcessSpawner\"\nimport * as Schema from \"effect/Schema\"\nimport { isAbsolute } from \"node:path\"\nimport * as BunFileSystem from \"./BunFileSystem.ts\"\n\n/**\n * Bun platform modules for selectively providing individual services.\n *\n * `AtomicFileSystem` is here for the same reason `NodeHost` re-exports it: it\n * owns the only configuration escape hatch the filesystem slot has, and a Bun\n * program with a different packaged helper path needs to reach\n * `AtomicFileSystem.layerWith` without hand-composing the other four tags.\n *\n * `BunJj` is deliberately absent: it belongs to `@smthrs/jj` and is imported\n * from there, never re-exported here. So is `@effect/platform-bun/BunHttpClient`:\n * its `layer` follows redirects to origins the capability kernel never\n * authorized. The network slot on its own is {@link layerHttpClient}.\n *\n * @category re-exports\n * @since 1.0.0-rc.0\n * @slop\n */\nexport { AtomicFileSystem, BunChildProcessSpawner, BunCrypto, BunFileSystem, HostLiveness, ProcessReaper }\n\n/**\n * The complete closed Host service union provided by Bun.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport type BunHost =\n | FileSystem\n | Path.Path\n | ChildProcessSpawner\n | Jj\n | HttpClient\n | KernelProcessConfinement.ProcessConfinement\n\n/**\n * The stable codes a `BunHost` factory refuses with.\n *\n * A code is the contract a caller branches on; the message beside it is for a\n * person. `invalid_repository_root` is the root handed to {@link layerAt} or\n * {@link layerContainedAt} not being absolute, the empty string included.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type BunHostErrorCode = \"invalid_repository_root\"\n\n/**\n * The refusal a `BunHost` factory throws before it builds a layer.\n *\n * It is thrown rather than failed because a factory is called while a program\n * composes its layers, where there is no fiber to fail yet and a wrong root is\n * a composition mistake, not a runtime outcome. It is this package's class,\n * not the `Jj` adapter's, because a Bun caller composes `BunHost` and should\n * learn nothing about the adapter behind the `Jj` slot from an error message.\n *\n * @category errors\n * @since 1.0.0-rc.0\n */\nexport class BunHostError extends Schema.TaggedError<BunHostError>()(\"@smthrs/platform-bun/BunHostError\", {\n code: Schema.Literal(\"invalid_repository_root\"),\n message: Schema.String\n}) {\n override readonly name = \"BunHostError\"\n}\n\n/** The longest message a factory refuses with, in UTF-16 code units. */\nconst messageLimit = 255\n\n/** How many code points of a refused root the message repeats at most. */\nconst previewLimit = 64\n\n/**\n * A bounded, quoted excerpt of a refused root that fits in `budget` code\n * units of the message.\n *\n * Counted in code points rather than UTF-16 units so a cut never splits a\n * surrogate pair, and passed through `JSON.stringify` so a newline in the root\n * cannot break the log line the message lands on. Each code point is encoded\n * on its own and admitted only while the quoted excerpt plus the length\n * suffix still fit, so a control character that widens to a `\\u0001` escape\n * shortens the excerpt instead of lengthening the message, and a cut never\n * lands inside an escape.\n */\nconst preview = (root: string, budget: number): string => {\n const points = Array.from(root)\n const whole = JSON.stringify(root)\n if (points.length <= previewLimit && whole.length <= budget) return whole\n const suffix = `... (${points.length} characters)`\n const room = budget - suffix.length - 2\n let excerpt = \"\"\n for (const point of points.slice(0, previewLimit)) {\n const encoded = JSON.stringify(point).slice(1, -1)\n if (excerpt.length + encoded.length > room) break\n excerpt += encoded\n }\n return `\"${excerpt}\"${suffix}`\n}\n\n/**\n * Refuses a root the `Jj` adapter underneath would refuse, here and in this\n * package's name.\n *\n * `BunJj.layerAt` and `BunJj.layerSpawnerAt` are `NodeJj`'s, and those throw a\n * bare `TypeError` that names `NodeJj`, carries no code, and echoes the whole\n * string. The predicate here is the same `isAbsolute` the adapter applies, so\n * a root that passes this check cannot reach that throw.\n */\nconst absoluteRoot = (factory: \"layerAt\" | \"layerContainedAt\", root: string): string => {\n if (isAbsolute(root)) return root\n const prefix = `BunHost.${factory} requires an absolute repository root, got `\n throw new BunHostError({\n code: \"invalid_repository_root\",\n message: `${prefix}${preview(root, messageLimit - prefix.length)}`\n })\n}\n\n/**\n * Stable implementation identities keyed by the closed Host service slots.\n *\n * Each value names the module behind that slot in the raw host bundle.\n * Contained POSIX factories replace the spawner with {@link ProcessReaper.layerSpawner}.\n * These are identity\n * tokens rather than import specifiers: the filesystem entry is\n * `@smthrs/platform-node/AtomicFileSystem` because that is the implementation,\n * even though a consumer reaches it through `@smthrs/platform-bun`.\n *\n * Nothing digests them yet. `@smthrs/plan`'s step key carries a `layers`\n * component these values are meant to feed, but no planner derives it from a\n * host bundle today, so changing one invalidates no cached step.\n *\n * The keys are written as literals rather than `HostServiceIds` positions so\n * that reordering the closed list cannot silently pair a slot with another\n * slot's implementation.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const implementationIds: Readonly<Record<(typeof HostServiceIds)[number], string>> = {\n \"effect/FileSystem\": \"@smthrs/platform-node/AtomicFileSystem\",\n \"effect/Path\": \"@effect/platform-bun/BunPath\",\n \"effect/process/ChildProcessSpawner\": \"@effect/platform-bun/BunChildProcessSpawner\",\n \"@smthrs/jj/Jj\": \"@smthrs/jj/bun/BunJj\",\n \"effect/HttpClient\": \"@smthrs/platform-node/EgressHttpClient\"\n}\n\n/**\n * What a caller may configure about containment.\n *\n * `ContainedSpawner.Options.platform` is deliberately not part of it. It\n * decides one thing, whether a command that names no `detached` option gets a\n * process group of its own, and this native host detaches by `process.platform`\n * whatever a record claims. A\n * caller-supplied `\"win32\"` on a POSIX host would therefore record\n * `pgid: null` for a child that really does lead a group, and\n * {@link ProcessReaper.reap} would retire that record as `no-group` and leave\n * the orphan running forever: a durable lie rather than a compile error.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type ContainedOptions = ProcessReaper.SpawnerOptions & ProcessReaper.Options\n\n/** The reaper half of {@link ContainedOptions}. */\nconst reaping = (options?: ContainedOptions): ProcessReaper.Options => ({\n ownerPid: options?.ownerPid,\n system: options?.system\n})\n\n/** The two services `BunChildProcessSpawner` resolves paths and files with. */\nconst platform = Layer.mergeAll(BunFileSystem.layer, BunPath.layer, BunCrypto.layer)\n\n/**\n * The network slot on its own: the shared egress client, which never follows a\n * redirect. A `3xx` comes back to the caller with its `location` intact and the\n * second origin uncontacted; following a redirect is `@smthrs/kernel`'s guarded\n * `HttpClient.layer`, which rechecks every hop.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerHttpClient: Layer.Layer<HttpClient> = EgressHttpClient.layer(process.env)\n\n/**\n * Provides all five Bun Host services, including the runtime-independent Path\n * service.\n *\n * Fails at construction with {@link JjError} carrying `not_installed` or\n * `unsupported_version`: the `Jj` slot is merged eagerly and probes\n * `jj --version` while the layer is built, before the program body runs and\n * whether or not the program asks for `Jj`. Requires jj 0.39.0 or later.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layer: Layer.Layer<BunHost | Crypto.Crypto, JjError> = Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n Layer.provide(BunChildProcessSpawner.layer, platform),\n BunJj.layer,\n layerHttpClient\n)\n\n/**\n * Provides all five Bun Host services bound to one absolute repository root.\n *\n * Throws {@link BunHostError} with code `invalid_repository_root` when `root`\n * is not absolute; the empty string counts. The check runs when the factory is\n * called, before any layer exists.\n *\n * Fails at construction with {@link JjError} exactly as {@link layer} does:\n * the `Jj` slot probes `jj --version` while the layer is built.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerAt = (root: string): Layer.Layer<BunHost | Crypto.Crypto, JjError> => {\n const repositoryRoot = absoluteRoot(\"layerAt\", root)\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n Layer.provide(BunChildProcessSpawner.layer, platform),\n BunJj.layerAt(repositoryRoot),\n layerHttpClient\n )\n}\n\n/**\n * Provides the Bun host with process containment turned on.\n *\n * Bun uses the same prepared native process lifecycle as Node on POSIX, while\n * preserving its underlying runtime spawner on Windows. `ContainedSpawner` gives\n * every child an escalation deadline and a ledger entry, and\n * `ProcessReaper` sweeps the entries a crashed incarnation of this host left\n * behind. The reaper module lives in `@smthrs/platform-node` because the calls\n * it makes, `process.kill` and `taskkill`, are Node's, and Bun implements\n * them unchanged.\n *\n * Fails at construction with {@link JjError} exactly as {@link layer} does;\n * here the `jj --version` probe runs through the contained spawner, so its\n * record enters and leaves the ledger like any other child.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerContained = (\n options?: ContainedOptions\n): Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger> => {\n const spawner = Layer.provide(\n ProcessReaper.layerSpawner({ graceMs: options?.graceMs }),\n Layer.provide(BunChildProcessSpawner.layer, platform)\n )\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n layerHttpClient,\n // jj goes through the CONTAINED spawner here, not around it, exactly as in\n // `NodeHost.layerContained`: a jj child that starts its own process leads\n // no recorded group, is in no ledger, and outlives the host that ran it.\n Layer.provideMerge(BunJj.layerSpawner, spawner)\n ).pipe(Layer.provideMerge(ProcessReaper.layer(reaping(options))))\n}\n\n/**\n * Provides the contained Bun host bound to one absolute repository root.\n *\n * Refuses a root exactly as {@link layerAt} does: {@link BunHostError} with\n * code `invalid_repository_root`, thrown when the factory is called.\n * Fails at construction with {@link JjError} exactly as {@link layerContained}\n * does.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerContainedAt = (\n root: string,\n options?: ContainedOptions\n): Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger> => {\n const repositoryRoot = absoluteRoot(\"layerContainedAt\", root)\n const spawner = Layer.provide(\n ProcessReaper.layerSpawner({ graceMs: options?.graceMs }),\n Layer.provide(BunChildProcessSpawner.layer, platform)\n )\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n layerHttpClient,\n Layer.provideMerge(BunJj.layerSpawnerAt(repositoryRoot), spawner)\n ).pipe(Layer.provideMerge(ProcessReaper.layer(reaping(options))))\n}\n"]}
1
+ {"version":3,"file":"BunHost.js","sourceRoot":"","sources":["../../src/BunHost.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Aggregate Bun Host bundle.\n *\n * Runtime-specific dependencies stay inside this package; callers get the same\n * closed five-service Host surface every other bundle provides, plus native\n * `ProcessConfinement` for guarded commands.\n *\n * There is no Bun shell module: running a command is Effect's\n * `ChildProcessSpawner`, and `@effect/platform-bun`'s implementation is\n * `@effect/platform-node-shared`'s re-exported: the same code on both\n * runtimes, so no runtime detection is needed here.\n *\n * Outgoing HTTP uses the shared EgressHttpClient on both runtimes. Web\n * requests pin approved addresses; ordinary requests retain the shared pool.\n * Redirects remain visible to the capability kernel, and proxy routing follows\n * the host environment.\n *\n * The filesystem slot is `@smthrs/platform-node`'s `AtomicFileSystem`, byte for\n * byte the layer `NodeHost` uses, so a guarded path operation is\n * descriptor-relative and no-follow on both runtimes.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as BunChildProcessSpawner from \"@effect/platform-bun/BunChildProcessSpawner\"\nimport * as BunCrypto from \"@effect/platform-bun/BunCrypto\"\nimport * as BunPath from \"@effect/platform-bun/BunPath\"\nimport type { Jj } from \"@smthrs/jj\"\nimport * as BunJj from \"@smthrs/jj/bun/BunJj\"\nimport type { HostServiceIds } from \"@smthrs/kernel/HostServices\"\nimport type * as KernelProcessConfinement from \"@smthrs/kernel/ProcessConfinement\"\nimport type * as ProcessLedger from \"@smthrs/kernel/ProcessLedger\"\nimport * as AtomicFileSystem from \"@smthrs/platform-node/AtomicFileSystem\"\nimport * as EgressHttpClient from \"@smthrs/platform-node/EgressHttpClient\"\nimport * as HostLiveness from \"@smthrs/platform-node/HostLiveness\"\nimport * as ProcessConfinement from \"@smthrs/platform-node/ProcessConfinement\"\nimport * as ProcessReaper from \"@smthrs/platform-node/ProcessReaper\"\nimport type * as Crypto from \"effect/Crypto\"\nimport type { FileSystem } from \"effect/FileSystem\"\nimport type { HttpClient } from \"effect/http/HttpClient\"\nimport * as Layer from \"effect/Layer\"\nimport type * as Path from \"effect/Path\"\nimport type { ChildProcessSpawner } from \"effect/process/ChildProcessSpawner\"\nimport * as Schema from \"effect/Schema\"\nimport { isAbsolute } from \"node:path\"\nimport * as BunFileSystem from \"./BunFileSystem.ts\"\n\n/**\n * Bun platform modules for selectively providing individual services.\n *\n * `AtomicFileSystem` is here for the same reason `NodeHost` re-exports it: it\n * owns the only configuration escape hatch the filesystem slot has, and a Bun\n * program with a different packaged helper path needs to reach\n * `AtomicFileSystem.layerWith` without hand-composing the other four tags.\n *\n * `BunJj` is deliberately absent: it belongs to `@smthrs/jj` and is imported\n * from there, never re-exported here. So is `@effect/platform-bun/BunHttpClient`:\n * its `layer` follows redirects to origins the capability kernel never\n * authorized. The network slot on its own is {@link layerHttpClient}.\n *\n * @category re-exports\n * @since 1.0.0-rc.0\n * @slop\n */\nexport { AtomicFileSystem, BunChildProcessSpawner, BunCrypto, BunFileSystem, HostLiveness, ProcessReaper }\n\n/**\n * The complete closed Host service union provided by Bun.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport type BunHost =\n | FileSystem\n | Path.Path\n | ChildProcessSpawner\n | Jj\n | HttpClient\n | KernelProcessConfinement.ProcessConfinement\n\n/**\n * The stable codes a `BunHost` factory refuses with.\n *\n * A code is the contract a caller branches on; the message beside it is for a\n * person. `invalid_repository_root` is the root handed to {@link layerAt} or\n * {@link layerContainedAt} not being absolute or containing NUL, the empty\n * string included.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type BunHostErrorCode = \"invalid_repository_root\"\n\n/**\n * The refusal a `BunHost` factory throws before it builds a layer.\n *\n * It is thrown rather than failed because a factory is called while a program\n * composes its layers, where there is no fiber to fail yet and a wrong root is\n * a composition mistake, not a runtime outcome. It is this package's class,\n * not the `Jj` adapter's, because a Bun caller composes `BunHost` and should\n * learn nothing about the adapter behind the `Jj` slot from an error message.\n *\n * @category errors\n * @since 1.0.0-rc.0\n */\nexport class BunHostError extends Schema.TaggedError<BunHostError>()(\"@smthrs/platform-bun/BunHostError\", {\n code: Schema.Literal(\"invalid_repository_root\"),\n message: Schema.String\n}) {\n override readonly name = \"BunHostError\"\n}\n\n/** The longest message a factory refuses with, in UTF-16 code units. */\nconst messageLimit = 255\n\n/** How many code points of a refused root the message repeats at most. */\nconst previewLimit = 64\n\n/**\n * A bounded, quoted excerpt of a refused root that fits in `budget` code\n * units of the message.\n *\n * Counted in code points rather than UTF-16 units so a cut never splits a\n * surrogate pair, and passed through `JSON.stringify` so a newline in the root\n * cannot break the log line the message lands on. Each code point is encoded\n * on its own and admitted only while the quoted excerpt plus the length\n * suffix still fit, so a control character that widens to a `\\u0001` escape\n * shortens the excerpt instead of lengthening the message, and a cut never\n * lands inside an escape.\n */\nconst preview = (root: string, budget: number): string => {\n const points = Array.from(root)\n const whole = JSON.stringify(root)\n if (points.length <= previewLimit && whole.length <= budget) return whole\n const suffix = `... (${points.length} characters)`\n const room = budget - suffix.length - 2\n let excerpt = \"\"\n for (const point of points.slice(0, previewLimit)) {\n const encoded = JSON.stringify(point).slice(1, -1)\n if (excerpt.length + encoded.length > room) break\n excerpt += encoded\n }\n return `\"${excerpt}\"${suffix}`\n}\n\n/**\n * Refuses a root the `Jj` adapter underneath would refuse, here and in this\n * package's name.\n *\n * `BunJj.layerAt` and `BunJj.layerSpawnerAt` are `NodeJj`'s, and those throw a\n * bare `TypeError` that names `NodeJj`, carries no code, and echoes the whole\n * string. The predicate includes the same `isAbsolute` the adapter applies, so\n * a root that passes this check cannot reach that throw. An absolute root\n * containing NUL is refused too: no filesystem or process API can represent it.\n */\nconst absoluteRoot = (factory: \"layerAt\" | \"layerContainedAt\", root: string): string => {\n // An absolute root with NUL names that defect, not absoluteness it already has.\n const requirement = !isAbsolute(root)\n ? \"an absolute repository root\"\n : root.includes(\"\\0\")\n ? \"a repository root without NUL\"\n : undefined\n if (requirement === undefined) return root\n const prefix = `BunHost.${factory} requires ${requirement}, got `\n throw new BunHostError({\n code: \"invalid_repository_root\",\n message: `${prefix}${preview(root, messageLimit - prefix.length)}`\n })\n}\n\n/**\n * Stable implementation identities keyed by the closed Host service slots.\n *\n * Each value names the module behind that slot in the raw host bundle.\n * Contained POSIX factories replace the spawner with {@link ProcessReaper.layerSpawner}.\n * These are identity\n * tokens rather than import specifiers: the filesystem entry is\n * `@smthrs/platform-node/AtomicFileSystem` because that is the implementation,\n * even though a consumer reaches it through `@smthrs/platform-bun`.\n *\n * Nothing digests them yet. `@smthrs/plan`'s step key carries a `layers`\n * component these values are meant to feed, but no planner derives it from a\n * host bundle today, so changing one invalidates no cached step.\n *\n * The keys are written as literals rather than `HostServiceIds` positions so\n * that reordering the closed list cannot silently pair a slot with another\n * slot's implementation.\n *\n * @category models\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const implementationIds: Readonly<Record<(typeof HostServiceIds)[number], string>> = {\n \"effect/FileSystem\": \"@smthrs/platform-node/AtomicFileSystem\",\n \"effect/Path\": \"@effect/platform-bun/BunPath\",\n \"effect/process/ChildProcessSpawner\": \"@effect/platform-bun/BunChildProcessSpawner\",\n \"@smthrs/jj/Jj\": \"@smthrs/jj/bun/BunJj\",\n \"effect/HttpClient\": \"@smthrs/platform-node/EgressHttpClient\"\n}\n\n/**\n * What a caller may configure about containment.\n *\n * `ContainedSpawner.Options.platform` is deliberately not part of it. It\n * decides one thing, whether a command that names no `detached` option gets a\n * process group of its own, and this native host detaches by `process.platform`\n * whatever a record claims. A\n * caller-supplied `\"win32\"` on a POSIX host would therefore record\n * `pgid: null` for a child that really does lead a group, and\n * {@link ProcessReaper.reap} would retire that record as `no-group` and leave\n * the orphan running forever: a durable lie rather than a compile error.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type ContainedOptions = ProcessReaper.SpawnerOptions & ProcessReaper.Options\n\n/** The reaper half of {@link ContainedOptions}. */\nconst reaping = (options?: ContainedOptions): ProcessReaper.Options => ({\n ownerPid: options?.ownerPid,\n system: options?.system\n})\n\n/** The two services `BunChildProcessSpawner` resolves paths and files with. */\nconst platform = Layer.mergeAll(BunFileSystem.layer, BunPath.layer, BunCrypto.layer)\n\n/**\n * The network slot on its own: the shared egress client, which never follows a\n * redirect. A `3xx` comes back to the caller with its `location` intact and the\n * second origin uncontacted; following a redirect is `@smthrs/kernel`'s guarded\n * `HttpClient.layer`, which rechecks every hop.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerHttpClient: Layer.Layer<HttpClient> = EgressHttpClient.layer(process.env)\n\n/**\n * Provides all five Bun Host services, including the runtime-independent Path\n * service.\n *\n * Builds on a host without jj. The `Jj` slot probes `jj --version` on its\n * first operation and fails that operation with `JjError` carrying\n * `not_installed` or `unsupported_version`; a program that never calls `Jj`\n * runs without jj. `Jj` requires jj 0.39.0 or later.\n *\n * @category layers\n * @since 1.0.0-rc.0\n * @slop\n */\nexport const layer: Layer.Layer<BunHost | Crypto.Crypto> = Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n Layer.provide(BunChildProcessSpawner.layer, platform),\n BunJj.layer,\n layerHttpClient\n)\n\n/**\n * Provides all five Bun Host services bound to one absolute repository root.\n *\n * Throws {@link BunHostError} with code `invalid_repository_root` when `root`\n * is not absolute or contains NUL; the empty string counts. The check runs\n * when the factory is called, before any layer exists.\n *\n * Builds without jj exactly as {@link layer} does: the `Jj` slot probes\n * `jj --version` on its first operation.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerAt = (root: string): Layer.Layer<BunHost | Crypto.Crypto> => {\n const repositoryRoot = absoluteRoot(\"layerAt\", root)\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n Layer.provide(BunChildProcessSpawner.layer, platform),\n BunJj.layerAt(repositoryRoot),\n layerHttpClient\n )\n}\n\n/**\n * Provides the Bun host with process containment turned on.\n *\n * Bun uses the same prepared native process lifecycle as Node on POSIX, while\n * preserving its underlying runtime spawner on Windows. `ContainedSpawner` gives\n * every child an escalation deadline and a ledger entry, and\n * `ProcessReaper` sweeps the entries a crashed incarnation of this host left\n * behind. The reaper module lives in `@smthrs/platform-node` because the calls\n * it makes, `process.kill` and `taskkill`, are Node's, and Bun implements\n * them unchanged.\n *\n * Builds without jj exactly as {@link layer} does; here the first\n * operation's `jj --version` probe runs through the contained spawner, so its\n * record enters and leaves the ledger like any other child.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerContained = (\n options?: ContainedOptions\n): Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger> => {\n const spawner = Layer.provide(\n ProcessReaper.layerSpawner({ graceMs: options?.graceMs }),\n Layer.provide(BunChildProcessSpawner.layer, platform)\n )\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n layerHttpClient,\n // jj goes through the CONTAINED spawner here, not around it, exactly as in\n // `NodeHost.layerContained`: a jj child that starts its own process leads\n // no recorded group, is in no ledger, and outlives the host that ran it.\n Layer.provideMerge(BunJj.layerSpawner, spawner)\n ).pipe(Layer.provideMerge(ProcessReaper.layer(reaping(options))))\n}\n\n/**\n * Provides the contained Bun host bound to one absolute repository root.\n *\n * Refuses a root exactly as {@link layerAt} does: {@link BunHostError} with\n * code `invalid_repository_root`, thrown when the factory is called.\n * Builds without jj exactly as {@link layerContained} does.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerContainedAt = (\n root: string,\n options?: ContainedOptions\n): Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger> => {\n const repositoryRoot = absoluteRoot(\"layerContainedAt\", root)\n const spawner = Layer.provide(\n ProcessReaper.layerSpawner({ graceMs: options?.graceMs }),\n Layer.provide(BunChildProcessSpawner.layer, platform)\n )\n return Layer.mergeAll(\n ProcessConfinement.layer(),\n platform,\n layerHttpClient,\n Layer.provideMerge(BunJj.layerSpawnerAt(repositoryRoot), spawner)\n ).pipe(Layer.provideMerge(ProcessReaper.layer(reaping(options))))\n}\n"]}
package/docs/README.md CHANGED
@@ -37,13 +37,16 @@ is Effect's Node spawner re-exported; contained POSIX variants use
37
37
 
38
38
  ## Install
39
39
 
40
- Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
40
+ ```bash
41
+ bun add @smthrs/platform-bun@1.0.0-rc.9 effect
42
+ ```
41
43
 
42
44
  `effect` is a required peer in the range `^4.0.4`. `@effect/platform-bun` is
43
- bundled privately, built from `effect@4.0.4`. The filesystem slot
44
- also needs the `smithers-jj-export` helper, and every complete bundle needs
45
- jj 0.39.0 or later because it probes jj while the layer is built.
46
- [Installation](./installation.md) covers all three and the import forms.
45
+ bundled privately, built from `effect@4.0.4`. The filesystem slot runs the
46
+ `smithers-jj-export` helper that the required peer `@smthrs/platform-node`
47
+ ships. The `Jj` slot needs jj 0.39.0 or later, checked on its first operation;
48
+ every bundle builds without jj. [Installation](./installation.md) covers all
49
+ three and the import forms.
47
50
 
48
51
  ## Run a command and a file operation through the host
49
52
 
@@ -107,8 +110,8 @@ that runs them is yours, and this package when that program runs on Bun.
107
110
 
108
111
  ## Where to go next
109
112
 
110
- - [Installation](./installation.md): the peer dependency, the
111
- `smithers-jj-export` helper, the supported runtimes, and the import forms.
113
+ - [Installation](./installation.md): the install line, the peer dependencies,
114
+ the `smithers-jj-export` helper, the supported runtimes, and the import forms.
112
115
  - [Quickstart](./quickstart.md): the program above, run twice, the second time
113
116
  with containment on and a child visible in the ledger.
114
117
  - [The Host surface on Bun](./concepts/host-surface.md): what each of the five
package/docs/api.md CHANGED
@@ -27,13 +27,13 @@ a browser; a page composes
27
27
  [`@smthrs/platform-browser`](/api/platform-browser) instead.
28
28
  :::
29
29
 
30
- The complete host bundles require jj 0.39.0 or newer. Each bundle checks its jj
31
- executable before exposing repository operations. Construction can fail with
32
- `JjError`, including `not_installed` or `unsupported_version`. The contained
33
- bundles route that probe
34
- through their process spawner and retire its ledger entry when it exits. Probe
35
- results are cached per resolved absolute executable path and runner; contained
36
- spawners share a result only within the same spawner instance.
30
+ Every host bundle builds without jj. The `Jj` slot requires jj 0.39.0 or newer
31
+ and checks its executable on its first operation, which fails with `JjError`
32
+ `not_installed` or `unsupported_version` when jj is missing or older. The
33
+ contained bundles route that probe through their process spawner and retire its
34
+ ledger entry when it exits. Probe results are cached per resolved absolute
35
+ executable path and runner; contained spawners share a result only within the
36
+ same spawner instance.
37
37
 
38
38
  ## Entry points
39
39
 
@@ -52,24 +52,24 @@ Supported runtimes are Bun >=1.4.0 and Node.js >=26.4.0.
52
52
 
53
53
  ## BunHost
54
54
 
55
- The complete closed Host bundle: layers, construction errors, models,
55
+ The complete closed Host bundle: layers, root errors, models,
56
56
  implementation identities, and platform module re-exports.
57
57
 
58
58
  ### Layers
59
59
 
60
- | Export | Signature |
61
- | ------------------ | ------------------------------------------------------------------------------------------------------------- |
62
- | `layer` | `Layer.Layer<BunHost \| Crypto.Crypto, JjError>` |
63
- | `layerAt` | `(root: string) => Layer.Layer<BunHost \| Crypto.Crypto, JjError>` |
64
- | `layerContained` | `(options?: ContainedOptions) => Layer.Layer<BunHost \| Crypto.Crypto, JjError, ProcessLedger>` |
65
- | `layerContainedAt` | `(root: string, options?: ContainedOptions) => Layer.Layer<BunHost \| Crypto.Crypto, JjError, ProcessLedger>` |
66
- | `layerHttpClient` | `Layer.Layer<HttpClient>` |
60
+ | Export | Signature |
61
+ | ------------------ | ----------------------------------------------------------------------------------------------------------- |
62
+ | `layer` | `Layer.Layer<BunHost \| Crypto.Crypto>` |
63
+ | `layerAt` | `(root: string) => Layer.Layer<BunHost \| Crypto.Crypto>` |
64
+ | `layerContained` | `(options?: ContainedOptions) => Layer.Layer<BunHost \| Crypto.Crypto, never, ProcessLedger>` |
65
+ | `layerContainedAt` | `(root: string, options?: ContainedOptions) => Layer.Layer<BunHost \| Crypto.Crypto, never, ProcessLedger>` |
66
+ | `layerHttpClient` | `Layer.Layer<HttpClient>` |
67
67
 
68
68
  `layer` provides all five Host services, including the runtime-independent
69
69
  `Path`. `Jj` is bound to the process working directory.
70
70
 
71
71
  `layerAt` is `layer` with `Jj` bound to `root` instead. `root` must be an
72
- absolute path; see [BunHostError](#bunhosterror).
72
+ absolute path without NUL; see [BunHostError](#bunhosterror).
73
73
 
74
74
  `layerContained` is `layer` with process containment turned on: every child is
75
75
  spawned through `@smthrs/kernel`'s `ContainedSpawner` with
@@ -117,10 +117,10 @@ type BunHostErrorCode = "invalid_repository_root"
117
117
  ```
118
118
 
119
119
  The refusal `layerAt` and `layerContainedAt` throw when `root` is not absolute,
120
- the empty string included. It is thrown rather than failed because a factory
121
- runs while a program composes its layers, where there is no fiber to fail into
122
- and a wrong root is a composition mistake, not a runtime outcome. The check
123
- runs when the factory is called, before any layer exists.
120
+ the empty string included, or contains NUL. It is thrown rather than failed
121
+ because a factory runs while a program composes its layers, where there is no
122
+ fiber to fail into and a wrong root is a composition mistake, not a runtime
123
+ outcome. The check runs when the factory is called, before any layer exists.
124
124
 
125
125
  It is this package's class rather than the `Jj` adapter's: a Bun caller
126
126
  composed `BunHost` and should learn nothing about the adapter behind the `Jj`
@@ -42,8 +42,8 @@ BunHost.layerContainedAt("/srv/repositories/app", { graceMs: 2_000 })
42
42
 
43
43
  ## Pass an absolute root
44
44
 
45
- Both factories require an absolute path. A relative root, and the empty string,
46
- are refused:
45
+ Both factories require an absolute path without NUL. A relative root, the
46
+ empty string, and an absolute root containing NUL are refused:
47
47
 
48
48
  ```ts
49
49
  BunHost.layerAt("repositories/app")
@@ -9,20 +9,17 @@ the `smithers-jj-export` helper the adapter spawns, which is what makes the
9
9
  operation descriptor-relative and no-follow: a symlink swapped in after
10
10
  authorization cannot redirect it somewhere else.
11
11
 
12
- The adapter uses the first of `executable`, `SMITHERS_WORKSPACE_JJ_EXPORT_BINARY`,
13
- the package's `bin/<platform>-<arch>/smithers-jj-export`, a source checkout's
12
+ The required peer `@smthrs/platform-node` ships the helper for macOS (arm64,
13
+ x64), Linux (arm64, x64), and Windows (x64), so an npm or Bun install needs no
14
+ configuration. The adapter uses the first of `executable`,
15
+ `SMITHERS_WORKSPACE_JJ_EXPORT_BINARY`, that package's
16
+ `bin/<platform>-<arch>/smithers-jj-export`, a source checkout's
14
17
  `target/release` or `target/debug` build, and
15
18
  `/usr/local/bin/smithers-jj-export`. It never searches `PATH`.
16
19
 
17
- ## Build or name the helper
20
+ ## Name another helper
18
21
 
19
- In a source checkout:
20
-
21
- ```bash
22
- cargo build --locked --release -p smithers-ffi --bin smithers-jj-export
23
- ```
24
-
25
- Anywhere else, set the absolute path:
22
+ To run a different helper, set the absolute path:
26
23
 
27
24
  ```bash
28
25
  export SMITHERS_WORKSPACE_JJ_EXPORT_BINARY=/opt/smithers/bin/smithers-jj-export
@@ -40,6 +37,13 @@ The executable is re-validated on every request rather than read once when the
40
37
  layer is built, because the file a path names can be replaced while the host
41
38
  runs.
42
39
 
40
+ A source checkout of the Smithers repository has no packaged helper; build one
41
+ there with:
42
+
43
+ ```bash
44
+ cargo build --locked --release -p smithers-ffi --bin smithers-jj-export
45
+ ```
46
+
43
47
  ## Use it as the whole host's filesystem slot
44
48
 
45
49
  `BunHost.layer` builds its filesystem slot from `BunFileSystem.layer`, which
@@ -130,16 +130,18 @@ const status = Effect.flatMap(Jj, (jj) => jj.status()).pipe(
130
130
  ```
131
131
 
132
132
  That is the observable difference: the same call under `BunHost.layer` leaves
133
- the ledger empty. The initial `jj --version` probe uses the selected runner
134
- too, so a contained host records and retires it before exposing `Jj`. Probe
135
- results are cached per resolved absolute executable path and runner, with a
136
- separate cache for each spawner instance.
137
-
138
- Every aggregate host waits up to 5000 ms for the startup probe, then interrupts
139
- it and awaits process cleanup. The typed `JjError` has `method: "version"`,
140
- `code: "unknown"`, `cause.code: "ETIMEDOUT"`, and the executable path in its
141
- command and message. Cleanup can add the contained spawner's shutdown grace
142
- period to the startup budget. Raw hosts close the probe's pipes and kill its
133
+ the ledger empty. The `jj --version` probe that the first `Jj` operation runs
134
+ uses the selected runner too, so a contained host records and retires it like
135
+ any other child. Building the host spawns nothing. Probe results are cached per
136
+ resolved absolute executable path and runner, with a separate cache for each
137
+ spawner instance.
138
+
139
+ The first `Jj` operation on every aggregate host waits up to 5000 ms for the
140
+ version probe, then interrupts it and awaits process cleanup. A probe that misses this budget, or that the
141
+ spawner was too busy to start in time, is retried once. A second timeout fails
142
+ the operation with a typed `JjError` that has `method: "version"`, `code: "unknown"`,
143
+ `cause.code: "ETIMEDOUT"`, and the executable path in its command and message.
144
+ Cleanup can add the contained spawner's shutdown grace period to each attempt. Raw hosts close the probe's pipes and kill its
143
145
  direct child; they do not add a ledger entry.
144
146
 
145
147
  Override the budget per host layer through the shared Node and Bun adapter:
@@ -153,7 +155,7 @@ const boundedHost = host.pipe(
153
155
  ```
154
156
 
155
157
  The budget is a positive, finite number of milliseconds, at most 2147483647.
156
- Timeout and interruption allow a later construction to retry the probe.
158
+ Timeout and interruption allow the next operation to retry the probe.
157
159
 
158
160
  ## What the reaper refuses to kill
159
161
 
@@ -1,30 +1,35 @@
1
1
  ---
2
2
  title: "Installation"
3
- description: "Install @smthrs/platform-bun, its required peers, the smithers-jj-export helper the filesystem slot needs, the jj executable every complete bundle probes at startup, and the import forms for each entry point."
3
+ description: "Install @smthrs/platform-bun from npm with its required peers, the smithers-jj-export helper @smthrs/platform-node ships for the filesystem slot, the jj executable the Jj slot probes on first use, and the import forms for each entry point."
4
4
  sidebar:
5
5
  order: 1
6
6
  ---
7
7
 
8
8
  ## Install the package and its peers
9
9
 
10
- Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
10
+ The package is on npm under the `next` tag, which carries the 1.0 release
11
+ candidates. Install the current candidate with Bun:
11
12
 
12
- Version 1.0.0-rc.0 is not on npm yet. Until it is published, take the package
13
- from [the repository](https://github.com/smithersai/smithers); the rest of this
14
- page applies either way.
13
+ ```bash
14
+ bun add @smthrs/platform-bun@1.0.0-rc.9 effect @smthrs/kernel@1.0.0-rc.9
15
+ ```
16
+
17
+ `@smthrs/kernel` is on the line because the [Quickstart](./quickstart.md)'s
18
+ `contained.ts` imports its `ProcessLedger`, which the contained bundles require.
19
+ Bun's isolated linker and pnpm link only the packages a project declares.
15
20
 
16
21
  The manifest declares two required peers, neither optional:
17
22
 
18
- | Peer | Version | Why |
19
- | ----------------------- | ------------ | --------------------------------------------------------- |
20
- | `effect` | `^4.0.4` | The runtime every service tag belongs to. |
21
- | `@smthrs/platform-node` | `1.0.0-rc.0` | The atomic filesystem slot and the containment machinery. |
23
+ | Peer | Version | Why |
24
+ | ----------------------- | ------------------------------ | --------------------------------------------------------- |
25
+ | `effect` | `^4.0.4` | The runtime every service tag belongs to. |
26
+ | `@smthrs/platform-node` | exactly this package's version | The atomic filesystem slot and the containment machinery. |
22
27
 
23
- Package managers that resolve required peers install both automatically.
24
- Install one `effect` in that range so the host shares one Effect runtime.
25
- `@effect/platform-bun`, `@effect/platform-node` and
26
- `@effect/platform-node-shared` are bundled privately, built from
27
- `effect@4.0.4`.
28
+ Bun installs required peers, so the line above also installs
29
+ `@smthrs/platform-node` at the matching version. Install one `effect` in the
30
+ peer range so the host shares one Effect runtime. `@effect/platform-bun`,
31
+ `@effect/platform-node` and `@effect/platform-node-shared` are bundled
32
+ privately, built from `effect@4.0.4`, so your project installs none of them.
28
33
 
29
34
  ## Supported runtimes
30
35
 
@@ -34,32 +39,31 @@ Bun 1.4.0 or later, and Node.js 26.4.0 or later. Both are declared in
34
39
  [Runtime parity with Node](./concepts/runtime-parity.md) for what that does and
35
40
  does not buy you.
36
41
 
37
- ## Install the filesystem helper
42
+ ## The filesystem helper
38
43
 
39
44
  The filesystem slot is `@smthrs/platform-node`'s `AtomicFileSystem`, which
40
45
  carries the kernel's atomic host extension. That extension does not run
41
46
  in-process: it executes each guarded path operation through the
42
47
  `smithers-jj-export` helper so the operation is descriptor-relative and
43
- no-follow. From a source checkout, build it with:
44
-
45
- ```bash
46
- cargo build --locked --release -p smithers-ffi --bin smithers-jj-export
47
- ```
48
+ no-follow.
48
49
 
49
- Elsewhere, set `SMITHERS_WORKSPACE_JJ_EXPORT_BINARY` to its absolute path; see
50
+ `@smthrs/platform-node` ships the helper prebuilt for macOS (arm64, x64), Linux
51
+ (arm64, x64), and Windows (x64), so installing the package installs it; there
52
+ is nothing to build. `SMITHERS_WORKSPACE_JJ_EXPORT_BINARY` only overrides the
53
+ packaged helper with another absolute path; see
50
54
  [Configure the filesystem helper](./guides/configure-the-filesystem-helper.md).
51
55
 
52
56
  Windows is unsupported for this slot.
53
57
 
54
- ## Install Jujutsu for the complete bundles
58
+ ## Install Jujutsu for the Jj slot
55
59
 
56
- Every complete bundle, `BunHost.layer`, `layerAt`, `layerContained`, and
57
- `layerContainedAt`, requires jj 0.39.0 or later on the host, whether or not
58
- the program uses the `Jj` slot. Each factory merges its `Jj` layer with the
59
- other four, and that layer runs one `jj --version` probe while the layer is
60
- built. On a host without the executable, or with an older one, construction
61
- fails with `JjError` carrying `not_installed` or `unsupported_version` before
62
- the program body runs, even for a program that asked for only `FileSystem`.
60
+ Every bundle, `BunHost.layer`, `layerAt`, `layerContained`, and
61
+ `layerContainedAt`, builds without jj, so a program that never calls `Jj` runs
62
+ on a host without it. The `Jj` slot requires jj 0.39.0 or later: its first
63
+ operation runs one `jj --version` probe, and on a host without the executable,
64
+ or with an older one, that operation fails with `JjError` carrying
65
+ `not_installed` or `unsupported_version`. The slot selects the executable when
66
+ the layer is built, so restart the program after installing jj.
63
67
 
64
68
  Install [Jujutsu](https://jj-vcs.github.io), a version-control system that
65
69
  works on a Git repository, and confirm the version:
@@ -72,9 +76,9 @@ This package vendors no binaries. [`@smthrs/jj`](/api/jj) documents the
72
76
  resolution order and the `SMITHERS_JJ_PATH` override for an executable that is
73
77
  not on `PATH`.
74
78
 
75
- A program that must run without jj composes the individual service layers
76
- instead of a complete bundle: `BunFileSystem.layer`, `Path.layer`, and the
77
- spawner and HTTP client layers each build without a probe. See
79
+ A program that wants only some of the services composes the individual service
80
+ layers instead of a complete bundle: `BunFileSystem.layer`, `Path.layer`, and
81
+ the spawner and HTTP client layers. See
78
82
  [The Host surface on Bun](./concepts/host-surface.md) for taking one service
79
83
  without the other four.
80
84
 
@@ -117,9 +121,9 @@ page composes [`@smthrs/platform-browser`](/api/platform-browser) instead.
117
121
  [Contain and reap child processes](./guides/contain-child-processes.md).
118
122
 
119
123
  The `Jj` slot spawns the `jj` command that
120
- [Install Jujutsu for the complete bundles](#install-jujutsu-for-the-complete-bundles)
121
- covers. A bundle probes it at construction, so the executable is a startup
122
- requirement rather than a slot the program opts into.
124
+ [Install Jujutsu for the Jj slot](#install-jujutsu-for-the-jj-slot) covers. The
125
+ slot probes it on its first operation, so jj is a requirement of the programs
126
+ that call `Jj`, not of every host.
123
127
 
124
128
  ## Next step
125
129
 
@@ -13,15 +13,18 @@ appear in, and disappear from, the process ledger.
13
13
  ## Prerequisites
14
14
 
15
15
  - Bun 1.4.0 or later, or Node.js 26.4.0 or later.
16
- - The `smithers-jj-export` helper. See
17
- [Installation](./installation.md#install-the-filesystem-helper).
18
- - [Jujutsu](https://jj-vcs.github.io) at jj 0.39.0 or later on `PATH`. Confirm
19
- it with `jj --version`. Neither program below touches `Jj`, but every
20
- complete bundle probes jj while the layer is built and fails with `JjError`
21
- before the program body runs when it is missing. See
22
- [Installation](./installation.md) for `SMITHERS_JJ_PATH` and for composing
23
- service layers without jj.
24
- - A package with the dependencies installed. Not on npm yet; see [Installation](https://github.com/smithersai/smithers/blob/main/packages/smithers/flows/flow/docs/installation.md#use-the-libraries).
16
+ - A project with the packages installed:
17
+ `bun add @smthrs/platform-bun@1.0.0-rc.9 effect @smthrs/kernel@1.0.0-rc.9`.
18
+ That also installs the required peer `@smthrs/platform-node`, which ships the
19
+ `smithers-jj-export` helper the filesystem slot runs. `@smthrs/kernel` is on
20
+ the line because `contained.ts` imports its `ProcessLedger`, and Bun's
21
+ isolated linker and pnpm link only the packages a project declares. See
22
+ [Installation](./installation.md).
23
+ - [Jujutsu](https://jj-vcs.github.io) at jj 0.39.0 or later on `PATH`, only for
24
+ a program that calls `Jj`. Neither program below does, and every bundle
25
+ builds without jj; a `Jj` operation on a host without it fails with
26
+ `JjError` `not_installed`. See [Installation](./installation.md) for
27
+ `SMITHERS_JJ_PATH`.
25
28
 
26
29
  ## Run a file operation and a command
27
30
 
@@ -63,8 +66,9 @@ bun run quickstart.ts
63
66
  ```
64
67
 
65
68
  `BunHost.layer` provided all five slots; the program used two of them. The
66
- other three were built and never asked for, and building `Jj` is where the
67
- jj version probe ran.
69
+ other three were built and never asked for. Building `Jj` spawned nothing: its
70
+ jj version probe runs on the first `Jj` operation, which this program never
71
+ makes.
68
72
 
69
73
  ## Turn containment on
70
74
 
@@ -1,33 +1,12 @@
1
1
  ---
2
2
  title: "Troubleshooting"
3
- description: "The failures @smthrs/platform-bun produces: a missing required peer, a refused repository root, a filesystem guard that fails closed, an unfollowed redirect, a missing process ledger, and a browser bundle that will not resolve."
3
+ description: "The failures @smthrs/platform-bun produces: a refused repository root, a filesystem guard that fails closed, an unfollowed redirect, a missing process ledger, and a browser bundle that will not resolve."
4
4
  ---
5
5
 
6
6
  Find the symptom, read the cause, apply the fix. Every failure here is one this
7
7
  package or its slots actually produce; the signatures behind them are on the
8
8
  [API reference](./api.md).
9
9
 
10
- ## ERR_MODULE_NOT_FOUND for @effect/platform-bun/BunChildProcessSpawner
11
-
12
- **Symptom.** The first `import { BunHost } from "@smthrs/platform-bun"`, or
13
- `import * as BunHost from "@smthrs/platform-bun/BunHost"`, throws
14
- `ERR_MODULE_NOT_FOUND` before any of your code runs.
15
-
16
- **Cause.** The install is missing the required `@effect/platform-bun` peer,
17
- which both the root entry point and `BunHost` import at module load. This can
18
- happen with an older package version that marked it optional, or when peer
19
- installation was disabled.
20
-
21
- **Fix.** Install it yourself, at the version your composition's `effect` is
22
- built against:
23
-
24
- ```bash
25
- pnpm add @effect/platform-bun @effect/platform-node-shared
26
- ```
27
-
28
- `@smthrs/platform-bun/BunFileSystem` is the one entry point that resolves
29
- without it, because it imports nothing from `@effect/platform-bun`.
30
-
31
10
  ## BunHostError: requires an absolute repository root
32
11
 
33
12
  **Symptom.** `BunHost.layerAt` or `BunHost.layerContainedAt` throws while your
@@ -35,10 +14,12 @@ program is still composing its layers:
35
14
 
36
15
  ```text
37
16
  BunHostError: BunHost.layerAt requires an absolute repository root, got "repositories/app"
17
+ BunHostError: BunHost.layerAt requires a repository root without NUL, got "/repo\u0000"
38
18
  ```
39
19
 
40
- **Cause.** The root is not absolute. The empty string counts, and so does a
41
- path that looks absolute only after the process working directory is applied.
20
+ **Cause.** The root is not absolute, or it contains NUL, which no filesystem
21
+ or process API can represent. The empty string is not absolute, and neither is
22
+ a path that looks absolute only after the process working directory is applied.
42
23
 
43
24
  **Fix.** Resolve the root before you hand it over, and branch on
44
25
  `error.code === "invalid_repository_root"` rather than on the message text. The
@@ -63,9 +44,12 @@ the one it was told to spawn is missing or not executable. The guard fails close
63
44
  operation, because a path-based fallback is exactly the symlink race the
64
45
  extension exists to prevent.
65
46
 
66
- **Fix.** Run `cargo build --locked --release -p smithers-ffi --bin smithers-jj-export`
67
- in a source checkout, or set `SMITHERS_WORKSPACE_JJ_EXPORT_BINARY` to an
68
- absolute helper path. See
47
+ **Fix.** Install `@smthrs/platform-node`, the required peer that ships the
48
+ helper for macOS, Linux, and Windows, at this package's version. If
49
+ `SMITHERS_WORKSPACE_JJ_EXPORT_BINARY` is set, it overrides the packaged helper:
50
+ point it at an executable helper or unset it. A source checkout of the
51
+ repository has no packaged helper; build one there with
52
+ `cargo build --locked --release -p smithers-ffi --bin smithers-jj-export`. See
69
53
  [Configure the filesystem helper](./guides/configure-the-filesystem-helper.md).
70
54
 
71
55
  This failure is unguarded-path-safe: it appears only under
@@ -112,7 +96,7 @@ your code.
112
96
  `@smthrs/kernel/ProcessLedger`.
113
97
 
114
98
  **Cause.** Both contained factories return
115
- `Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger>`.
99
+ `Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger>`.
116
100
  The ledger is a
117
101
  requirement rather than a built-in default, because the durable half is only as
118
102
  good as the journal underneath it, and only your program knows whether it has
@@ -164,15 +148,16 @@ between them does not change. See
164
148
 
165
149
  ## Jj fails with not_installed
166
150
 
167
- **Symptom.** Building any complete bundle fails with a `JjError` whose code
168
- is `not_installed` or `unsupported_version` before the program body runs,
169
- even when the program never asks for `Jj`.
151
+ **Symptom.** A `Jj` operation fails with a `JjError` whose code is
152
+ `not_installed` or `unsupported_version` and whose method is `version`. The
153
+ host built and every other service works.
170
154
 
171
- **Cause.** No usable `jj` executable, or one older than 0.39.0. Every
172
- `BunHost` factory builds its `Jj` layer eagerly, and that layer probes
173
- `jj --version` at construction. This package vendors no binaries and installs
174
- nothing on your behalf.
155
+ **Cause.** No usable `jj` executable, or one older than 0.39.0. The `Jj` slot
156
+ probes `jj --version` on its first operation, and the operation that triggered
157
+ the probe did not run. This package vendors no binaries and installs nothing on
158
+ your behalf.
175
159
 
176
160
  **Fix.** Install [Jujutsu](https://jj-vcs.github.io), which provides the `jj`
177
- command, or set `SMITHERS_JJ_PATH` to the executable you want spawned.
161
+ command, or set `SMITHERS_JJ_PATH` to the executable you want spawned, then
162
+ restart the program: the slot selects the executable when the layer is built.
178
163
  [`@smthrs/jj`](/api/jj) documents the resolution order and the failure codes.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@smthrs/platform-bun",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.7",
4
+ "version": "1.0.0-rc.9",
5
5
  "license": "MIT",
6
6
  "smthrs": {
7
7
  "group": "engine",
@@ -102,15 +102,15 @@
102
102
  "tag": "next"
103
103
  },
104
104
  "dependencies": {
105
- "@smthrs/jj": "1.0.0-rc.7",
106
- "@smthrs/kernel": "1.0.0-rc.7",
105
+ "@smthrs/jj": "1.0.0-rc.9",
106
+ "@smthrs/kernel": "1.0.0-rc.9",
107
107
  "undici": "8.11.2",
108
108
  "redis": "6.2.1",
109
109
  "@types/ws": "8.18.2",
110
110
  "ws": "8.22.0"
111
111
  },
112
112
  "peerDependencies": {
113
- "@smthrs/platform-node": "1.0.0-rc.7",
113
+ "@smthrs/platform-node": "1.0.0-rc.9",
114
114
  "effect": "^4.0.4"
115
115
  },
116
116
  "devDependencies": {
@@ -120,7 +120,7 @@
120
120
  "@effect/platform-node-shared": "4.0.4",
121
121
  "@effect/vitest": "4.0.4",
122
122
  "@eslint/js": "9.39.5",
123
- "@smthrs/platform-node": "1.0.0-rc.7",
123
+ "@smthrs/platform-node": "1.0.0-rc.9",
124
124
  "@types/node": "26.4.1",
125
125
  "@vitest/coverage-v8": "5.0.0",
126
126
  "dprint": "0.57.1",
@@ -135,7 +135,7 @@
135
135
  "typescript": "5.9.3",
136
136
  "typescript-eslint": "8.69.0",
137
137
  "vitest": "5.0.0",
138
- "@smthrs/artifacts": "1.0.0-rc.7"
138
+ "@smthrs/artifacts": "1.0.0-rc.9"
139
139
  },
140
140
  "scripts": {
141
141
  "lint": "eslint src --max-warnings=0 && dprint check",
package/src/BunHost.ts CHANGED
@@ -25,7 +25,7 @@
25
25
  import * as BunChildProcessSpawner from "@effect/platform-bun/BunChildProcessSpawner"
26
26
  import * as BunCrypto from "@effect/platform-bun/BunCrypto"
27
27
  import * as BunPath from "@effect/platform-bun/BunPath"
28
- import type { Jj, JjError } from "@smthrs/jj"
28
+ import type { Jj } from "@smthrs/jj"
29
29
  import * as BunJj from "@smthrs/jj/bun/BunJj"
30
30
  import type { HostServiceIds } from "@smthrs/kernel/HostServices"
31
31
  import type * as KernelProcessConfinement from "@smthrs/kernel/ProcessConfinement"
@@ -84,7 +84,8 @@ export type BunHost =
84
84
  *
85
85
  * A code is the contract a caller branches on; the message beside it is for a
86
86
  * person. `invalid_repository_root` is the root handed to {@link layerAt} or
87
- * {@link layerContainedAt} not being absolute, the empty string included.
87
+ * {@link layerContainedAt} not being absolute or containing NUL, the empty
88
+ * string included.
88
89
  *
89
90
  * @category models
90
91
  * @since 1.0.0-rc.0
@@ -149,12 +150,19 @@ const preview = (root: string, budget: number): string => {
149
150
  *
150
151
  * `BunJj.layerAt` and `BunJj.layerSpawnerAt` are `NodeJj`'s, and those throw a
151
152
  * bare `TypeError` that names `NodeJj`, carries no code, and echoes the whole
152
- * string. The predicate here is the same `isAbsolute` the adapter applies, so
153
- * a root that passes this check cannot reach that throw.
153
+ * string. The predicate includes the same `isAbsolute` the adapter applies, so
154
+ * a root that passes this check cannot reach that throw. An absolute root
155
+ * containing NUL is refused too: no filesystem or process API can represent it.
154
156
  */
155
157
  const absoluteRoot = (factory: "layerAt" | "layerContainedAt", root: string): string => {
156
- if (isAbsolute(root)) return root
157
- const prefix = `BunHost.${factory} requires an absolute repository root, got `
158
+ // An absolute root with NUL names that defect, not absoluteness it already has.
159
+ const requirement = !isAbsolute(root)
160
+ ? "an absolute repository root"
161
+ : root.includes("\0")
162
+ ? "a repository root without NUL"
163
+ : undefined
164
+ if (requirement === undefined) return root
165
+ const prefix = `BunHost.${factory} requires ${requirement}, got `
158
166
  throw new BunHostError({
159
167
  code: "invalid_repository_root",
160
168
  message: `${prefix}${preview(root, messageLimit - prefix.length)}`
@@ -232,16 +240,16 @@ export const layerHttpClient: Layer.Layer<HttpClient> = EgressHttpClient.layer(p
232
240
  * Provides all five Bun Host services, including the runtime-independent Path
233
241
  * service.
234
242
  *
235
- * Fails at construction with {@link JjError} carrying `not_installed` or
236
- * `unsupported_version`: the `Jj` slot is merged eagerly and probes
237
- * `jj --version` while the layer is built, before the program body runs and
238
- * whether or not the program asks for `Jj`. Requires jj 0.39.0 or later.
243
+ * Builds on a host without jj. The `Jj` slot probes `jj --version` on its
244
+ * first operation and fails that operation with `JjError` carrying
245
+ * `not_installed` or `unsupported_version`; a program that never calls `Jj`
246
+ * runs without jj. `Jj` requires jj 0.39.0 or later.
239
247
  *
240
248
  * @category layers
241
249
  * @since 1.0.0-rc.0
242
250
  * @slop
243
251
  */
244
- export const layer: Layer.Layer<BunHost | Crypto.Crypto, JjError> = Layer.mergeAll(
252
+ export const layer: Layer.Layer<BunHost | Crypto.Crypto> = Layer.mergeAll(
245
253
  ProcessConfinement.layer(),
246
254
  platform,
247
255
  Layer.provide(BunChildProcessSpawner.layer, platform),
@@ -253,16 +261,16 @@ export const layer: Layer.Layer<BunHost | Crypto.Crypto, JjError> = Layer.mergeA
253
261
  * Provides all five Bun Host services bound to one absolute repository root.
254
262
  *
255
263
  * Throws {@link BunHostError} with code `invalid_repository_root` when `root`
256
- * is not absolute; the empty string counts. The check runs when the factory is
257
- * called, before any layer exists.
264
+ * is not absolute or contains NUL; the empty string counts. The check runs
265
+ * when the factory is called, before any layer exists.
258
266
  *
259
- * Fails at construction with {@link JjError} exactly as {@link layer} does:
260
- * the `Jj` slot probes `jj --version` while the layer is built.
267
+ * Builds without jj exactly as {@link layer} does: the `Jj` slot probes
268
+ * `jj --version` on its first operation.
261
269
  *
262
270
  * @category layers
263
271
  * @since 1.0.0-rc.0
264
272
  */
265
- export const layerAt = (root: string): Layer.Layer<BunHost | Crypto.Crypto, JjError> => {
273
+ export const layerAt = (root: string): Layer.Layer<BunHost | Crypto.Crypto> => {
266
274
  const repositoryRoot = absoluteRoot("layerAt", root)
267
275
  return Layer.mergeAll(
268
276
  ProcessConfinement.layer(),
@@ -284,8 +292,8 @@ export const layerAt = (root: string): Layer.Layer<BunHost | Crypto.Crypto, JjEr
284
292
  * it makes, `process.kill` and `taskkill`, are Node's, and Bun implements
285
293
  * them unchanged.
286
294
  *
287
- * Fails at construction with {@link JjError} exactly as {@link layer} does;
288
- * here the `jj --version` probe runs through the contained spawner, so its
295
+ * Builds without jj exactly as {@link layer} does; here the first
296
+ * operation's `jj --version` probe runs through the contained spawner, so its
289
297
  * record enters and leaves the ledger like any other child.
290
298
  *
291
299
  * @category layers
@@ -293,7 +301,7 @@ export const layerAt = (root: string): Layer.Layer<BunHost | Crypto.Crypto, JjEr
293
301
  */
294
302
  export const layerContained = (
295
303
  options?: ContainedOptions
296
- ): Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger> => {
304
+ ): Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger> => {
297
305
  const spawner = Layer.provide(
298
306
  ProcessReaper.layerSpawner({ graceMs: options?.graceMs }),
299
307
  Layer.provide(BunChildProcessSpawner.layer, platform)
@@ -314,8 +322,7 @@ export const layerContained = (
314
322
  *
315
323
  * Refuses a root exactly as {@link layerAt} does: {@link BunHostError} with
316
324
  * code `invalid_repository_root`, thrown when the factory is called.
317
- * Fails at construction with {@link JjError} exactly as {@link layerContained}
318
- * does.
325
+ * Builds without jj exactly as {@link layerContained} does.
319
326
  *
320
327
  * @category layers
321
328
  * @since 1.0.0-rc.0
@@ -323,7 +330,7 @@ export const layerContained = (
323
330
  export const layerContainedAt = (
324
331
  root: string,
325
332
  options?: ContainedOptions
326
- ): Layer.Layer<BunHost | Crypto.Crypto, JjError, ProcessLedger.ProcessLedger> => {
333
+ ): Layer.Layer<BunHost | Crypto.Crypto, never, ProcessLedger.ProcessLedger> => {
327
334
  const repositoryRoot = absoluteRoot("layerContainedAt", root)
328
335
  const spawner = Layer.provide(
329
336
  ProcessReaper.layerSpawner({ graceMs: options?.graceMs }),