@effect-agent/storage-cloudflare 0.1.0-beta.23 → 0.1.0-beta.25

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/src/routing.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  AdmissionIndeterminate,
3
+ AdmissionConflict,
3
4
  AppendConflict,
4
5
  ChildAttachmentSnapshot,
5
6
  ConversationMaterialization,
@@ -13,11 +14,10 @@ import {
13
14
  SettlementConflict,
14
15
  SubmissionLedger,
15
16
  SubmissionLookupById,
16
- type AdmissionConflict,
17
17
  type SubmissionLookupByKey,
18
18
  type SubmissionSnapshot,
19
19
  } from "@effect-agent/session";
20
- import { Context, Effect, Layer, Option, Schema, Stream } from "effect";
20
+ import { Context, Effect, Layer, Option, Predicate, Schema, Stream } from "effect";
21
21
 
22
22
  import {
23
23
  boundPortDiagnostic,
@@ -91,20 +91,34 @@ export class PortTransportError extends Schema.TaggedError<PortTransportError>()
91
91
  },
92
92
  ) {}
93
93
 
94
+ const safeTransportDiagnostic = (cause: unknown): string => {
95
+ try {
96
+ const message = cause instanceof Error ? cause.message : cause;
97
+ return boundPortDiagnostic(typeof message === "string" ? message : String(message));
98
+ } catch {
99
+ return "[unavailable transport diagnostic]";
100
+ }
101
+ };
102
+
103
+ const transportRetryableSignal = (cause: unknown): boolean | undefined => {
104
+ if (!Predicate.isObjectKeyword(cause)) return undefined;
105
+ try {
106
+ const signal = Reflect.get(cause, "retryable");
107
+ return typeof signal === "boolean" ? signal : undefined;
108
+ } catch {
109
+ return undefined;
110
+ }
111
+ };
112
+
94
113
  /**
95
114
  * Build a `PortTransportError` from an arbitrary thrown transport cause, preserving the
96
115
  * platform stub's own `retryable` signal when present.
97
116
  */
98
117
  export const portTransportFailure = (target: string, cause: unknown): PortTransportError => {
99
- const message = cause instanceof Error ? cause.message : String(cause);
100
- let retryable: boolean | undefined;
101
- if (typeof cause === "object" && cause !== null && "retryable" in cause) {
102
- const signal = cause.retryable;
103
- if (typeof signal === "boolean") retryable = signal;
104
- }
118
+ const retryable = transportRetryableSignal(cause);
105
119
  return PortTransportError.make({
106
120
  target,
107
- message: boundPortDiagnostic(message),
121
+ message: safeTransportDiagnostic(cause),
108
122
  ...(retryable === undefined ? {} : { retryable }),
109
123
  cause,
110
124
  });
@@ -181,26 +195,13 @@ const routableSubmissionTarget = (
181
195
  );
182
196
  });
183
197
 
184
- const isResultTag =
185
- <Tag extends PortResult["_tag"]>(tag: Tag) =>
186
- (result: PortResult): result is Extract<PortResult, { readonly _tag: Tag }> =>
187
- result._tag === tag;
188
-
189
- const isAdmitConflict = (failure: PortFailure): failure is AdmissionConflict =>
190
- failure._tag === "AdmissionConflict";
191
- const isAbortConflict = (failure: PortFailure): failure is SettlementConflict | JoinedToHost =>
192
- failure._tag === "SettlementConflict" || failure._tag === "JoinedToHost";
193
- const noExtraFailure = (_failure: PortFailure): _failure is never => false;
194
- const isFenceRejected = (failure: PortFailure): failure is FenceRejected =>
195
- failure._tag === "FenceRejected";
196
- const isAppendFailure = (
197
- failure: PortFailure,
198
- ): failure is ConversationNotMaterialized | AppendConflict | FenceRejected =>
199
- failure._tag === "ConversationNotMaterialized" ||
200
- failure._tag === "AppendConflict" ||
201
- failure._tag === "FenceRejected";
202
- const isNotMaterialized = (failure: PortFailure): failure is ConversationNotMaterialized =>
203
- failure._tag === "ConversationNotMaterialized";
198
+ const NoAdditionalPortFailure = Schema.Never;
199
+ const AbortPortFailure = Schema.Union([SettlementConflict, JoinedToHost]);
200
+ const AppendPortFailure = Schema.Union([
201
+ ConversationNotMaterialized,
202
+ AppendConflict,
203
+ FenceRejected,
204
+ ]);
204
205
 
205
206
  /**
206
207
  * The fail-fast refusal for any foreign operation OUTSIDE the closed route-capable subset
@@ -272,22 +273,19 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
272
273
  * any failure outside the operation's declared surface — including protocol anomalies — is
273
274
  * folded into a `LedgerError` naming the anomaly instead of being erased or re-thrown raw.
274
275
  */
275
- const foreignLedgerCall = <Tag extends PortResult["_tag"], ExpectedFailure extends PortFailure>(
276
+ const foreignLedgerCall = <ResultSchema extends Schema.Top, FailureSchema extends Schema.Top>(
276
277
  operation: string,
277
278
  target: ConversationId,
278
279
  call: PortRequest,
279
- resultTag: Tag,
280
- isExpectedFailure: (failure: PortFailure) => failure is ExpectedFailure,
281
- ): Effect.Effect<Extract<PortResult, { readonly _tag: Tag }>, ExpectedFailure | LedgerError> =>
282
- transportCall(target, call).pipe(
280
+ resultSchema: ResultSchema,
281
+ failureSchema: FailureSchema,
282
+ ): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | LedgerError> => {
283
+ const isExpectedResult = Schema.is(resultSchema);
284
+ const isExpectedFailure = Schema.is(failureSchema);
285
+ return transportCall(target, call).pipe(
283
286
  Effect.mapError(routeFailure(operation, target)),
284
287
  Effect.flatMap(
285
- (
286
- response,
287
- ): Effect.Effect<
288
- Extract<PortResult, { readonly _tag: Tag }>,
289
- ExpectedFailure | LedgerError
290
- > => {
288
+ (response): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | LedgerError> => {
291
289
  if (response._tag === "PortFailed") {
292
290
  const failure = response.failure;
293
291
  if (isExpectedFailure(failure)) return Effect.fail(failure);
@@ -304,13 +302,13 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
304
302
  );
305
303
  }
306
304
  const result = response.result;
307
- if (!isResultTag(resultTag)(result)) {
305
+ if (!isExpectedResult(result)) {
308
306
  return Effect.fail(
309
307
  LedgerError.make({
310
308
  operation,
311
309
  message:
312
310
  `The Conversation Object owning ${target} answered ${operation} with the ` +
313
- `mismatched result ${result._tag}; expected ${resultTag}.`,
311
+ `mismatched result ${result._tag}.`,
314
312
  }),
315
313
  );
316
314
  }
@@ -321,6 +319,7 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
321
319
  attributes: { operation, target },
322
320
  }),
323
321
  );
322
+ };
324
323
 
325
324
  /**
326
325
  * Routed `resolveAdmission` — where the S2 tri-state becomes real (plan §1.3): when the
@@ -394,8 +393,8 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
394
393
  operation,
395
394
  target,
396
395
  LedgerLookupCall.make({ request: SubmissionLookupById.make({ submissionId }) }),
397
- "LedgerLookupResult",
398
- noExtraFailure,
396
+ LedgerLookupResult,
397
+ NoAdditionalPortFailure,
399
398
  ).pipe(
400
399
  Effect.map((result) =>
401
400
  result.submission === undefined ? Option.none() : Option.some(result.submission),
@@ -460,8 +459,8 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
460
459
  "ledger admit",
461
460
  request.conversationId,
462
461
  LedgerAdmitCall.make({ request }),
463
- "LedgerAdmitResult",
464
- isAdmitConflict,
462
+ LedgerAdmitResult,
463
+ AdmissionConflict,
465
464
  ).pipe(Effect.map((reply) => reply.result)),
466
465
 
467
466
  markReady: (request) =>
@@ -473,8 +472,8 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
473
472
  "ledger mark ready",
474
473
  target.conversationId,
475
474
  LedgerMarkReadyCall.make({ request }),
476
- "LedgerMarkReadyResult",
477
- noExtraFailure,
475
+ LedgerMarkReadyResult,
476
+ NoAdditionalPortFailure,
478
477
  ).pipe(Effect.asVoid),
479
478
  ),
480
479
  ),
@@ -494,8 +493,8 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
494
493
  "ledger lookup",
495
494
  request.conversationId,
496
495
  LedgerLookupCall.make({ request }),
497
- "LedgerLookupResult",
498
- noExtraFailure,
496
+ LedgerLookupResult,
497
+ NoAdditionalPortFailure,
499
498
  ).pipe(
500
499
  Effect.map((result) =>
501
500
  result.submission === undefined ? Option.none() : Option.some(result.submission),
@@ -516,8 +515,8 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
516
515
  "ledger request abort",
517
516
  target.conversationId,
518
517
  LedgerRequestAbortCall.make({ request }),
519
- "LedgerRequestAbortResult",
520
- isAbortConflict,
518
+ LedgerRequestAbortResult,
519
+ AbortPortFailure,
521
520
  ).pipe(Effect.map((reply) => reply.intent)),
522
521
  ),
523
522
  ),
@@ -531,8 +530,8 @@ const makeRoutedLedgerServices = Effect.fn("DoPortRouting.makeRoutedLedgerServic
531
530
  "ledger record child settled",
532
531
  target.conversationId,
533
532
  LedgerRecordChildSettledCall.make({ request }),
534
- "LedgerRecordChildSettledResult",
535
- noExtraFailure,
533
+ LedgerRecordChildSettledResult,
534
+ NoAdditionalPortFailure,
536
535
  ).pipe(Effect.map((reply) => reply.outcome)),
537
536
  ),
538
537
  ),
@@ -732,25 +731,21 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
732
731
  });
733
732
 
734
733
  /** The store twin of `foreignLedgerCall` with `ConversationStoreError` as the base error. */
735
- const foreignStoreCall = <Tag extends PortResult["_tag"], ExpectedFailure extends PortFailure>(
734
+ const foreignStoreCall = <ResultSchema extends Schema.Top, FailureSchema extends Schema.Top>(
736
735
  operation: string,
737
736
  target: ConversationId,
738
737
  call: PortRequest,
739
- resultTag: Tag,
740
- isExpectedFailure: (failure: PortFailure) => failure is ExpectedFailure,
741
- ): Effect.Effect<
742
- Extract<PortResult, { readonly _tag: Tag }>,
743
- ExpectedFailure | ConversationStoreError
744
- > =>
745
- transportCall(target, call).pipe(
738
+ resultSchema: ResultSchema,
739
+ failureSchema: FailureSchema,
740
+ ): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ConversationStoreError> => {
741
+ const isExpectedResult = Schema.is(resultSchema);
742
+ const isExpectedFailure = Schema.is(failureSchema);
743
+ return transportCall(target, call).pipe(
746
744
  Effect.mapError(routeFailure(operation, target)),
747
745
  Effect.flatMap(
748
746
  (
749
747
  response,
750
- ): Effect.Effect<
751
- Extract<PortResult, { readonly _tag: Tag }>,
752
- ExpectedFailure | ConversationStoreError
753
- > => {
748
+ ): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ConversationStoreError> => {
754
749
  if (response._tag === "PortFailed") {
755
750
  const failure = response.failure;
756
751
  if (isExpectedFailure(failure)) return Effect.fail(failure);
@@ -767,13 +762,13 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
767
762
  );
768
763
  }
769
764
  const result = response.result;
770
- if (!isResultTag(resultTag)(result)) {
765
+ if (!isExpectedResult(result)) {
771
766
  return Effect.fail(
772
767
  ConversationStoreError.make({
773
768
  operation,
774
769
  message:
775
770
  `The Conversation Object owning ${target} answered ${operation} with the ` +
776
- `mismatched result ${result._tag}; expected ${resultTag}.`,
771
+ `mismatched result ${result._tag}.`,
777
772
  }),
778
773
  );
779
774
  }
@@ -784,6 +779,7 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
784
779
  attributes: { operation, target },
785
780
  }),
786
781
  );
782
+ };
787
783
 
788
784
  const routed = ConversationStore.of({
789
785
  materialize: (request) =>
@@ -793,8 +789,8 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
793
789
  "conversation materialize",
794
790
  request.conversationId,
795
791
  StoreMaterializeCall.make({ request }),
796
- "StoreMaterializeResult",
797
- isFenceRejected,
792
+ StoreMaterializeResult,
793
+ FenceRejected,
798
794
  ).pipe(Effect.asVoid),
799
795
 
800
796
  append: (request) =>
@@ -804,8 +800,8 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
804
800
  "conversation append",
805
801
  request.conversationId,
806
802
  StoreAppendCall.make({ request }),
807
- "StoreAppendResult",
808
- isAppendFailure,
803
+ StoreAppendResult,
804
+ AppendPortFailure,
809
805
  ).pipe(Effect.map((reply) => reply.result)),
810
806
 
811
807
  read: (request) =>
@@ -816,8 +812,8 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
816
812
  "conversation read",
817
813
  request.conversationId,
818
814
  StoreReadPageCall.make({ request }),
819
- "StoreReadPageResult",
820
- isNotMaterialized,
815
+ StoreReadPageResult,
816
+ ConversationNotMaterialized,
821
817
  ).pipe(Effect.map((reply) => Stream.fromIterable(reply.records))),
822
818
  ),
823
819
 
@@ -828,8 +824,8 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
828
824
  "conversation inspect tail",
829
825
  request.conversationId,
830
826
  StoreInspectTailCall.make({ request }),
831
- "StoreInspectTailResult",
832
- isNotMaterialized,
827
+ StoreInspectTailResult,
828
+ ConversationNotMaterialized,
833
829
  ).pipe(Effect.map((reply) => reply.tail)),
834
830
 
835
831
  export: (request) =>
@@ -839,8 +835,8 @@ const makeRoutedStoreServices = Effect.fn("DoPortRouting.makeRoutedStoreServices
839
835
  "conversation export",
840
836
  request.conversationId,
841
837
  StoreExportCall.make({ request }),
842
- "StoreExportResult",
843
- isNotMaterialized,
838
+ StoreExportResult,
839
+ ConversationNotMaterialized,
844
840
  ).pipe(Effect.map((reply) => reply.export)),
845
841
 
846
842
  // Observation and checkpoints are lane-local by construction (plan §1.3): the closed