@smthrs/kernel 0.0.0-stage → 1.0.0-rc.4
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/CHANGELOG.md +206 -0
- package/LICENSE +21 -0
- package/README.md +154 -2
- package/dist/cjs/CapabilitySet.d.ts +6 -0
- package/dist/cjs/CapabilitySet.d.ts.map +1 -0
- package/dist/cjs/CapabilitySet.js +19 -0
- package/dist/cjs/CapabilitySet.js.map +7 -0
- package/dist/cjs/ChildProcessEnvironment.d.ts +60 -0
- package/dist/cjs/ChildProcessEnvironment.d.ts.map +1 -0
- package/dist/cjs/ChildProcessEnvironment.js +61 -0
- package/dist/cjs/ChildProcessEnvironment.js.map +7 -0
- package/dist/cjs/ChildProcessSpawner.d.ts +100 -0
- package/dist/cjs/ChildProcessSpawner.d.ts.map +1 -0
- package/dist/cjs/ChildProcessSpawner.js +291 -0
- package/dist/cjs/ChildProcessSpawner.js.map +7 -0
- package/dist/cjs/CommandLine.d.ts +173 -0
- package/dist/cjs/CommandLine.d.ts.map +1 -0
- package/dist/cjs/CommandLine.js +54 -0
- package/dist/cjs/CommandLine.js.map +7 -0
- package/dist/cjs/ContainedSpawner.d.ts +135 -0
- package/dist/cjs/ContainedSpawner.d.ts.map +1 -0
- package/dist/cjs/ContainedSpawner.js +141 -0
- package/dist/cjs/ContainedSpawner.js.map +7 -0
- package/dist/cjs/FileSystem.d.ts +452 -0
- package/dist/cjs/FileSystem.d.ts.map +1 -0
- package/dist/cjs/FileSystem.js +890 -0
- package/dist/cjs/FileSystem.js.map +7 -0
- package/dist/cjs/FileSystemBatch.d.ts +94 -0
- package/dist/cjs/FileSystemBatch.d.ts.map +1 -0
- package/dist/cjs/FileSystemBatch.js +31 -0
- package/dist/cjs/FileSystemBatch.js.map +7 -0
- package/dist/cjs/GrantEvent.d.ts +240 -0
- package/dist/cjs/GrantEvent.d.ts.map +1 -0
- package/dist/cjs/GrantEvent.js +115 -0
- package/dist/cjs/GrantEvent.js.map +7 -0
- package/dist/cjs/GrantStore.d.ts +270 -0
- package/dist/cjs/GrantStore.d.ts.map +1 -0
- package/dist/cjs/GrantStore.js +857 -0
- package/dist/cjs/GrantStore.js.map +7 -0
- package/dist/cjs/HostError.d.ts +36 -0
- package/dist/cjs/HostError.d.ts.map +1 -0
- package/dist/cjs/HostError.js +17 -0
- package/dist/cjs/HostError.js.map +7 -0
- package/dist/cjs/HostServices.d.ts +101 -0
- package/dist/cjs/HostServices.d.ts.map +1 -0
- package/dist/cjs/HostServices.js +66 -0
- package/dist/cjs/HostServices.js.map +7 -0
- package/dist/cjs/HttpClient.d.ts +177 -0
- package/dist/cjs/HttpClient.d.ts.map +1 -0
- package/dist/cjs/HttpClient.js +217 -0
- package/dist/cjs/HttpClient.js.map +7 -0
- package/dist/cjs/Jj.d.ts +67 -0
- package/dist/cjs/Jj.d.ts.map +1 -0
- package/dist/cjs/Jj.js +118 -0
- package/dist/cjs/Jj.js.map +7 -0
- package/dist/cjs/JournalGrantStore.d.ts +65 -0
- package/dist/cjs/JournalGrantStore.d.ts.map +1 -0
- package/dist/cjs/JournalGrantStore.js +354 -0
- package/dist/cjs/JournalGrantStore.js.map +7 -0
- package/dist/cjs/Path.d.ts +37 -0
- package/dist/cjs/Path.d.ts.map +1 -0
- package/dist/cjs/Path.js +46 -0
- package/dist/cjs/Path.js.map +7 -0
- package/dist/cjs/ProcessConfinement.d.ts +55 -0
- package/dist/cjs/ProcessConfinement.d.ts.map +1 -0
- package/dist/cjs/ProcessConfinement.js +94 -0
- package/dist/cjs/ProcessConfinement.js.map +7 -0
- package/dist/cjs/ProcessLedger.d.ts +260 -0
- package/dist/cjs/ProcessLedger.d.ts.map +1 -0
- package/dist/cjs/ProcessLedger.js +190 -0
- package/dist/cjs/ProcessLedger.js.map +7 -0
- package/dist/cjs/Rooted.d.ts +53 -0
- package/dist/cjs/Rooted.d.ts.map +1 -0
- package/dist/cjs/Rooted.js +101 -0
- package/dist/cjs/Rooted.js.map +7 -0
- package/dist/cjs/Unreachable.d.ts +27 -0
- package/dist/cjs/Unreachable.d.ts.map +1 -0
- package/dist/cjs/Unreachable.js +43 -0
- package/dist/cjs/Unreachable.js.map +7 -0
- package/dist/cjs/Workspace.d.ts +62 -0
- package/dist/cjs/Workspace.d.ts.map +1 -0
- package/dist/cjs/Workspace.js +35 -0
- package/dist/cjs/Workspace.js.map +7 -0
- package/dist/cjs/index.d.ts +161 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +73 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/Containment.d.ts +24 -0
- package/dist/cjs/internal/Containment.d.ts.map +1 -0
- package/dist/cjs/internal/Containment.js +30 -0
- package/dist/cjs/internal/Containment.js.map +7 -0
- package/dist/cjs/internal/Pipeline.d.ts +18 -0
- package/dist/cjs/internal/Pipeline.d.ts.map +1 -0
- package/dist/cjs/internal/Pipeline.js +90 -0
- package/dist/cjs/internal/Pipeline.js.map +7 -0
- package/dist/cjs/internal/makeCapability.d.ts +16 -0
- package/dist/cjs/internal/makeCapability.d.ts.map +1 -0
- package/dist/cjs/internal/makeCapability.js +44 -0
- package/dist/cjs/internal/makeCapability.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/test/FileSystemBatchContract.d.ts +15 -0
- package/dist/cjs/test/FileSystemBatchContract.d.ts.map +1 -0
- package/dist/cjs/test/FileSystemBatchContract.js +76 -0
- package/dist/cjs/test/FileSystemBatchContract.js.map +7 -0
- package/dist/cjs/test/HostContract.d.ts +225 -0
- package/dist/cjs/test/HostContract.d.ts.map +1 -0
- package/dist/cjs/test/HostContract.js +489 -0
- package/dist/cjs/test/HostContract.js.map +7 -0
- package/dist/cjs/test/TestGrantStore.d.ts +31 -0
- package/dist/cjs/test/TestGrantStore.d.ts.map +1 -0
- package/dist/cjs/test/TestGrantStore.js +65 -0
- package/dist/cjs/test/TestGrantStore.js.map +7 -0
- package/dist/esm/CapabilitySet.d.ts +6 -0
- package/dist/esm/CapabilitySet.d.ts.map +1 -0
- package/dist/esm/CapabilitySet.js +6 -0
- package/dist/esm/CapabilitySet.js.map +1 -0
- package/dist/esm/ChildProcessEnvironment.d.ts +60 -0
- package/dist/esm/ChildProcessEnvironment.d.ts.map +1 -0
- package/dist/esm/ChildProcessEnvironment.js +91 -0
- package/dist/esm/ChildProcessEnvironment.js.map +1 -0
- package/dist/esm/ChildProcessSpawner.d.ts +100 -0
- package/dist/esm/ChildProcessSpawner.d.ts.map +1 -0
- package/dist/esm/ChildProcessSpawner.js +342 -0
- package/dist/esm/ChildProcessSpawner.js.map +1 -0
- package/dist/esm/CommandLine.d.ts +173 -0
- package/dist/esm/CommandLine.d.ts.map +1 -0
- package/dist/esm/CommandLine.js +195 -0
- package/dist/esm/CommandLine.js.map +1 -0
- package/dist/esm/ContainedSpawner.d.ts +135 -0
- package/dist/esm/ContainedSpawner.d.ts.map +1 -0
- package/dist/esm/ContainedSpawner.js +180 -0
- package/dist/esm/ContainedSpawner.js.map +1 -0
- package/dist/esm/FileSystem.d.ts +452 -0
- package/dist/esm/FileSystem.d.ts.map +1 -0
- package/dist/esm/FileSystem.js +989 -0
- package/dist/esm/FileSystem.js.map +1 -0
- package/dist/esm/FileSystemBatch.d.ts +94 -0
- package/dist/esm/FileSystemBatch.d.ts.map +1 -0
- package/dist/esm/FileSystemBatch.js +26 -0
- package/dist/esm/FileSystemBatch.js.map +1 -0
- package/dist/esm/GrantEvent.d.ts +240 -0
- package/dist/esm/GrantEvent.d.ts.map +1 -0
- package/dist/esm/GrantEvent.js +155 -0
- package/dist/esm/GrantEvent.js.map +1 -0
- package/dist/esm/GrantStore.d.ts +270 -0
- package/dist/esm/GrantStore.d.ts.map +1 -0
- package/dist/esm/GrantStore.js +951 -0
- package/dist/esm/GrantStore.js.map +1 -0
- package/dist/esm/HostError.d.ts +36 -0
- package/dist/esm/HostError.d.ts.map +1 -0
- package/dist/esm/HostError.js +20 -0
- package/dist/esm/HostError.js.map +1 -0
- package/dist/esm/HostServices.d.ts +101 -0
- package/dist/esm/HostServices.d.ts.map +1 -0
- package/dist/esm/HostServices.js +76 -0
- package/dist/esm/HostServices.js.map +1 -0
- package/dist/esm/HttpClient.d.ts +177 -0
- package/dist/esm/HttpClient.d.ts.map +1 -0
- package/dist/esm/HttpClient.js +299 -0
- package/dist/esm/HttpClient.js.map +1 -0
- package/dist/esm/Jj.d.ts +67 -0
- package/dist/esm/Jj.d.ts.map +1 -0
- package/dist/esm/Jj.js +111 -0
- package/dist/esm/Jj.js.map +1 -0
- package/dist/esm/JournalGrantStore.d.ts +65 -0
- package/dist/esm/JournalGrantStore.d.ts.map +1 -0
- package/dist/esm/JournalGrantStore.js +350 -0
- package/dist/esm/JournalGrantStore.js.map +1 -0
- package/dist/esm/Path.d.ts +37 -0
- package/dist/esm/Path.d.ts.map +1 -0
- package/dist/esm/Path.js +34 -0
- package/dist/esm/Path.js.map +1 -0
- package/dist/esm/ProcessConfinement.d.ts +55 -0
- package/dist/esm/ProcessConfinement.d.ts.map +1 -0
- package/dist/esm/ProcessConfinement.js +106 -0
- package/dist/esm/ProcessConfinement.js.map +1 -0
- package/dist/esm/ProcessLedger.d.ts +260 -0
- package/dist/esm/ProcessLedger.d.ts.map +1 -0
- package/dist/esm/ProcessLedger.js +283 -0
- package/dist/esm/ProcessLedger.js.map +1 -0
- package/dist/esm/Rooted.d.ts +53 -0
- package/dist/esm/Rooted.d.ts.map +1 -0
- package/dist/esm/Rooted.js +104 -0
- package/dist/esm/Rooted.js.map +1 -0
- package/dist/esm/Unreachable.d.ts +27 -0
- package/dist/esm/Unreachable.d.ts.map +1 -0
- package/dist/esm/Unreachable.js +26 -0
- package/dist/esm/Unreachable.js.map +1 -0
- package/dist/esm/Workspace.d.ts +62 -0
- package/dist/esm/Workspace.d.ts.map +1 -0
- package/dist/esm/Workspace.js +51 -0
- package/dist/esm/Workspace.js.map +1 -0
- package/dist/esm/index.d.ts +161 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +151 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/Containment.d.ts +24 -0
- package/dist/esm/internal/Containment.d.ts.map +1 -0
- package/dist/esm/internal/Containment.js +27 -0
- package/dist/esm/internal/Containment.js.map +1 -0
- package/dist/esm/internal/Pipeline.d.ts +18 -0
- package/dist/esm/internal/Pipeline.d.ts.map +1 -0
- package/dist/esm/internal/Pipeline.js +68 -0
- package/dist/esm/internal/Pipeline.js.map +1 -0
- package/dist/esm/internal/makeCapability.d.ts +16 -0
- package/dist/esm/internal/makeCapability.d.ts.map +1 -0
- package/dist/esm/internal/makeCapability.js +22 -0
- package/dist/esm/internal/makeCapability.js.map +1 -0
- package/dist/esm/test/FileSystemBatchContract.d.ts +15 -0
- package/dist/esm/test/FileSystemBatchContract.d.ts.map +1 -0
- package/dist/esm/test/FileSystemBatchContract.js +57 -0
- package/dist/esm/test/FileSystemBatchContract.js.map +1 -0
- package/dist/esm/test/HostContract.d.ts +225 -0
- package/dist/esm/test/HostContract.d.ts.map +1 -0
- package/dist/esm/test/HostContract.js +460 -0
- package/dist/esm/test/HostContract.js.map +1 -0
- package/dist/esm/test/TestGrantStore.d.ts +31 -0
- package/dist/esm/test/TestGrantStore.d.ts.map +1 -0
- package/dist/esm/test/TestGrantStore.js +57 -0
- package/dist/esm/test/TestGrantStore.js.map +1 -0
- package/docs/README.md +162 -0
- package/docs/api.md +1532 -0
- package/docs/concepts/decoration-in-place.md +117 -0
- package/docs/concepts/filesystem-batches.md +58 -0
- package/docs/concepts/filesystem-confinement.md +168 -0
- package/docs/concepts/grant-decisions.md +181 -0
- package/docs/concepts/process-containment.md +155 -0
- package/docs/contract.md +14 -0
- package/docs/guides/adapt-a-new-host-platform.md +148 -0
- package/docs/guides/answer-permission-requests.md +151 -0
- package/docs/guides/authorize-network-and-model-calls.md +118 -0
- package/docs/guides/contain-spawned-processes.md +118 -0
- package/docs/guides/guard-a-host-bundle.md +109 -0
- package/docs/guides/persist-grants-across-restarts.md +135 -0
- package/docs/guides/write-a-capability-policy.md +159 -0
- package/docs/installation.md +115 -0
- package/docs/quickstart.md +182 -0
- package/docs/testing.md +108 -0
- package/docs/troubleshooting.md +281 -0
- package/package.json +344 -3
- package/src/CapabilitySet.ts +6 -0
- package/src/ChildProcessEnvironment.ts +98 -0
- package/src/ChildProcessSpawner.ts +414 -0
- package/src/CommandLine.ts +250 -0
- package/src/ContainedSpawner.ts +284 -0
- package/src/FileSystem.ts +1655 -0
- package/src/FileSystemBatch.ts +90 -0
- package/src/GrantEvent.ts +174 -0
- package/src/GrantStore.ts +1345 -0
- package/src/HostError.ts +37 -0
- package/src/HostServices.ts +133 -0
- package/src/HttpClient.ts +406 -0
- package/src/Jj.ts +159 -0
- package/src/JournalGrantStore.ts +461 -0
- package/src/Path.ts +47 -0
- package/src/ProcessConfinement.ts +156 -0
- package/src/ProcessLedger.ts +423 -0
- package/src/Rooted.ts +128 -0
- package/src/Unreachable.ts +30 -0
- package/src/Workspace.ts +65 -0
- package/src/index.ts +181 -0
- package/src/internal/Containment.ts +37 -0
- package/src/internal/Pipeline.ts +85 -0
- package/src/internal/makeCapability.ts +26 -0
- package/src/test/FileSystemBatchContract.ts +59 -0
- package/src/test/HostContract.ts +785 -0
- package/src/test/TestGrantStore.ts +73 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host's ledger of the processes it spawned.
|
|
3
|
+
*
|
|
4
|
+
* A cancelled run must not leave a child agent running, and the engine cannot
|
|
5
|
+
* do anything about that on its own: cancellation interrupts fibers, and a
|
|
6
|
+
* fiber closing its scope only reaches the processes THIS incarnation of the
|
|
7
|
+
* host still holds a handle for. A host that crashed left its children with no
|
|
8
|
+
* one holding anything.
|
|
9
|
+
*
|
|
10
|
+
* The ledger is the missing half. Every spawn is recorded here in memory and
|
|
11
|
+
* written to the journal as an ownerless durable record. It is ownerless
|
|
12
|
+
* because it describes the host rather than a run, and first-writer-wins is
|
|
13
|
+
* exactly the semantics a spawn record wants. The next incarnation of the same
|
|
14
|
+
* `hostId` replays those records, subtracts the ones that reported an exit,
|
|
15
|
+
* and is left with {@link Service.orphans}: the process groups a dead owner
|
|
16
|
+
* abandoned, which a platform reaper can then signal.
|
|
17
|
+
*
|
|
18
|
+
* The kernel stays BELOW the engine: this module records pids and process
|
|
19
|
+
* groups and knows nothing about runs, attempts, or ownership fences. It also
|
|
20
|
+
* stays platform-neutral, because signalling a process group is a platform
|
|
21
|
+
* concern and lives in `@smthrs/platform-node`.
|
|
22
|
+
*
|
|
23
|
+
* Governing design:
|
|
24
|
+
* `docs/specs/Concepts/Host Adapters.md` and
|
|
25
|
+
* `docs/specs/Concepts/Journal Queue.md`.
|
|
26
|
+
*
|
|
27
|
+
* @since 1.0.0-rc.0
|
|
28
|
+
*/
|
|
29
|
+
import * as JournalModule from "@smthrs/journal/Journal";
|
|
30
|
+
import * as JournalEvent from "@smthrs/journal/JournalEvent";
|
|
31
|
+
import * as Context from "effect/Context";
|
|
32
|
+
import * as Effect from "effect/Effect";
|
|
33
|
+
import * as Layer from "effect/Layer";
|
|
34
|
+
import * as Schema from "effect/Schema";
|
|
35
|
+
/**
|
|
36
|
+
* The event type recording that a host started a process.
|
|
37
|
+
*
|
|
38
|
+
* @category models
|
|
39
|
+
* @since 1.0.0-rc.0
|
|
40
|
+
*/
|
|
41
|
+
export const SpawnedEventType = "flows.host.process-spawned.v1";
|
|
42
|
+
/**
|
|
43
|
+
* The event type recording that a recorded process ended.
|
|
44
|
+
*
|
|
45
|
+
* @category models
|
|
46
|
+
* @since 1.0.0-rc.0
|
|
47
|
+
*/
|
|
48
|
+
export const ExitedEventType = "flows.host.process-exited.v1";
|
|
49
|
+
/**
|
|
50
|
+
* The event type recording that a later incarnation killed an abandoned
|
|
51
|
+
* process group.
|
|
52
|
+
*
|
|
53
|
+
* @category models
|
|
54
|
+
* @since 1.0.0-rc.0
|
|
55
|
+
*/
|
|
56
|
+
export const ReapedEventType = "flows.host.process-reaped.v1";
|
|
57
|
+
/**
|
|
58
|
+
* The event type recording that a later incarnation retired a record WITHOUT
|
|
59
|
+
* signalling anything.
|
|
60
|
+
*
|
|
61
|
+
* A stored pid outlives the process that wrote it, so a reaper that cannot
|
|
62
|
+
* prove the number still names the process the record describes must not
|
|
63
|
+
* signal it. Retiring the record is still correct: nothing will ever be able
|
|
64
|
+
* to prove it again, and leaving it would make every later incarnation
|
|
65
|
+
* re-examine a number the operating system has moved on from.
|
|
66
|
+
*
|
|
67
|
+
* @category models
|
|
68
|
+
* @since 1.0.0-rc.0
|
|
69
|
+
*/
|
|
70
|
+
export const SkippedEventType = "flows.host.process-reap-skipped.v1";
|
|
71
|
+
/**
|
|
72
|
+
* The producer id every ledger record is written under.
|
|
73
|
+
*
|
|
74
|
+
* Replay checks it: a record forged by another producer cannot make a host
|
|
75
|
+
* signal a process group.
|
|
76
|
+
*
|
|
77
|
+
* @category models
|
|
78
|
+
* @since 1.0.0-rc.0
|
|
79
|
+
*/
|
|
80
|
+
export const sourceId = "@smthrs/kernel/ProcessLedger";
|
|
81
|
+
/**
|
|
82
|
+
* The journal run a host's process records live on.
|
|
83
|
+
*
|
|
84
|
+
* Process records describe a host, not a run, so they get a run of their own,
|
|
85
|
+
* named after the host. Two incarnations of one `hostId` therefore share a
|
|
86
|
+
* history, which is what makes an abandoned process discoverable.
|
|
87
|
+
*
|
|
88
|
+
* @category constructors
|
|
89
|
+
* @since 1.0.0-rc.0
|
|
90
|
+
*/
|
|
91
|
+
export const hostRunId = (hostId) => `flows.host:${hostId}`;
|
|
92
|
+
/**
|
|
93
|
+
* Schema for a recorded process, as it round-trips through the journal.
|
|
94
|
+
*
|
|
95
|
+
* @category schemas
|
|
96
|
+
* @since 1.0.0-rc.0
|
|
97
|
+
*/
|
|
98
|
+
export const ProcessRecord = Schema.Struct({
|
|
99
|
+
pid: Schema.Int,
|
|
100
|
+
pgid: Schema.NullOr(Schema.Int),
|
|
101
|
+
hostId: Schema.String,
|
|
102
|
+
ownerPid: Schema.Int,
|
|
103
|
+
startedAtMs: Schema.Int,
|
|
104
|
+
commandDigest: Schema.String
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* Why a ledger history could not be replayed.
|
|
108
|
+
*
|
|
109
|
+
* - `journal_unreadable`: a page of the host's history could not be read.
|
|
110
|
+
* - `cursor_stalled`: the journal reported more history but did not advance.
|
|
111
|
+
*
|
|
112
|
+
* @category models
|
|
113
|
+
* @since 1.0.0-rc.1
|
|
114
|
+
*/
|
|
115
|
+
export const ProcessLedgerReplayErrorCode = Schema.Literals(["journal_unreadable", "cursor_stalled"]);
|
|
116
|
+
/**
|
|
117
|
+
* The ledger could not read a previous incarnation's history to its end.
|
|
118
|
+
*
|
|
119
|
+
* An unread history is never reported as an empty one: a caller that treated
|
|
120
|
+
* it as empty would leave every abandoned child running with no owner.
|
|
121
|
+
*
|
|
122
|
+
* @category errors
|
|
123
|
+
* @since 1.0.0-rc.1
|
|
124
|
+
*/
|
|
125
|
+
export class ProcessLedgerReplayError extends Schema.TaggedError()("@smthrs/kernel/ProcessLedgerReplayError", {
|
|
126
|
+
code: ProcessLedgerReplayErrorCode,
|
|
127
|
+
message: Schema.String,
|
|
128
|
+
cause: Schema.optional(Schema.Defect())
|
|
129
|
+
}) {
|
|
130
|
+
}
|
|
131
|
+
const decodeRecord = Schema.decodeUnknownResult(ProcessRecord);
|
|
132
|
+
const encodeRecord = Schema.encodeSync(ProcessRecord);
|
|
133
|
+
/**
|
|
134
|
+
* The process-ledger service tag.
|
|
135
|
+
*
|
|
136
|
+
* @category services
|
|
137
|
+
* @since 1.0.0-rc.0
|
|
138
|
+
*/
|
|
139
|
+
export class ProcessLedger extends Context.Service()("@smthrs/kernel/ProcessLedger") {
|
|
140
|
+
}
|
|
141
|
+
/** The event types that retire a record from the inherited set. */
|
|
142
|
+
const retires = (eventType) => eventType === ExitedEventType || eventType === ReapedEventType || eventType === SkippedEventType;
|
|
143
|
+
/** A pid may be reused before the previous process's scope releases its record. */
|
|
144
|
+
const identity = (record) => `${record.pid}:${record.startedAtMs}`;
|
|
145
|
+
/**
|
|
146
|
+
* One entry folded onto the live set.
|
|
147
|
+
*
|
|
148
|
+
* Returns the entry when it is one of the ledger's own records but its payload
|
|
149
|
+
* does not decode, so replay can report it. Such an entry names no process
|
|
150
|
+
* that could be signalled, so failing the whole replay over it would stop
|
|
151
|
+
* every later incarnation from reaping the well-formed records beside it.
|
|
152
|
+
*/
|
|
153
|
+
const apply = (live, entry) => {
|
|
154
|
+
if (entry.sourceId !== sourceId)
|
|
155
|
+
return undefined;
|
|
156
|
+
if (entry.eventType !== SpawnedEventType && !retires(entry.eventType))
|
|
157
|
+
return undefined;
|
|
158
|
+
const decoded = decodeRecord(entry.payload);
|
|
159
|
+
if (decoded._tag === "Failure")
|
|
160
|
+
return entry;
|
|
161
|
+
if (entry.eventType === SpawnedEventType) {
|
|
162
|
+
live.set(identity(decoded.success), decoded.success);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
live.delete(identity(decoded.success));
|
|
166
|
+
return undefined;
|
|
167
|
+
};
|
|
168
|
+
const PAGE = 256;
|
|
169
|
+
const journalSink = (options, journal) => ({
|
|
170
|
+
append: (eventType, record, extra) => journal.emitDurableUnfenced(new JournalEvent.Input({
|
|
171
|
+
runId: hostRunId(options.hostId),
|
|
172
|
+
sourceId: sourceId,
|
|
173
|
+
eventType,
|
|
174
|
+
payload: extra === undefined ? encodeRecord(record) : { ...encodeRecord(record), ...extra }
|
|
175
|
+
})).pipe(
|
|
176
|
+
// The failure is REPORTED, not logged away. A swallowed write leaves a
|
|
177
|
+
// child that no incarnation of this host can discover, which is the one
|
|
178
|
+
// outcome containment exists to prevent; the caller decides what to do
|
|
179
|
+
// about it, and every caller in this repository declines to pretend the
|
|
180
|
+
// process was recorded.
|
|
181
|
+
Effect.tapCause((cause) => Effect.logWarning(`process ledger could not journal ${eventType}`, cause)), Effect.asVoid),
|
|
182
|
+
replay: Effect.gen(function* () {
|
|
183
|
+
const live = new Map();
|
|
184
|
+
let after;
|
|
185
|
+
for (;;) {
|
|
186
|
+
const page = yield* journal.entries({
|
|
187
|
+
runId: hostRunId(options.hostId),
|
|
188
|
+
limit: PAGE,
|
|
189
|
+
...(after === undefined ? {} : { after })
|
|
190
|
+
}).pipe(Effect.mapError((cause) => new ProcessLedgerReplayError({
|
|
191
|
+
code: "journal_unreadable",
|
|
192
|
+
message: `process ledger could not read the history of host ${options.hostId}`,
|
|
193
|
+
cause
|
|
194
|
+
})));
|
|
195
|
+
for (const entry of page.entries) {
|
|
196
|
+
const malformed = apply(live, entry);
|
|
197
|
+
if (malformed !== undefined) {
|
|
198
|
+
yield* Effect.logWarning(`process ledger skipped an undecodable ${malformed.eventType} record at seq ${malformed.seq}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const last = page.entries.at(-1);
|
|
202
|
+
if (last === undefined || !page.hasMore)
|
|
203
|
+
break;
|
|
204
|
+
// A page that ends where the previous one did would be replayed
|
|
205
|
+
// forever, and stopping there would report a partial history as whole.
|
|
206
|
+
if (after !== undefined && last.seq <= after) {
|
|
207
|
+
return yield* Effect.fail(new ProcessLedgerReplayError({
|
|
208
|
+
code: "cursor_stalled",
|
|
209
|
+
message: `process ledger history of host ${options.hostId} stopped advancing at seq ${last.seq}`
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
after = last.seq;
|
|
213
|
+
}
|
|
214
|
+
return [...live.values()];
|
|
215
|
+
})
|
|
216
|
+
});
|
|
217
|
+
const memorySink = {
|
|
218
|
+
append: () => Effect.void,
|
|
219
|
+
replay: Effect.succeed([])
|
|
220
|
+
};
|
|
221
|
+
const makeWith = (options, sink) => {
|
|
222
|
+
const live = new Map();
|
|
223
|
+
return ProcessLedger.of({
|
|
224
|
+
record: (spawned) => Effect.gen(function* () {
|
|
225
|
+
const startedAtMs = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
226
|
+
const record = {
|
|
227
|
+
pid: spawned.pid,
|
|
228
|
+
pgid: spawned.pgid,
|
|
229
|
+
hostId: options.hostId,
|
|
230
|
+
ownerPid: options.ownerPid,
|
|
231
|
+
startedAtMs,
|
|
232
|
+
commandDigest: spawned.commandDigest
|
|
233
|
+
};
|
|
234
|
+
live.set(identity(record), record);
|
|
235
|
+
// A failed append tells the caller the spawn was not recorded, and the
|
|
236
|
+
// caller kills the child. Keeping it live would lie to every later
|
|
237
|
+
// reader about a process this incarnation still holds.
|
|
238
|
+
yield* sink.append(SpawnedEventType, record).pipe(Effect.tapCause(() => Effect.sync(() => live.delete(identity(record)))));
|
|
239
|
+
return record;
|
|
240
|
+
}),
|
|
241
|
+
release: (record) => Effect.suspend(() => {
|
|
242
|
+
live.delete(identity(record));
|
|
243
|
+
return sink.append(ExitedEventType, record);
|
|
244
|
+
}),
|
|
245
|
+
reaped: (record) => sink.append(ReapedEventType, record),
|
|
246
|
+
skipped: (record, reason) => sink.append(SkippedEventType, record, { reason }),
|
|
247
|
+
live: Effect.sync(() => [...live.values()]),
|
|
248
|
+
orphans: Effect.map(sink.replay, (records) => records.filter((record) => record.ownerPid !== options.ownerPid))
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Builds a journal-backed ledger.
|
|
253
|
+
*
|
|
254
|
+
* @category constructors
|
|
255
|
+
* @since 1.0.0-rc.0
|
|
256
|
+
*/
|
|
257
|
+
export const make = (options) => Effect.map(JournalModule.Journal, (journal) => makeWith(options, journalSink(options, journal)));
|
|
258
|
+
/**
|
|
259
|
+
* Builds a ledger that records nothing durably.
|
|
260
|
+
*
|
|
261
|
+
* A host without a journal still contains the processes it holds handles for —
|
|
262
|
+
* scope closure does that — but it inherits nothing from a previous
|
|
263
|
+
* incarnation, so {@link Service.orphans} is always empty.
|
|
264
|
+
*
|
|
265
|
+
* @category constructors
|
|
266
|
+
* @since 1.0.0-rc.0
|
|
267
|
+
*/
|
|
268
|
+
export const makeMemory = (options) => Effect.sync(() => makeWith(options, memorySink));
|
|
269
|
+
/**
|
|
270
|
+
* Provides a journal-backed ledger.
|
|
271
|
+
*
|
|
272
|
+
* @category layers
|
|
273
|
+
* @since 1.0.0-rc.0
|
|
274
|
+
*/
|
|
275
|
+
export const layer = (options) => Layer.effect(ProcessLedger, make(options));
|
|
276
|
+
/**
|
|
277
|
+
* Provides a ledger that records nothing durably.
|
|
278
|
+
*
|
|
279
|
+
* @category layers
|
|
280
|
+
* @since 1.0.0-rc.0
|
|
281
|
+
*/
|
|
282
|
+
export const layerMemory = (options) => Layer.effect(ProcessLedger, makeMemory(options));
|
|
283
|
+
//# sourceMappingURL=ProcessLedger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProcessLedger.js","sourceRoot":"","sources":["../../src/ProcessLedger.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The host's ledger of the processes it spawned.\n *\n * A cancelled run must not leave a child agent running, and the engine cannot\n * do anything about that on its own: cancellation interrupts fibers, and a\n * fiber closing its scope only reaches the processes THIS incarnation of the\n * host still holds a handle for. A host that crashed left its children with no\n * one holding anything.\n *\n * The ledger is the missing half. Every spawn is recorded here in memory and\n * written to the journal as an ownerless durable record. It is ownerless\n * because it describes the host rather than a run, and first-writer-wins is\n * exactly the semantics a spawn record wants. The next incarnation of the same\n * `hostId` replays those records, subtracts the ones that reported an exit,\n * and is left with {@link Service.orphans}: the process groups a dead owner\n * abandoned, which a platform reaper can then signal.\n *\n * The kernel stays BELOW the engine: this module records pids and process\n * groups and knows nothing about runs, attempts, or ownership fences. It also\n * stays platform-neutral, because signalling a process group is a platform\n * concern and lives in `@smthrs/platform-node`.\n *\n * Governing design:\n * `docs/specs/Concepts/Host Adapters.md` and\n * `docs/specs/Concepts/Journal Queue.md`.\n *\n * @since 1.0.0-rc.0\n */\n\nimport * as JournalModule from \"@smthrs/journal/Journal\"\nimport type { JournalError } from \"@smthrs/journal/Journal\"\nimport * as JournalEvent from \"@smthrs/journal/JournalEvent\"\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Schema from \"effect/Schema\"\n\n/**\n * The event type recording that a host started a process.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const SpawnedEventType = \"flows.host.process-spawned.v1\"\n\n/**\n * The event type recording that a recorded process ended.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const ExitedEventType = \"flows.host.process-exited.v1\"\n\n/**\n * The event type recording that a later incarnation killed an abandoned\n * process group.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const ReapedEventType = \"flows.host.process-reaped.v1\"\n\n/**\n * The event type recording that a later incarnation retired a record WITHOUT\n * signalling anything.\n *\n * A stored pid outlives the process that wrote it, so a reaper that cannot\n * prove the number still names the process the record describes must not\n * signal it. Retiring the record is still correct: nothing will ever be able\n * to prove it again, and leaving it would make every later incarnation\n * re-examine a number the operating system has moved on from.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const SkippedEventType = \"flows.host.process-reap-skipped.v1\"\n\n/**\n * The producer id every ledger record is written under.\n *\n * Replay checks it: a record forged by another producer cannot make a host\n * signal a process group.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport const sourceId = \"@smthrs/kernel/ProcessLedger\"\n\n/**\n * The journal run a host's process records live on.\n *\n * Process records describe a host, not a run, so they get a run of their own,\n * named after the host. Two incarnations of one `hostId` therefore share a\n * history, which is what makes an abandoned process discoverable.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n */\nexport const hostRunId = (hostId: string): JournalEvent.RunId => `flows.host:${hostId}` as JournalEvent.RunId\n\n/**\n * What the spawner knows about a process the moment it starts.\n *\n * `pgid` is `null` when the child shares the host's own process group. Such a\n * process cannot be group-signalled — signalling that group would signal the\n * host itself — so the ledger records the absence rather than a number a\n * reaper could misread.\n *\n * `commandDigest` names the executable and nothing else, as\n * `CommandLine.executable` renders it. Arguments carry credentials\n * (`curl -u user:password`, `mysql -phunter2`) and these records are permanent\n * journal entries, so the ledger never writes an argument down. A reader that\n * has to act on a record matches it by pid and process group; the program name\n * is there to make the row legible.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Spawned {\n readonly pid: number\n readonly pgid: number | null\n readonly commandDigest: string\n}\n\n/**\n * Schema for a recorded process, as it round-trips through the journal.\n *\n * @category schemas\n * @since 1.0.0-rc.0\n */\nexport const ProcessRecord = Schema.Struct({\n pid: Schema.Int,\n pgid: Schema.NullOr(Schema.Int),\n hostId: Schema.String,\n ownerPid: Schema.Int,\n startedAtMs: Schema.Int,\n commandDigest: Schema.String\n})\n\n/**\n * One process a host started, with the identity of the incarnation that\n * started it.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport type ProcessRecord = typeof ProcessRecord.Type\n\n/**\n * Why a ledger history could not be replayed.\n *\n * - `journal_unreadable`: a page of the host's history could not be read.\n * - `cursor_stalled`: the journal reported more history but did not advance.\n *\n * @category models\n * @since 1.0.0-rc.1\n */\nexport const ProcessLedgerReplayErrorCode = Schema.Literals([\"journal_unreadable\", \"cursor_stalled\"])\n\n/**\n * The ledger could not read a previous incarnation's history to its end.\n *\n * An unread history is never reported as an empty one: a caller that treated\n * it as empty would leave every abandoned child running with no owner.\n *\n * @category errors\n * @since 1.0.0-rc.1\n */\nexport class ProcessLedgerReplayError extends Schema.TaggedError<ProcessLedgerReplayError>()(\n \"@smthrs/kernel/ProcessLedgerReplayError\",\n {\n code: ProcessLedgerReplayErrorCode,\n message: Schema.String,\n cause: Schema.optional(Schema.Defect())\n }\n) {}\n\nconst decodeRecord = Schema.decodeUnknownResult(ProcessRecord)\nconst encodeRecord = Schema.encodeSync(ProcessRecord)\n\n/**\n * Ledger operations.\n *\n * Every write that has a durable half REPORTS whether that half committed.\n * The in-memory set is still updated first, so a caller that decides to carry\n * on has an accurate view of this incarnation, but the failure is not\n * swallowed: a spawn whose record did not commit is a child that no later\n * incarnation can discover, which is the exact hole this ledger exists to\n * close. {@link ContainedSpawner} therefore refuses such a spawn, and the\n * reaper leaves a record it could not retire in place so a later incarnation\n * tries again.\n *\n * A ledger built with no journal ({@link makeMemory}) never fails: it promises\n * nothing durable in the first place.\n *\n * @category services\n * @since 1.0.0-rc.0\n */\nexport interface Service {\n /** Records a started process and returns the durable record written for it. */\n readonly record: (spawned: Spawned) => Effect.Effect<ProcessRecord, JournalError>\n /** Records that a process ended, and drops it from {@link Service.live}. */\n readonly release: (record: ProcessRecord) => Effect.Effect<void, JournalError>\n /** Records that an abandoned process group was signalled by this host. */\n readonly reaped: (record: ProcessRecord) => Effect.Effect<void, JournalError>\n /**\n * Retires a record this host refused to signal, naming why. The record\n * leaves {@link Service.orphans} without anything having been killed.\n */\n readonly skipped: (record: ProcessRecord, reason: string) => Effect.Effect<void, JournalError>\n /** The processes this incarnation started and has not yet released. */\n readonly live: Effect.Effect<ReadonlyArray<ProcessRecord>>\n /**\n * The processes a previous incarnation of this host left running.\n *\n * Fails when that history cannot be read to its end, so an unreadable\n * history is never mistaken for one with nothing left running.\n */\n readonly orphans: Effect.Effect<ReadonlyArray<ProcessRecord>, ProcessLedgerReplayError>\n}\n\n/**\n * The process-ledger service tag.\n *\n * @category services\n * @since 1.0.0-rc.0\n */\nexport class ProcessLedger extends Context.Service<ProcessLedger, Service>()(\"@smthrs/kernel/ProcessLedger\") {}\n\n/**\n * Ledger configuration.\n *\n * `hostId` is the durable identity two incarnations share; `ownerPid` is the\n * process id of THIS incarnation, which is how a replay tells its own live\n * processes from an abandoned one's.\n *\n * @category models\n * @since 1.0.0-rc.0\n */\nexport interface Options {\n readonly hostId: string\n readonly ownerPid: number\n}\n\n/** Where records are appended, and where a previous incarnation's are read. */\ninterface Sink {\n readonly append: (\n eventType: string,\n record: ProcessRecord,\n extra?: Record<string, unknown>\n ) => Effect.Effect<void, JournalError>\n readonly replay: Effect.Effect<ReadonlyArray<ProcessRecord>, ProcessLedgerReplayError>\n}\n\n/** The event types that retire a record from the inherited set. */\nconst retires = (eventType: string): boolean =>\n eventType === ExitedEventType || eventType === ReapedEventType || eventType === SkippedEventType\n\n/** A pid may be reused before the previous process's scope releases its record. */\nconst identity = (record: ProcessRecord): string => `${record.pid}:${record.startedAtMs}`\n\n/**\n * One entry folded onto the live set.\n *\n * Returns the entry when it is one of the ledger's own records but its payload\n * does not decode, so replay can report it. Such an entry names no process\n * that could be signalled, so failing the whole replay over it would stop\n * every later incarnation from reaping the well-formed records beside it.\n */\nconst apply = (live: Map<string, ProcessRecord>, entry: JournalEvent.Entry): JournalEvent.Entry | undefined => {\n if (entry.sourceId !== sourceId) return undefined\n if (entry.eventType !== SpawnedEventType && !retires(entry.eventType)) return undefined\n const decoded = decodeRecord(entry.payload)\n if (decoded._tag === \"Failure\") return entry\n if (entry.eventType === SpawnedEventType) {\n live.set(identity(decoded.success), decoded.success)\n return\n }\n live.delete(identity(decoded.success))\n return undefined\n}\n\nconst PAGE = 256\n\nconst journalSink = (options: Options, journal: JournalModule.Service): Sink => ({\n append: (eventType, record, extra) =>\n journal.emitDurableUnfenced(\n new JournalEvent.Input({\n runId: hostRunId(options.hostId),\n sourceId: sourceId as JournalEvent.SourceId,\n eventType,\n payload: extra === undefined ? encodeRecord(record) : { ...encodeRecord(record), ...extra }\n })\n ).pipe(\n // The failure is REPORTED, not logged away. A swallowed write leaves a\n // child that no incarnation of this host can discover, which is the one\n // outcome containment exists to prevent; the caller decides what to do\n // about it, and every caller in this repository declines to pretend the\n // process was recorded.\n Effect.tapCause((cause) => Effect.logWarning(`process ledger could not journal ${eventType}`, cause)),\n Effect.asVoid\n ),\n replay: Effect.gen(function*() {\n const live = new Map<string, ProcessRecord>()\n let after: JournalEvent.Seq | undefined\n for (;;) {\n const page = yield* journal.entries({\n runId: hostRunId(options.hostId),\n limit: PAGE,\n ...(after === undefined ? {} : { after })\n }).pipe(\n Effect.mapError((cause) =>\n new ProcessLedgerReplayError({\n code: \"journal_unreadable\",\n message: `process ledger could not read the history of host ${options.hostId}`,\n cause\n })\n )\n )\n for (const entry of page.entries) {\n const malformed = apply(live, entry)\n if (malformed !== undefined) {\n yield* Effect.logWarning(\n `process ledger skipped an undecodable ${malformed.eventType} record at seq ${malformed.seq}`\n )\n }\n }\n const last = page.entries.at(-1)\n if (last === undefined || !page.hasMore) break\n // A page that ends where the previous one did would be replayed\n // forever, and stopping there would report a partial history as whole.\n if (after !== undefined && last.seq <= after) {\n return yield* Effect.fail(\n new ProcessLedgerReplayError({\n code: \"cursor_stalled\",\n message: `process ledger history of host ${options.hostId} stopped advancing at seq ${last.seq}`\n })\n )\n }\n after = last.seq\n }\n return [...live.values()]\n })\n})\n\nconst memorySink: Sink = {\n append: () => Effect.void,\n replay: Effect.succeed([])\n}\n\nconst makeWith = (options: Options, sink: Sink): Service => {\n const live = new Map<string, ProcessRecord>()\n return ProcessLedger.of({\n record: (spawned) =>\n Effect.gen(function*() {\n const startedAtMs = yield* Effect.clockWith((clock) => clock.currentTimeMillis)\n const record: ProcessRecord = {\n pid: spawned.pid,\n pgid: spawned.pgid,\n hostId: options.hostId,\n ownerPid: options.ownerPid,\n startedAtMs,\n commandDigest: spawned.commandDigest\n }\n live.set(identity(record), record)\n // A failed append tells the caller the spawn was not recorded, and the\n // caller kills the child. Keeping it live would lie to every later\n // reader about a process this incarnation still holds.\n yield* sink.append(SpawnedEventType, record).pipe(\n Effect.tapCause(() => Effect.sync(() => live.delete(identity(record))))\n )\n return record\n }),\n release: (record) =>\n Effect.suspend(() => {\n live.delete(identity(record))\n return sink.append(ExitedEventType, record)\n }),\n reaped: (record) => sink.append(ReapedEventType, record),\n skipped: (record, reason) => sink.append(SkippedEventType, record, { reason }),\n live: Effect.sync(() => [...live.values()]),\n orphans: Effect.map(sink.replay, (records) => records.filter((record) => record.ownerPid !== options.ownerPid))\n })\n}\n\n/**\n * Builds a journal-backed ledger.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n */\nexport const make = (options: Options): Effect.Effect<Service, never, JournalModule.Journal> =>\n Effect.map(JournalModule.Journal, (journal) => makeWith(options, journalSink(options, journal)))\n\n/**\n * Builds a ledger that records nothing durably.\n *\n * A host without a journal still contains the processes it holds handles for —\n * scope closure does that — but it inherits nothing from a previous\n * incarnation, so {@link Service.orphans} is always empty.\n *\n * @category constructors\n * @since 1.0.0-rc.0\n */\nexport const makeMemory = (options: Options): Effect.Effect<Service> => Effect.sync(() => makeWith(options, memorySink))\n\n/**\n * Provides a journal-backed ledger.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layer = (options: Options): Layer.Layer<ProcessLedger, never, JournalModule.Journal> =>\n Layer.effect(ProcessLedger, make(options))\n\n/**\n * Provides a ledger that records nothing durably.\n *\n * @category layers\n * @since 1.0.0-rc.0\n */\nexport const layerMemory = (options: Options): Layer.Layer<ProcessLedger> =>\n Layer.effect(ProcessLedger, makeMemory(options))\n"]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem, path and process services bound to one root directory.
|
|
3
|
+
*
|
|
4
|
+
* Node resolves a relative path against `process.cwd()`. A host serves one
|
|
5
|
+
* directory whatever the process's own, so here every relative path, and every
|
|
6
|
+
* command spawned without a `cwd`, resolves against `root`. Absolute paths pass
|
|
7
|
+
* through unchanged, and so does a symlink's target, which is relative to the
|
|
8
|
+
* link rather than to any directory.
|
|
9
|
+
*
|
|
10
|
+
* @since 1.0.0
|
|
11
|
+
*/
|
|
12
|
+
import { FileSystem, Layer, Path } from "effect";
|
|
13
|
+
import * as ChildProcess from "effect/unstable/process/ChildProcess";
|
|
14
|
+
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
|
|
15
|
+
/**
|
|
16
|
+
* `base` with every relative path resolved against the absolute `root`.
|
|
17
|
+
*
|
|
18
|
+
* @category constructors
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
*/
|
|
21
|
+
export declare const path: (base: Path.Path, root: string) => Path.Path;
|
|
22
|
+
/**
|
|
23
|
+
* `base` with every relative path resolved by `rooted`, a {@link path}.
|
|
24
|
+
*
|
|
25
|
+
* @category constructors
|
|
26
|
+
* @since 1.0.0
|
|
27
|
+
*/
|
|
28
|
+
export declare const fileSystem: (base: FileSystem.FileSystem, rooted: Path.Path) => FileSystem.FileSystem;
|
|
29
|
+
/**
|
|
30
|
+
* `value` with every stage's `cwd` resolved by `rooted`, a {@link path}; its
|
|
31
|
+
* root for a stage without one.
|
|
32
|
+
*
|
|
33
|
+
* @category constructors
|
|
34
|
+
* @since 1.0.0
|
|
35
|
+
*/
|
|
36
|
+
export declare const command: (value: ChildProcess.Command, rooted: Path.Path) => ChildProcess.Command;
|
|
37
|
+
/**
|
|
38
|
+
* `base` spawning each command as {@link command} roots it. A contained `base`
|
|
39
|
+
* stays contained.
|
|
40
|
+
*
|
|
41
|
+
* @category constructors
|
|
42
|
+
* @since 1.0.0
|
|
43
|
+
*/
|
|
44
|
+
export declare const spawner: (base: ChildProcessSpawner.ChildProcessSpawner["Service"], rooted: Path.Path) => ChildProcessSpawner.ChildProcessSpawner["Service"];
|
|
45
|
+
/**
|
|
46
|
+
* Decorates the `FileSystem`, `Path` and `ChildProcessSpawner` it is provided
|
|
47
|
+
* in place, so everything downstream resolves against `root`.
|
|
48
|
+
*
|
|
49
|
+
* @category layers
|
|
50
|
+
* @since 1.0.0
|
|
51
|
+
*/
|
|
52
|
+
export declare const layer: (root: string) => Layer.Layer<FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner, never, FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
|
|
53
|
+
//# sourceMappingURL=Rooted.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Rooted.d.ts","sourceRoot":"","sources":["../../src/Rooted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAmB,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AACjE,OAAO,KAAK,YAAY,MAAM,sCAAsC,CAAA;AACpE,OAAO,KAAK,mBAAmB,MAAM,6CAA6C,CAAA;AAGlF;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,IAAI,CAAC,IAAI,EAAE,MAAM,MAAM,KAAG,IAAI,CAAC,IAQzD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,UAAU,CAAC,UAAU,EAAE,QAAQ,IAAI,CAAC,IAAI,KAAG,UAAU,CAAC,UAqCtF,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,YAAY,CAAC,OAAO,EAAE,QAAQ,IAAI,CAAC,IAAI,KAAG,YAAY,CAAC,OAGa,CAAA;AAEnG;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAClB,MAAM,mBAAmB,CAAC,mBAAmB,CAAC,SAAS,CAAC,EACxD,QAAQ,IAAI,CAAC,IAAI,KAChB,mBAAmB,CAAC,mBAAmB,CAAC,SAAS,CACgD,CAAA;AAEpG;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,GAChB,MAAM,MAAM,KACX,KAAK,CAAC,KAAK,CACZ,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC,mBAAmB,EAC3E,KAAK,EACL,UAAU,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC,mBAAmB,CAYxE,CAAA"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem, path and process services bound to one root directory.
|
|
3
|
+
*
|
|
4
|
+
* Node resolves a relative path against `process.cwd()`. A host serves one
|
|
5
|
+
* directory whatever the process's own, so here every relative path, and every
|
|
6
|
+
* command spawned without a `cwd`, resolves against `root`. Absolute paths pass
|
|
7
|
+
* through unchanged, and so does a symlink's target, which is relative to the
|
|
8
|
+
* link rather than to any directory.
|
|
9
|
+
*
|
|
10
|
+
* @since 1.0.0
|
|
11
|
+
*/
|
|
12
|
+
import { Context, Effect, FileSystem, Layer, Path } from "effect";
|
|
13
|
+
import * as ChildProcess from "effect/unstable/process/ChildProcess";
|
|
14
|
+
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
|
|
15
|
+
import * as Containment from "./internal/Containment.js";
|
|
16
|
+
/**
|
|
17
|
+
* `base` with every relative path resolved against the absolute `root`.
|
|
18
|
+
*
|
|
19
|
+
* @category constructors
|
|
20
|
+
* @since 1.0.0
|
|
21
|
+
*/
|
|
22
|
+
export const path = (base, root) => {
|
|
23
|
+
const resolve = (...segments) => base.resolve(root, ...segments);
|
|
24
|
+
return {
|
|
25
|
+
...base,
|
|
26
|
+
resolve,
|
|
27
|
+
relative: (from, to) => base.relative(resolve(from), resolve(to)),
|
|
28
|
+
toFileUrl: (value) => base.toFileUrl(resolve(value))
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* `base` with every relative path resolved by `rooted`, a {@link path}.
|
|
33
|
+
*
|
|
34
|
+
* @category constructors
|
|
35
|
+
* @since 1.0.0
|
|
36
|
+
*/
|
|
37
|
+
export const fileSystem = (base, rooted) => {
|
|
38
|
+
const at = (value) => rooted.resolve(value);
|
|
39
|
+
const temp = (options) => options?.directory === undefined ? options : { ...options, directory: at(options.directory) };
|
|
40
|
+
return {
|
|
41
|
+
...base,
|
|
42
|
+
access: (value, options) => base.access(at(value), options),
|
|
43
|
+
copy: (from, to, options) => base.copy(at(from), at(to), options),
|
|
44
|
+
copyFile: (from, to) => base.copyFile(at(from), at(to)),
|
|
45
|
+
chmod: (value, mode) => base.chmod(at(value), mode),
|
|
46
|
+
chown: (value, uid, gid) => base.chown(at(value), uid, gid),
|
|
47
|
+
glob: (pattern, options) => base.glob(pattern, { ...options, root: at(options?.root ?? ".") }),
|
|
48
|
+
exists: (value) => base.exists(at(value)),
|
|
49
|
+
link: (from, to) => base.link(at(from), at(to)),
|
|
50
|
+
makeDirectory: (value, options) => base.makeDirectory(at(value), options),
|
|
51
|
+
makeTempDirectory: (options) => base.makeTempDirectory(temp(options)),
|
|
52
|
+
makeTempDirectoryScoped: (options) => base.makeTempDirectoryScoped(temp(options)),
|
|
53
|
+
makeTempFile: (options) => base.makeTempFile(temp(options)),
|
|
54
|
+
makeTempFileScoped: (options) => base.makeTempFileScoped(temp(options)),
|
|
55
|
+
open: (value, options) => base.open(at(value), options),
|
|
56
|
+
readDirectory: (value, options) => base.readDirectory(at(value), options),
|
|
57
|
+
readFile: (value) => base.readFile(at(value)),
|
|
58
|
+
readFileString: (value, encoding) => base.readFileString(at(value), encoding),
|
|
59
|
+
readLink: (value) => base.readLink(at(value)),
|
|
60
|
+
realPath: (value) => base.realPath(at(value)),
|
|
61
|
+
remove: (value, options) => base.remove(at(value), options),
|
|
62
|
+
rename: (from, to) => base.rename(at(from), at(to)),
|
|
63
|
+
sink: (value, options) => base.sink(at(value), options),
|
|
64
|
+
stat: (value) => base.stat(at(value)),
|
|
65
|
+
stream: (value, options) => base.stream(at(value), options),
|
|
66
|
+
symlink: (target, value) => base.symlink(target, at(value)),
|
|
67
|
+
truncate: (value, length) => base.truncate(at(value), length),
|
|
68
|
+
utimes: (value, atime, mtime) => base.utimes(at(value), atime, mtime),
|
|
69
|
+
watch: (value, options) => base.watch(at(value), options),
|
|
70
|
+
writeFile: (value, data, options) => base.writeFile(at(value), data, options),
|
|
71
|
+
writeFileString: (value, data, options) => base.writeFileString(at(value), data, options)
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* `value` with every stage's `cwd` resolved by `rooted`, a {@link path}; its
|
|
76
|
+
* root for a stage without one.
|
|
77
|
+
*
|
|
78
|
+
* @category constructors
|
|
79
|
+
* @since 1.0.0
|
|
80
|
+
*/
|
|
81
|
+
export const command = (value, rooted) => ChildProcess.isStandardCommand(value)
|
|
82
|
+
? ChildProcess.setCwd(value, rooted.resolve(value.options.cwd ?? "."))
|
|
83
|
+
: ChildProcess.pipeTo(command(value.left, rooted), command(value.right, rooted), value.options);
|
|
84
|
+
/**
|
|
85
|
+
* `base` spawning each command as {@link command} roots it. A contained `base`
|
|
86
|
+
* stays contained.
|
|
87
|
+
*
|
|
88
|
+
* @category constructors
|
|
89
|
+
* @since 1.0.0
|
|
90
|
+
*/
|
|
91
|
+
export const spawner = (base, rooted) => Containment.inherit(base, ChildProcessSpawner.make((value) => base.spawn(command(value, rooted))));
|
|
92
|
+
/**
|
|
93
|
+
* Decorates the `FileSystem`, `Path` and `ChildProcessSpawner` it is provided
|
|
94
|
+
* in place, so everything downstream resolves against `root`.
|
|
95
|
+
*
|
|
96
|
+
* @category layers
|
|
97
|
+
* @since 1.0.0
|
|
98
|
+
*/
|
|
99
|
+
export const layer = (root) => Layer.effectContext(Effect.gen(function* () {
|
|
100
|
+
const base = yield* Path.Path;
|
|
101
|
+
const rooted = path(base, base.resolve(root));
|
|
102
|
+
return Context.make(Path.Path, rooted).pipe(Context.add(FileSystem.FileSystem, fileSystem(yield* FileSystem.FileSystem, rooted)), Context.add(ChildProcessSpawner.ChildProcessSpawner, spawner(yield* ChildProcessSpawner.ChildProcessSpawner, rooted)));
|
|
103
|
+
}));
|
|
104
|
+
//# sourceMappingURL=Rooted.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Rooted.js","sourceRoot":"","sources":["../../src/Rooted.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Filesystem, path and process services bound to one root directory.\n *\n * Node resolves a relative path against `process.cwd()`. A host serves one\n * directory whatever the process's own, so here every relative path, and every\n * command spawned without a `cwd`, resolves against `root`. Absolute paths pass\n * through unchanged, and so does a symlink's target, which is relative to the\n * link rather than to any directory.\n *\n * @since 1.0.0\n */\n\nimport { Context, Effect, FileSystem, Layer, Path } from \"effect\"\nimport * as ChildProcess from \"effect/unstable/process/ChildProcess\"\nimport * as ChildProcessSpawner from \"effect/unstable/process/ChildProcessSpawner\"\nimport * as Containment from \"./internal/Containment.ts\"\n\n/**\n * `base` with every relative path resolved against the absolute `root`.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const path = (base: Path.Path, root: string): Path.Path => {\n const resolve = (...segments: ReadonlyArray<string>) => base.resolve(root, ...segments)\n return {\n ...base,\n resolve,\n relative: (from, to) => base.relative(resolve(from), resolve(to)),\n toFileUrl: (value) => base.toFileUrl(resolve(value))\n }\n}\n\n/**\n * `base` with every relative path resolved by `rooted`, a {@link path}.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const fileSystem = (base: FileSystem.FileSystem, rooted: Path.Path): FileSystem.FileSystem => {\n const at = (value: string) => rooted.resolve(value)\n const temp = <O extends { readonly directory?: string | undefined }>(options: O | undefined) =>\n options?.directory === undefined ? options : { ...options, directory: at(options.directory) }\n return {\n ...base,\n access: (value, options) => base.access(at(value), options),\n copy: (from, to, options) => base.copy(at(from), at(to), options),\n copyFile: (from, to) => base.copyFile(at(from), at(to)),\n chmod: (value, mode) => base.chmod(at(value), mode),\n chown: (value, uid, gid) => base.chown(at(value), uid, gid),\n glob: (pattern, options) => base.glob(pattern, { ...options, root: at(options?.root ?? \".\") }),\n exists: (value) => base.exists(at(value)),\n link: (from, to) => base.link(at(from), at(to)),\n makeDirectory: (value, options) => base.makeDirectory(at(value), options),\n makeTempDirectory: (options) => base.makeTempDirectory(temp(options)),\n makeTempDirectoryScoped: (options) => base.makeTempDirectoryScoped(temp(options)),\n makeTempFile: (options) => base.makeTempFile(temp(options)),\n makeTempFileScoped: (options) => base.makeTempFileScoped(temp(options)),\n open: (value, options) => base.open(at(value), options),\n readDirectory: (value, options) => base.readDirectory(at(value), options),\n readFile: (value) => base.readFile(at(value)),\n readFileString: (value, encoding) => base.readFileString(at(value), encoding),\n readLink: (value) => base.readLink(at(value)),\n realPath: (value) => base.realPath(at(value)),\n remove: (value, options) => base.remove(at(value), options),\n rename: (from, to) => base.rename(at(from), at(to)),\n sink: (value, options) => base.sink(at(value), options),\n stat: (value) => base.stat(at(value)),\n stream: (value, options) => base.stream(at(value), options),\n symlink: (target, value) => base.symlink(target, at(value)),\n truncate: (value, length) => base.truncate(at(value), length),\n utimes: (value, atime, mtime) => base.utimes(at(value), atime, mtime),\n watch: (value, options) => base.watch(at(value), options),\n writeFile: (value, data, options) => base.writeFile(at(value), data, options),\n writeFileString: (value, data, options) => base.writeFileString(at(value), data, options)\n }\n}\n\n/**\n * `value` with every stage's `cwd` resolved by `rooted`, a {@link path}; its\n * root for a stage without one.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const command = (value: ChildProcess.Command, rooted: Path.Path): ChildProcess.Command =>\n ChildProcess.isStandardCommand(value)\n ? ChildProcess.setCwd(value, rooted.resolve(value.options.cwd ?? \".\"))\n : ChildProcess.pipeTo(command(value.left, rooted), command(value.right, rooted), value.options)\n\n/**\n * `base` spawning each command as {@link command} roots it. A contained `base`\n * stays contained.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const spawner = (\n base: ChildProcessSpawner.ChildProcessSpawner[\"Service\"],\n rooted: Path.Path\n): ChildProcessSpawner.ChildProcessSpawner[\"Service\"] =>\n Containment.inherit(base, ChildProcessSpawner.make((value) => base.spawn(command(value, rooted))))\n\n/**\n * Decorates the `FileSystem`, `Path` and `ChildProcessSpawner` it is provided\n * in place, so everything downstream resolves against `root`.\n *\n * @category layers\n * @since 1.0.0\n */\nexport const layer = (\n root: string\n): Layer.Layer<\n FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner,\n never,\n FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner\n> =>\n Layer.effectContext(Effect.gen(function*() {\n const base = yield* Path.Path\n const rooted = path(base, base.resolve(root))\n return Context.make(Path.Path, rooted).pipe(\n Context.add(FileSystem.FileSystem, fileSystem(yield* FileSystem.FileSystem, rooted)),\n Context.add(\n ChildProcessSpawner.ChildProcessSpawner,\n spawner(yield* ChildProcessSpawner.ChildProcessSpawner, rooted)\n )\n )\n }))\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed host connectivity failures, including failed command diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* @since 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
declare const Unreachable_base: Schema.Class<Unreachable, Schema.TaggedStruct<"@smthrs/kernel/Unreachable", {
|
|
8
|
+
readonly message: Schema.String;
|
|
9
|
+
readonly cause: Schema.optional<Schema.Unknown>;
|
|
10
|
+
}>, import("effect/Cause").YieldableError>;
|
|
11
|
+
/**
|
|
12
|
+
* A temporarily unreachable resolver, connection, or HTTP service.
|
|
13
|
+
*
|
|
14
|
+
* @category errors
|
|
15
|
+
* @since 1.0.0
|
|
16
|
+
*/
|
|
17
|
+
export declare class Unreachable extends Unreachable_base {
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Classifies stderr from a failed host command; other exits keep their domain error.
|
|
21
|
+
*
|
|
22
|
+
* @category constructors
|
|
23
|
+
* @since 1.0.0
|
|
24
|
+
*/
|
|
25
|
+
export declare const classifyExit: (stderr: string) => Unreachable | undefined;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=Unreachable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Unreachable.d.ts","sourceRoot":"","sources":["../../src/Unreachable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;;;;;AAEvC;;;;;GAKG;AACH,qBAAa,WAAY,SAAQ,gBAG/B;CAAG;AAKL;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM,KAAG,WAAW,GAAG,SACY,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed host connectivity failures, including failed command diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* @since 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
/**
|
|
8
|
+
* A temporarily unreachable resolver, connection, or HTTP service.
|
|
9
|
+
*
|
|
10
|
+
* @category errors
|
|
11
|
+
* @since 1.0.0
|
|
12
|
+
*/
|
|
13
|
+
export class Unreachable extends Schema.TaggedError()("@smthrs/kernel/Unreachable", {
|
|
14
|
+
message: Schema.String,
|
|
15
|
+
cause: Schema.optional(Schema.Unknown)
|
|
16
|
+
}) {
|
|
17
|
+
}
|
|
18
|
+
const outage = /Could not resolve host|Failed to connect to|Connection timed out|Operation timed out|Connection reset|Recv failure|Network is unreachable|Temporary failure in name resolution|SSL_ERROR_SYSCALL|The remote end hung up unexpectedly|\bEAI_AGAIN\b|\bENOTFOUND\b|\bHTTP(?:\/\d(?:\.\d)?)?\s*(?:(?:error|status|code)\s*[:=]?\s*)?[:=]?\s*(?:429|5\d{2})\b/i;
|
|
19
|
+
/**
|
|
20
|
+
* Classifies stderr from a failed host command; other exits keep their domain error.
|
|
21
|
+
*
|
|
22
|
+
* @category constructors
|
|
23
|
+
* @since 1.0.0
|
|
24
|
+
*/
|
|
25
|
+
export const classifyExit = (stderr) => outage.test(stderr) ? new Unreachable({ message: stderr }) : undefined;
|
|
26
|
+
//# sourceMappingURL=Unreachable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Unreachable.js","sourceRoot":"","sources":["../../src/Unreachable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,OAAO,WAAY,SAAQ,MAAM,CAAC,WAAW,EAAe,CAAC,4BAA4B,EAAE;IAC/F,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC;CACvC,CAAC;CAAG;AAEL,MAAM,MAAM,GACV,4VAA4V,CAAA;AAE9V;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAc,EAA2B,EAAE,CACtE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA","sourcesContent":["/**\n * Typed host connectivity failures, including failed command diagnostics.\n *\n * @since 1.0.0\n */\n\nimport * as Schema from \"effect/Schema\"\n\n/**\n * A temporarily unreachable resolver, connection, or HTTP service.\n *\n * @category errors\n * @since 1.0.0\n */\nexport class Unreachable extends Schema.TaggedError<Unreachable>()(\"@smthrs/kernel/Unreachable\", {\n message: Schema.String,\n cause: Schema.optional(Schema.Unknown)\n}) {}\n\nconst outage =\n /Could not resolve host|Failed to connect to|Connection timed out|Operation timed out|Connection reset|Recv failure|Network is unreachable|Temporary failure in name resolution|SSL_ERROR_SYSCALL|The remote end hung up unexpectedly|\\bEAI_AGAIN\\b|\\bENOTFOUND\\b|\\bHTTP(?:\\/\\d(?:\\.\\d)?)?\\s*(?:(?:error|status|code)\\s*[:=]?\\s*)?[:=]?\\s*(?:429|5\\d{2})\\b/i\n\n/**\n * Classifies stderr from a failed host command; other exits keep their domain error.\n *\n * @category constructors\n * @since 1.0.0\n */\nexport const classifyExit = (stderr: string): Unreachable | undefined =>\n outage.test(stderr) ? new Unreachable({ message: stderr }) : undefined\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workspace root used to make filesystem capability resources stable.
|
|
3
|
+
*
|
|
4
|
+
* It lives beside the closed host list rather than in a platform package: a
|
|
5
|
+
* workspace root is a policy decision the kernel needs before it can name a
|
|
6
|
+
* filesystem capability, not something a platform can answer.
|
|
7
|
+
*
|
|
8
|
+
* Governing design:
|
|
9
|
+
* `docs/specs/Concepts/Effect Taxonomy.md` and
|
|
10
|
+
* `docs/specs/Concepts/Permission Kernel.md`.
|
|
11
|
+
*
|
|
12
|
+
* @since 1.0.0-rc.0
|
|
13
|
+
*/
|
|
14
|
+
import { Context, Layer } from "effect";
|
|
15
|
+
/**
|
|
16
|
+
* Workspace-root configuration.
|
|
17
|
+
*
|
|
18
|
+
* @category models
|
|
19
|
+
* @since 1.0.0-rc.0
|
|
20
|
+
*/
|
|
21
|
+
export interface Service {
|
|
22
|
+
readonly root: string;
|
|
23
|
+
}
|
|
24
|
+
declare const Workspace_base: Context.ServiceClass<Workspace, "@smthrs/kernel/Workspace", Service>;
|
|
25
|
+
/**
|
|
26
|
+
* The workspace-root service tag.
|
|
27
|
+
*
|
|
28
|
+
* @category services
|
|
29
|
+
* @since 1.0.0-rc.0
|
|
30
|
+
*/
|
|
31
|
+
export declare class Workspace extends Workspace_base {
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Constructs workspace-root configuration.
|
|
35
|
+
*
|
|
36
|
+
* @category constructors
|
|
37
|
+
* @since 1.0.0-rc.0
|
|
38
|
+
*/
|
|
39
|
+
export declare const make: (root: string) => Service;
|
|
40
|
+
/**
|
|
41
|
+
* Provides a workspace root.
|
|
42
|
+
*
|
|
43
|
+
* @category layers
|
|
44
|
+
* @since 1.0.0-rc.0
|
|
45
|
+
*/
|
|
46
|
+
export declare const layer: (root: string) => Layer.Layer<Workspace>;
|
|
47
|
+
/**
|
|
48
|
+
* Constructs a relative workspace-root stub for tests.
|
|
49
|
+
*
|
|
50
|
+
* @category constructors
|
|
51
|
+
* @since 1.0.0-rc.0
|
|
52
|
+
*/
|
|
53
|
+
export declare const makeNoop: Service;
|
|
54
|
+
/**
|
|
55
|
+
* Provides a relative workspace-root stub for tests.
|
|
56
|
+
*
|
|
57
|
+
* @category layers
|
|
58
|
+
* @since 1.0.0-rc.0
|
|
59
|
+
*/
|
|
60
|
+
export declare const layerNoop: Layer.Layer<Workspace>;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=Workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Workspace.d.ts","sourceRoot":"","sources":["../../src/Workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;;AAED;;;;;GAKG;AACH,qBAAa,SAAU,SAAQ,cAAiE;CAAG;AAEnG;;;;;GAKG;AACH,eAAO,MAAM,IAAI,GAAI,MAAM,MAAM,KAAG,OAAiC,CAAA;AAErE;;;;;GAKG;AACH,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,KAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAyC,CAAA;AAEnG;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,OAAmB,CAAA;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,CAAsC,CAAA"}
|