@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 +13 -15
- package/dist/cjs/BunHost.d.ts +18 -18
- package/dist/cjs/BunHost.d.ts.map +1 -1
- package/dist/cjs/BunHost.js +3 -2
- package/dist/cjs/BunHost.js.map +2 -2
- package/dist/esm/BunHost.d.ts +18 -18
- package/dist/esm/BunHost.d.ts.map +1 -1
- package/dist/esm/BunHost.js +22 -16
- package/dist/esm/BunHost.js.map +1 -1
- package/docs/README.md +10 -7
- package/docs/api.md +20 -20
- package/docs/guides/bind-a-repository-root.md +2 -2
- package/docs/guides/configure-the-filesystem-helper.md +14 -10
- package/docs/guides/contain-child-processes.md +13 -11
- package/docs/installation.md +39 -35
- package/docs/quickstart.md +15 -11
- package/docs/troubleshooting.md +21 -36
- package/package.json +6 -6
- package/src/BunHost.ts +29 -22
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
|
-
|
|
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
|
|
34
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
`
|
|
67
|
-
selected process runner. Contained bundles record
|
|
68
|
-
host process ledger. Probe results are cached per
|
|
69
|
-
path and runner, with a separate cache for each
|
|
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
|
|
package/dist/cjs/BunHost.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* `
|
|
153
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
168
|
-
*
|
|
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
|
|
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
|
-
*
|
|
186
|
-
*
|
|
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,
|
|
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
|
-
*
|
|
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,
|
|
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,
|
|
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"}
|
package/dist/cjs/BunHost.js
CHANGED
|
@@ -79,8 +79,9 @@ const preview = (root, budget) => {
|
|
|
79
79
|
return `"${excerpt}"${suffix}`;
|
|
80
80
|
};
|
|
81
81
|
const absoluteRoot = (factory, root) => {
|
|
82
|
-
|
|
83
|
-
|
|
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)}`
|
package/dist/cjs/BunHost.js.map
CHANGED
|
@@ -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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBA,6BAAwC;AACxC,gBAA2B;AAC3B,cAAyB;AAEzB,YAAuB;AAIvB,uBAAkC;AAClC,uBAAkC;AAClC,mBAA8B;AAC9B,yBAAoC;AACpC,oBAA+B;AAI/B,YAAuB;AAGvB,aAAwB;AACxB,uBAA2B;AAC3B,oBAA+B;
|
|
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
|
}
|
package/dist/esm/BunHost.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* `
|
|
153
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
168
|
-
*
|
|
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
|
|
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
|
-
*
|
|
186
|
-
*
|
|
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,
|
|
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
|
-
*
|
|
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,
|
|
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,
|
|
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"}
|
package/dist/esm/BunHost.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* `
|
|
176
|
-
*
|
|
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
|
|
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
|
-
*
|
|
191
|
-
*
|
|
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
|
-
*
|
|
212
|
-
*
|
|
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
|
-
*
|
|
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
|
package/dist/esm/BunHost.js.map
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
45
|
-
jj 0.39.0 or later
|
|
46
|
-
[Installation](./installation.md) covers all
|
|
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
|
|
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
|
-
|
|
31
|
-
executable
|
|
32
|
-
`
|
|
33
|
-
bundles route that probe
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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,
|
|
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
|
|
63
|
-
| `layerAt` | `(root: string) => Layer.Layer<BunHost \| Crypto.Crypto
|
|
64
|
-
| `layerContained` | `(options?: ContainedOptions) => Layer.Layer<BunHost \| Crypto.Crypto,
|
|
65
|
-
| `layerContainedAt` | `(root: string, options?: ContainedOptions) => Layer.Layer<BunHost \| Crypto.Crypto,
|
|
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
|
|
121
|
-
runs while a program composes its layers, where there is no
|
|
122
|
-
and a wrong root is a composition mistake, not a runtime
|
|
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,
|
|
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
|
|
13
|
-
|
|
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
|
-
##
|
|
20
|
+
## Name another helper
|
|
18
21
|
|
|
19
|
-
|
|
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
|
|
134
|
-
too, so a contained host records and retires it
|
|
135
|
-
|
|
136
|
-
separate cache for each
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
158
|
+
Timeout and interruption allow the next operation to retry the probe.
|
|
157
159
|
|
|
158
160
|
## What the reaper refuses to kill
|
|
159
161
|
|
package/docs/installation.md
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Installation"
|
|
3
|
-
description: "Install @smthrs/platform-bun
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
19
|
-
| ----------------------- |
|
|
20
|
-
| `effect` | `^4.0.4`
|
|
21
|
-
| `@smthrs/platform-node` |
|
|
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
|
-
|
|
24
|
-
Install one `effect` in
|
|
25
|
-
`@effect/platform-bun`,
|
|
26
|
-
`@effect/platform-node-shared` are bundled
|
|
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
|
-
##
|
|
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.
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
cargo build --locked --release -p smithers-ffi --bin smithers-jj-export
|
|
47
|
-
```
|
|
48
|
+
no-follow.
|
|
48
49
|
|
|
49
|
-
|
|
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
|
|
58
|
+
## Install Jujutsu for the Jj slot
|
|
55
59
|
|
|
56
|
-
Every
|
|
57
|
-
`layerContainedAt`,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
the
|
|
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
|
|
76
|
-
instead of a complete bundle: `BunFileSystem.layer`, `Path.layer`, and
|
|
77
|
-
spawner and HTTP client layers
|
|
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
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
package/docs/quickstart.md
CHANGED
|
@@ -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
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
[Installation](./installation.md)
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
67
|
-
jj version probe
|
|
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
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -1,33 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Troubleshooting"
|
|
3
|
-
description: "The failures @smthrs/platform-bun produces: a
|
|
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
|
|
41
|
-
|
|
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.**
|
|
67
|
-
|
|
68
|
-
|
|
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,
|
|
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.**
|
|
168
|
-
|
|
169
|
-
|
|
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.
|
|
172
|
-
`
|
|
173
|
-
|
|
174
|
-
|
|
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.
|
|
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.
|
|
106
|
-
"@smthrs/kernel": "1.0.0-rc.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
157
|
-
const
|
|
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
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* `
|
|
238
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
260
|
-
*
|
|
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
|
|
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
|
-
*
|
|
288
|
-
*
|
|
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,
|
|
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
|
-
*
|
|
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,
|
|
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 }),
|