effect-cf 0.27.0 → 0.28.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/{Artifacts-DcoXcRP5.d.mts → Artifacts-DnHQA-Ax.d.mts} +42 -25
  3. package/dist/{Artifacts-BhWrdyqX.mjs → Artifacts-rq3hUKQE.mjs} +46 -30
  4. package/dist/{Binding-COCtoJXa.d.mts → Binding-xJJJPmiD.d.mts} +6 -5
  5. package/dist/ComputerArtifacts.d.mts +1 -1
  6. package/dist/ComputerArtifacts.mjs +9 -9
  7. package/dist/ComputerWorkspace.d.mts +5 -5
  8. package/dist/ComputerWorkspace.mjs +107 -75
  9. package/dist/ComputerWorkspaceHost.d.mts +1 -1
  10. package/dist/ComputerWorkspaceHost.mjs +18 -16
  11. package/dist/{DurableObjectState-biHrHwrh.d.mts → DurableObjectState-B3uFY1eM.d.mts} +85 -62
  12. package/dist/{DurableObjectState-Dux8KfjG.mjs → DurableObjectState-BjkK2kwH.mjs} +3 -3
  13. package/dist/{ErrorMessage-_lg53p3W.mjs → ErrorMessage-BM0K-Nik.mjs} +1 -4
  14. package/dist/{Hyperdrive-6p59yyVk.d.mts → Hyperdrive-z-9fFz15.d.mts} +2 -2
  15. package/dist/HyperdrivePg.d.mts +2 -2
  16. package/dist/Vitest.d.mts +6 -5
  17. package/dist/Vitest.mjs +2 -2
  18. package/dist/{WorkflowBinding-zlRPCXVf.d.mts → WorkflowBinding-wZ-yGHow.d.mts} +5 -10
  19. package/dist/index.d.mts +82 -60
  20. package/dist/index.mjs +268 -170
  21. package/package.json +1 -1
  22. package/src/AiGateway.ts +62 -7
  23. package/src/AnalyticsEngine.ts +17 -13
  24. package/src/Artifacts.ts +111 -47
  25. package/src/Binding.ts +34 -23
  26. package/src/BrowserRendering.ts +72 -33
  27. package/src/CloudflareOtlp.ts +8 -5
  28. package/src/ComputerArtifacts.ts +17 -9
  29. package/src/ComputerWorkspace.ts +202 -98
  30. package/src/ComputerWorkspaceHost.ts +55 -27
  31. package/src/ContainerNamespace.ts +8 -9
  32. package/src/D1.ts +8 -14
  33. package/src/DurableObject.ts +27 -11
  34. package/src/DurableObjectAlarm.ts +17 -5
  35. package/src/DurableObjectDefinition.ts +89 -72
  36. package/src/DurableObjectNamespace.ts +26 -12
  37. package/src/DurableObjectRpcWebSocket.ts +9 -10
  38. package/src/DurableObjectState.ts +1 -0
  39. package/src/DurableObjectStorage.ts +5 -2
  40. package/src/DurableObjectWebSocket.ts +4 -1
  41. package/src/Email.ts +26 -25
  42. package/src/Hyperdrive.ts +4 -10
  43. package/src/Images.ts +12 -14
  44. package/src/Kv.ts +15 -17
  45. package/src/Queue.ts +2 -1
  46. package/src/QueueBinding.ts +7 -12
  47. package/src/QueueDefinition.ts +7 -2
  48. package/src/R2.ts +33 -24
  49. package/src/Rpc.ts +15 -21
  50. package/src/RpcDefinition.ts +33 -38
  51. package/src/ServiceBinding.ts +26 -8
  52. package/src/Vectorize.ts +45 -18
  53. package/src/Vitest.ts +9 -3
  54. package/src/WebTransport.ts +11 -5
  55. package/src/Worker.ts +91 -21
  56. package/src/WorkerDefinition.ts +143 -63
  57. package/src/WorkersAi.ts +50 -28
  58. package/src/Workflow.ts +66 -13
  59. package/src/WorkflowBinding.ts +18 -25
  60. package/src/WorkflowDefinition.ts +4 -2
  61. package/src/internal/Entrypoint.ts +29 -13
  62. package/src/internal/ErrorMessage.ts +7 -6
  63. package/src/internal/RpcInvocation.ts +34 -11
  64. package/src/internal/Runtime.ts +33 -22
  65. package/src/internal/WorkerContext.ts +3 -3
@@ -27,7 +27,7 @@ import type {
27
27
  GitLogOptions as CloudflareGitLogOptions,
28
28
  GitUpdateRefOptions as CloudflareGitUpdateRefOptions,
29
29
  } from "@cloudflare/computer/git";
30
- import { Context, Effect, Layer, Schema, type Scope, Stream } from "effect";
30
+ import { Context, Effect, Layer, Predicate, Schema, type Scope, Stream } from "effect";
31
31
 
32
32
  import * as ComputerArtifacts from "./ComputerArtifacts";
33
33
  import { DurableObjectState } from "./DurableObjectState";
@@ -369,7 +369,7 @@ export type ComputerWorkspaceThink = {
369
369
  };
370
370
 
371
371
  /** Effect service over every client surface exposed by Cloudflare Computer. */
372
- export interface ComputerWorkspaceShape {
372
+ export interface ComputerWorkspaceService {
373
373
  readonly fs: ComputerWorkspaceFilesystem;
374
374
  readonly git: ComputerWorkspaceGit;
375
375
  readonly runtime: ComputerWorkspaceRuntime;
@@ -403,52 +403,95 @@ export interface ComputerWorkspaceShape {
403
403
  readonly disposeExec: ComputerWorkspaceRuntime["disposeExec"];
404
404
  }
405
405
 
406
+ export type { ComputerWorkspaceService as "ComputerWorkspaceShape" };
407
+
408
+ interface WorkspaceFsErrorProperties {
409
+ operation: string;
410
+ path: string;
411
+ message: string;
412
+ cause: unknown;
413
+ code?: string;
414
+ }
415
+
416
+ interface WorkspaceOperationErrorProperties {
417
+ operation: string;
418
+ message: string;
419
+ cause: unknown;
420
+ code?: string;
421
+ }
422
+
406
423
  const errorMessage = (cause: unknown): string =>
407
424
  cause instanceof Error ? cause.message : String(cause);
408
425
 
409
426
  const errorCode = (cause: unknown): string | undefined => {
410
- if (typeof cause !== "object" || cause === null) {
411
- return undefined;
427
+ if (Predicate.hasProperty(cause, "code") && Predicate.isString(cause.code)) {
428
+ return cause.code;
412
429
  }
413
430
 
414
- const code = Reflect.get(cause, "code");
415
-
416
- return typeof code === "string" ? code : undefined;
431
+ return undefined;
417
432
  };
418
433
 
419
- const fsError = (operation: string, path: string, cause: unknown) =>
420
- WorkspaceFsError.make({
434
+ const fsError = (operation: string, path: string, cause: unknown) => {
435
+ const properties: WorkspaceFsErrorProperties = {
421
436
  operation,
422
437
  path,
423
438
  message: errorMessage(cause),
424
439
  cause,
425
- ...(errorCode(cause) === undefined ? {} : { code: errorCode(cause) }),
426
- });
440
+ };
441
+ const code = errorCode(cause);
442
+
443
+ if (code !== undefined) {
444
+ properties.code = code;
445
+ }
427
446
 
428
- const gitError = (operation: string, cause: unknown) =>
429
- WorkspaceGitError.make({
447
+ return WorkspaceFsError.make(properties);
448
+ };
449
+
450
+ const gitError = (operation: string, cause: unknown) => {
451
+ const properties: WorkspaceOperationErrorProperties = {
430
452
  operation,
431
453
  message: errorMessage(cause),
432
454
  cause,
433
- ...(errorCode(cause) === undefined ? {} : { code: errorCode(cause) }),
434
- });
455
+ };
456
+ const code = errorCode(cause);
457
+
458
+ if (code !== undefined) {
459
+ properties.code = code;
460
+ }
435
461
 
436
- const execError = (operation: string, cause: unknown) =>
437
- WorkspaceExecError.make({
462
+ return WorkspaceGitError.make(properties);
463
+ };
464
+
465
+ const execError = (operation: string, cause: unknown) => {
466
+ const properties: WorkspaceOperationErrorProperties = {
438
467
  operation,
439
468
  message: errorMessage(cause),
440
469
  cause,
441
- ...(errorCode(cause) === undefined ? {} : { code: errorCode(cause) }),
442
- });
470
+ };
471
+ const code = errorCode(cause);
443
472
 
444
- const assetsError = (operation: string, path: string, cause: unknown) =>
445
- WorkspaceAssetsError.make({
473
+ if (code !== undefined) {
474
+ properties.code = code;
475
+ }
476
+
477
+ return WorkspaceExecError.make(properties);
478
+ };
479
+
480
+ const assetsError = (operation: string, path: string, cause: unknown) => {
481
+ const properties: WorkspaceFsErrorProperties = {
446
482
  operation,
447
483
  path,
448
484
  message: errorMessage(cause),
449
485
  cause,
450
- ...(errorCode(cause) === undefined ? {} : { code: errorCode(cause) }),
451
- });
486
+ };
487
+ const code = errorCode(cause);
488
+
489
+ if (code !== undefined) {
490
+ properties.code = code;
491
+ }
492
+
493
+ return WorkspaceAssetsError.make(properties);
494
+ };
452
495
 
453
496
  const tryFs = <A>(operation: string, path: string, evaluate: () => Promise<A>) =>
454
497
  Effect.tryPromise({
@@ -479,6 +522,8 @@ const makeFilesystem = (fsClient: CloudflareWorkspaceFilesystem): ComputerWorksp
479
522
  );
480
523
  });
481
524
 
525
+ // SAFETY: the implementation branches on the declared encoding discriminant and returns the
526
+ // corresponding Cloudflare read result for both overloads.
482
527
  const readFile = readFileImplementation as WorkspaceReadFile;
483
528
 
484
529
  const stat = Effect.fn("ComputerWorkspace.fs.stat", {
@@ -640,14 +685,14 @@ const makeGit = (gitClient: CloudflareGitClient): ComputerWorkspaceGit => {
640
685
  attributes: { family: "git", operation: "log" },
641
686
  })(function* (options?: WorkspaceGitLogOptions) {
642
687
  const { maxCount, ...upstreamOptions } = options ?? {};
688
+ const logOptions: CloudflareGitLogOptions = { ...upstreamOptions };
689
+
690
+ if (logOptions.depth === undefined && maxCount !== undefined) {
691
+ logOptions.depth = maxCount;
692
+ }
693
+
643
694
  const commits = yield* Effect.tryPromise({
644
- try: () =>
645
- gitClient.log({
646
- ...upstreamOptions,
647
- ...(upstreamOptions.depth === undefined && maxCount !== undefined
648
- ? { depth: maxCount }
649
- : {}),
650
- }),
695
+ try: () => gitClient.log(logOptions),
651
696
  catch: (cause) => gitError("log", cause),
652
697
  });
653
698
 
@@ -657,7 +702,7 @@ const makeGit = (gitClient: CloudflareGitClient): ComputerWorkspaceGit => {
657
702
  const revParseImplementation = Effect.fn("ComputerWorkspace.git.revParse", {
658
703
  attributes: { family: "git", operation: "revParse" },
659
704
  })(function* (input: string | Parameters<CloudflareGitClient["revParse"]>[0]) {
660
- const options = typeof input === "string" ? { ref: input } : input;
705
+ const options = Predicate.isString(input) ? { ref: input } : input;
661
706
 
662
707
  return yield* Effect.tryPromise({
663
708
  try: () => gitClient.revParse(options),
@@ -665,19 +710,21 @@ const makeGit = (gitClient: CloudflareGitClient): ComputerWorkspaceGit => {
665
710
  });
666
711
  });
667
712
 
713
+ // SAFETY: the implementation normalizes the string overload to the exact options object accepted
714
+ // by Cloudflare and forwards the options overload unchanged.
668
715
  const revParse = revParseImplementation as ComputerWorkspaceGit["revParse"];
669
716
 
670
717
  return {
671
- clone: method("clone", (options) => gitClient.clone(options as CloudflareGitCloneOptions)),
672
- diff: method("diff", (options) => gitClient.diff(options as CloudflareGitDiffOptions)),
673
- diffSummary: method("diffSummary", (options) =>
674
- gitClient.diffSummary(options as CloudflareGitDiffOptions),
718
+ clone: method("clone", (options: CloudflareGitCloneOptions) => gitClient.clone(options)),
719
+ diff: method("diff", (options?: CloudflareGitDiffOptions) => gitClient.diff(options)),
720
+ diffSummary: method("diffSummary", (options?: CloudflareGitDiffOptions) =>
721
+ gitClient.diffSummary(options),
675
722
  ),
676
723
  init: method("init", (options) => gitClient.init(options)),
677
724
  status: method("status", (options) => gitClient.status(options)),
678
725
  add: method("add", (options) => gitClient.add(options)),
679
726
  rm: method("rm", (options) => gitClient.rm(options)),
680
- commit: method("commit", (options) => gitClient.commit(options as CloudflareGitCommitOptions)),
727
+ commit: method("commit", (options: CloudflareGitCommitOptions) => gitClient.commit(options)),
681
728
  log,
682
729
  show: method("show", (options) => gitClient.show(options)),
683
730
  revParse,
@@ -692,7 +739,7 @@ const makeGit = (gitClient: CloudflareGitClient): ComputerWorkspaceGit => {
692
739
  tagDelete: method("tagDelete", (options) => gitClient.tagDelete(options)),
693
740
  tagList: method("tagList", (options) => gitClient.tagList(options)),
694
741
  checkout: method("checkout", (options) => gitClient.checkout(options)),
695
- fetch: method("fetch", (options) => gitClient.fetch(options as CloudflareGitFetchOptions)),
742
+ fetch: method("fetch", (options?: CloudflareGitFetchOptions) => gitClient.fetch(options)),
696
743
  push: method("push", (options) => gitClient.push(options)),
697
744
  pull: method("pull", (options) => gitClient.pull(options)),
698
745
  merge: method("merge", (options) => gitClient.merge(options)),
@@ -701,8 +748,8 @@ const makeGit = (gitClient: CloudflareGitClient): ComputerWorkspaceGit => {
701
748
  remoteList: method("remoteList", (options) => gitClient.remoteList(options)),
702
749
  hashObject: method("hashObject", (options) => gitClient.hashObject(options)),
703
750
  catFile: method("catFile", (options) => gitClient.catFile(options)),
704
- updateRef: method("updateRef", (options) =>
705
- gitClient.updateRef(options as CloudflareGitUpdateRefOptions),
751
+ updateRef: method("updateRef", (options: CloudflareGitUpdateRefOptions) =>
752
+ gitClient.updateRef(options),
706
753
  ),
707
754
  configGet: method("configGet", (options) => gitClient.configGet(options)),
708
755
  configSet: method("configSet", (options) => gitClient.configSet(options)),
@@ -712,7 +759,7 @@ const makeGit = (gitClient: CloudflareGitClient): ComputerWorkspaceGit => {
712
759
  reset: method("reset", (options) => gitClient.reset(options)),
713
760
  clean: method("clean", (options) => gitClient.clean(options)),
714
761
  cli: method("cli", (input) => gitClient.cli(input)),
715
- } as ComputerWorkspaceGit;
762
+ } satisfies ComputerWorkspaceGit;
716
763
  };
717
764
 
718
765
  const runtimeExecOptions = <Encoding extends WorkspaceExecEncoding>(
@@ -720,13 +767,19 @@ const runtimeExecOptions = <Encoding extends WorkspaceExecEncoding>(
720
767
  ): CloudflareRuntimeExecOptions => {
721
768
  const { encoding = "utf8", timeoutMillis, timeoutMs, ...rest } = options ?? {};
722
769
 
723
- return {
724
- ...rest,
725
- ...(encoding === "utf8" ? { encoding: "utf8" as const } : {}),
726
- ...(timeoutMs === undefined && timeoutMillis === undefined
727
- ? {}
728
- : { timeoutMs: timeoutMs ?? timeoutMillis }),
729
- };
770
+ const runtimeOptions: CloudflareRuntimeExecOptions = { ...rest };
771
+
772
+ if (encoding === "utf8") {
773
+ runtimeOptions.encoding = "utf8";
774
+ }
775
+
776
+ const selectedTimeout = timeoutMs ?? timeoutMillis;
777
+
778
+ if (selectedTimeout !== undefined) {
779
+ runtimeOptions.timeoutMs = selectedTimeout;
780
+ }
781
+
782
+ return runtimeOptions;
730
783
  };
731
784
 
732
785
  const runtimeGetOptions = <Encoding extends WorkspaceExecEncoding>(
@@ -734,34 +787,47 @@ const runtimeGetOptions = <Encoding extends WorkspaceExecEncoding>(
734
787
  ): CloudflareRuntimeGetOptions => {
735
788
  const { encoding = "utf8", ...rest } = options ?? {};
736
789
 
737
- return {
738
- ...rest,
739
- ...(encoding === "utf8" ? { encoding: "utf8" as const } : {}),
740
- };
790
+ const runtimeOptions: CloudflareRuntimeGetOptions = { ...rest };
791
+
792
+ if (encoding === "utf8") {
793
+ runtimeOptions.encoding = "utf8";
794
+ }
795
+
796
+ return runtimeOptions;
741
797
  };
742
798
 
743
799
  const wrapRuntimeResult = <Encoding extends WorkspaceExecEncoding>(
744
800
  result: CloudflareWorkspaceRuntimeResult<CloudflareEncoding<Encoding>>,
745
- ): WorkspaceExecResult<Encoding> => ({
746
- status: result.status,
747
- exitCode: result.exitCode,
748
- stdout: result.stdout as WorkspaceExecChunk<Encoding>,
749
- stderr: result.stderr as WorkspaceExecChunk<Encoding>,
750
- ...(result.value === undefined ? {} : { value: result.value }),
751
- pushed: result.pushed,
752
- pulled: result.pulled,
753
- skipped: result.skipped,
754
- sync: result.sync,
755
- });
801
+ ): WorkspaceExecResult<Encoding> => {
802
+ // SAFETY: Cloudflare's result encoding generic determines stdout and stderr in the same way as
803
+ // WorkspaceExecChunk; CloudflareEncoding maps the public "binary" name to upstream undefined.
804
+ const stdout = result.stdout as WorkspaceExecChunk<Encoding>;
805
+ // SAFETY: the same encoding invariant applies independently to stderr.
806
+ const stderr = result.stderr as WorkspaceExecChunk<Encoding>;
807
+ const wrapped = {
808
+ status: result.status,
809
+ exitCode: result.exitCode,
810
+ stdout,
811
+ stderr,
812
+ pushed: result.pushed,
813
+ pulled: result.pulled,
814
+ skipped: result.skipped,
815
+ sync: result.sync,
816
+ };
817
+
818
+ return result.value === undefined ? wrapped : { ...wrapped, value: result.value };
819
+ };
756
820
 
757
- const wrapRuntimeEvent = <Encoding extends WorkspaceExecEncoding>(
758
- event: CloudflareWorkspaceRuntimeEvent<CloudflareEncoding<Encoding>>,
759
- ): WorkspaceExecEvent<Encoding> => {
821
+ type AnyWorkspaceExecEvent = WorkspaceExecEvent<"utf8"> | WorkspaceExecEvent<"binary">;
822
+
823
+ const wrapRuntimeEvent = (
824
+ event: CloudflareWorkspaceRuntimeEvent<"utf8" | undefined>,
825
+ ): AnyWorkspaceExecEvent => {
760
826
  switch (event.name) {
761
827
  case "stdout": {
762
- const chunk = event.value as unknown as string | Uint8Array;
828
+ const chunk = event.value;
763
829
 
764
- return (typeof chunk === "string"
830
+ return Predicate.isString(chunk)
765
831
  ? { _tag: "Stdout", id: event.id, sequence: event.seq, chunk, text: chunk }
766
832
  : {
767
833
  _tag: "Stdout",
@@ -769,12 +835,12 @@ const wrapRuntimeEvent = <Encoding extends WorkspaceExecEncoding>(
769
835
  sequence: event.seq,
770
836
  chunk,
771
837
  bytes: chunk,
772
- }) as unknown as WorkspaceExecEvent<Encoding>;
838
+ };
773
839
  }
774
840
  case "stderr": {
775
- const chunk = event.value as unknown as string | Uint8Array;
841
+ const chunk = event.value;
776
842
 
777
- return (typeof chunk === "string"
843
+ return Predicate.isString(chunk)
778
844
  ? { _tag: "Stderr", id: event.id, sequence: event.seq, chunk, text: chunk }
779
845
  : {
780
846
  _tag: "Stderr",
@@ -782,29 +848,35 @@ const wrapRuntimeEvent = <Encoding extends WorkspaceExecEncoding>(
782
848
  sequence: event.seq,
783
849
  chunk,
784
850
  bytes: chunk,
785
- }) as unknown as WorkspaceExecEvent<Encoding>;
851
+ };
786
852
  }
787
853
  case "exit":
788
- return {
789
- _tag: "Exit",
854
+ const exitEvent = {
855
+ _tag: "Exit" as const,
790
856
  id: event.id,
791
857
  sequence: event.seq,
792
858
  exitCode: event.code,
793
- ...(event.result === undefined ? {} : { value: event.result }),
794
859
  };
860
+
861
+ return event.result === undefined ? exitEvent : { ...exitEvent, value: event.result };
795
862
  }
796
863
  };
797
864
 
798
865
  const wrapRun = <Encoding extends WorkspaceExecEncoding>(
799
866
  handle: CloudflareWorkspaceRuntimeExecHandle<CloudflareEncoding<Encoding>>,
800
867
  ): WorkspaceExecRun<Encoding> => {
868
+ // SAFETY: wrapRuntimeEvent preserves Cloudflare's encoding-specific event arm; this adapter
869
+ // restores the correlation hidden when the implementation handles both upstream encodings.
870
+ const mapRuntimeEvent = wrapRuntimeEvent as (
871
+ event: CloudflareWorkspaceRuntimeEvent<CloudflareEncoding<Encoding>>,
872
+ ) => WorkspaceExecEvent<Encoding>;
801
873
  const events = Stream.fromReadableStream<
802
874
  CloudflareWorkspaceRuntimeEvent<CloudflareEncoding<Encoding>>,
803
875
  WorkspaceExecError
804
876
  >({
805
877
  evaluate: () => handle,
806
878
  onError: (cause) => execError("events", cause),
807
- }).pipe(Stream.map(wrapRuntimeEvent<Encoding>));
879
+ }).pipe(Stream.map(mapRuntimeEvent));
808
880
 
809
881
  const result = Effect.tryPromise({
810
882
  try: () => handle.result(),
@@ -834,23 +906,29 @@ const makeRuntime = (client: CloudflareWorkspaceClient): ComputerWorkspaceRuntim
834
906
  optionsOrValue?: WorkspaceExecOptions<Encoding> | CloudflareShellValue,
835
907
  ...remainingValues: Array<CloudflareShellValue>
836
908
  ) {
837
- const options =
838
- typeof source === "string"
839
- ? (optionsOrValue as WorkspaceExecOptions<Encoding> | undefined)
840
- : undefined;
909
+ // SAFETY: the WorkspaceExec overload associates string sources with an options object; template
910
+ // invocations associate the second argument with a shell interpolation value.
911
+ const options = Predicate.isString(source)
912
+ ? (optionsOrValue as WorkspaceExecOptions<Encoding> | undefined)
913
+ : undefined;
841
914
 
842
915
  yield* Effect.annotateCurrentSpan({ backend: options?.backend, runId: options?.id });
843
916
  const handle = yield* Effect.acquireRelease(
844
917
  Effect.tryPromise({
845
918
  try: () => {
846
- if (typeof source === "string") {
919
+ if (Predicate.isString(source)) {
847
920
  return client.runtime.exec(source, runtimeExecOptions(options));
848
921
  }
849
922
 
850
923
  const values =
851
924
  optionsOrValue === undefined
852
925
  ? remainingValues
853
- : [optionsOrValue as CloudflareShellValue, ...remainingValues];
926
+ : [
927
+ // SAFETY: this branch is reachable only for the tagged-template overload, whose
928
+ // second argument is the first Cloudflare shell interpolation value.
929
+ optionsOrValue as CloudflareShellValue,
930
+ ...remainingValues,
931
+ ];
854
932
 
855
933
  return client.runtime.exec(source, ...values);
856
934
  },
@@ -859,10 +937,13 @@ const makeRuntime = (client: CloudflareWorkspaceClient): ComputerWorkspaceRuntim
859
937
  releaseRun,
860
938
  );
861
939
 
940
+ // SAFETY: runtimeExecOptions carries Encoding to Cloudflare's corresponding handle overload.
862
941
  return wrapRun<Encoding>(
863
942
  handle as CloudflareWorkspaceRuntimeExecHandle<CloudflareEncoding<Encoding>>,
864
943
  );
865
944
  });
945
+ // SAFETY: execImplementation implements each overload by preserving the source/encoding
946
+ // relationship and delegates tagged-template inputs to Cloudflare's tagged-template overload.
866
947
  const exec = execImplementation as WorkspaceExec;
867
948
 
868
949
  const getExecImplementation = Effect.fn("ComputerWorkspace.runtime.getExec", {
@@ -880,10 +961,13 @@ const makeRuntime = (client: CloudflareWorkspaceClient): ComputerWorkspaceRuntim
880
961
  releaseRun,
881
962
  );
882
963
 
964
+ // SAFETY: runtimeGetOptions carries Encoding to Cloudflare's corresponding handle overload.
883
965
  return wrapRun<Encoding>(
884
966
  handle as CloudflareWorkspaceRuntimeExecHandle<CloudflareEncoding<Encoding>>,
885
967
  );
886
968
  });
969
+ // SAFETY: getExecImplementation preserves the option encoding and therefore satisfies each
970
+ // WorkspaceGetExec overload.
887
971
  const getExec = getExecImplementation as WorkspaceGetExec;
888
972
 
889
973
  const killExec = Effect.fn("ComputerWorkspace.runtime.killExec", {
@@ -921,6 +1005,8 @@ const makeRuntime = (client: CloudflareWorkspaceClient): ComputerWorkspaceRuntim
921
1005
  }),
922
1006
  );
923
1007
  });
1008
+ // SAFETY: execCollectImplementation delegates to the checked exec overload implementation and
1009
+ // returns the result associated with that same encoding.
924
1010
  const execCollect = execCollectImplementation as WorkspaceExecCollect;
925
1011
 
926
1012
  return { exec, execCollect, getExec, killExec, disposeExec };
@@ -973,50 +1059,59 @@ const makeThink = (client: CloudflareWorkspaceClient): ComputerWorkspaceThink |
973
1059
  return yield* tryFs(`think.${operation}`, path, () => evaluate(...args));
974
1060
  });
975
1061
 
1062
+ const readFile = client.readFile.bind(client);
1063
+ const readFileBytes = client.readFileBytes.bind(client);
1064
+ const writeFile = client.writeFile.bind(client);
1065
+ const readDir = client.readDir.bind(client);
1066
+ const rm = client.rm.bind(client);
1067
+ const glob = client.glob.bind(client);
1068
+ const mkdir = client.mkdir.bind(client);
1069
+ const stat = client.stat.bind(client);
1070
+
976
1071
  return {
977
1072
  readFile: method(
978
1073
  "readFile",
979
- (path) => path as string,
980
- (path) => client.readFile!(path as string),
1074
+ (path: string) => path,
1075
+ (path: string) => readFile(path),
981
1076
  ),
982
1077
  readFileBytes: method(
983
1078
  "readFileBytes",
984
- (path) => path as string,
985
- (path) => client.readFileBytes!(path as string),
1079
+ (path: string) => path,
1080
+ (path: string) => readFileBytes(path),
986
1081
  ),
987
1082
  writeFile: method(
988
1083
  "writeFile",
989
1084
  (path: string, _content: string) => path,
990
- (path: string, content: string) => client.writeFile!(path, content),
1085
+ (path: string, content: string) => writeFile(path, content),
991
1086
  ),
992
1087
  readDir: method(
993
1088
  "readDir",
994
1089
  (path: string, _options?: { readonly limit?: number; readonly offset?: number }) => path,
995
1090
  (path: string, options?: { readonly limit?: number; readonly offset?: number }) =>
996
- client.readDir!(path, options),
1091
+ readDir(path, options),
997
1092
  ),
998
1093
  rm: method(
999
1094
  "rm",
1000
1095
  (path: string, _options?: { readonly recursive?: boolean; readonly force?: boolean }) => path,
1001
1096
  (path: string, options?: { readonly recursive?: boolean; readonly force?: boolean }) =>
1002
- client.rm!(path, options),
1097
+ rm(path, options),
1003
1098
  ),
1004
1099
  glob: method(
1005
1100
  "glob",
1006
- (pattern) => pattern as string,
1007
- (pattern) => client.glob!(pattern as string),
1101
+ (pattern: string) => pattern,
1102
+ (pattern: string) => glob(pattern),
1008
1103
  ),
1009
1104
  mkdir: method(
1010
1105
  "mkdir",
1011
1106
  (path: string, _options?: { readonly recursive?: boolean }) => path,
1012
- (path: string, options?: { readonly recursive?: boolean }) => client.mkdir!(path, options),
1107
+ (path: string, options?: { readonly recursive?: boolean }) => mkdir(path, options),
1013
1108
  ),
1014
1109
  stat: method(
1015
1110
  "stat",
1016
- (path) => path as string,
1017
- (path) => client.stat!(path as string),
1111
+ (path: string) => path,
1112
+ (path: string) => stat(path),
1018
1113
  ),
1019
- } as ComputerWorkspaceThink;
1114
+ } satisfies ComputerWorkspaceThink;
1020
1115
  };
1021
1116
 
1022
1117
  /**
@@ -1024,14 +1119,22 @@ const makeThink = (client: CloudflareWorkspaceClient): ComputerWorkspaceThink |
1024
1119
  * {@link ComputerWorkspace.layer} in Durable Objects so the parent client and
1025
1120
  * every runtime handle are disposed by Effect `Scope`.
1026
1121
  */
1027
- export const fromWorkspaceClient = (client: CloudflareWorkspaceClient): ComputerWorkspaceShape => {
1122
+ export const fromWorkspaceClient = (
1123
+ client: CloudflareWorkspaceClient,
1124
+ ): ComputerWorkspaceService => {
1028
1125
  const fs = makeFilesystem(client.fs);
1126
+ // SAFETY: @cloudflare/computer currently publishes these WorkspaceClient properties as `any`,
1127
+ // while its runtime exposes the concrete clients imported above.
1029
1128
  const git = makeGit(client.git as CloudflareGitClient);
1030
1129
  const runtime = makeRuntime(client);
1031
1130
  const artifacts = ComputerArtifacts.fromClient(
1131
+ // SAFETY: the upstream WorkspaceClient declaration erases this concrete artifact client to
1132
+ // `any`; Workspace always exposes its ArtifactClient here.
1032
1133
  client.artifacts as CloudflareComputerArtifactClient,
1033
1134
  { binding: "ComputerWorkspace.artifacts" },
1034
1135
  );
1136
+ // SAFETY: the upstream WorkspaceClient declaration erases this optional concrete assets client
1137
+ // to `any`; Workspace exposes AssetsClient | undefined here.
1035
1138
  const assets = makeAssets(client.assets as CloudflareComputerAssetsClient | undefined);
1036
1139
  const think = makeThink(client);
1037
1140
 
@@ -1067,9 +1170,10 @@ export const fromWorkspaceClient = (client: CloudflareWorkspaceClient): Computer
1067
1170
  };
1068
1171
 
1069
1172
  /** Effect service for the workspace owned by the enclosing Durable Object. */
1070
- export class ComputerWorkspace extends Context.Service<ComputerWorkspace, ComputerWorkspaceShape>()(
1071
- "effect-cf/ComputerWorkspace",
1072
- ) {
1173
+ export class ComputerWorkspace extends Context.Service<
1174
+ ComputerWorkspace,
1175
+ ComputerWorkspaceService
1176
+ >()("effect-cf/ComputerWorkspace") {
1073
1177
  /**
1074
1178
  * Acquires the single workspace registered by `withComputerWorkspace` and
1075
1179
  * releases its RPC client when the layer scope closes.