@graftland/plugin-remote 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/dist/Auth.d.ts +2 -2
  2. package/dist/Auth.d.ts.map +1 -1
  3. package/dist/Auth.js.map +1 -1
  4. package/dist/{Bootstrap-Dr6sX7el.js → Bootstrap-H9gDsgsC.js} +2 -2
  5. package/dist/{Contract-DL2mHvaT.js → Contract-DsJtisvL.js} +2 -2
  6. package/dist/Contract.js +1 -1
  7. package/dist/{Errors-BMKmu5Y7.js → Errors-DYhsGZcZ.js} +3 -1
  8. package/dist/Errors.d.ts +7 -0
  9. package/dist/Errors.d.ts.map +1 -1
  10. package/dist/Errors.js +3 -1
  11. package/dist/Errors.js.map +1 -1
  12. package/dist/{FailureGroup-BcH0XOBi.js → FailureGroup-BgT73AaR.js} +14 -4
  13. package/dist/{Framing-zaYdOJnT.js → Framing-G9eSaeLb.js} +1 -1
  14. package/dist/{Graft-CVyueNOf.js → Graft-B71CPA2Y.js} +20 -16
  15. package/dist/{Packages-JNL34JpS.js → Packages-AgoCGotL.js} +23 -26
  16. package/dist/{Peer-BT7eAGmh.js → Peer-DXeVrGBh.js} +5 -5
  17. package/dist/{ProcessObservation-DEGrEMfP.js → ProcessObservation-gLgGirYv.js} +1 -1
  18. package/dist/{ServeLifecycle-CJM0gG5B.js → ServeLifecycle-Bd1FPJ6e.js} +1 -1
  19. package/dist/Transport.d.ts +2 -2
  20. package/dist/Transport.d.ts.map +1 -1
  21. package/dist/Transport.js.map +1 -1
  22. package/dist/bin.js +48 -47
  23. package/dist/bin.js.map +1 -1
  24. package/dist/{childRuntime-An0HsIzl.js → childRuntime-Bm3iSg_N.js} +29 -26
  25. package/dist/{connectionEnded-DCpFCjMX.js → connectionEnded-BwcxQ7W8.js} +4 -4
  26. package/dist/{entry-B89sepMU.js → entry-psOpX3Bj.js} +72 -35
  27. package/dist/host/auth/bearer.js +1 -1
  28. package/dist/host/auth/jwt.js +1 -1
  29. package/dist/host/auth/mtls.js +2 -2
  30. package/dist/host/auth/none.js +1 -1
  31. package/dist/host/command.js +4 -4
  32. package/dist/host/local.js +3 -3
  33. package/dist/host/unix.js +3 -3
  34. package/dist/host/websocket.js +3 -3
  35. package/dist/host/worker.js +4 -5
  36. package/dist/{importer-Bk6U_sn4.js → importer-Cvf88eAY.js} +5 -8
  37. package/dist/index.js +9 -9
  38. package/dist/index.js.map +1 -1
  39. package/dist/internal/Certificates.js +4 -4
  40. package/dist/internal/Certificates.js.map +1 -1
  41. package/dist/internal/FailureGroup.js +3 -2
  42. package/dist/internal/FailureGroup.js.map +1 -1
  43. package/dist/internal/assembly.js +9 -2
  44. package/dist/internal/assembly.js.map +1 -1
  45. package/dist/internal/auth/mtls.js +1 -1
  46. package/dist/internal/auth/mtls.js.map +1 -1
  47. package/dist/internal/child.js +20 -17
  48. package/dist/internal/child.js.map +1 -1
  49. package/dist/internal/childRuntime.js +4 -4
  50. package/dist/internal/childRuntime.js.map +1 -1
  51. package/dist/internal/directTransport.d.ts +1 -1
  52. package/dist/internal/entry.d.ts.map +1 -1
  53. package/dist/internal/entry.js +1 -1
  54. package/dist/internal/entry.js.map +1 -1
  55. package/dist/internal/execution.js +40 -5
  56. package/dist/internal/execution.js.map +1 -1
  57. package/dist/internal/host/runner.js +7 -6
  58. package/dist/internal/host/runner.js.map +1 -1
  59. package/dist/internal/host.js +5 -5
  60. package/dist/internal/host.js.map +1 -1
  61. package/dist/internal/port.js +5 -5
  62. package/dist/internal/port.js.map +1 -1
  63. package/dist/internal/release.js +1 -1
  64. package/dist/internal/release.js.map +1 -1
  65. package/dist/internal/runner/Connection.d.ts +1 -1
  66. package/dist/internal/runner/Connection.js +4 -4
  67. package/dist/internal/runner/Connection.js.map +1 -1
  68. package/dist/internal/runner/Execution.js +3 -3
  69. package/dist/internal/runner/Execution.js.map +1 -1
  70. package/dist/internal/runner/Framing.js +1 -1
  71. package/dist/internal/runner/Framing.js.map +1 -1
  72. package/dist/internal/runner/Local.js +1 -1
  73. package/dist/internal/runner/Local.js.map +1 -1
  74. package/dist/internal/runner/Lock.js +1 -1
  75. package/dist/internal/runner/Lock.js.map +1 -1
  76. package/dist/internal/runner/Packages.d.ts +1 -1
  77. package/dist/internal/runner/Packages.js +22 -25
  78. package/dist/internal/runner/Packages.js.map +1 -1
  79. package/dist/internal/runner/Processes.js +7 -9
  80. package/dist/internal/runner/Processes.js.map +1 -1
  81. package/dist/internal/runner/Store.js +3 -3
  82. package/dist/internal/runner/Store.js.map +1 -1
  83. package/dist/internal/runner/Supervisor.js +46 -47
  84. package/dist/internal/runner/Supervisor.js.map +1 -1
  85. package/dist/internal/runner/accept.js +3 -3
  86. package/dist/internal/runner/accept.js.map +1 -1
  87. package/dist/internal/runner/model.js +1 -1
  88. package/dist/internal/runner/model.js.map +1 -1
  89. package/dist/internal/serve.js +1 -1
  90. package/dist/internal/serve.js.map +1 -1
  91. package/dist/internal/supervisorPlugin.js +1 -1
  92. package/dist/internal/watchdogMain.js +5 -5
  93. package/dist/internal/watchdogMain.js.map +1 -1
  94. package/dist/internal/websocketServer.js +3 -1
  95. package/dist/internal/websocketServer.js.map +1 -1
  96. package/dist/internal/workerMain.js +3 -3
  97. package/dist/{port-DSVUY8Bq.js → port--3x2zJ4D.js} +8 -8
  98. package/dist/{release-BY4Mlluy.js → release-DfZPuIcK.js} +1 -1
  99. package/dist/runner/auth/bearer.js +2 -2
  100. package/dist/runner/auth/jwt.js +4 -4
  101. package/dist/runner/auth/jwt.js.map +1 -1
  102. package/dist/runner/auth/mtls.js +3 -3
  103. package/dist/runner/auth/none.js +1 -1
  104. package/dist/runner/index.js +5 -5
  105. package/dist/runner/stdio.js +3 -3
  106. package/dist/runner/unix.js +3 -3
  107. package/dist/runner/websocket.js +4 -4
  108. package/dist/{socketChannel-C_ppdSzz.js → socketChannel-CJvL2DiB.js} +2 -2
  109. package/dist/{supervisorPlugin-CivhM7x5.js → supervisorPlugin-t6Z6PrhD.js} +72 -76
  110. package/dist/{token-Cc_1CTbE.js → token-CYBNNlhS.js} +1 -1
  111. package/dist/{tokenCredentials-CI6mMRRd.js → tokenCredentials-CbCuk-Rt.js} +3 -3
  112. package/dist/{websocketServer-D2Hk7wsA.js → websocketServer-NQhODlEt.js} +5 -3
  113. package/dist/{workerChannel-cmLc13vm.js → workerChannel-C8hhBJzA.js} +1 -1
  114. package/package.json +3 -3
  115. package/src/Auth.ts +9 -2
  116. package/src/Errors.ts +8 -0
  117. package/src/Transport.ts +6 -2
  118. package/src/bin.ts +28 -30
  119. package/src/index.ts +5 -3
  120. package/src/internal/Certificates.ts +10 -13
  121. package/src/internal/FailureGroup.ts +128 -121
  122. package/src/internal/assembly.ts +19 -10
  123. package/src/internal/auth/mtls.ts +19 -20
  124. package/src/internal/child.ts +248 -239
  125. package/src/internal/childRuntime.ts +81 -80
  126. package/src/internal/entry.ts +10 -11
  127. package/src/internal/execution.ts +69 -12
  128. package/src/internal/host/runner.ts +153 -153
  129. package/src/internal/host.ts +116 -120
  130. package/src/internal/port.ts +100 -103
  131. package/src/internal/release.ts +3 -2
  132. package/src/internal/runner/Connection.ts +52 -60
  133. package/src/internal/runner/Execution.ts +163 -164
  134. package/src/internal/runner/Framing.ts +9 -10
  135. package/src/internal/runner/Local.ts +26 -27
  136. package/src/internal/runner/Lock.ts +23 -25
  137. package/src/internal/runner/Packages.ts +67 -70
  138. package/src/internal/runner/Processes.ts +89 -92
  139. package/src/internal/runner/Store.ts +110 -114
  140. package/src/internal/runner/Supervisor.ts +392 -405
  141. package/src/internal/runner/accept.ts +76 -84
  142. package/src/internal/runner/model.ts +20 -21
  143. package/src/internal/serve.ts +15 -13
  144. package/src/internal/watchdogMain.ts +16 -11
  145. package/src/internal/websocketServer.ts +18 -3
  146. package/src/runner/auth/jwt.ts +10 -6
  147. package/dist/cleanup-CXLa1Wd9.js +0 -13
@@ -45,192 +45,191 @@ export class Channels extends Context.Service<
45
45
  }
46
46
  >()("@graftland/plugin-remote/internal/runner/Execution/Channels") {}
47
47
 
48
- export const make = (root: string) =>
49
- Effect.gen(function* () {
50
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
51
- const fs = yield* FileSystem.FileSystem
52
- const path = yield* Path.Path
53
- const platform = Context.make(
54
- ChildProcessSpawner.ChildProcessSpawner,
55
- spawner,
56
- ).pipe(Context.add(FileSystem.FileSystem, fs), Context.add(Path.Path, path))
48
+ export const make = Effect.fnUntraced(function* (root: string) {
49
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
50
+ const fs = yield* FileSystem.FileSystem
51
+ const path = yield* Path.Path
52
+ const platform = Context.make(
53
+ ChildProcessSpawner.ChildProcessSpawner,
54
+ spawner,
55
+ ).pipe(Context.add(FileSystem.FileSystem, fs), Context.add(Path.Path, path))
57
56
 
58
- const peers = yield* Ref.make(HashMap.empty<string, Multiplex.Peer>())
59
- const claimed = yield* Ref.make(HashSet.empty<string>())
60
- const driver = Execution.of({
61
- recover: (child) =>
62
- Processes.recover(child).pipe(Effect.provideContext(platform)),
63
- spawn: (handle, request, limits) =>
64
- Effect.gen(function* () {
65
- const resolved = yield* Packages.resolve(
66
- root,
67
- request.specifier,
68
- request.digest,
69
- )
70
- const argv = yield* command()
71
- const child = yield* spawner.spawn(
72
- ChildProcess.make(
73
- argv[0],
74
- [
75
- ...Array.drop(argv, 1),
76
- "child",
77
- resolved.entry,
78
- handle.token,
79
- yield* Schema.encodeEffect(Schema.fromJsonString(Bootstrap))({
80
- identity: {
81
- id: handle.key.instance,
82
- generation: handle.generation,
83
- },
84
- limits,
85
- }),
86
- ],
87
- {
88
- stdin: "pipe",
89
- stdout: "pipe",
90
- stderr: "pipe",
91
- additionalFds: {
92
- fd3: { type: "output" },
93
- fd4: { type: "input" },
57
+ const peers = yield* Ref.make(HashMap.empty<string, Multiplex.Peer>())
58
+ const claimed = yield* Ref.make(HashSet.empty<string>())
59
+ const driver = Execution.of({
60
+ recover: (child) =>
61
+ Processes.recover(child).pipe(Effect.provideContext(platform)),
62
+ spawn: Effect.fnUntraced(
63
+ function* (handle, request, limits) {
64
+ const resolved = yield* Packages.resolve(
65
+ root,
66
+ request.specifier,
67
+ request.digest,
68
+ )
69
+ const argv = yield* command()
70
+ const child = yield* spawner.spawn(
71
+ ChildProcess.make(
72
+ argv[0],
73
+ [
74
+ ...Array.drop(argv, 1),
75
+ "child",
76
+ resolved.entry,
77
+ handle.token,
78
+ yield* Schema.encodeEffect(Schema.fromJsonString(Bootstrap))({
79
+ identity: {
80
+ id: handle.key.instance,
81
+ generation: handle.generation,
94
82
  },
95
- forceKillAfter: "1 second",
83
+ limits,
84
+ }),
85
+ ],
86
+ {
87
+ stdin: "pipe",
88
+ stdout: "pipe",
89
+ stderr: "pipe",
90
+ additionalFds: {
91
+ fd3: { type: "output" },
92
+ fd4: { type: "input" },
96
93
  },
97
- ),
98
- )
99
- const observation = yield* ProcessObservation.observe(child)
100
- const exited = observation.exited.pipe(
94
+ forceKillAfter: "1 second",
95
+ },
96
+ ),
97
+ )
98
+ const observation = yield* ProcessObservation.observe(child)
99
+ const exited = observation.exited.pipe(
100
+ Effect.mapError((error) =>
101
+ ExecutionError.make({
102
+ detail: "Observing child exit failed",
103
+ cause: Cause.fail(error),
104
+ }),
105
+ ),
106
+ )
107
+ const failures = yield* FailureGroup.make<ExecutionError>()
108
+ yield* failures.run(
109
+ observation.failure.pipe(
101
110
  Effect.mapError((error) =>
102
111
  ExecutionError.make({
103
- detail: "Observing child exit failed",
112
+ detail: "Reading child output failed",
104
113
  cause: Cause.fail(error),
105
114
  }),
106
115
  ),
107
- )
108
- const failures = yield* FailureGroup.make<ExecutionError>()
109
- yield* failures.run(
110
- observation.failure.pipe(
111
- Effect.mapError((error) =>
112
- ExecutionError.make({
113
- detail: "Reading child output failed",
114
- cause: Cause.fail(error),
115
- }),
116
- ),
117
- ),
118
- )
119
- const controls = yield* Queue.bounded<string>(4)
116
+ ),
117
+ )
118
+ const controls = yield* Queue.bounded<string>(4)
120
119
 
121
- yield* failures.run(
122
- Stream.fromQueue(controls).pipe(
123
- Stream.map((line) => new TextEncoder().encode(line + "\n")),
124
- Stream.run(child.getInputFd(4)),
120
+ yield* failures.run(
121
+ Stream.fromQueue(controls).pipe(
122
+ Stream.map((line) => new TextEncoder().encode(line + "\n")),
123
+ Stream.run(child.getInputFd(4)),
124
+ Effect.mapError((error) =>
125
+ ExecutionError.make({
126
+ detail: "Child supervision channel failed",
127
+ cause: Cause.fail(error),
128
+ }),
129
+ ),
130
+ ),
131
+ )
132
+ const send = (value: typeof Command.Type) =>
133
+ Effect.raceFirst(
134
+ Schema.encodeEffect(CommandJson)(value).pipe(
135
+ Effect.flatMap((line) => Queue.offer(controls, line)),
136
+ Effect.asVoid,
125
137
  Effect.mapError((error) =>
126
138
  ExecutionError.make({
127
- detail: "Child supervision channel failed",
139
+ detail: "Encoding watchdog command failed",
128
140
  cause: Cause.fail(error),
129
141
  }),
130
142
  ),
131
143
  ),
144
+ failures.await,
132
145
  )
133
- const send = (value: typeof Command.Type) =>
134
- Effect.raceFirst(
135
- Schema.encodeEffect(CommandJson)(value).pipe(
136
- Effect.flatMap((line) => Queue.offer(controls, line)),
137
- Effect.asVoid,
138
- Effect.mapError((error) =>
139
- ExecutionError.make({
140
- detail: "Encoding watchdog command failed",
141
- cause: Cause.fail(error),
142
- }),
143
- ),
144
- ),
145
- failures.await,
146
- )
147
- const peer = yield* Multiplex.make({
148
- input: child
149
- .getOutputFd(3)
150
- .pipe(
151
- Stream.mapError((error) =>
152
- TransportError.make({ cause: Cause.fail(error) }),
153
- ),
154
- ),
155
- output: child.stdin.pipe(
156
- Sink.mapError((error) =>
146
+ const peer = yield* Multiplex.make({
147
+ input: child
148
+ .getOutputFd(3)
149
+ .pipe(
150
+ Stream.mapError((error) =>
157
151
  TransportError.make({ cause: Cause.fail(error) }),
158
152
  ),
159
153
  ),
160
- })
161
- const identity = yield* Processes.identity(child.pid)
162
- yield* Ref.update(peers, HashMap.set(handle.token, peer))
163
- yield* Effect.addFinalizer(() =>
164
- Ref.update(peers, HashMap.remove(handle.token)),
165
- )
166
- return {
167
- code: resolved.identity,
168
- start: send({ kind: "start" }),
169
- identity,
170
- exited,
171
- interrupt: send({ kind: "stop" }),
172
- kill: child.kill({ killSignal: "SIGKILL" }).pipe(
173
- Effect.mapError((error) =>
174
- ExecutionError.make({
175
- detail: "Killing the child failed",
176
- cause: Cause.fail(error),
177
- }),
178
- ),
154
+ output: child.stdin.pipe(
155
+ Sink.mapError((error) =>
156
+ TransportError.make({ cause: Cause.fail(error) }),
179
157
  ),
180
- renew: (millis: number) => send({ kind: "renew", millis }),
181
- }
182
- }).pipe(
183
- Effect.provideContext(platform),
184
- Effect.catchTags({
185
- PlatformError: (error) =>
186
- Effect.fail(
187
- ExecutionError.make({
188
- detail: "Starting the child failed",
189
- cause: Cause.fail(error),
190
- }),
191
- ),
192
- SchemaError: (error) =>
193
- Effect.fail(
194
- ExecutionError.make({
195
- detail: "Encoding child bootstrap failed",
196
- cause: Cause.fail(error),
197
- }),
198
- ),
199
- }),
200
- ),
201
- })
202
- return {
203
- driver,
204
- peer: (token: string) =>
205
- Effect.acquireRelease(
206
- Effect.gen(function* () {
207
- const peer = yield* Ref.get(peers).pipe(
208
- Effect.flatMap((all) => {
209
- const peer = HashMap.get(all, token)
210
- return peer._tag === "Some"
211
- ? Effect.succeed(peer.value)
212
- : Effect.fail(
213
- SessionStopped.make({
214
- detail: "The execution channel is no longer available",
215
- }),
216
- )
158
+ ),
159
+ })
160
+ const identity = yield* Processes.identity(child.pid)
161
+ yield* Ref.update(peers, HashMap.set(handle.token, peer))
162
+ yield* Effect.addFinalizer(() =>
163
+ Ref.update(peers, HashMap.remove(handle.token)),
164
+ )
165
+ return {
166
+ code: resolved.identity,
167
+ start: send({ kind: "start" }),
168
+ identity,
169
+ exited,
170
+ interrupt: send({ kind: "stop" }),
171
+ kill: child.kill({ killSignal: "SIGKILL" }).pipe(
172
+ Effect.mapError((error) =>
173
+ ExecutionError.make({
174
+ detail: "Killing the child failed",
175
+ cause: Cause.fail(error),
217
176
  }),
218
- )
219
- const occupied = yield* Ref.modify(
220
- claimed,
221
- (all) =>
222
- [HashSet.has(all, token), HashSet.add(all, token)] as const,
223
- )
224
- if (occupied)
225
- return yield* SessionConflict.make({
226
- detail: "The instance channel is already attached",
227
- })
228
- return peer
229
- }),
230
- () => Ref.update(claimed, HashSet.remove(token)),
231
- ),
232
- }
177
+ ),
178
+ ),
179
+ renew: (millis: number) => send({ kind: "renew", millis }),
180
+ }
181
+ },
182
+ Effect.provideContext(platform),
183
+ Effect.catchTags({
184
+ PlatformError: (error) =>
185
+ Effect.fail(
186
+ ExecutionError.make({
187
+ detail: "Starting the child failed",
188
+ cause: Cause.fail(error),
189
+ }),
190
+ ),
191
+ SchemaError: (error) =>
192
+ Effect.fail(
193
+ ExecutionError.make({
194
+ detail: "Encoding child bootstrap failed",
195
+ cause: Cause.fail(error),
196
+ }),
197
+ ),
198
+ }),
199
+ ),
233
200
  })
201
+ return {
202
+ driver,
203
+ peer: (token: string) =>
204
+ Effect.acquireRelease(
205
+ Effect.gen(function* () {
206
+ const peer = yield* Ref.get(peers).pipe(
207
+ Effect.flatMap((all) => {
208
+ const peer = HashMap.get(all, token)
209
+ return peer._tag === "Some"
210
+ ? Effect.succeed(peer.value)
211
+ : Effect.fail(
212
+ SessionStopped.make({
213
+ detail: "The execution channel is no longer available",
214
+ }),
215
+ )
216
+ }),
217
+ )
218
+ const occupied = yield* Ref.modify(
219
+ claimed,
220
+ (all) =>
221
+ [HashSet.has(all, token), HashSet.add(all, token)] as const,
222
+ )
223
+ if (occupied)
224
+ return yield* SessionConflict.make({
225
+ detail: "The instance channel is already attached",
226
+ })
227
+ return peer
228
+ }),
229
+ () => Ref.update(claimed, HashSet.remove(token)),
230
+ ),
231
+ }
232
+ })
234
233
 
235
234
  export const layer = (root: string) =>
236
235
  Layer.effectContext(
@@ -27,16 +27,15 @@ export const maxPayload = 1024 * 1024
27
27
  const invalid = (detail: string) => FrameError.make({ detail })
28
28
 
29
29
  /** Version 2 carries only channel and length; request admission belongs to port. */
30
- export const encode = (frame: Frame) =>
31
- Effect.gen(function* () {
32
- if (frame.payload.byteLength > maxPayload)
33
- return yield* invalid("Runner frame exceeds the payload limit")
34
- const bytes = new Uint8Array(headerSize + frame.payload.byteLength)
35
- bytes.set([2, frame.channel, 0, 0])
36
- new DataView(bytes.buffer).setUint32(4, frame.payload.byteLength)
37
- bytes.set(frame.payload, headerSize)
38
- return bytes
39
- })
30
+ export const encode = Effect.fnUntraced(function* (frame: Frame) {
31
+ if (frame.payload.byteLength > maxPayload)
32
+ return yield* invalid("Runner frame exceeds the payload limit")
33
+ const bytes = new Uint8Array(headerSize + frame.payload.byteLength)
34
+ bytes.set([2, frame.channel, 0, 0])
35
+ new DataView(bytes.buffer).setUint32(4, frame.payload.byteLength)
36
+ bytes.set(frame.payload, headerSize)
37
+ return bytes
38
+ })
40
39
 
41
40
  /** Check a message boundary; the decoder still validates the frame's contents. */
42
41
  export const isCompleteFrame = (bytes: Uint8Array): boolean =>
@@ -41,33 +41,32 @@ export const connect = Effect.fnUntraced(function* (
41
41
  resources: yield* Effect.scope,
42
42
  transport: { ...peer, failure: failures.await, report },
43
43
  diagnostics: Fiber.await(exited).pipe(Effect.andThen(failures.cause)),
44
- terminate: (graceful: boolean) =>
45
- Effect.gen(function* () {
46
- // Stop observation before intentionally ending stdin or killing the process.
47
- yield* failures.close
48
- if (!graceful) return
49
- // If this fails, the execution scope forces termination and waits for exit.
50
- const status = yield* peer.end.pipe(
51
- // Peer EOF can interrupt the writer as its output ends. Still confirm the unit's exit.
52
- Effect.catchCauseIf(Cause.hasInterruptsOnly, () => Effect.void),
53
- Effect.andThen(Fiber.join(exited)),
54
- Effect.interruptible,
55
- Effect.timeoutOrElse({
56
- duration: stdinExitTimeout,
57
- orElse: () =>
58
- LocalTerminationTimeout.make({
59
- ...Option.match(execution.pid, {
60
- onNone: () => ({}),
61
- onSome: (pid) => ({ pid }),
62
- }),
63
- ...(execution.threadId === undefined
64
- ? {}
65
- : { threadId: execution.threadId }),
44
+ terminate: Effect.fnUntraced(function* (graceful: boolean) {
45
+ // Stop observation before intentionally ending stdin or killing the process.
46
+ yield* failures.close
47
+ if (!graceful) return
48
+ // If this fails, the execution scope forces termination and waits for exit.
49
+ const status = yield* peer.end.pipe(
50
+ // Peer EOF can interrupt the writer as its output ends. Still confirm the unit's exit.
51
+ Effect.catchCauseIf(Cause.hasInterruptsOnly, () => Effect.void),
52
+ Effect.andThen(Fiber.join(exited)),
53
+ Effect.interruptible,
54
+ Effect.timeoutOrElse({
55
+ duration: stdinExitTimeout,
56
+ orElse: () =>
57
+ LocalTerminationTimeout.make({
58
+ ...Option.match(execution.pid, {
59
+ onNone: () => ({}),
60
+ onSome: (pid) => ({ pid }),
66
61
  }),
67
- }),
68
- )
69
- if (status.exitCode !== 0 || status.statusCause.reasons.length > 0)
70
- return yield* status
71
- }),
62
+ ...(execution.threadId === undefined
63
+ ? {}
64
+ : { threadId: execution.threadId }),
65
+ }),
66
+ }),
67
+ )
68
+ if (status.exitCode !== 0 || status.statusCause.reasons.length > 0)
69
+ return yield* status
70
+ }),
72
71
  }
73
72
  })
@@ -2,28 +2,26 @@ import { Cause, Effect, FileSystem, Path } from "effect"
2
2
  import { StateDirectoryLocked } from "./model"
3
3
 
4
4
  /** SQLite owns the local directory lock for the enclosing scope, including failed startup. */
5
- export const acquire = (directory: string) =>
6
- Effect.gen(function* () {
7
- const fs = yield* FileSystem.FileSystem
8
- const path = yield* Path.Path
9
- yield* fs
10
- .makeDirectory(directory, { recursive: true })
11
- .pipe(Effect.uninterruptible)
12
- const { DatabaseSync } = yield* Effect.promise(
13
- () => import("node:sqlite"),
14
- ).pipe(Effect.uninterruptible)
15
- const locked = (cause: unknown) =>
16
- StateDirectoryLocked.make({
17
- detail:
18
- "The runner state directory is already owned or cannot be locked",
19
- cause: Cause.die(cause),
20
- })
21
- const db = yield* Effect.acquireRelease(
22
- Effect.try({
23
- try: () => new DatabaseSync(path.join(directory, "owner.sqlite")),
24
- catch: locked,
25
- }),
26
- (db) => Effect.sync(() => db.close()),
27
- )
28
- yield* Effect.try({ try: () => db.exec("BEGIN EXCLUSIVE"), catch: locked })
29
- })
5
+ export const acquire = Effect.fnUntraced(function* (directory: string) {
6
+ const fs = yield* FileSystem.FileSystem
7
+ const path = yield* Path.Path
8
+ yield* fs
9
+ .makeDirectory(directory, { recursive: true })
10
+ .pipe(Effect.uninterruptible)
11
+ const { DatabaseSync } = yield* Effect.promise(
12
+ () => import("node:sqlite"),
13
+ ).pipe(Effect.uninterruptible)
14
+ const locked = (cause: unknown) =>
15
+ StateDirectoryLocked.make({
16
+ detail: "The runner state directory is already owned or cannot be locked",
17
+ cause: Cause.die(cause),
18
+ })
19
+ const db = yield* Effect.acquireRelease(
20
+ Effect.try({
21
+ try: () => new DatabaseSync(path.join(directory, "owner.sqlite")),
22
+ catch: locked,
23
+ }),
24
+ (db) => Effect.sync(() => db.close()),
25
+ )
26
+ yield* Effect.try({ try: () => db.exec("BEGIN EXCLUSIVE"), catch: locked })
27
+ })
@@ -44,39 +44,38 @@ export const digest = Effect.fnUntraced(function* (directory: string) {
44
44
  const hash = yield* Effect.sync(() =>
45
45
  createHash("sha256").update("graft-package-sha256-v1\0"),
46
46
  )
47
- const visit = (
47
+ const visit = Effect.fnUntraced(function* (
48
48
  relative: string,
49
- ): Effect.Effect<void, PackageError | PlatformError.PlatformError> =>
50
- Effect.gen(function* () {
51
- const filename = path.join(directory, relative)
52
- const info = yield* fs.stat(filename)
53
- if (info.type !== "File" && info.type !== "Directory")
54
- return yield* PackageError.make({
55
- detail: `Unsupported package entry: ${relative}`,
56
- })
57
- const name = new TextEncoder().encode(relative)
58
- yield* Effect.sync(() => {
59
- hash.update(
60
- `${info.type}:${name.byteLength}:${info.type === "File" ? info.size : 0}:`,
61
- )
62
- hash.update(name)
49
+ ): Effect.fn.Return<void, PackageError | PlatformError.PlatformError> {
50
+ const filename = path.join(directory, relative)
51
+ const info = yield* fs.stat(filename)
52
+ if (info.type !== "File" && info.type !== "Directory")
53
+ return yield* PackageError.make({
54
+ detail: `Unsupported package entry: ${relative}`,
63
55
  })
64
- if (info.type === "File") {
65
- yield* fs.stream(filename).pipe(
66
- Stream.runForEach((bytes) =>
67
- Effect.sync(() => {
68
- hash.update(bytes)
69
- }),
70
- ),
71
- )
72
- } else {
73
- const entries = yield* fs.readDirectory(filename)
74
- for (const entry of Array.sort(entries, Order.String)) {
75
- if (entry === "node_modules") continue
76
- yield* visit(relative === "" ? entry : `${relative}/${entry}`)
77
- }
78
- }
56
+ const name = new TextEncoder().encode(relative)
57
+ yield* Effect.sync(() => {
58
+ hash.update(
59
+ `${info.type}:${name.byteLength}:${info.type === "File" ? info.size : 0}:`,
60
+ )
61
+ hash.update(name)
79
62
  })
63
+ if (info.type === "File") {
64
+ yield* fs.stream(filename).pipe(
65
+ Stream.runForEach((bytes) =>
66
+ Effect.sync(() => {
67
+ hash.update(bytes)
68
+ }),
69
+ ),
70
+ )
71
+ } else {
72
+ const entries = yield* fs.readDirectory(filename)
73
+ for (const entry of Array.sort(entries, Order.String)) {
74
+ if (entry === "node_modules") continue
75
+ yield* visit(relative === "" ? entry : `${relative}/${entry}`)
76
+ }
77
+ }
78
+ })
80
79
  yield* visit("")
81
80
  return yield* Effect.sync(() => hash.digest("hex"))
82
81
  })
@@ -136,43 +135,41 @@ export const resolve = Effect.fnUntraced(
136
135
  }),
137
136
  )
138
137
 
139
- export const resolveEntry = (installation: string, specifier: string) =>
140
- Effect.gen(function* () {
141
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
142
- return yield* Effect.scoped(
143
- Effect.gen(function* () {
144
- const child = yield* spawner.spawn(
145
- ChildProcess.make(
146
- process.execPath,
147
- [
148
- "--input-type=module",
149
- "--eval",
150
- "process.stdout.write(import.meta.resolve(process.argv[1]))",
151
- "--",
152
- specifier,
153
- ],
154
- {
155
- cwd: installation,
156
- env: {},
157
- stdin: "ignore",
158
- stdout: "pipe",
159
- stderr: "pipe",
160
- },
161
- ),
162
- )
163
- const [code, stdout, stderr] = yield* Effect.all(
164
- [
165
- child.exitCode,
166
- child.stdout.pipe(Stream.decodeText(), Stream.mkString),
167
- child.stderr.pipe(Stream.decodeText(), Stream.mkString),
168
- ],
169
- { concurrency: "unbounded" },
170
- )
171
- if (code !== 0)
172
- return yield* PackageError.make({
173
- detail: `Package resolution failed: ${stderr}`,
174
- })
175
- return stdout
176
- }),
177
- )
178
- })
138
+ export const resolveEntry = Effect.fnUntraced(function* (
139
+ installation: string,
140
+ specifier: string,
141
+ ) {
142
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
143
+ const child = yield* spawner.spawn(
144
+ ChildProcess.make(
145
+ process.execPath,
146
+ [
147
+ "--input-type=module",
148
+ "--eval",
149
+ "process.stdout.write(import.meta.resolve(process.argv[1]))",
150
+ "--",
151
+ specifier,
152
+ ],
153
+ {
154
+ cwd: installation,
155
+ env: {},
156
+ stdin: "ignore",
157
+ stdout: "pipe",
158
+ stderr: "pipe",
159
+ },
160
+ ),
161
+ )
162
+ const [code, stdout, stderr] = yield* Effect.all(
163
+ [
164
+ child.exitCode,
165
+ child.stdout.pipe(Stream.decodeText(), Stream.mkString),
166
+ child.stderr.pipe(Stream.decodeText(), Stream.mkString),
167
+ ],
168
+ { concurrency: "unbounded" },
169
+ )
170
+ if (code !== 0)
171
+ return yield* PackageError.make({
172
+ detail: `Package resolution failed: ${stderr}`,
173
+ })
174
+ return stdout
175
+ }, Effect.scoped)