@yielded/agent-platform-cloudflare 0.1.0-beta.175 → 0.1.0-beta.176

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/Alarm.ts CHANGED
@@ -217,7 +217,7 @@ type MaintenanceCheckpoint = {
217
217
  readonly mutations: Parameters<
218
218
  Parameters<ThreadMaintenanceAlarm["Service"]["transaction"]>[0]
219
219
  >[0];
220
- readonly requireActive: Effect.Effect<void, DurableAlarmError>;
220
+ readonly isActive: (fiber: Fiber.Fiber<unknown, unknown>) => boolean;
221
221
  };
222
222
 
223
223
  const CurrentCheckpoint = Context.Reference<MaintenanceCheckpoint | undefined>(
@@ -232,31 +232,27 @@ const makeAlarmOperation = Effect.gen(function* () {
232
232
  const { ctx } = yield* DurableObjectContext;
233
233
  const queue = DueQueue.make(ctx.storage);
234
234
 
235
- const transaction: SqlClient["withTransaction"] = (body) =>
235
+ const transaction = <A, E, R>(
236
+ body: (owner: Fiber.Fiber<unknown, unknown>) => Effect.Effect<A, E, R>,
237
+ ): Effect.Effect<A, E | SqlError, R> =>
236
238
  alarms
237
239
  .transaction(
238
240
  (mutations) =>
239
241
  Effect.withFiber((owner) => {
240
242
  let active = true;
241
243
 
242
- const requireActive = Effect.withFiber((fiber) =>
243
- active && fiber === owner
244
- ? Effect.void
245
- : Effect.fail(
246
- DurableAlarmError.make({
247
- operation: "enroll maintenance source",
248
- message: "Source intent requires its active transaction callback's fiber",
249
- }),
250
- ),
251
- );
252
-
253
- return body.pipe(
254
- Effect.provideService(CurrentCheckpoint, { mutations, requireActive }),
255
- Effect.ensuring(
256
- Effect.sync(() => {
257
- active = false;
244
+ return Effect.onExitPrimitive(
245
+ body(owner).pipe(
246
+ Effect.provideService(CurrentCheckpoint, {
247
+ mutations,
248
+ isActive: (fiber) => active && fiber === owner,
258
249
  }),
259
250
  ),
251
+ () => {
252
+ active = false;
253
+
254
+ return undefined;
255
+ },
260
256
  );
261
257
  }),
262
258
  { sqlClient: sql },
@@ -276,7 +272,9 @@ const makeAlarmOperation = Effect.gen(function* () {
276
272
  );
277
273
 
278
274
  const withTransaction = <A, E, R>(body: Effect.Effect<A, E, R>) =>
279
- Effect.flatMap(Effect.serviceOption(sql.transactionService), (ambient) => {
275
+ Effect.withFiber((fiber) => {
276
+ const ambient = Context.getOption(fiber.context, sql.transactionService);
277
+
280
278
  if (ambient._tag === "Some" && !DueQueue.ownsTransaction(ctx.storage, ambient.value[1]))
281
279
  return Effect.fail(
282
280
  SqlError.make({
@@ -287,53 +285,53 @@ const makeAlarmOperation = Effect.gen(function* () {
287
285
  }),
288
286
  }),
289
287
  );
288
+ let frame: ReturnType<typeof queue.begin> | undefined;
290
289
 
291
290
  return Effect.uninterruptibleMask((restore) =>
292
- Effect.suspend(() => {
293
- let frame: ReturnType<typeof queue.begin> | undefined;
291
+ Effect.onExitPrimitive(
292
+ transaction((owner) => {
293
+ const current = Context.getOption(owner.context, sql.transactionService);
294
294
 
295
- return transaction(
296
- Effect.flatMap(Effect.serviceOption(sql.transactionService), (transaction) =>
297
- Effect.gen(function* () {
298
- if (transaction._tag === "None")
299
- return yield* SqlError.make({
295
+ if (current._tag === "None")
296
+ return SqlError.make({
297
+ reason: UnknownError.make({
298
+ operation: "begin maintenance source transaction",
299
+ message: "SQL transaction unavailable",
300
+ cause: "SQL transaction unavailable",
301
+ }),
302
+ });
303
+ const started = queue.begin(current.value[1]);
304
+
305
+ frame = started;
306
+
307
+ return Effect.flatMap(restore(body), (value) =>
308
+ Effect.try({
309
+ try: () => {
310
+ queue.flush(started);
311
+
312
+ return value;
313
+ },
314
+ catch: (cause) =>
315
+ SqlError.make({
300
316
  reason: UnknownError.make({
301
- operation: "begin maintenance source transaction",
302
- message: "SQL transaction unavailable",
303
- cause: "SQL transaction unavailable",
304
- }),
305
- });
306
- frame = queue.begin(transaction.value[1]);
307
- }),
308
- ).pipe(
309
- Effect.andThen(restore(body)),
310
- Effect.tap(() =>
311
- Effect.try({
312
- try: () => {
313
- if (frame !== undefined) queue.flush(frame);
314
- },
315
- catch: (cause) =>
316
- SqlError.make({
317
- reason: UnknownError.make({
318
- cause,
319
- operation: "flush maintenance due queue",
320
- message: "Maintenance intent could not commit",
321
- }),
317
+ cause,
318
+ operation: "flush maintenance due queue",
319
+ message: "Maintenance intent could not commit",
322
320
  }),
323
- }),
324
- ),
325
- ),
326
- ).pipe(
327
- Effect.onExit((exit) =>
328
- Effect.sync(() => {
329
- if (frame !== undefined) queue.finish(frame, Exit.isSuccess(exit));
321
+ }),
330
322
  }),
331
- ),
332
- // A released permit can resume another transaction. Do not auto-yield
333
- // between the driver's settlement and removing this transaction's frame.
334
- Effect.provideService(Scheduler.PreventSchedulerYield, true),
335
- );
336
- }),
323
+ );
324
+ }),
325
+ (exit) => {
326
+ if (frame !== undefined) queue.finish(frame, Exit.isSuccess(exit));
327
+
328
+ return undefined;
329
+ },
330
+ ).pipe(
331
+ // A released permit can resume another transaction. Do not auto-yield
332
+ // between the driver's settlement and removing this transaction's frame.
333
+ Effect.provideService(Scheduler.PreventSchedulerYield, true),
334
+ ),
337
335
  );
338
336
  }).pipe(Effect.provideService(SqlClient, sql));
339
337
 
@@ -352,32 +350,33 @@ const makeAlarmOperation = Effect.gen(function* () {
352
350
  }),
353
351
  );
354
352
 
355
- return tx.requireActive.pipe(
356
- Effect.andThen(
357
- Effect.suspend(() => {
358
- let checkpoint: ReturnType<DurableObjectAlarm.AlarmScheduler["scheduleAlarm"]> =
359
- Effect.void;
353
+ if (!tx.isActive(fiber))
354
+ return Effect.fail(
355
+ DurableAlarmError.make({
356
+ operation: "enroll maintenance source",
357
+ message: "Source intent requires its active transaction callback's fiber",
358
+ }),
359
+ );
360
360
 
361
- const intent: CheckpointIntent = {
362
- at: (at) => {
363
- checkpoint = tx.mutations.scheduleAlarm(maintenanceAlarmInput(at));
364
- },
365
- earlier: (at) => {
366
- checkpoint = tx.mutations.scheduleAlarmEarlier(maintenanceAlarmInput(at));
367
- },
368
- cancel: () => {
369
- checkpoint = tx.mutations.cancelAlarm(maintenanceAlarmRef);
370
- },
371
- };
361
+ let checkpoint: ReturnType<DurableObjectAlarm.AlarmScheduler["scheduleAlarm"]> = Effect.void;
372
362
 
373
- // The SQL/alarm callback already owns the native transaction; KV uses the same storage.
374
- return Effect.tryPromise({
375
- try: () => execute(ctx.storage, intent),
376
- catch: alarmFailure(operation),
377
- }).pipe(Effect.tap(() => checkpoint));
378
- }),
379
- ),
380
- );
363
+ const intent: CheckpointIntent = {
364
+ at: (at) => {
365
+ checkpoint = tx.mutations.scheduleAlarm(maintenanceAlarmInput(at));
366
+ },
367
+ earlier: (at) => {
368
+ checkpoint = tx.mutations.scheduleAlarmEarlier(maintenanceAlarmInput(at));
369
+ },
370
+ cancel: () => {
371
+ checkpoint = tx.mutations.cancelAlarm(maintenanceAlarmRef);
372
+ },
373
+ };
374
+
375
+ // The SQL/alarm callback already owns the native transaction; KV uses the same storage.
376
+ return Effect.tryPromise({
377
+ try: () => execute(ctx.storage, intent),
378
+ catch: alarmFailure(operation),
379
+ }).pipe(Effect.tap(() => checkpoint));
381
380
  });
382
381
 
383
382
  return Effect.withFiber((fiber) => {
@@ -388,7 +387,7 @@ const makeAlarmOperation = Effect.gen(function* () {
388
387
 
389
388
  // Preserve eager writes and invalidation for an unwrapped host SQL transaction.
390
389
  return ambient._tag === "Some" && !DueQueue.buffering(ctx.storage)
391
- ? transaction(commit).pipe(
390
+ ? transaction(() => commit).pipe(
392
391
  Effect.ensuring(Effect.sync(() => DueQueue.invalidate(ctx.storage))),
393
392
  )
394
393
  : withTransaction(commit);
@@ -7,6 +7,7 @@ import {
7
7
  import { submissionLedgerLayer } from "@yielded/agent-storage-cloudflare/do-submission-ledger";
8
8
  import {
9
9
  threadStoreLayer,
10
+ journalLayer,
10
11
  storageConfigLayer,
11
12
  storageFailpointLayer,
12
13
  sqlOwnerLayer,
@@ -83,8 +84,15 @@ import {
83
84
  type WorkerStopCommand,
84
85
  } from "@yielded/agent/submission-ledger";
85
86
  import { ThreadProjectionMaintenance } from "@yielded/agent/thread-projection-maintenance";
86
- import type { ThreadReader } from "@yielded/agent/thread-store";
87
- import { AppendResult, ThreadStoreError, ThreadStore } from "@yielded/agent/thread-store";
87
+ import {
88
+ type ThreadReader,
89
+ AppendResult,
90
+ type AppendRequest,
91
+ type ThreadStoreFailure,
92
+ PreparedAppend,
93
+ ThreadStoreError,
94
+ ThreadStore,
95
+ } from "@yielded/agent/thread-store";
88
96
  import { ToolReconciler } from "@yielded/agent/tool-reconciler";
89
97
  import { type WakeScheduler } from "@yielded/agent/wake-scheduler";
90
98
  import {
@@ -715,6 +723,7 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
715
723
  // The RPC executor must never receive routed ports and bounce requests between Objects.
716
724
  const rawLocalPorts = Layer.suspend(() =>
717
725
  Layer.mergeAll(threadStoreLayer, submissionLedgerLayer).pipe(
726
+ Layer.provide(journalLayer),
718
727
  Layer.provide(localInfrastructure),
719
728
  Layer.provide(sourceProgress),
720
729
  ),
@@ -785,6 +794,54 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
785
794
  // Publication remains outside this local source/index critical section.
786
795
  const sourceCommits = yield* Semaphore.make(1);
787
796
 
797
+ const observeAppend = (
798
+ request: AppendRequest,
799
+ body: Effect.Effect<AppendResult, ThreadStoreFailure>,
800
+ ) =>
801
+ mutations
802
+ .withMutation(
803
+ sourceCommits
804
+ .withPermit(
805
+ body.pipe(
806
+ Effect.tap((result) =>
807
+ index
808
+ .applyCommitted(request, result)
809
+ .pipe(
810
+ Effect.catchCause((cause) =>
811
+ Cause.hasInterrupts(cause)
812
+ ? Effect.interrupt
813
+ : Effect.logError(
814
+ "Thread projection deferred after source commit",
815
+ cause,
816
+ ),
817
+ ),
818
+ ),
819
+ ),
820
+ ),
821
+ )
822
+ .pipe(Effect.tap(() => afterCommit)),
823
+ {
824
+ lanes: [
825
+ ...(options.projection === undefined ? [] : [DueQueue.Projection]),
826
+ ...(options.lifecyclePublication === undefined ||
827
+ !request.batch.records.some((record) =>
828
+ Schema.is(Schema.toType(LifecyclePublicationFact))(record.payload),
829
+ )
830
+ ? []
831
+ : [DueQueue.Lifecycle]),
832
+ ],
833
+ },
834
+ )
835
+ .pipe(
836
+ Effect.catchTag("DurableAlarmError", (cause) =>
837
+ ThreadStoreError.make({
838
+ operation: "prearm publication append",
839
+ message: cause.message,
840
+ cause,
841
+ }),
842
+ ),
843
+ );
844
+
788
845
  // Every runtime-owned producer prearms too: a crash between commit and invalidation
789
846
  // leaves a NEW, uncertified generation. Source errors keep their native port types.
790
847
  const observedStore = ThreadStore.of({
@@ -807,52 +864,7 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
807
864
  ),
808
865
  },
809
866
  }),
810
- append: (request) =>
811
- mutations
812
- .withMutation(
813
- sourceCommits
814
- .withPermit(
815
- store
816
- .append(request)
817
- .pipe(
818
- Effect.tap((result) =>
819
- index
820
- .applyCommitted(request, result)
821
- .pipe(
822
- Effect.catchCause((cause) =>
823
- Cause.hasInterrupts(cause)
824
- ? Effect.interrupt
825
- : Effect.logError(
826
- "Thread projection deferred after source commit",
827
- cause,
828
- ),
829
- ),
830
- ),
831
- ),
832
- ),
833
- )
834
- .pipe(Effect.tap(() => afterCommit)),
835
- {
836
- lanes: [
837
- ...(options.projection === undefined ? [] : [DueQueue.Projection]),
838
- ...(options.lifecyclePublication === undefined ||
839
- !request.batch.records.some((record) =>
840
- Schema.is(Schema.toType(LifecyclePublicationFact))(record.payload),
841
- )
842
- ? []
843
- : [DueQueue.Lifecycle]),
844
- ],
845
- },
846
- )
847
- .pipe(
848
- Effect.catchTag("DurableAlarmError", (cause) =>
849
- ThreadStoreError.make({
850
- operation: "prearm publication append",
851
- message: cause.message,
852
- cause,
853
- }),
854
- ),
855
- ),
867
+ append: (request) => observeAppend(request, store.append(request)),
856
868
  });
857
869
 
858
870
  const observeIntent = <A, Failure>(
@@ -906,6 +918,7 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
906
918
  );
907
919
 
908
920
  const stopWorker = ledger.stopWorker;
921
+ const appendInput = ledger.appendInput;
909
922
 
910
923
  return Context.make(ThreadStore, observedStore).pipe(
911
924
  Context.add(StorageOwner, storageOwner),
@@ -967,6 +980,14 @@ const sharedLayer = <A, E, R, PE = never, PR = never>(
967
980
  }),
968
981
  Context.add(SubmissionLedger, {
969
982
  ...ledger,
983
+ ...(appendInput === undefined
984
+ ? {}
985
+ : {
986
+ appendInput: (submissionId, input) =>
987
+ Effect.flatMap(PreparedAppend.capture(input), (request) =>
988
+ observeAppend(request, appendInput(submissionId, request)),
989
+ ),
990
+ }),
970
991
  ...(options.hostLanesForMutation === undefined &&
971
992
  options.lifecyclePublication === undefined
972
993
  ? {}