effect-cf 0.47.0 → 0.49.0

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # effect-cf
2
2
 
3
+ ## 0.49.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#194](https://github.com/danieljvdm/effect-cf/pull/194) [`adf4e33`](https://github.com/danieljvdm/effect-cf/commit/adf4e339aaaef21ef2abb87595c07406a3e64ac3) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Reacquire sandbox RPC clients after a failed native channel. Retained sandbox instances now use a fresh channel for subsequent operations while preserving invocation-local channel reuse. Failed operations are still returned to the caller without automatic replay.
8
+
9
+ `SandboxInstanceClient.rawUnsafe` now includes `SandboxOperationError` because acquiring a fresh native client can fail.
10
+
11
+ ## 0.48.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [#192](https://github.com/danieljvdm/effect-cf/pull/192) [`761c4f4`](https://github.com/danieljvdm/effect-cf/commit/761c4f411e11ae0491f61c6fe7c7697faf68e7f0) Thanks [@danieljvdm](https://github.com/danieljvdm)! - Report absent Artifacts commits as `ArtifactsOperationError` with `NOT_FOUND` instead of succeeding with `null`. Correct the raw `ArtifactsRepoBinding.readCommit` return type to include `null`; the Effect client continues to return a commit or a typed failure.
16
+
3
17
  ## 0.47.0
4
18
 
5
19
  ### Minor Changes
@@ -145,7 +145,7 @@ interface ArtifactsRepoBinding extends Partial<ArtifactsRepoInfo> {
145
145
  readonly revokeToken: (tokenOrId: string) => Promise<boolean>;
146
146
  readonly fork: (name: RepoName, options?: ArtifactsForkOptions) => Promise<ArtifactsCreateRepoResult>;
147
147
  readonly log: (options?: ArtifactsLogOptions) => Promise<ArtifactsLogResult>;
148
- readonly readCommit: (hash: string) => Promise<ArtifactsCommit>;
148
+ readonly readCommit: (hash: string) => Promise<ArtifactsCommit | null>;
149
149
  readonly readTree: (hash: string) => Promise<ArtifactsTree>;
150
150
  }
151
151
  interface ArtifactsBinding {
@@ -105,7 +105,17 @@ const wrapRepo = (binding, repo, info) => ({
105
105
  revokeToken: Effect.fn("Artifacts.revokeToken", spanOptions(binding, "revokeToken"))((tokenOrId) => tryArtifactsPromise(binding, "revokeToken", () => repo.revokeToken(tokenOrId))),
106
106
  fork: Effect.fn("Artifacts.fork", spanOptions(binding, "fork"))((name, options) => tryArtifactsPromise(binding, "fork", () => repo.fork(name, options))),
107
107
  log: Effect.fn("Artifacts.log", spanOptions(binding, "log"))((options) => tryArtifactsPromise(binding, "log", () => repo.log(options))),
108
- readCommit: Effect.fn("Artifacts.readCommit", spanOptions(binding, "readCommit"))((hash) => tryArtifactsPromise(binding, "readCommit", () => repo.readCommit(hash))),
108
+ readCommit: Effect.fn("Artifacts.readCommit", spanOptions(binding, "readCommit"))(function* (hash) {
109
+ const commit = yield* tryArtifactsPromise(binding, "readCommit", () => repo.readCommit(hash));
110
+ if (commit === null) return yield* new ArtifactsOperationError({
111
+ binding,
112
+ operation: "readCommit",
113
+ code: "NOT_FOUND",
114
+ numericCode: artifactsErrorNumericCodes.NOT_FOUND,
115
+ cause: /* @__PURE__ */ new Error("Commit not found")
116
+ });
117
+ return commit;
118
+ }),
109
119
  readTree: Effect.fn("Artifacts.readTree", spanOptions(binding, "readTree"))((hash) => tryArtifactsPromise(binding, "readTree", () => repo.readTree(hash))),
110
120
  raw: repo
111
121
  });
@@ -115,7 +115,7 @@ interface SandboxTunnelsClient {
115
115
  readonly destroy: (portOrInfo: number | TunnelInfo$1) => Effect.Effect<void, SandboxOperationError>;
116
116
  }
117
117
  interface SandboxInstanceClient {
118
- readonly rawUnsafe: Effect.Effect<SandboxClientResource>;
118
+ readonly rawUnsafe: Effect.Effect<SandboxClientResource, SandboxOperationError>;
119
119
  readonly exec: (command: SandboxCommand$1, options?: ExecOptions$1) => Effect.Effect<SandboxProcessHandle, SandboxOperationError>;
120
120
  readonly getProcess: (id: string) => Effect.Effect<Option.Option<SandboxProcessHandle>, SandboxOperationError>;
121
121
  readonly listProcesses: Effect.Effect<Array<ProcessStatus$1>, SandboxOperationError>;
package/dist/Sandbox.mjs CHANGED
@@ -146,147 +146,146 @@ const makeTerminalHandle = (definition, instance, terminal, target) => {
146
146
  })
147
147
  };
148
148
  };
149
- const fromSandboxClient = (client, definition, instance) => makeSandboxClient(client, definition, instance, client);
150
- const makeSandboxClient = (client, definition, instance, target) => {
151
- const attempt = (operation, evaluate) => trySandboxPromise(definition, instance, operation, evaluate, target);
152
- const stream = (operation, acquire) => sandboxStream(definition, instance, operation, acquire, target);
149
+ const fromSandboxClient = (client, definition, instance) => makeSandboxClient(Effect.succeed({
150
+ client,
151
+ target: client
152
+ }), definition, instance);
153
+ const makeSandboxClient = (resolve, definition, instance) => {
154
+ const attempt = (operation, evaluate) => Effect.flatMap(resolve, ({ client, target }) => trySandboxPromise(definition, instance, operation, (signal) => evaluate(client, signal, target), target));
155
+ const stream = (operation, acquire) => Stream.unwrap(Effect.map(resolve, ({ client, target }) => sandboxStream(definition, instance, operation, (signal) => acquire(client, signal), target)));
153
156
  const spanOptions = (operation) => ({ attributes: {
154
157
  binding: definition.binding,
155
158
  instance,
156
159
  operation
157
160
  } });
158
161
  return {
159
- rawUnsafe: Effect.succeed(client),
162
+ rawUnsafe: Effect.map(resolve, ({ client }) => client),
160
163
  exec: Effect.fn("Sandbox.exec", spanOptions("exec"))(function* (command, options) {
161
- const process = yield* attempt("exec", () => client.exec(command, options));
162
- return makeProcessHandle(definition, instance, process, target);
164
+ return yield* attempt("exec", (client, _signal, target) => client.exec(command, options).then((process) => makeProcessHandle(definition, instance, process, target)));
163
165
  }),
164
166
  getProcess: Effect.fn("Sandbox.getProcess", spanOptions("getProcess"))(function* (id) {
165
- const process = yield* attempt("getProcess", () => client.getProcess(id));
166
- return Option.map(Option.fromNullishOr(process), (found) => makeProcessHandle(definition, instance, found, target));
167
+ return yield* attempt("getProcess", (client, _signal, target) => client.getProcess(id).then((process) => Option.map(Option.fromNullishOr(process), (found) => makeProcessHandle(definition, instance, found, target))));
167
168
  }),
168
- listProcesses: attempt("listProcesses", () => client.listProcesses()).pipe(Effect.withSpan("Sandbox.listProcesses", spanOptions("listProcesses"))),
169
+ listProcesses: attempt("listProcesses", (client) => client.listProcesses()).pipe(Effect.withSpan("Sandbox.listProcesses", spanOptions("listProcesses"))),
169
170
  writeFile: Effect.fn("Sandbox.writeFile", spanOptions("writeFile"))(function* (path, content, options) {
170
171
  yield* Effect.annotateCurrentSpan("path", path);
171
- return yield* attempt("writeFile", () => client.writeFile(path, content, options));
172
+ return yield* attempt("writeFile", (client) => client.writeFile(path, content, options));
172
173
  }),
173
174
  readFile: Effect.fn("Sandbox.readFile", spanOptions("readFile"))(function* (path, options) {
174
175
  yield* Effect.annotateCurrentSpan("path", path);
175
- return yield* attempt("readFile", () => client.readFile(path, options));
176
+ return yield* attempt("readFile", (client) => client.readFile(path, options));
176
177
  }),
177
- readFileStream: (path) => stream("readFileStream", () => client.readFileStream(path)),
178
+ readFileStream: (path) => stream("readFileStream", (client) => client.readFileStream(path)),
178
179
  mkdir: Effect.fn("Sandbox.mkdir", spanOptions("mkdir"))(function* (path, options) {
179
180
  yield* Effect.annotateCurrentSpan("path", path);
180
- return yield* attempt("mkdir", () => client.mkdir(path, options));
181
+ return yield* attempt("mkdir", (client) => client.mkdir(path, options));
181
182
  }),
182
183
  deleteFile: Effect.fn("Sandbox.deleteFile", spanOptions("deleteFile"))(function* (path) {
183
184
  yield* Effect.annotateCurrentSpan("path", path);
184
- return yield* attempt("deleteFile", () => client.deleteFile(path));
185
+ return yield* attempt("deleteFile", (client) => client.deleteFile(path));
185
186
  }),
186
187
  renameFile: Effect.fn("Sandbox.renameFile", spanOptions("renameFile"))(function* (oldPath, newPath) {
187
188
  yield* Effect.annotateCurrentSpan({
188
189
  oldPath,
189
190
  newPath
190
191
  });
191
- return yield* attempt("renameFile", () => client.renameFile(oldPath, newPath));
192
+ return yield* attempt("renameFile", (client) => client.renameFile(oldPath, newPath));
192
193
  }),
193
194
  moveFile: Effect.fn("Sandbox.moveFile", spanOptions("moveFile"))(function* (sourcePath, destinationPath) {
194
195
  yield* Effect.annotateCurrentSpan({
195
196
  sourcePath,
196
197
  destinationPath
197
198
  });
198
- return yield* attempt("moveFile", () => client.moveFile(sourcePath, destinationPath));
199
+ return yield* attempt("moveFile", (client) => client.moveFile(sourcePath, destinationPath));
199
200
  }),
200
201
  listFiles: Effect.fn("Sandbox.listFiles", spanOptions("listFiles"))(function* (path, options) {
201
202
  yield* Effect.annotateCurrentSpan("path", path);
202
- return yield* attempt("listFiles", () => client.listFiles(path, options));
203
+ return yield* attempt("listFiles", (client) => client.listFiles(path, options));
203
204
  }),
204
205
  exists: Effect.fn("Sandbox.exists", spanOptions("exists"))(function* (path) {
205
206
  yield* Effect.annotateCurrentSpan("path", path);
206
- return yield* attempt("exists", () => client.exists(path));
207
+ return yield* attempt("exists", (client) => client.exists(path));
207
208
  }),
208
- watch: (path, options) => decodeSseEvents(stream("watch", () => client.watch(path, options))),
209
+ watch: (path, options) => decodeSseEvents(stream("watch", (client) => client.watch(path, options))),
209
210
  checkChanges: Effect.fn("Sandbox.checkChanges", spanOptions("checkChanges"))(function* (path, options) {
210
211
  yield* Effect.annotateCurrentSpan("path", path);
211
- return yield* attempt("checkChanges", () => client.checkChanges(path, options));
212
+ return yield* attempt("checkChanges", (client) => client.checkChanges(path, options));
212
213
  }),
213
214
  setEnvVars: Effect.fn("Sandbox.setEnvVars", spanOptions("setEnvVars"))(function* (envVars) {
214
- return yield* attempt("setEnvVars", () => client.setEnvVars(envVars));
215
+ return yield* attempt("setEnvVars", (client) => client.setEnvVars(envVars));
215
216
  }),
216
217
  exposePort: Effect.fn("Sandbox.exposePort", spanOptions("exposePort"))(function* (port, options) {
217
218
  yield* Effect.annotateCurrentSpan("port", port);
218
- return yield* attempt("exposePort", () => client.exposePort(port, options));
219
+ return yield* attempt("exposePort", (client) => client.exposePort(port, options));
219
220
  }),
220
221
  unexposePort: Effect.fn("Sandbox.unexposePort", spanOptions("unexposePort"))(function* (port) {
221
222
  yield* Effect.annotateCurrentSpan("port", port);
222
- return yield* attempt("unexposePort", () => client.unexposePort(port));
223
+ return yield* attempt("unexposePort", (client) => client.unexposePort(port));
223
224
  }),
224
225
  getExposedPorts: Effect.fn("Sandbox.getExposedPorts", spanOptions("getExposedPorts"))(function* (hostname) {
225
- return yield* attempt("getExposedPorts", () => client.getExposedPorts(hostname));
226
+ return yield* attempt("getExposedPorts", (client) => client.getExposedPorts(hostname));
226
227
  }),
227
228
  isPortExposed: Effect.fn("Sandbox.isPortExposed", spanOptions("isPortExposed"))(function* (port) {
228
229
  yield* Effect.annotateCurrentSpan("port", port);
229
- return yield* attempt("isPortExposed", () => client.isPortExposed(port));
230
+ return yield* attempt("isPortExposed", (client) => client.isPortExposed(port));
230
231
  }),
231
232
  validatePortToken: Effect.fn("Sandbox.validatePortToken", spanOptions("validatePortToken"))(function* (port, token) {
232
233
  yield* Effect.annotateCurrentSpan("port", port);
233
- return yield* attempt("validatePortToken", () => client.validatePortToken(port, token));
234
+ return yield* attempt("validatePortToken", (client) => client.validatePortToken(port, token));
234
235
  }),
235
236
  containerFetch: Effect.fn("Sandbox.containerFetch", spanOptions("containerFetch"))(function* (requestOrUrl, port) {
236
- return yield* attempt("containerFetch", () => client.containerFetch(requestOrUrl, port));
237
+ return yield* attempt("containerFetch", (client) => client.containerFetch(requestOrUrl, port));
237
238
  }),
238
239
  wsConnect: Effect.fn("Sandbox.wsConnect", spanOptions("wsConnect"))(function* (request, port) {
239
240
  yield* Effect.annotateCurrentSpan("port", port);
240
- return yield* attempt("wsConnect", () => client.wsConnect(request, port));
241
+ return yield* attempt("wsConnect", (client) => client.wsConnect(request, port));
241
242
  }),
242
243
  mountBucket: Effect.fn("Sandbox.mountBucket", spanOptions("mountBucket"))(function* (bucket, mountPath, options) {
243
244
  yield* Effect.annotateCurrentSpan({
244
245
  bucket,
245
246
  mountPath
246
247
  });
247
- return yield* attempt("mountBucket", () => client.mountBucket(bucket, mountPath, options));
248
+ return yield* attempt("mountBucket", (client) => client.mountBucket(bucket, mountPath, options));
248
249
  }),
249
250
  unmountBucket: Effect.fn("Sandbox.unmountBucket", spanOptions("unmountBucket"))(function* (mountPath) {
250
251
  yield* Effect.annotateCurrentSpan("mountPath", mountPath);
251
- return yield* attempt("unmountBucket", () => client.unmountBucket(mountPath));
252
+ return yield* attempt("unmountBucket", (client) => client.unmountBucket(mountPath));
252
253
  }),
253
254
  createBackup: Effect.fn("Sandbox.createBackup", spanOptions("createBackup"))(function* (options) {
254
255
  yield* Effect.annotateCurrentSpan("dir", options.dir);
255
- return yield* attempt("createBackup", () => client.createBackup(options));
256
+ return yield* attempt("createBackup", (client) => client.createBackup(options));
256
257
  }),
257
258
  restoreBackup: Effect.fn("Sandbox.restoreBackup", spanOptions("restoreBackup"))(function* (backup) {
258
259
  yield* Effect.annotateCurrentSpan({
259
260
  backupId: backup.id,
260
261
  dir: backup.dir
261
262
  });
262
- return yield* attempt("restoreBackup", () => client.restoreBackup(backup));
263
+ return yield* attempt("restoreBackup", (client) => client.restoreBackup(backup));
263
264
  }),
264
265
  createTerminal: Effect.fn("Sandbox.createTerminal", spanOptions("createTerminal"))(function* (options) {
265
- const terminal = yield* attempt("createTerminal", () => client.createTerminal(options));
266
- return makeTerminalHandle(definition, instance, terminal, target);
266
+ return yield* attempt("createTerminal", (client, _signal, target) => client.createTerminal(options).then((terminal) => makeTerminalHandle(definition, instance, terminal, target)));
267
267
  }),
268
268
  getTerminal: Effect.fn("Sandbox.getTerminal", spanOptions("getTerminal"))(function* (id) {
269
- const terminal = yield* attempt("getTerminal", () => client.getTerminal(id));
270
- return Option.map(Option.fromNullishOr(terminal), (found) => makeTerminalHandle(definition, instance, found, target));
269
+ return yield* attempt("getTerminal", (client, _signal, target) => client.getTerminal(id).then((terminal) => Option.map(Option.fromNullishOr(terminal), (found) => makeTerminalHandle(definition, instance, found, target))));
271
270
  }),
272
- listTerminals: attempt("listTerminals", () => client.listTerminals()).pipe(Effect.map((terminals) => terminals.map((terminal) => makeTerminalHandle(definition, instance, terminal, target))), Effect.withSpan("Sandbox.listTerminals", spanOptions("listTerminals"))),
271
+ listTerminals: attempt("listTerminals", (client, _signal, target) => client.listTerminals().then((terminals) => terminals.map((terminal) => makeTerminalHandle(definition, instance, terminal, target)))).pipe(Effect.withSpan("Sandbox.listTerminals", spanOptions("listTerminals"))),
273
272
  tunnels: {
274
273
  get: Effect.fn("Sandbox.tunnels.get", spanOptions("tunnels.get"))(function* (port, options) {
275
274
  yield* Effect.annotateCurrentSpan("port", port);
276
- return yield* attempt("tunnels.get", () => client.tunnels.get(port, options));
275
+ return yield* attempt("tunnels.get", (client) => client.tunnels.get(port, options));
277
276
  }),
278
- list: attempt("tunnels.list", () => client.tunnels.list()).pipe(Effect.withSpan("Sandbox.tunnels.list", spanOptions("tunnels.list"))),
277
+ list: attempt("tunnels.list", (client) => client.tunnels.list()).pipe(Effect.withSpan("Sandbox.tunnels.list", spanOptions("tunnels.list"))),
279
278
  destroy: Effect.fn("Sandbox.tunnels.destroy", spanOptions("tunnels.destroy"))(function* (portOrInfo) {
280
- return yield* attempt("tunnels.destroy", () => client.tunnels.destroy(portOrInfo));
279
+ return yield* attempt("tunnels.destroy", (client) => client.tunnels.destroy(portOrInfo));
281
280
  })
282
281
  },
283
282
  start: Effect.fn("Sandbox.start", spanOptions("start"))(function* (options) {
284
- return yield* attempt("start", () => client.start(options));
283
+ return yield* attempt("start", (client) => client.start(options));
285
284
  }),
286
285
  stop: Effect.fn("Sandbox.stop", spanOptions("stop"))(function* (signal) {
287
- return yield* attempt("stop", () => client.stop(signal));
286
+ return yield* attempt("stop", (client) => client.stop(signal));
288
287
  }),
289
- destroy: attempt("destroy", () => client.destroy()).pipe(Effect.withSpan("Sandbox.destroy", spanOptions("destroy")))
288
+ destroy: attempt("destroy", (client) => client.destroy()).pipe(Effect.withSpan("Sandbox.destroy", spanOptions("destroy")))
290
289
  };
291
290
  };
292
291
  /**
@@ -315,30 +314,35 @@ const makeClient = (definition) => (namespace) => {
315
314
  } })(function* (name, options) {
316
315
  yield* Effect.annotateCurrentSpan("instance", name);
317
316
  const sandboxModule = yield* importSandboxModule(definition, name);
318
- const targets = yield* Effect.serviceOption(CurrentTargets);
319
- const scopedNamespace = yield* get(namespace, "sandbox-namespace", () => {
320
- const adapter = {
321
- idFromName: (name) => namespace.idFromName(name),
322
- get: (id, options) => Option.isSome(targets) ? targets.value.get(adapter, JSON.stringify([id.toString(), options]), () => namespace.get(id, options)) : namespace.get(id, options)
323
- };
324
- return adapter;
325
- }).pipe(Effect.mapError((error) => new SandboxOperationError({
326
- binding: definition.binding,
327
- instance: name,
328
- operation: "get",
329
- cause: error.cause
330
- })));
331
- const nativeNamespace = scopedNamespace;
332
- const client = yield* Effect.try({
333
- try: () => sandboxModule.getSandbox(nativeNamespace, name, options),
334
- catch: (cause) => new SandboxOperationError({
317
+ const resolve = Effect.gen(function* () {
318
+ const targets = yield* Effect.serviceOption(CurrentTargets);
319
+ const scopedNamespace = yield* get(namespace, "sandbox-namespace", () => {
320
+ const adapter = {
321
+ idFromName: (name) => namespace.idFromName(name),
322
+ get: (id, options) => Option.isSome(targets) ? targets.value.get(adapter, JSON.stringify([id.toString(), options]), () => namespace.get(id, options)) : namespace.get(id, options)
323
+ };
324
+ return adapter;
325
+ }).pipe(Effect.mapError((error) => new SandboxOperationError({
335
326
  binding: definition.binding,
336
327
  instance: name,
337
328
  operation: "get",
338
- cause
339
- })
329
+ cause: error.cause
330
+ })));
331
+ const nativeNamespace = scopedNamespace;
332
+ return {
333
+ client: yield* Effect.try({
334
+ try: () => sandboxModule.getSandbox(nativeNamespace, name, options),
335
+ catch: (cause) => new SandboxOperationError({
336
+ binding: definition.binding,
337
+ instance: name,
338
+ operation: "get",
339
+ cause
340
+ })
341
+ }),
342
+ target: scopedNamespace
343
+ };
340
344
  });
341
- return makeSandboxClient(client, definition, name, scopedNamespace);
345
+ return makeSandboxClient(resolve, definition, name);
342
346
  }),
343
347
  rawUnsafe: Effect.succeed(namespace)
344
348
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-cf",
3
- "version": "0.47.0",
3
+ "version": "0.49.0",
4
4
  "description": "Effect-native primitives for Cloudflare Workers and bindings.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/Artifacts.ts CHANGED
@@ -222,7 +222,7 @@ export interface ArtifactsRepoBinding extends Partial<ArtifactsRepoInfo> {
222
222
  options?: ArtifactsForkOptions,
223
223
  ) => Promise<ArtifactsCreateRepoResult>;
224
224
  readonly log: (options?: ArtifactsLogOptions) => Promise<ArtifactsLogResult>;
225
- readonly readCommit: (hash: string) => Promise<ArtifactsCommit>;
225
+ readonly readCommit: (hash: string) => Promise<ArtifactsCommit | null>;
226
226
  readonly readTree: (hash: string) => Promise<ArtifactsTree>;
227
227
  }
228
228
 
@@ -451,7 +451,20 @@ const wrapRepo = (
451
451
  readCommit: Effect.fn(
452
452
  "Artifacts.readCommit",
453
453
  spanOptions(binding, "readCommit"),
454
- )((hash: string) => tryArtifactsPromise(binding, "readCommit", () => repo.readCommit(hash))),
454
+ )(function* (hash: string) {
455
+ const commit = yield* tryArtifactsPromise(binding, "readCommit", () => repo.readCommit(hash));
456
+
457
+ if (commit === null)
458
+ return yield* new ArtifactsOperationError({
459
+ binding,
460
+ operation: "readCommit",
461
+ code: "NOT_FOUND",
462
+ numericCode: artifactsErrorNumericCodes.NOT_FOUND,
463
+ cause: new Error("Commit not found"),
464
+ });
465
+
466
+ return commit;
467
+ }),
455
468
  readTree: Effect.fn(
456
469
  "Artifacts.readTree",
457
470
  spanOptions(binding, "readTree"),
package/src/Sandbox.ts CHANGED
@@ -240,7 +240,7 @@ export interface SandboxTunnelsClient {
240
240
  }
241
241
 
242
242
  export interface SandboxInstanceClient {
243
- readonly rawUnsafe: Effect.Effect<SandboxClientResource>;
243
+ readonly rawUnsafe: Effect.Effect<SandboxClientResource, SandboxOperationError>;
244
244
  readonly exec: (
245
245
  command: SandboxCommand,
246
246
  options?: ExecOptions,
@@ -610,45 +610,77 @@ export const fromSandboxClient = (
610
610
  client: SandboxClientResource,
611
611
  definition: SandboxDefinition,
612
612
  instance: string,
613
- ): SandboxInstanceClient => makeSandboxClient(client, definition, instance, client);
613
+ ): SandboxInstanceClient =>
614
+ makeSandboxClient(Effect.succeed({ client, target: client }), definition, instance);
614
615
 
615
616
  const makeSandboxClient = (
616
- client: SandboxClientResource,
617
+ resolve: Effect.Effect<
618
+ {
619
+ readonly client: SandboxClientResource;
620
+ readonly target: SandboxClientResource | SandboxNamespaceResource;
621
+ },
622
+ SandboxOperationError
623
+ >,
617
624
  definition: SandboxDefinition,
618
625
  instance: string,
619
- target: SandboxClientResource | SandboxNamespaceResource,
620
626
  ): SandboxInstanceClient => {
621
- const attempt = <A>(operation: string, evaluate: (signal: AbortSignal) => PromiseLike<A>) =>
622
- trySandboxPromise(definition, instance, operation, evaluate, target);
627
+ const attempt = <A>(
628
+ operation: string,
629
+ evaluate: (
630
+ client: SandboxClientResource,
631
+ signal: AbortSignal,
632
+ target: SandboxClientResource | SandboxNamespaceResource,
633
+ ) => PromiseLike<A>,
634
+ ) =>
635
+ Effect.flatMap(resolve, ({ client, target }) =>
636
+ trySandboxPromise(
637
+ definition,
638
+ instance,
639
+ operation,
640
+ (signal) => evaluate(client, signal, target),
641
+ target,
642
+ ),
643
+ );
623
644
  const stream = <A>(
624
645
  operation: string,
625
- acquire: (signal: AbortSignal) => PromiseLike<ReadableStream<A>>,
626
- ) => sandboxStream(definition, instance, operation, acquire, target);
646
+ acquire: (client: SandboxClientResource, signal: AbortSignal) => PromiseLike<ReadableStream<A>>,
647
+ ) =>
648
+ Stream.unwrap(
649
+ Effect.map(resolve, ({ client, target }) =>
650
+ sandboxStream(definition, instance, operation, (signal) => acquire(client, signal), target),
651
+ ),
652
+ );
627
653
  const spanOptions = (operation: string) => ({
628
654
  attributes: { binding: definition.binding, instance, operation },
629
655
  });
630
656
 
631
657
  return {
632
- rawUnsafe: Effect.succeed(client),
658
+ rawUnsafe: Effect.map(resolve, ({ client }) => client),
633
659
  exec: Effect.fn(
634
660
  "Sandbox.exec",
635
661
  spanOptions("exec"),
636
662
  )(function* (command: SandboxCommand, options?: ExecOptions) {
637
- const process = yield* attempt("exec", () => client.exec(command, options));
638
-
639
- return makeProcessHandle(definition, instance, process, target);
663
+ return yield* attempt("exec", (client, _signal, target) =>
664
+ client
665
+ .exec(command, options)
666
+ .then((process) => makeProcessHandle(definition, instance, process, target)),
667
+ );
640
668
  }),
641
669
  getProcess: Effect.fn(
642
670
  "Sandbox.getProcess",
643
671
  spanOptions("getProcess"),
644
672
  )(function* (id: string) {
645
- const process = yield* attempt("getProcess", () => client.getProcess(id));
646
-
647
- return Option.map(Option.fromNullishOr(process), (found) =>
648
- makeProcessHandle(definition, instance, found, target),
673
+ return yield* attempt("getProcess", (client, _signal, target) =>
674
+ client
675
+ .getProcess(id)
676
+ .then((process) =>
677
+ Option.map(Option.fromNullishOr(process), (found) =>
678
+ makeProcessHandle(definition, instance, found, target),
679
+ ),
680
+ ),
649
681
  );
650
682
  }),
651
- listProcesses: attempt("listProcesses", () => client.listProcesses()).pipe(
683
+ listProcesses: attempt("listProcesses", (client) => client.listProcesses()).pipe(
652
684
  Effect.withSpan("Sandbox.listProcesses", spanOptions("listProcesses")),
653
685
  ),
654
686
  writeFile: Effect.fn(
@@ -661,7 +693,7 @@ const makeSandboxClient = (
661
693
  ) {
662
694
  yield* Effect.annotateCurrentSpan("path", path);
663
695
 
664
- return yield* attempt("writeFile", () => client.writeFile(path, content, options));
696
+ return yield* attempt("writeFile", (client) => client.writeFile(path, content, options));
665
697
  }),
666
698
  readFile: Effect.fn(
667
699
  "Sandbox.readFile",
@@ -669,16 +701,17 @@ const makeSandboxClient = (
669
701
  )(function* (path: string, options?: { readonly encoding?: ReadFileEncoding }) {
670
702
  yield* Effect.annotateCurrentSpan("path", path);
671
703
 
672
- return yield* attempt("readFile", () => client.readFile(path, options));
704
+ return yield* attempt("readFile", (client) => client.readFile(path, options));
673
705
  }),
674
- readFileStream: (path: string) => stream("readFileStream", () => client.readFileStream(path)),
706
+ readFileStream: (path: string) =>
707
+ stream("readFileStream", (client) => client.readFileStream(path)),
675
708
  mkdir: Effect.fn(
676
709
  "Sandbox.mkdir",
677
710
  spanOptions("mkdir"),
678
711
  )(function* (path: string, options?: { readonly recursive?: boolean }) {
679
712
  yield* Effect.annotateCurrentSpan("path", path);
680
713
 
681
- return yield* attempt("mkdir", () => client.mkdir(path, options));
714
+ return yield* attempt("mkdir", (client) => client.mkdir(path, options));
682
715
  }),
683
716
  deleteFile: Effect.fn(
684
717
  "Sandbox.deleteFile",
@@ -686,7 +719,7 @@ const makeSandboxClient = (
686
719
  )(function* (path: string) {
687
720
  yield* Effect.annotateCurrentSpan("path", path);
688
721
 
689
- return yield* attempt("deleteFile", () => client.deleteFile(path));
722
+ return yield* attempt("deleteFile", (client) => client.deleteFile(path));
690
723
  }),
691
724
  renameFile: Effect.fn(
692
725
  "Sandbox.renameFile",
@@ -694,7 +727,7 @@ const makeSandboxClient = (
694
727
  )(function* (oldPath: string, newPath: string) {
695
728
  yield* Effect.annotateCurrentSpan({ oldPath, newPath });
696
729
 
697
- return yield* attempt("renameFile", () => client.renameFile(oldPath, newPath));
730
+ return yield* attempt("renameFile", (client) => client.renameFile(oldPath, newPath));
698
731
  }),
699
732
  moveFile: Effect.fn(
700
733
  "Sandbox.moveFile",
@@ -702,7 +735,7 @@ const makeSandboxClient = (
702
735
  )(function* (sourcePath: string, destinationPath: string) {
703
736
  yield* Effect.annotateCurrentSpan({ sourcePath, destinationPath });
704
737
 
705
- return yield* attempt("moveFile", () => client.moveFile(sourcePath, destinationPath));
738
+ return yield* attempt("moveFile", (client) => client.moveFile(sourcePath, destinationPath));
706
739
  }),
707
740
  listFiles: Effect.fn(
708
741
  "Sandbox.listFiles",
@@ -710,7 +743,7 @@ const makeSandboxClient = (
710
743
  )(function* (path: string, options?: ListFilesOptions) {
711
744
  yield* Effect.annotateCurrentSpan("path", path);
712
745
 
713
- return yield* attempt("listFiles", () => client.listFiles(path, options));
746
+ return yield* attempt("listFiles", (client) => client.listFiles(path, options));
714
747
  }),
715
748
  exists: Effect.fn(
716
749
  "Sandbox.exists",
@@ -718,11 +751,11 @@ const makeSandboxClient = (
718
751
  )(function* (path: string) {
719
752
  yield* Effect.annotateCurrentSpan("path", path);
720
753
 
721
- return yield* attempt("exists", () => client.exists(path));
754
+ return yield* attempt("exists", (client) => client.exists(path));
722
755
  }),
723
756
  watch: (path: string, options?: WatchOptions) =>
724
757
  decodeSseEvents<FileWatchSSEEvent, SandboxOperationError, never>(
725
- stream("watch", () => client.watch(path, options)),
758
+ stream("watch", (client) => client.watch(path, options)),
726
759
  ),
727
760
  checkChanges: Effect.fn(
728
761
  "Sandbox.checkChanges",
@@ -730,13 +763,13 @@ const makeSandboxClient = (
730
763
  )(function* (path: string, options?: CheckChangesOptions) {
731
764
  yield* Effect.annotateCurrentSpan("path", path);
732
765
 
733
- return yield* attempt("checkChanges", () => client.checkChanges(path, options));
766
+ return yield* attempt("checkChanges", (client) => client.checkChanges(path, options));
734
767
  }),
735
768
  setEnvVars: Effect.fn(
736
769
  "Sandbox.setEnvVars",
737
770
  spanOptions("setEnvVars"),
738
771
  )(function* (envVars: Record<string, string | undefined>) {
739
- return yield* attempt("setEnvVars", () => client.setEnvVars(envVars));
772
+ return yield* attempt("setEnvVars", (client) => client.setEnvVars(envVars));
740
773
  }),
741
774
  exposePort: Effect.fn(
742
775
  "Sandbox.exposePort",
@@ -744,7 +777,7 @@ const makeSandboxClient = (
744
777
  )(function* (port: number, options: ExposePortOptions) {
745
778
  yield* Effect.annotateCurrentSpan("port", port);
746
779
 
747
- return yield* attempt("exposePort", () => client.exposePort(port, options));
780
+ return yield* attempt("exposePort", (client) => client.exposePort(port, options));
748
781
  }),
749
782
  unexposePort: Effect.fn(
750
783
  "Sandbox.unexposePort",
@@ -752,13 +785,13 @@ const makeSandboxClient = (
752
785
  )(function* (port: number) {
753
786
  yield* Effect.annotateCurrentSpan("port", port);
754
787
 
755
- return yield* attempt("unexposePort", () => client.unexposePort(port));
788
+ return yield* attempt("unexposePort", (client) => client.unexposePort(port));
756
789
  }),
757
790
  getExposedPorts: Effect.fn(
758
791
  "Sandbox.getExposedPorts",
759
792
  spanOptions("getExposedPorts"),
760
793
  )(function* (hostname: string) {
761
- return yield* attempt("getExposedPorts", () => client.getExposedPorts(hostname));
794
+ return yield* attempt("getExposedPorts", (client) => client.getExposedPorts(hostname));
762
795
  }),
763
796
  isPortExposed: Effect.fn(
764
797
  "Sandbox.isPortExposed",
@@ -766,7 +799,7 @@ const makeSandboxClient = (
766
799
  )(function* (port: number) {
767
800
  yield* Effect.annotateCurrentSpan("port", port);
768
801
 
769
- return yield* attempt("isPortExposed", () => client.isPortExposed(port));
802
+ return yield* attempt("isPortExposed", (client) => client.isPortExposed(port));
770
803
  }),
771
804
  validatePortToken: Effect.fn(
772
805
  "Sandbox.validatePortToken",
@@ -774,13 +807,15 @@ const makeSandboxClient = (
774
807
  )(function* (port: number, token: string) {
775
808
  yield* Effect.annotateCurrentSpan("port", port);
776
809
 
777
- return yield* attempt("validatePortToken", () => client.validatePortToken(port, token));
810
+ return yield* attempt("validatePortToken", (client) => client.validatePortToken(port, token));
778
811
  }),
779
812
  containerFetch: Effect.fn(
780
813
  "Sandbox.containerFetch",
781
814
  spanOptions("containerFetch"),
782
815
  )(function* (requestOrUrl: Request | string | URL, port?: number) {
783
- return yield* attempt("containerFetch", () => client.containerFetch(requestOrUrl, port));
816
+ return yield* attempt("containerFetch", (client) =>
817
+ client.containerFetch(requestOrUrl, port),
818
+ );
784
819
  }),
785
820
  wsConnect: Effect.fn(
786
821
  "Sandbox.wsConnect",
@@ -788,7 +823,7 @@ const makeSandboxClient = (
788
823
  )(function* (request: Request, port: number) {
789
824
  yield* Effect.annotateCurrentSpan("port", port);
790
825
 
791
- return yield* attempt("wsConnect", () => client.wsConnect(request, port));
826
+ return yield* attempt("wsConnect", (client) => client.wsConnect(request, port));
792
827
  }),
793
828
  mountBucket: Effect.fn(
794
829
  "Sandbox.mountBucket",
@@ -796,7 +831,9 @@ const makeSandboxClient = (
796
831
  )(function* (bucket: string, mountPath: string, options: MountBucketOptions) {
797
832
  yield* Effect.annotateCurrentSpan({ bucket, mountPath });
798
833
 
799
- return yield* attempt("mountBucket", () => client.mountBucket(bucket, mountPath, options));
834
+ return yield* attempt("mountBucket", (client) =>
835
+ client.mountBucket(bucket, mountPath, options),
836
+ );
800
837
  }),
801
838
  unmountBucket: Effect.fn(
802
839
  "Sandbox.unmountBucket",
@@ -804,7 +841,7 @@ const makeSandboxClient = (
804
841
  )(function* (mountPath: string) {
805
842
  yield* Effect.annotateCurrentSpan("mountPath", mountPath);
806
843
 
807
- return yield* attempt("unmountBucket", () => client.unmountBucket(mountPath));
844
+ return yield* attempt("unmountBucket", (client) => client.unmountBucket(mountPath));
808
845
  }),
809
846
  createBackup: Effect.fn(
810
847
  "Sandbox.createBackup",
@@ -812,7 +849,7 @@ const makeSandboxClient = (
812
849
  )(function* (options: BackupOptions) {
813
850
  yield* Effect.annotateCurrentSpan("dir", options.dir);
814
851
 
815
- return yield* attempt("createBackup", () => client.createBackup(options));
852
+ return yield* attempt("createBackup", (client) => client.createBackup(options));
816
853
  }),
817
854
  restoreBackup: Effect.fn(
818
855
  "Sandbox.restoreBackup",
@@ -820,32 +857,39 @@ const makeSandboxClient = (
820
857
  )(function* (backup: DirectoryBackup) {
821
858
  yield* Effect.annotateCurrentSpan({ backupId: backup.id, dir: backup.dir });
822
859
 
823
- return yield* attempt("restoreBackup", () => client.restoreBackup(backup));
860
+ return yield* attempt("restoreBackup", (client) => client.restoreBackup(backup));
824
861
  }),
825
862
  createTerminal: Effect.fn(
826
863
  "Sandbox.createTerminal",
827
864
  spanOptions("createTerminal"),
828
865
  )(function* (options: CreateTerminalOptions) {
829
- const terminal = yield* attempt("createTerminal", () => client.createTerminal(options));
830
-
831
- return makeTerminalHandle(definition, instance, terminal, target);
866
+ return yield* attempt("createTerminal", (client, _signal, target) =>
867
+ client
868
+ .createTerminal(options)
869
+ .then((terminal) => makeTerminalHandle(definition, instance, terminal, target)),
870
+ );
832
871
  }),
833
872
  getTerminal: Effect.fn(
834
873
  "Sandbox.getTerminal",
835
874
  spanOptions("getTerminal"),
836
875
  )(function* (id: string) {
837
- const terminal = yield* attempt("getTerminal", () => client.getTerminal(id));
838
-
839
- return Option.map(Option.fromNullishOr(terminal), (found) =>
840
- makeTerminalHandle(definition, instance, found, target),
876
+ return yield* attempt("getTerminal", (client, _signal, target) =>
877
+ client
878
+ .getTerminal(id)
879
+ .then((terminal) =>
880
+ Option.map(Option.fromNullishOr(terminal), (found) =>
881
+ makeTerminalHandle(definition, instance, found, target),
882
+ ),
883
+ ),
841
884
  );
842
885
  }),
843
- listTerminals: attempt("listTerminals", () => client.listTerminals()).pipe(
844
- Effect.map((terminals) =>
845
- terminals.map((terminal) => makeTerminalHandle(definition, instance, terminal, target)),
846
- ),
847
- Effect.withSpan("Sandbox.listTerminals", spanOptions("listTerminals")),
848
- ),
886
+ listTerminals: attempt("listTerminals", (client, _signal, target) =>
887
+ client
888
+ .listTerminals()
889
+ .then((terminals) =>
890
+ terminals.map((terminal) => makeTerminalHandle(definition, instance, terminal, target)),
891
+ ),
892
+ ).pipe(Effect.withSpan("Sandbox.listTerminals", spanOptions("listTerminals"))),
849
893
  tunnels: {
850
894
  get: Effect.fn(
851
895
  "Sandbox.tunnels.get",
@@ -853,31 +897,31 @@ const makeSandboxClient = (
853
897
  )(function* (port: number, options?: TunnelOptions) {
854
898
  yield* Effect.annotateCurrentSpan("port", port);
855
899
 
856
- return yield* attempt("tunnels.get", () => client.tunnels.get(port, options));
900
+ return yield* attempt("tunnels.get", (client) => client.tunnels.get(port, options));
857
901
  }),
858
- list: attempt("tunnels.list", () => client.tunnels.list()).pipe(
902
+ list: attempt("tunnels.list", (client) => client.tunnels.list()).pipe(
859
903
  Effect.withSpan("Sandbox.tunnels.list", spanOptions("tunnels.list")),
860
904
  ),
861
905
  destroy: Effect.fn(
862
906
  "Sandbox.tunnels.destroy",
863
907
  spanOptions("tunnels.destroy"),
864
908
  )(function* (portOrInfo: number | TunnelInfo) {
865
- return yield* attempt("tunnels.destroy", () => client.tunnels.destroy(portOrInfo));
909
+ return yield* attempt("tunnels.destroy", (client) => client.tunnels.destroy(portOrInfo));
866
910
  }),
867
911
  },
868
912
  start: Effect.fn(
869
913
  "Sandbox.start",
870
914
  spanOptions("start"),
871
915
  )(function* (options?: ContainerStartOptions) {
872
- return yield* attempt("start", () => client.start(options));
916
+ return yield* attempt("start", (client) => client.start(options));
873
917
  }),
874
918
  stop: Effect.fn(
875
919
  "Sandbox.stop",
876
920
  spanOptions("stop"),
877
921
  )(function* (signal?: ContainerStopSignal) {
878
- return yield* attempt("stop", () => client.stop(signal));
922
+ return yield* attempt("stop", (client) => client.stop(signal));
879
923
  }),
880
- destroy: attempt("destroy", () => client.destroy()).pipe(
924
+ destroy: attempt("destroy", (client) => client.destroy()).pipe(
881
925
  Effect.withSpan("Sandbox.destroy", spanOptions("destroy")),
882
926
  ),
883
927
  };
@@ -921,52 +965,58 @@ export const makeClient =
921
965
  yield* Effect.annotateCurrentSpan("instance", name);
922
966
 
923
967
  const sandboxModule = yield* importSandboxModule(definition, name);
924
- const targets = yield* Effect.serviceOption(CurrentTargets);
925
- const scopedNamespace = yield* RpcTargets.get(namespace, "sandbox-namespace", () => {
926
- const adapter: SandboxNamespaceResource = {
927
- idFromName: (name) => namespace.idFromName(name),
928
- get: (id, options) =>
929
- Option.isSome(targets)
930
- ? targets.value.get(adapter, JSON.stringify([id.toString(), options]), () =>
931
- namespace.get(id, options),
932
- )
933
- : namespace.get(id, options),
934
- };
935
-
936
- return adapter;
937
- }).pipe(
938
- Effect.mapError(
939
- (error) =>
968
+ // Resolve through the invocation cache on each operation. A retained client
969
+ // must observe invalidation after a native stub fails; failed work is not replayed.
970
+ const resolve = Effect.gen(function* () {
971
+ const targets = yield* Effect.serviceOption(CurrentTargets);
972
+ const scopedNamespace = yield* RpcTargets.get(namespace, "sandbox-namespace", () => {
973
+ const adapter: SandboxNamespaceResource = {
974
+ idFromName: (name) => namespace.idFromName(name),
975
+ get: (id, options) =>
976
+ Option.isSome(targets)
977
+ ? targets.value.get(adapter, JSON.stringify([id.toString(), options]), () =>
978
+ namespace.get(id, options),
979
+ )
980
+ : namespace.get(id, options),
981
+ };
982
+
983
+ return adapter;
984
+ }).pipe(
985
+ Effect.mapError(
986
+ (error) =>
987
+ new SandboxOperationError({
988
+ binding: definition.binding,
989
+ instance: name,
990
+ operation: "get",
991
+ cause: error.cause,
992
+ }),
993
+ ),
994
+ );
995
+ const nativeNamespace: unknown = scopedNamespace;
996
+ const client = yield* Effect.try({
997
+ try: () =>
998
+ sandboxModule.getSandbox(
999
+ // SAFETY: the layer validated the binding as a Durable Object
1000
+ // namespace, and getSandbox only requires idFromName() and get();
1001
+ // the structural namespace type is narrower than the SDK's nominal
1002
+ // one, so the conversion goes through unknown.
1003
+ nativeNamespace as Parameters<typeof sandboxModule.getSandbox>[0],
1004
+ name,
1005
+ options,
1006
+ ),
1007
+ catch: (cause) =>
940
1008
  new SandboxOperationError({
941
1009
  binding: definition.binding,
942
1010
  instance: name,
943
1011
  operation: "get",
944
- cause: error.cause,
1012
+ cause,
945
1013
  }),
946
- ),
947
- );
948
- const nativeNamespace: unknown = scopedNamespace;
949
- const client = yield* Effect.try({
950
- try: () =>
951
- sandboxModule.getSandbox(
952
- // SAFETY: the layer validated the binding as a Durable Object
953
- // namespace, and getSandbox only requires idFromName() and get();
954
- // the structural namespace type is narrower than the SDK's nominal
955
- // one, so the conversion goes through unknown.
956
- nativeNamespace as Parameters<typeof sandboxModule.getSandbox>[0],
957
- name,
958
- options,
959
- ),
960
- catch: (cause) =>
961
- new SandboxOperationError({
962
- binding: definition.binding,
963
- instance: name,
964
- operation: "get",
965
- cause,
966
- }),
1014
+ });
1015
+
1016
+ return { client, target: scopedNamespace };
967
1017
  });
968
1018
 
969
- return makeSandboxClient(client, definition, name, scopedNamespace);
1019
+ return makeSandboxClient(resolve, definition, name);
970
1020
  });
971
1021
 
972
1022
  return {