ignotum 0.0.12 → 0.0.13

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 (52) hide show
  1. package/dist/cli/bin.mjs +825 -403
  2. package/dist/cli/bin.mjs.map +1 -1
  3. package/dist/runtime/{api-DzcR7spt.js → api-CAgKDij7.js} +38 -2
  4. package/dist/runtime/api-CAgKDij7.js.map +1 -0
  5. package/dist/runtime/{api-5XSrIeqW.d.ts → api-Cv3hMbzo.d.ts} +4 -4
  6. package/dist/runtime/client.d.ts +39 -9
  7. package/dist/runtime/client.js +339 -60
  8. package/dist/runtime/client.js.map +1 -1
  9. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +154 -0
  10. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +1 -0
  11. package/dist/runtime/id-Bt9XWRGL.js +423 -0
  12. package/dist/runtime/id-Bt9XWRGL.js.map +1 -0
  13. package/dist/runtime/{id-Cs82tq9Q-CK-maMgN.d.ts → id-BzFHf3Wo-DGPCjgrf.d.ts} +2 -2
  14. package/dist/runtime/id-Dz0apuB3.d.ts +1 -0
  15. package/dist/runtime/{index-CrWg4Z0y.d.ts → index-D54flWtH.d.ts} +9 -5
  16. package/dist/runtime/internal/api.d.ts +1 -1
  17. package/dist/runtime/internal/api.js +1 -1
  18. package/dist/runtime/internal/host.d.ts +9 -6
  19. package/dist/runtime/internal/host.js +15 -6
  20. package/dist/runtime/internal/host.js.map +1 -1
  21. package/dist/runtime/internal/server.d.ts +1 -1
  22. package/dist/runtime/internal/server.js +1 -1
  23. package/dist/runtime/internal/types.d.ts +1 -1
  24. package/dist/runtime/internal/types.js +1 -1
  25. package/dist/runtime/{pagination-D-R9NR61-CpIoHFoI.d.ts → pagination-DnKg3dkI-r5ZUxBBx.d.ts} +31 -6
  26. package/dist/runtime/pagination-Dz0apuB3.d.ts +1 -0
  27. package/dist/runtime/result-DKAA4gpS.d.ts +1 -0
  28. package/dist/runtime/{schema-B9XxyRO8.js → schema-1Zs03-iS.js} +5 -3
  29. package/dist/runtime/schema-1Zs03-iS.js.map +1 -0
  30. package/dist/runtime/server.d.ts +6 -4
  31. package/dist/runtime/server.js +2 -2
  32. package/dist/runtime/server.js.map +1 -1
  33. package/dist/runtime/{sync-avN7NkcU.d.ts → sync-Bs8J3fIr.d.ts} +2 -2
  34. package/package.json +1 -1
  35. package/src/cli/agent-files.ts +6 -0
  36. package/src/client/hooks.ts +68 -0
  37. package/src/client/id.ts +259 -0
  38. package/src/client/index.ts +5 -2
  39. package/src/client/sync.ts +143 -10
  40. package/src/dev-runtime/functions.ts +111 -88
  41. package/src/dev-runtime/id.ts +175 -0
  42. package/src/dev-runtime/query-cache.ts +105 -0
  43. package/src/dev-runtime/sync.ts +149 -17
  44. package/src/server/index.ts +2 -0
  45. package/dist/runtime/api-DzcR7spt.js.map +0 -1
  46. package/dist/runtime/descriptor-XzDX2JDw-j3O6YHgW.js +0 -330
  47. package/dist/runtime/descriptor-XzDX2JDw-j3O6YHgW.js.map +0 -1
  48. package/dist/runtime/file-C1abuMgd.js +0 -173
  49. package/dist/runtime/file-C1abuMgd.js.map +0 -1
  50. package/dist/runtime/pagination-CX5IPbEi.d.ts +0 -1
  51. package/dist/runtime/result-DIjKM-p4.d.ts +0 -1
  52. package/dist/runtime/schema-B9XxyRO8.js.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { BrowserSession } from "./id.js";
1
2
  import {
2
3
  Context,
3
4
  Deferred,
@@ -7,6 +8,7 @@ import {
7
8
  HashMap,
8
9
  Layer,
9
10
  ManagedRuntime,
11
+ Option,
10
12
  Schedule,
11
13
  Schema,
12
14
  } from "effect";
@@ -59,6 +61,7 @@ interface QueryEntry {
59
61
  readonly listeners: Set<Listener>;
60
62
  readonly subscriptionId: SubscriptionId;
61
63
  generation: number;
64
+ dependsOnId?: boolean;
62
65
  revision: AppStateRevision | undefined;
63
66
  references: number;
64
67
  result: QueryResult<unknown, ErrorValue> | ClientInfrastructureError;
@@ -73,6 +76,8 @@ interface QueryObserver {
73
76
  type QueryLifecycleMessage = Extract<ClientMessage, { readonly type: "Subscribe" | "Unsubscribe" }>;
74
77
 
75
78
  interface QueryCache {
79
+ readonly invalidate: (ids?: ReadonlyArray<SubscriptionId>) => void;
80
+ readonly invalidatePrivate: () => void;
76
81
  readonly entries: () => Iterable<QueryEntry>;
77
82
  readonly getById: (id: SubscriptionId) => QueryEntry | undefined;
78
83
  readonly setResult: (
@@ -165,7 +170,19 @@ const makeQueryCache = (
165
170
  } satisfies QueryObserver;
166
171
  });
167
172
 
173
+ const clear = (entry: QueryEntry) => {
174
+ entry.result = pending();
175
+ entry.revision = undefined;
176
+ for (const listener of entry.listeners) listener();
177
+ };
168
178
  return {
179
+ invalidate: (ids) => {
180
+ for (const entry of queries.values())
181
+ if (ids === undefined || ids.includes(entry.subscriptionId)) clear(entry);
182
+ },
183
+ invalidatePrivate: () => {
184
+ for (const entry of queries.values()) if (entry.dependsOnId !== false) clear(entry);
185
+ },
169
186
  entries: () => queries.values(),
170
187
  getById: (id: SubscriptionId) => queriesById.get(id),
171
188
  observe,
@@ -251,6 +268,7 @@ interface SyncClientService {
251
268
  }
252
269
 
253
270
  interface PendingInvocation {
271
+ readonly sessionEpoch: string;
254
272
  readonly deferred: Deferred.Deferred<
255
273
  IgnotumResult<unknown, ErrorValue>,
256
274
  ClientInfrastructureError
@@ -270,10 +288,12 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
270
288
  SyncClient,
271
289
  Effect.gen(function* () {
272
290
  const ids = yield* IdGenerator;
291
+ const session = yield* BrowserSession;
273
292
  let invocations = HashMap.empty<InvocationId, PendingInvocation>();
274
293
  let rememberedHandshake: SyncHandshake | undefined;
275
294
  let reloadRequested = false;
276
295
  let writer: SocketWriter | undefined;
296
+ let sessionSuspended: Deferred.Deferred<void> | undefined;
277
297
 
278
298
  const encode = (message: ClientMessage) =>
279
299
  Schema.encodeEffect(ClientMessageJson)(message).pipe(
@@ -316,8 +336,17 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
316
336
  ),
317
337
  );
318
338
 
339
+ const unsubscribeSession = session.subscribe(() => {
340
+ if (session.current() === undefined) {
341
+ queryCache.invalidatePrivate();
342
+ writer = undefined;
343
+ if (sessionSuspended !== undefined) Deferred.doneUnsafe(sessionSuspended, Effect.void);
344
+ }
345
+ });
346
+ yield* Effect.addFinalizer(() => Effect.sync(unsubscribeSession));
347
+
319
348
  const rejectInvocation = (id: InvocationId, error: ClientInfrastructureError) => {
320
- const pendingInvocation = HashMap.getUnsafe(invocations, id);
349
+ const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, id));
321
350
  if (pendingInvocation === undefined) {
322
351
  return Effect.void;
323
352
  }
@@ -333,9 +362,52 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
333
362
  message: Exclude<ServerMessage, SyncHandshake | { readonly type: "Deployment" }>,
334
363
  ) {
335
364
  switch (message.type) {
365
+ case "Session": {
366
+ if (message.event === "Updated") {
367
+ const previous = session.current();
368
+ if (previous !== undefined && previous.sessionEpoch !== message.session.sessionEpoch)
369
+ return;
370
+ if (previous?.viewRevision !== message.session.viewRevision)
371
+ queryCache.invalidatePrivate();
372
+ session.accept(message.session, message.serverTime);
373
+ } else {
374
+ if (rememberedHandshake?.session?.sessionEpoch !== message.sessionEpoch) return;
375
+ session.suspend(
376
+ message.event === "Unavailable"
377
+ ? "Your ID session is temporarily unavailable."
378
+ : undefined,
379
+ );
380
+ if (message.event !== "Unavailable") {
381
+ yield* Effect.forEach(HashMap.keys(invocations), (id) =>
382
+ rejectInvocation(
383
+ id,
384
+ ServerError.make({
385
+ code: "SessionChanged",
386
+ message: "The session changed. This operation may already have completed.",
387
+ }),
388
+ ),
389
+ );
390
+ }
391
+ }
392
+ return;
393
+ }
394
+ case "Invalidate": {
395
+ if (message.sessionEpoch === session.current()?.sessionEpoch)
396
+ queryCache.invalidate(message.ids);
397
+ return;
398
+ }
336
399
  case "Snapshot": {
400
+ const current = session.current();
401
+ if (current === undefined || message.identity?.sessionEpoch !== current.sessionEpoch)
402
+ return;
403
+ if (
404
+ message.identity.dependsOnId &&
405
+ message.identity.viewRevision !== current.viewRevision
406
+ )
407
+ return;
337
408
  const entry = queryCache.getById(message.id);
338
409
  if (entry === undefined) return;
410
+ entry.dependsOnId = message.identity.dependsOnId;
339
411
  if (entry.revision !== undefined && message.revision < entry.revision) return;
340
412
  yield* resultFromWire(message.result, message.files ?? []).pipe(
341
413
  Effect.tap((result) =>
@@ -351,14 +423,16 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
351
423
  return;
352
424
  }
353
425
  case "Preparation": {
354
- const pendingInvocation = HashMap.getUnsafe(invocations, message.id);
426
+ if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
427
+ const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, message.id));
355
428
  if (pendingInvocation !== undefined) {
356
429
  yield* Deferred.succeed(pendingInvocation.prepared, message.uploads);
357
430
  }
358
431
  return;
359
432
  }
360
433
  case "Result": {
361
- const pendingInvocation = HashMap.getUnsafe(invocations, message.id);
434
+ if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
435
+ const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, message.id));
362
436
  if (pendingInvocation === undefined) return;
363
437
  yield* resultFromWire(message.result).pipe(
364
438
  Effect.flatMap((result) => {
@@ -372,6 +446,7 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
372
446
  return;
373
447
  }
374
448
  case "Error": {
449
+ if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
375
450
  const error =
376
451
  message.operation === undefined
377
452
  ? ServerError.make({
@@ -410,12 +485,18 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
410
485
  const connect = Effect.scoped(
411
486
  Effect.gen(function* () {
412
487
  if (reloadRequested) return yield* Effect.never;
413
- const socket = yield* Socket.makeWebSocket(socketUrl(), {
414
- closeCodeIsError: (code) => {
415
- if (shouldReloadForCloseCode(code)) requestReload();
416
- return false;
488
+ yield* session.bootstrap.pipe(
489
+ Effect.mapError((error) => ConnectionUnavailable.make({ message: error.message })),
490
+ );
491
+ const socket = yield* Socket.makeWebSocket(
492
+ `${socketUrl()}${session.socketParameters()}`,
493
+ {
494
+ closeCodeIsError: (code) => {
495
+ if (shouldReloadForCloseCode(code)) requestReload();
496
+ return false;
497
+ },
417
498
  },
418
- });
499
+ );
419
500
  const write = yield* socket.writer;
420
501
  const handshakeReceived = yield* Deferred.make<void>();
421
502
  let handshakeAccepted = false;
@@ -428,6 +509,41 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
428
509
  return;
429
510
  }
430
511
  if (decision === "Ignore") return;
512
+ if (handshake.session === undefined || handshake.serverTime === undefined) {
513
+ requestReload();
514
+ return;
515
+ }
516
+ if (
517
+ rememberedHandshake?.session !== undefined &&
518
+ rememberedHandshake.session.sessionEpoch !== handshake.session.sessionEpoch
519
+ ) {
520
+ queryCache.invalidatePrivate();
521
+ yield* Effect.forEach(HashMap.keys(invocations), (id) =>
522
+ rejectInvocation(
523
+ id,
524
+ ServerError.make({
525
+ code: "SessionChanged",
526
+ message: "The session changed. This operation may already have completed.",
527
+ }),
528
+ ),
529
+ );
530
+ } else if (
531
+ rememberedHandshake?.session?.viewRevision !== handshake.session.viewRevision
532
+ ) {
533
+ queryCache.invalidatePrivate();
534
+ }
535
+ yield* Effect.forEach(HashMap.entries(invocations), ([id, pending]) =>
536
+ pending.sessionEpoch === handshake.session!.sessionEpoch
537
+ ? Effect.void
538
+ : rejectInvocation(
539
+ id,
540
+ ServerError.make({
541
+ code: "SessionChanged",
542
+ message: "The session changed. This operation may already have completed.",
543
+ }),
544
+ ),
545
+ );
546
+ session.accept(handshake.session, handshake.serverTime);
431
547
  rememberedHandshake = handshake;
432
548
  handshakeAccepted = true;
433
549
  writer = write;
@@ -465,12 +581,19 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
465
581
  return yield* handleMessage(message);
466
582
  });
467
583
 
584
+ const suspended = yield* Deferred.make<void>();
585
+ sessionSuspended = suspended;
586
+ yield* Effect.addFinalizer(() =>
587
+ Effect.sync(() => {
588
+ if (sessionSuspended === suspended) sessionSuspended = undefined;
589
+ }),
590
+ );
468
591
  const run = yield* socket.runString(handleText).pipe(Effect.forkChild);
469
592
  yield* Effect.raceFirst(
470
593
  Deferred.await(handshakeReceived).pipe(Effect.timeout("10 seconds")),
471
594
  Fiber.join(run),
472
595
  );
473
- yield* Fiber.join(run);
596
+ yield* Effect.raceFirst(Fiber.join(run), Deferred.await(suspended));
474
597
  }),
475
598
  ).pipe(
476
599
  Effect.mapError((cause) =>
@@ -511,6 +634,12 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
511
634
 
512
635
  const mutate: SyncClientService["mutate"] = Effect.fn("SyncClient.mutate")(
513
636
  function* (functionAddress, args) {
637
+ const admittedSession =
638
+ session.current() ??
639
+ rememberedHandshake?.session ??
640
+ (yield* session.bootstrap.pipe(
641
+ Effect.mapError((error) => ConnectionUnavailable.make({ message: error.message })),
642
+ ));
514
643
  const invocationId = yield* ids.generate(InvocationId);
515
644
  const preparedArguments = yield* prepareMutationArguments(args).pipe(
516
645
  Effect.provideService(IdGenerator, ids),
@@ -559,6 +688,7 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
559
688
  files: preparedArguments.fileIds,
560
689
  };
561
690
  invocations = HashMap.set(invocations, invocationId, {
691
+ sessionEpoch: admittedSession.sessionEpoch,
562
692
  deferred,
563
693
  function: functionAddress,
564
694
  message,
@@ -630,7 +760,9 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
630
760
  }),
631
761
  { concurrency: 3, discard: true },
632
762
  );
633
- const pendingInvocation = HashMap.getUnsafe(invocations, invocationId);
763
+ const pendingInvocation = Option.getOrUndefined(
764
+ HashMap.get(invocations, invocationId),
765
+ );
634
766
  if (pendingInvocation === undefined) return yield* Deferred.await(deferred);
635
767
  invocations = HashMap.set(invocations, invocationId, {
636
768
  ...pendingInvocation,
@@ -663,6 +795,7 @@ export class SyncClient extends Context.Service<SyncClient, SyncClientService>()
663
795
 
664
796
  export const syncRuntime = ManagedRuntime.make(
665
797
  SyncClient.layer.pipe(
798
+ Layer.provideMerge(BrowserSession.layer),
666
799
  Layer.provideMerge(clientErrorReporterLayer),
667
800
  Layer.provideMerge(IdGenerator.layer),
668
801
  ),
@@ -1,3 +1,4 @@
1
+ import { makeIdContext, type IdContext, type SessionState } from "@ignotum/contracts/id";
1
2
  import {
2
3
  FunctionUnavailable,
3
4
  InvalidArguments,
@@ -61,7 +62,9 @@ import {
61
62
  export { FunctionUnavailable, InvalidArguments, UnknownFunction, WrongFunctionKind };
62
63
  export type { FunctionArguments, FunctionKind };
63
64
 
64
- export type RuntimeHandlerContext = LocalMutationContext | LocalQueryContext;
65
+ export type RuntimeHandlerContext = (LocalMutationContext | LocalQueryContext) & {
66
+ readonly id: IdContext;
67
+ };
65
68
  export type RuntimeFunctionDefinition = InternalRuntimeFunctionDefinition<RuntimeHandlerContext>;
66
69
 
67
70
  const loadDevelopmentEnvironment = Effect.fn("DevelopmentEnvironment.load")(function* (
@@ -243,6 +246,7 @@ export class FunctionExecutor extends Context.Service<
243
246
  functionAddress: FunctionAddress,
244
247
  kind: FunctionKind,
245
248
  resolved: ResolvedFunction,
249
+ identity?: SessionState,
246
250
  ) => Effect.Effect<RuntimeInvocationResult>;
247
251
  }
248
252
  >()("ignotum/dev-runtime/functions/FunctionExecutor") {
@@ -282,96 +286,113 @@ export class FunctionExecutor extends Context.Service<
282
286
  );
283
287
 
284
288
  return FunctionExecutor.of({
285
- execute: Effect.fn("FunctionExecutor.execute")(function* (functionAddress, kind, resolved) {
286
- const invoke = (context: RuntimeHandlerContext) =>
287
- Effect.gen(() =>
288
- resolved.definition.handler(
289
- Object.freeze({ ...context, env: resolved.environment ?? Object.freeze({}) }),
290
- resolved.args,
291
- ),
292
- ).pipe(
293
- Effect.matchEffect({
294
- onFailure: (error) => encodeFailure(resolved.definition, error),
295
- onSuccess: (value) => encodeSuccess(resolved.definition, value),
289
+ execute: Effect.fn("FunctionExecutor.execute")(
290
+ function* (functionAddress, kind, resolved, identity) {
291
+ let readId = false;
292
+ const id = makeIdContext(
293
+ Effect.sync(() => {
294
+ readId = true;
295
+ return identity?.user ?? null;
296
296
  }),
297
297
  );
298
+ const invoke = (context: LocalMutationContext | LocalQueryContext) =>
299
+ Effect.gen(() =>
300
+ resolved.definition.handler(
301
+ Object.freeze({ ...context, id, env: resolved.environment ?? Object.freeze({}) }),
302
+ resolved.args,
303
+ ),
304
+ ).pipe(
305
+ Effect.matchEffect({
306
+ onFailure: (error) => encodeFailure(resolved.definition, error),
307
+ onSuccess: (value) => encodeSuccess(resolved.definition, value),
308
+ }),
309
+ );
298
310
 
299
- const execution =
300
- kind === "Query"
301
- ? database.trackedQueryTransaction(resolved.schema, invoke).pipe(
302
- Effect.map(
303
- ({ dependencies, observedRevision, value }): RuntimeInvocationResult => {
304
- const files =
305
- value.type === "Success" && value.value !== undefined
306
- ? encodedFileOccurrencesOf(value.value)
307
- : [];
308
- const result = {
309
- type: "Query",
310
- result: value,
311
- dependencies,
312
- observedRevision,
313
- } as const;
314
- return files.length === 0 ? result : { ...result, files };
315
- },
316
- ),
317
- )
318
- : mutationSemaphore.withPermits(1)(
319
- database
320
- .trackedMutationTransaction(resolved.schema, (context) =>
321
- invoke(context).pipe(
322
- Effect.flatMap((result) =>
323
- result.type === "Failure"
324
- ? Effect.fail(MutationApplicationFailure.make({ error: result.error }))
325
- : Effect.succeed(result),
326
- ),
327
- ),
328
- )
329
- .pipe(
330
- Effect.map(
331
- ({ committedRevision, invalidations, value }): RuntimeInvocationResult => ({
332
- type: "Mutation",
311
+ const execution =
312
+ kind === "Query"
313
+ ? database.trackedQueryTransaction(resolved.schema, invoke).pipe(
314
+ Effect.map(
315
+ ({ dependencies, observedRevision, value }): RuntimeInvocationResult => {
316
+ const files =
317
+ value.type === "Success" && value.value !== undefined
318
+ ? encodedFileOccurrencesOf(value.value)
319
+ : [];
320
+ const result = {
321
+ type: "Query",
333
322
  result: value,
334
- invalidations,
335
- committedRevision,
336
- }),
337
- ),
338
- Effect.catchTags({
339
- MutationApplicationFailure: ({ error }) =>
340
- Effect.succeed<RuntimeInvocationResult>({
323
+ dependencies: readId
324
+ ? [...dependencies, { type: "Id" as const }]
325
+ : dependencies,
326
+ observedRevision,
327
+ } as const;
328
+ return files.length === 0 ? result : { ...result, files };
329
+ },
330
+ ),
331
+ )
332
+ : mutationSemaphore.withPermits(1)(
333
+ database
334
+ .trackedMutationTransaction(resolved.schema, (context) =>
335
+ invoke(context).pipe(
336
+ Effect.flatMap((result) =>
337
+ result.type === "Failure"
338
+ ? Effect.fail(
339
+ MutationApplicationFailure.make({ error: result.error }),
340
+ )
341
+ : Effect.succeed(result),
342
+ ),
343
+ ),
344
+ )
345
+ .pipe(
346
+ Effect.map(
347
+ ({
348
+ committedRevision,
349
+ invalidations,
350
+ value,
351
+ }): RuntimeInvocationResult => ({
341
352
  type: "Mutation",
342
- result: { type: "Failure", error },
353
+ result: value,
354
+ invalidations,
355
+ committedRevision,
343
356
  }),
344
- }),
345
- ),
346
- );
347
-
348
- return yield* execution.pipe(
349
- Effect.orDie,
350
- Effect.catchCauseIf(
351
- (cause) => !Cause.hasInterruptsOnly(cause),
352
- (cause) =>
353
- Effect.gen(function* () {
354
- const requestId = yield* ids.generate(RequestId);
355
- yield* Effect.logError(`${functionAddress} failed during execution.`).pipe(
356
- Effect.annotateLogs({ cause, function: functionAddress, requestId }),
357
+ ),
358
+ Effect.catchTags({
359
+ MutationApplicationFailure: ({ error }) =>
360
+ Effect.succeed<RuntimeInvocationResult>({
361
+ type: "Mutation",
362
+ result: { type: "Failure", error },
363
+ }),
364
+ }),
365
+ ),
357
366
  );
358
- const result = {
359
- type: "Failure" as const,
360
- error: { _tag: "InternalServerError", requestId },
361
- };
362
- if (kind === "Mutation") {
363
- return { type: "Mutation" as const, result };
364
- }
365
- return {
366
- type: "Query" as const,
367
- result,
368
- dependencies: [],
369
- observedRevision: yield* database.revision,
370
- };
371
- }),
372
- ),
373
- );
374
- }),
367
+
368
+ return yield* execution.pipe(
369
+ Effect.orDie,
370
+ Effect.catchCauseIf(
371
+ (cause) => !Cause.hasInterruptsOnly(cause),
372
+ (cause) =>
373
+ Effect.gen(function* () {
374
+ const requestId = yield* ids.generate(RequestId);
375
+ yield* Effect.logError(`${functionAddress} failed during execution.`).pipe(
376
+ Effect.annotateLogs({ cause, function: functionAddress, requestId }),
377
+ );
378
+ const result = {
379
+ type: "Failure" as const,
380
+ error: { _tag: "InternalServerError", requestId },
381
+ };
382
+ if (kind === "Mutation") {
383
+ return { type: "Mutation" as const, result };
384
+ }
385
+ return {
386
+ type: "Query" as const,
387
+ result,
388
+ dependencies: readId ? [{ type: "Id" as const }] : [],
389
+ observedRevision: yield* database.revision,
390
+ };
391
+ }),
392
+ ),
393
+ );
394
+ },
395
+ ),
375
396
  });
376
397
  }),
377
398
  );
@@ -384,10 +405,12 @@ export const functionRuntimeLayer = Layer.effect(
384
405
  const registry = yield* FunctionRegistry;
385
406
  const executor = yield* FunctionExecutor;
386
407
  return FunctionRuntime.of({
387
- prepare: Effect.fn("DevFunctionRuntime.prepare")(function* (functionAddress, kind, args) {
388
- const resolved = yield* registry.resolve(functionAddress, kind, args);
389
- return { execute: executor.execute(functionAddress, kind, resolved) };
390
- }),
408
+ prepare: Effect.fn("DevFunctionRuntime.prepare")(
409
+ function* (functionAddress, kind, args, identity) {
410
+ const resolved = yield* registry.resolve(functionAddress, kind, args);
411
+ return { execute: executor.execute(functionAddress, kind, resolved, identity) };
412
+ },
413
+ ),
391
414
  });
392
415
  }),
393
416
  );