@effect-agent/storage-sqlite 0.1.0-beta.24 → 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/dist/index.mjs CHANGED
@@ -1062,6 +1062,9 @@ const OffsetText = Schema.String.check(Schema.isMaxLength(4 * 1024));
1062
1062
  const SQLITE_OFFSET_PREFIX = "effect-agent-sqlite@1:";
1063
1063
  const ZERO_CANONICAL_SEQUENCE = Schema.decodeSync(CanonicalSequence)(0);
1064
1064
  const isDigest = Schema.is(Digest);
1065
+ const isSqliteFenceRejected = Schema.is(SqliteFenceRejected);
1066
+ const isSqliteAppendConflict = Schema.is(SqliteAppendConflict);
1067
+ const isSqliteCheckpointConflict = Schema.is(SqliteCheckpointConflict);
1065
1068
  const storeError = (operation, error) => ConversationStoreError.make({
1066
1069
  cause: error,
1067
1070
  operation,
@@ -1290,8 +1293,8 @@ const makeServices$1 = Effect.fn("SqliteConversationStore.makeServices")(functio
1290
1293
  }).pipe(Effect.mapError((error) => schemaStoreError("encode canonical append", error)));
1291
1294
  yield* hitFailpoint("append:before");
1292
1295
  const result = yield* journal.append(rawRequest).pipe(Effect.mapError((error) => {
1293
- if (error instanceof SqliteFenceRejected) return mapFence(validated.conversationId, error);
1294
- if (error instanceof SqliteAppendConflict) return error.actualTailSequence !== void 0 && isDigest(error.actualTailDigest) ? AppendConflict.make({
1296
+ if (isSqliteFenceRejected(error)) return mapFence(validated.conversationId, error);
1297
+ if (isSqliteAppendConflict(error)) return error.actualTailSequence !== void 0 && isDigest(error.actualTailDigest) ? AppendConflict.make({
1295
1298
  conversationId: validated.conversationId,
1296
1299
  batchId: validated.batch.batchId,
1297
1300
  reason: error.reason,
@@ -1392,7 +1395,7 @@ const makeServices$1 = Effect.fn("SqliteConversationStore.makeServices")(functio
1392
1395
  checkpointJson
1393
1396
  });
1394
1397
  yield* hitFailpoint("save-checkpoint:before");
1395
- yield* journal.saveCheckpoint(raw).pipe(Effect.mapError((error) => error instanceof SqliteCheckpointConflict ? CheckpointRejected.make({
1398
+ yield* journal.saveCheckpoint(raw).pipe(Effect.mapError((error) => isSqliteCheckpointConflict(error) ? CheckpointRejected.make({
1396
1399
  conversationId: validated.checkpoint.conversationId,
1397
1400
  reason: "digest-mismatch"
1398
1401
  }) : storeError("save checkpoint", error)));
@@ -1452,10 +1455,7 @@ const storageFailpointLayer = (options) => options.failpoint === void 0 ? Sqlite
1452
1455
  * A composition-root convenience Layer for canonical Conversations. Durable accepted work is
1453
1456
  * served by the separate SubmissionLedger port.
1454
1457
  */
1455
- const layer = (options) => {
1456
- const sqlLayer = SqliteClient.layer({ filename: options.filename });
1457
- return conversationStoreLayer.pipe(Layer.provide(Layer.mergeAll(storageConfigLayer(options), storageFailpointLayer(options), sqlLayer, NodeCrypto.layer)));
1458
- };
1458
+ const layer = (options) => Layer.unwrap(Effect.map(SqliteStorageConfig, (config) => conversationStoreLayer.pipe(Layer.provide(Layer.mergeAll(Layer.succeed(SqliteStorageConfig)(config), storageFailpointLayer(options), SqliteClient.layer({ filename: options.filename }), NodeCrypto.layer))))).pipe(Layer.provide(storageConfigLayer(options)));
1459
1459
  /** Create an adapter-owned resumable observation offset for a known canonical sequence. */
1460
1460
  const observationOffsetAt = makeOffset;
1461
1461
  //#endregion
@@ -1465,6 +1465,11 @@ const BoundedIdentifier = Schema.NonEmptyString.check(Schema.isMaxLength(1024));
1465
1465
  const BoundedTimestamp = Schema.NonEmptyString.check(Schema.isMaxLength(128));
1466
1466
  const SCAN_PAGE_SIZE = 256;
1467
1467
  const EPOCH_ZERO = Schema.decodeSync(ProducerEpoch)(0);
1468
+ const RESUME_IMMEDIATELY = "resume-immediately";
1469
+ const SUSPENDED = "suspended";
1470
+ const NOT_WAITING = "not-waiting";
1471
+ const STILL_WAITING = "still-waiting";
1472
+ const WOKEN = "woken";
1468
1473
  var SubmissionRow = class extends Schema.Class("SubmissionRow")({
1469
1474
  submission_id: BoundedIdentifier,
1470
1475
  conversation_id: BoundedIdentifier,
@@ -1617,6 +1622,9 @@ const decodeUnknownResolutionIntent = Schema.decodeUnknownEffect(UnknownResoluti
1617
1622
  const decodeParentLinkage = Schema.decodeUnknownEffect(ParentLinkage);
1618
1623
  const decodeChildReservationSnapshotUnknown = Schema.decodeUnknownEffect(ChildBudgetReservationSnapshot);
1619
1624
  const decodeChildAttachmentSnapshot = Schema.decodeUnknownEffect(ChildAttachmentSnapshot);
1625
+ const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
1626
+ const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
1627
+ const isSqliteTransactionFailure = Schema.is(Schema.Union([SqliteStorageError, SqliteWriteContention]));
1620
1628
  /** Wrap an adapter-internal failure into the port's LedgerError without erasing its tag. */
1621
1629
  const internalFailure = (operation) => (error) => LedgerError.make({
1622
1630
  operation,
@@ -1654,7 +1662,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1654
1662
  * acquisition failures surface as LedgerError carrying the typed retryable
1655
1663
  * SqliteWriteContention (or SqliteStorageError) as cause.
1656
1664
  */
1657
- const inWriteTransaction = (operation, effect) => journal.withWriteTransaction(operation)(effect).pipe(Effect.mapError((error) => error instanceof SqliteStorageError || error instanceof SqliteWriteContention ? internalFailure(operation)(error) : error));
1665
+ const inWriteTransaction = (operation, effect) => journal.withWriteTransaction(operation)(effect).pipe(Effect.mapError((error) => isSqliteTransactionFailure(error) ? internalFailure(operation)(error) : error));
1658
1666
  const mintIdentifier = (prefix, operation) => crypto.randomUUIDv7.pipe(Effect.map((uuid) => `${prefix}-${uuid}`), Effect.mapError((error) => internalFailure(operation)(error)));
1659
1667
  const currentInstant = Effect.map(Clock.currentTimeMillis, (millis) => ({
1660
1668
  millis,
@@ -2506,7 +2514,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2506
2514
  if (validated.reason._tag === "ApprovalPending") {
2507
2515
  const decisions = yield* readApprovalDecisions(operation, validated.submissionId);
2508
2516
  const decided = new Set(decisions.map((row) => row.tool_call_id));
2509
- if (validated.reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return "resume-immediately";
2517
+ if (validated.reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return RESUME_IMMEDIATELY;
2510
2518
  } else {
2511
2519
  let allSettled = true;
2512
2520
  for (const child of validated.reason.children) {
@@ -2516,7 +2524,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2516
2524
  break;
2517
2525
  }
2518
2526
  }
2519
- if (allSettled) return "resume-immediately";
2527
+ if (allSettled) return RESUME_IMMEDIATELY;
2520
2528
  }
2521
2529
  const now = yield* currentInstant;
2522
2530
  yield* sql`
@@ -2531,7 +2539,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2531
2539
  DELETE FROM effect_agent_submission_ownership
2532
2540
  WHERE submission_id = ${validated.submissionId}
2533
2541
  `.pipe(Effect.mapError(sqlFailure(operation)));
2534
- return "suspended";
2542
+ return SUSPENDED;
2535
2543
  }));
2536
2544
  yield* hitFailpoint("ledger:suspend:after", operation);
2537
2545
  return outcome;
@@ -2659,12 +2667,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2659
2667
  });
2660
2668
  }
2661
2669
  const existing = (yield* readUnknownResolutions(operation, validated.submissionId)).find((row) => row.tool_call_id === validated.toolCallId);
2662
- if (existing !== void 0 && existing.resolution_json !== resolutionJson) return yield* UnknownResolutionConflict.make({
2670
+ const existingIntent = existing === void 0 ? void 0 : yield* unknownResolutionIntentFromRow(operation, existing);
2671
+ if (existingIntent !== void 0 && !equivalentUnknownResolution(existingIntent.resolution, validated.resolution)) return yield* UnknownResolutionConflict.make({
2663
2672
  submissionId: validated.submissionId,
2664
2673
  toolCallId: validated.toolCallId
2665
2674
  });
2666
2675
  let resolved;
2667
- if (existing !== void 0) resolved = yield* unknownResolutionIntentFromRow(operation, existing);
2676
+ if (existingIntent !== void 0) resolved = existingIntent;
2668
2677
  else {
2669
2678
  const now = yield* currentInstant;
2670
2679
  yield* sql`
@@ -2724,14 +2733,14 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2724
2733
  operation,
2725
2734
  message: `Child submission ${validated.childSubmissionId} has no recorded settlement.`
2726
2735
  });
2727
- if (parent.state !== "suspended" || parent.suspended_reason_json === null) return "not-waiting";
2736
+ if (parent.state !== "suspended" || parent.suspended_reason_json === null) return NOT_WAITING;
2728
2737
  const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(parent.suspended_reason_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", parent.submission_id, error.message)));
2729
- if (reason._tag !== "WaitingForChild") return "not-waiting";
2730
- if (!reason.children.some((entry) => entry.childSubmissionId === validated.childSubmissionId)) return "not-waiting";
2738
+ if (reason._tag !== "WaitingForChild") return NOT_WAITING;
2739
+ if (!reason.children.some((entry) => entry.childSubmissionId === validated.childSubmissionId)) return NOT_WAITING;
2731
2740
  for (const entry of reason.children) {
2732
2741
  const listed = yield* readSubmission(operation, entry.childSubmissionId);
2733
2742
  const reservation = yield* readReservation(operation, entry.childSubmissionId);
2734
- if (!(Option.isSome(listed) && (listed.value.state === "settled" || listed.value.state === "terminalizing" && Option.isSome(reservation)))) return "still-waiting";
2743
+ if (!(Option.isSome(listed) && (listed.value.state === "settled" || listed.value.state === "terminalizing" && Option.isSome(reservation)))) return STILL_WAITING;
2735
2744
  }
2736
2745
  yield* sql`
2737
2746
  UPDATE effect_agent_submissions
@@ -2741,7 +2750,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2741
2750
  suspended_at = NULL
2742
2751
  WHERE submission_id = ${validated.parentSubmissionId}
2743
2752
  `.pipe(Effect.mapError(sqlFailure(operation)));
2744
- return "woken";
2753
+ return WOKEN;
2745
2754
  }));
2746
2755
  yield* hitFailpoint("ledger:child-settled:after", operation);
2747
2756
  return outcome;
@@ -2754,13 +2763,14 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2754
2763
  const reserved = yield* inWriteTransaction(operation, Effect.gen(function* () {
2755
2764
  const existing = yield* readChildReservation(operation, validated.reservationId);
2756
2765
  if (Option.isSome(existing)) {
2757
- if (!(existing.value.parent_submission_id === validated.parentSubmissionId && existing.value.parent_tool_call_id === validated.parentToolCallId && existing.value.allocation_digest === validated.allocationDigest && existing.value.allocation_json === allocationJson)) return yield* ChildReservationConflict.make({
2766
+ const existingSnapshot = yield* childReservationSnapshotFromRow(operation, existing.value);
2767
+ if (!(existing.value.parent_submission_id === validated.parentSubmissionId && existing.value.parent_tool_call_id === validated.parentToolCallId && existing.value.allocation_digest === validated.allocationDigest && equivalentPersistedJson(existingSnapshot.allocation, validated.allocation))) return yield* ChildReservationConflict.make({
2758
2768
  reservationId: validated.reservationId,
2759
2769
  status: existing.value.status,
2760
2770
  message: "A reservation with this identity exists with a different parent Tool Call or allocation."
2761
2771
  });
2762
2772
  return ReservedChildBudget.make({
2763
- reservation: yield* childReservationSnapshotFromRow(operation, existing.value),
2773
+ reservation: existingSnapshot,
2764
2774
  replayed: true
2765
2775
  });
2766
2776
  }
@@ -2856,7 +2866,8 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2856
2866
  message: `Unknown child reservation ${validated.reservationId}.`
2857
2867
  });
2858
2868
  if (existing.value.status !== "reserved") {
2859
- if (existing.value.accounting_json === accountingJson) return yield* childReservationSnapshotFromRow(operation, existing.value);
2869
+ const existingSnapshot = yield* childReservationSnapshotFromRow(operation, existing.value);
2870
+ if (existingSnapshot.accounting !== void 0 && equivalentPersistedJson(existingSnapshot.accounting, validated.accounting)) return existingSnapshot;
2860
2871
  return yield* ChildReservationConflict.make({
2861
2872
  reservationId: validated.reservationId,
2862
2873
  status: existing.value.status,
@@ -3081,7 +3092,7 @@ const submissionLedgerLayer = Layer.effectContext(makeServices());
3081
3092
  * A composition-root convenience Layer for the durable Submission Ledger. Point it at the
3082
3093
  * same database file as the ConversationStore so claims fence the same producer epochs.
3083
3094
  */
3084
- const ledgerLayer = (options) => submissionLedgerLayer.pipe(Layer.provide(Layer.mergeAll(storageConfigLayer(options), storageFailpointLayer(options), SqliteClient.layer({ filename: options.filename }), NodeCrypto.layer)));
3095
+ const ledgerLayer = (options) => Layer.unwrap(Effect.map(SqliteStorageConfig, (config) => submissionLedgerLayer.pipe(Layer.provide(Layer.mergeAll(Layer.succeed(SqliteStorageConfig)(config), storageFailpointLayer(options), SqliteClient.layer({ filename: options.filename }), NodeCrypto.layer))))).pipe(Layer.provide(storageConfigLayer(options)));
3085
3096
  //#endregion
3086
3097
  export { CurrentSqliteStorageVersion, SqliteAppendConflict, SqliteCheckpointConflict, SqliteFenceRejected, SqliteLedgerError, SqliteStorageCompatibilityError, SqliteStorageConfig, SqliteStorageConfigValue, SqliteStorageCorruptionError, SqliteStorageError, SqliteStorageFailpoint, SqliteStorageFailpointError, SqliteStorageFailpointLocation, SqliteStorageFailpointTestControl, SqliteWriteContention, conversationStoreLayer, layer, ledgerLayer, observationOffsetAt, sqliteMigrations, storageConfigLayer, storageFailpointLayer, submissionLedgerLayer };
3087
3098