@rivetkit/effect 0.0.0-main.879b6a2 → 0.0.0-main.b2d3b94

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 (67) hide show
  1. package/dist/Actor.d.ts +2 -3
  2. package/dist/Actor.d.ts.map +1 -1
  3. package/dist/Actor.js +2 -2
  4. package/dist/Actor.js.map +1 -1
  5. package/dist/Client.d.ts.map +1 -1
  6. package/dist/Client.js +6 -4
  7. package/dist/Client.js.map +1 -1
  8. package/dist/Registry.d.ts +69 -7
  9. package/dist/Registry.d.ts.map +1 -1
  10. package/dist/Registry.js +93 -40
  11. package/dist/Registry.js.map +1 -1
  12. package/dist/RivetError.d.ts +45 -3
  13. package/dist/RivetError.d.ts.map +1 -1
  14. package/dist/RivetError.js +112 -0
  15. package/dist/RivetError.js.map +1 -1
  16. package/dist/RivetLogger.d.ts +41 -0
  17. package/dist/RivetLogger.d.ts.map +1 -0
  18. package/dist/RivetLogger.js +41 -0
  19. package/dist/RivetLogger.js.map +1 -0
  20. package/dist/State.d.ts +92 -56
  21. package/dist/State.d.ts.map +1 -1
  22. package/dist/State.js +51 -57
  23. package/dist/State.js.map +1 -1
  24. package/dist/internal/ActionDispatcher.d.ts.map +1 -1
  25. package/dist/internal/ActionDispatcher.js.map +1 -1
  26. package/dist/internal/ActorInstanceManager.d.ts.map +1 -1
  27. package/dist/internal/ActorInstanceManager.js +4 -4
  28. package/dist/internal/ActorInstanceManager.js.map +1 -1
  29. package/dist/internal/ActorStateAdapter.d.ts +1 -1
  30. package/dist/internal/ActorStateAdapter.d.ts.map +1 -1
  31. package/dist/internal/ActorStateAdapter.js +2 -7
  32. package/dist/internal/ActorStateAdapter.js.map +1 -1
  33. package/dist/internal/StateOptions.d.ts +1 -0
  34. package/dist/internal/StateOptions.d.ts.map +1 -1
  35. package/dist/internal/logging.d.ts +6 -7
  36. package/dist/internal/logging.d.ts.map +1 -1
  37. package/dist/internal/logging.js +78 -102
  38. package/dist/internal/logging.js.map +1 -1
  39. package/dist/mod.d.ts +1 -1
  40. package/dist/mod.d.ts.map +1 -1
  41. package/dist/mod.js +1 -1
  42. package/dist/mod.js.map +1 -1
  43. package/package.json +8 -2
  44. package/src/Actor.test-d.ts +38 -0
  45. package/src/Actor.test.ts +4 -1
  46. package/src/Actor.ts +29 -4
  47. package/src/Client.test.ts +22 -26
  48. package/src/Client.ts +6 -4
  49. package/src/Registry.test.ts +12 -23
  50. package/src/Registry.ts +131 -70
  51. package/src/RivetError.test.ts +11 -0
  52. package/src/RivetError.ts +119 -0
  53. package/src/RivetLogger.ts +60 -0
  54. package/src/State.test.ts +163 -4
  55. package/src/State.ts +293 -97
  56. package/src/internal/ActionDispatcher.ts +8 -11
  57. package/src/internal/ActorInstanceManager.ts +32 -38
  58. package/src/internal/ActorStateAdapter.ts +5 -11
  59. package/src/internal/StateOptions.ts +4 -0
  60. package/src/internal/logging.test.ts +107 -121
  61. package/src/internal/logging.ts +103 -143
  62. package/src/mod.ts +1 -1
  63. package/dist/Logger.d.ts +0 -29
  64. package/dist/Logger.d.ts.map +0 -1
  65. package/dist/Logger.js +0 -31
  66. package/dist/Logger.js.map +0 -1
  67. package/src/Logger.ts +0 -43
package/src/RivetError.ts CHANGED
@@ -31,6 +31,9 @@ export class Forbidden extends Schema.TaggedErrorClass<Forbidden>(
31
31
  get public() {
32
32
  return this.cause.public;
33
33
  }
34
+ get rayId() {
35
+ return this.cause.rayId;
36
+ }
34
37
  get isRetryable(): boolean {
35
38
  return false;
36
39
  }
@@ -63,6 +66,9 @@ export class ActorNotFound extends Schema.TaggedErrorClass<ActorNotFound>(
63
66
  get public() {
64
67
  return this.cause.public;
65
68
  }
69
+ get rayId() {
70
+ return this.cause.rayId;
71
+ }
66
72
  get isRetryable(): boolean {
67
73
  return false;
68
74
  }
@@ -95,6 +101,9 @@ export class ActorStopping extends Schema.TaggedErrorClass<ActorStopping>(
95
101
  get public() {
96
102
  return this.cause.public;
97
103
  }
104
+ get rayId() {
105
+ return this.cause.rayId;
106
+ }
98
107
  get isRetryable(): boolean {
99
108
  return true;
100
109
  }
@@ -127,6 +136,9 @@ export class ActorRestarting extends Schema.TaggedErrorClass<ActorRestarting>(
127
136
  get public() {
128
137
  return this.cause.public;
129
138
  }
139
+ get rayId() {
140
+ return this.cause.rayId;
141
+ }
130
142
  get isRetryable(): boolean {
131
143
  return true;
132
144
  }
@@ -167,6 +179,9 @@ export class ActionNotFound extends Schema.TaggedErrorClass<ActionNotFound>(
167
179
  get public() {
168
180
  return this.cause.public;
169
181
  }
182
+ get rayId() {
183
+ return this.cause.rayId;
184
+ }
170
185
  get isRetryable(): boolean {
171
186
  return false;
172
187
  }
@@ -197,6 +212,9 @@ export class ActionTimedOut extends Schema.TaggedErrorClass<ActionTimedOut>(
197
212
  get public() {
198
213
  return this.cause.public;
199
214
  }
215
+ get rayId() {
216
+ return this.cause.rayId;
217
+ }
200
218
  get isRetryable(): boolean {
201
219
  return true;
202
220
  }
@@ -229,6 +247,9 @@ export class ActionAborted extends Schema.TaggedErrorClass<ActionAborted>(
229
247
  get public() {
230
248
  return this.cause.public;
231
249
  }
250
+ get rayId() {
251
+ return this.cause.rayId;
252
+ }
232
253
  get isRetryable(): boolean {
233
254
  return false;
234
255
  }
@@ -261,6 +282,9 @@ export class ActorOverloaded extends Schema.TaggedErrorClass<ActorOverloaded>(
261
282
  get public() {
262
283
  return this.cause.public;
263
284
  }
285
+ get rayId() {
286
+ return this.cause.rayId;
287
+ }
264
288
  get isRetryable(): boolean {
265
289
  return true;
266
290
  }
@@ -293,6 +317,9 @@ export class IncomingMessageTooLong extends Schema.TaggedErrorClass<IncomingMess
293
317
  get public() {
294
318
  return this.cause.public;
295
319
  }
320
+ get rayId() {
321
+ return this.cause.rayId;
322
+ }
296
323
  get isRetryable(): boolean {
297
324
  return false;
298
325
  }
@@ -325,6 +352,9 @@ export class OutgoingMessageTooLong extends Schema.TaggedErrorClass<OutgoingMess
325
352
  get public() {
326
353
  return this.cause.public;
327
354
  }
355
+ get rayId() {
356
+ return this.cause.rayId;
357
+ }
328
358
  get isRetryable(): boolean {
329
359
  return false;
330
360
  }
@@ -357,6 +387,9 @@ export class InvalidEncoding extends Schema.TaggedErrorClass<InvalidEncoding>(
357
387
  get public() {
358
388
  return this.cause.public;
359
389
  }
390
+ get rayId() {
391
+ return this.cause.rayId;
392
+ }
360
393
  get isRetryable(): boolean {
361
394
  return false;
362
395
  }
@@ -389,6 +422,9 @@ export class InvalidRequest extends Schema.TaggedErrorClass<InvalidRequest>(
389
422
  get public() {
390
423
  return this.cause.public;
391
424
  }
425
+ get rayId() {
426
+ return this.cause.rayId;
427
+ }
392
428
  get isRetryable(): boolean {
393
429
  return false;
394
430
  }
@@ -421,6 +457,44 @@ export class GuardActorReadyTimeout extends Schema.TaggedErrorClass<GuardActorRe
421
457
  get public() {
422
458
  return this.cause.public;
423
459
  }
460
+ get rayId() {
461
+ return this.cause.rayId;
462
+ }
463
+ get isRetryable(): boolean {
464
+ return true;
465
+ }
466
+ }
467
+
468
+ export class GuardActorWakeRetriesExceeded extends Schema.TaggedErrorClass<GuardActorWakeRetriesExceeded>(
469
+ `${ReasonTypeId}/GuardActorWakeRetriesExceeded`,
470
+ )("GuardActorWakeRetriesExceeded", {
471
+ cause: Schema.instanceOf(RivetkitErrors.RivetError),
472
+ }) {
473
+ readonly [ReasonTypeId] = ReasonTypeId;
474
+ override get message() {
475
+ return this.cause.message;
476
+ }
477
+ get group() {
478
+ return this.cause.group;
479
+ }
480
+ get code() {
481
+ return this.cause.code;
482
+ }
483
+ get metadata() {
484
+ return this.cause.metadata;
485
+ }
486
+ get actor() {
487
+ return this.cause.actor;
488
+ }
489
+ get statusCode() {
490
+ return this.cause.statusCode;
491
+ }
492
+ get public() {
493
+ return this.cause.public;
494
+ }
495
+ get rayId() {
496
+ return this.cause.rayId;
497
+ }
424
498
  get isRetryable(): boolean {
425
499
  return true;
426
500
  }
@@ -453,6 +527,9 @@ export class GuardActorRunnerFailed extends Schema.TaggedErrorClass<GuardActorRu
453
527
  get public() {
454
528
  return this.cause.public;
455
529
  }
530
+ get rayId() {
531
+ return this.cause.rayId;
532
+ }
456
533
  get isRetryable(): boolean {
457
534
  return false;
458
535
  }
@@ -485,6 +562,9 @@ export class GuardServiceUnavailable extends Schema.TaggedErrorClass<GuardServic
485
562
  get public() {
486
563
  return this.cause.public;
487
564
  }
565
+ get rayId() {
566
+ return this.cause.rayId;
567
+ }
488
568
  get isRetryable(): boolean {
489
569
  return true;
490
570
  }
@@ -517,6 +597,9 @@ export class GuardActorStoppedWhileWaiting extends Schema.TaggedErrorClass<Guard
517
597
  get public() {
518
598
  return this.cause.public;
519
599
  }
600
+ get rayId() {
601
+ return this.cause.rayId;
602
+ }
520
603
  get isRetryable(): boolean {
521
604
  return true;
522
605
  }
@@ -549,6 +632,9 @@ export class GuardTunnelRequestAborted extends Schema.TaggedErrorClass<GuardTunn
549
632
  get public() {
550
633
  return this.cause.public;
551
634
  }
635
+ get rayId() {
636
+ return this.cause.rayId;
637
+ }
552
638
  get isRetryable(): boolean {
553
639
  return true;
554
640
  }
@@ -581,6 +667,9 @@ export class GuardTunnelMessageTimeout extends Schema.TaggedErrorClass<GuardTunn
581
667
  get public() {
582
668
  return this.cause.public;
583
669
  }
670
+ get rayId() {
671
+ return this.cause.rayId;
672
+ }
584
673
  get isRetryable(): boolean {
585
674
  return true;
586
675
  }
@@ -613,6 +702,9 @@ export class GuardTunnelResponseClosed extends Schema.TaggedErrorClass<GuardTunn
613
702
  get public() {
614
703
  return this.cause.public;
615
704
  }
705
+ get rayId() {
706
+ return this.cause.rayId;
707
+ }
616
708
  get isRetryable(): boolean {
617
709
  return true;
618
710
  }
@@ -645,6 +737,9 @@ export class GuardGatewayResponseStartTimeout extends Schema.TaggedErrorClass<Gu
645
737
  get public() {
646
738
  return this.cause.public;
647
739
  }
740
+ get rayId() {
741
+ return this.cause.rayId;
742
+ }
648
743
  get isRetryable(): boolean {
649
744
  return true;
650
745
  }
@@ -677,6 +772,9 @@ export class InternalError extends Schema.TaggedErrorClass<InternalError>(
677
772
  get public() {
678
773
  return this.cause.public;
679
774
  }
775
+ get rayId() {
776
+ return this.cause.rayId;
777
+ }
680
778
  get isRetryable(): boolean {
681
779
  return false;
682
780
  }
@@ -710,6 +808,9 @@ export class ActionErrorDecodeFailed extends Schema.TaggedErrorClass<ActionError
710
808
  get public() {
711
809
  return this.rivetError.public;
712
810
  }
811
+ get rayId() {
812
+ return this.rivetError.rayId;
813
+ }
713
814
  get isRetryable(): boolean {
714
815
  return false;
715
816
  }
@@ -750,6 +851,9 @@ export class UnknownUserError extends Schema.TaggedErrorClass<UnknownUserError>(
750
851
  get public() {
751
852
  return this.cause.public;
752
853
  }
854
+ get rayId() {
855
+ return this.cause.rayId;
856
+ }
753
857
  get isRetryable(): boolean {
754
858
  return false;
755
859
  }
@@ -797,6 +901,11 @@ export class UnknownError extends Schema.TaggedErrorClass<UnknownError>(
797
901
  ? this.cause.public
798
902
  : undefined;
799
903
  }
904
+ get rayId() {
905
+ return this.cause instanceof RivetkitErrors.RivetError
906
+ ? this.cause.rayId
907
+ : undefined;
908
+ }
800
909
  get isRetryable(): boolean {
801
910
  return false;
802
911
  }
@@ -816,6 +925,7 @@ export type RivetErrorReason =
816
925
  | InvalidEncoding
817
926
  | InvalidRequest
818
927
  | GuardActorReadyTimeout
928
+ | GuardActorWakeRetriesExceeded
819
929
  | GuardActorRunnerFailed
820
930
  | GuardServiceUnavailable
821
931
  | GuardActorStoppedWhileWaiting
@@ -843,6 +953,7 @@ export const RivetErrorReason: Schema.Union<
843
953
  typeof InvalidEncoding,
844
954
  typeof InvalidRequest,
845
955
  typeof GuardActorReadyTimeout,
956
+ typeof GuardActorWakeRetriesExceeded,
846
957
  typeof GuardActorRunnerFailed,
847
958
  typeof GuardServiceUnavailable,
848
959
  typeof GuardActorStoppedWhileWaiting,
@@ -869,6 +980,7 @@ export const RivetErrorReason: Schema.Union<
869
980
  InvalidEncoding,
870
981
  InvalidRequest,
871
982
  GuardActorReadyTimeout,
983
+ GuardActorWakeRetriesExceeded,
872
984
  GuardActorRunnerFailed,
873
985
  GuardServiceUnavailable,
874
986
  GuardActorStoppedWhileWaiting,
@@ -952,6 +1064,11 @@ export class RivetError extends Schema.TaggedErrorClass<RivetError>(
952
1064
  return "public" in this.reason ? this.reason.public : undefined;
953
1065
  }
954
1066
 
1067
+ /** Delegates to the underlying reason's `rayId` if present. */
1068
+ get rayId(): string | undefined {
1069
+ return "rayId" in this.reason ? this.reason.rayId : undefined;
1070
+ }
1071
+
955
1072
  /** Delegates to the underlying reason's `isRetryable` getter. */
956
1073
  get isRetryable(): boolean {
957
1074
  return this.reason.isRetryable;
@@ -993,6 +1110,8 @@ const reasonByCode: { [key: string]: MakeRivetErrorReason | undefined } = {
993
1110
  "request.invalid": (error) => new InvalidRequest({ cause: error }),
994
1111
  "guard.actor_ready_timeout": (error) =>
995
1112
  new GuardActorReadyTimeout({ cause: error }),
1113
+ "guard.actor_wake_retries_exceeded": (error) =>
1114
+ new GuardActorWakeRetriesExceeded({ cause: error }),
996
1115
  "guard.actor_runner_failed": (error) =>
997
1116
  new GuardActorRunnerFailed({ cause: error }),
998
1117
  "guard.service_unavailable": (error) =>
@@ -0,0 +1,60 @@
1
+ import { Effect, Logger, Layer } from "effect";
2
+ import * as RivetkitLog from "rivetkit/log";
3
+ import {
4
+ BaseLogger,
5
+ getOrCreateBaseLogger,
6
+ makeLogger,
7
+ } from "./internal/logging.ts";
8
+
9
+ /**
10
+ * Builds an Effect logger from a custom Pino-compatible logger.
11
+ *
12
+ * Use this with Effect's `Logger.layer` when you only need to customize Effect
13
+ * log output. Use {@link layerFromPino} when RivetKit's underlying TypeScript SDK
14
+ * logs should use the same logger too.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { Logger } from "effect"
19
+ * import { RivetLogger } from "@rivetkit/effect"
20
+ * import { pino } from "pino"
21
+ *
22
+ * const baseLogger = pino({ transport: { target: "pino-pretty" } })
23
+ * const LoggerLive = Logger.layer([RivetLogger.fromPino(baseLogger)])
24
+ * ```
25
+ */
26
+ export const fromPino = (
27
+ baseLogger: RivetkitLog.Logger,
28
+ ): Logger.Logger<unknown, void> => makeLogger(baseLogger);
29
+
30
+ /**
31
+ * Builds a logging layer from a custom Pino-compatible logger.
32
+ *
33
+ * The layer installs the matching Effect logger and configures the underlying
34
+ * RivetKit TypeScript SDK logs to go through the same logger. The Effect tracer
35
+ * logger is installed alongside the Pino adapter so log events remain attached
36
+ * to active traces.
37
+ */
38
+ export const layerFromPino = (
39
+ baseLogger: RivetkitLog.Logger,
40
+ options?: {
41
+ readonly mergeWithExisting?: boolean | undefined;
42
+ },
43
+ ): Layer.Layer<BaseLogger> =>
44
+ Layer.mergeAll(
45
+ Layer.succeed(BaseLogger, baseLogger),
46
+ Logger.layer([Logger.tracerLogger, fromPino(baseLogger)], {
47
+ mergeWithExisting: options?.mergeWithExisting,
48
+ }),
49
+ );
50
+
51
+ /**
52
+ * Default RivetKit Effect logging layer.
53
+ *
54
+ * The layer creates a base logger from RivetKit log configuration and installs
55
+ * the Effect logger adapter. Applications that want custom formatting or
56
+ * transports should provide {@link layerFromPino} instead.
57
+ */
58
+ export const defaultLayer: Layer.Layer<never> = Layer.unwrap(
59
+ Effect.map(getOrCreateBaseLogger, layerFromPino),
60
+ );
package/src/State.test.ts CHANGED
@@ -36,6 +36,26 @@ describe("State", () => {
36
36
  }),
37
37
  );
38
38
 
39
+ it.effect(
40
+ "getAndSet returns the previous value and commits the new value",
41
+ () =>
42
+ Effect.gen(function* () {
43
+ const { s, cell } = yield* makeCellState(10);
44
+ const previous = yield* State.getAndSet(s, 15);
45
+ assert.strictEqual(previous, 10);
46
+ assert.strictEqual(cell.value, 15);
47
+ }),
48
+ );
49
+
50
+ it.effect("setAndGet returns the committed value", () =>
51
+ Effect.gen(function* () {
52
+ const { s, cell } = yield* makeCellState(10);
53
+ const next = yield* State.setAndGet(s, 15);
54
+ assert.strictEqual(next, 15);
55
+ assert.strictEqual(cell.value, 15);
56
+ }),
57
+ );
58
+
39
59
  it.effect("update applies f over read/write", () =>
40
60
  Effect.gen(function* () {
41
61
  const { s, cell } = yield* makeCellState(10);
@@ -44,6 +64,38 @@ describe("State", () => {
44
64
  }),
45
65
  );
46
66
 
67
+ it.effect("updateEffect applies an Effectful f over read/write", () =>
68
+ Effect.gen(function* () {
69
+ const { s, cell } = yield* makeCellState(10);
70
+ yield* State.updateEffect(s, (n) => Effect.succeed(n + 5));
71
+ assert.strictEqual(cell.value, 15);
72
+ }),
73
+ );
74
+
75
+ it.effect(
76
+ "getAndUpdate returns the previous value and commits the new value",
77
+ () =>
78
+ Effect.gen(function* () {
79
+ const { s, cell } = yield* makeCellState(10);
80
+ const previous = yield* State.getAndUpdate(s, (n) => n + 5);
81
+ assert.strictEqual(previous, 10);
82
+ assert.strictEqual(cell.value, 15);
83
+ }),
84
+ );
85
+
86
+ it.effect(
87
+ "getAndUpdateEffect returns the previous value and commits the new value",
88
+ () =>
89
+ Effect.gen(function* () {
90
+ const { s, cell } = yield* makeCellState(10);
91
+ const previous = yield* State.getAndUpdateEffect(s, (n) =>
92
+ Effect.succeed(n + 5),
93
+ );
94
+ assert.strictEqual(previous, 10);
95
+ assert.strictEqual(cell.value, 15);
96
+ }),
97
+ );
98
+
47
99
  it.effect("updateAndGet returns the new value and commits it", () =>
48
100
  Effect.gen(function* () {
49
101
  const { s, cell } = yield* makeCellState(10);
@@ -53,6 +105,17 @@ describe("State", () => {
53
105
  }),
54
106
  );
55
107
 
108
+ it.effect("updateAndGetEffect returns the new value and commits it", () =>
109
+ Effect.gen(function* () {
110
+ const { s, cell } = yield* makeCellState(10);
111
+ const next = yield* State.updateAndGetEffect(s, (n) =>
112
+ Effect.succeed(n + 5),
113
+ );
114
+ assert.strictEqual(next, 15);
115
+ assert.strictEqual(cell.value, 15);
116
+ }),
117
+ );
118
+
56
119
  it.effect("modify returns B and commits the new value", () =>
57
120
  Effect.gen(function* () {
58
121
  const { s, cell } = yield* makeCellState("a");
@@ -65,6 +128,17 @@ describe("State", () => {
65
128
  }),
66
129
  );
67
130
 
131
+ it.effect("modifyEffect returns B and commits the new value", () =>
132
+ Effect.gen(function* () {
133
+ const { s, cell } = yield* makeCellState("a");
134
+ const b = yield* State.modifyEffect(s, (str) =>
135
+ Effect.succeed([str.length, `${str}b`] as const),
136
+ );
137
+ assert.strictEqual(b, 1);
138
+ assert.strictEqual(cell.value, "ab");
139
+ }),
140
+ );
141
+
68
142
  it.effect(
69
143
  "update is atomic across concurrent fibers (no lost updates)",
70
144
  () =>
@@ -89,7 +163,7 @@ describe("State", () => {
89
163
  );
90
164
  assert.deepStrictEqual(initial, [0]);
91
165
 
92
- State.publishUnsafe(s, 7);
166
+ yield* s[State.RuntimeTypeId].publishEffect(Effect.succeed(7));
93
167
  const later = yield* State.changes(s).pipe(
94
168
  Stream.take(1),
95
169
  Stream.runCollect,
@@ -103,11 +177,17 @@ describe("State", () => {
103
177
  const { s } = yield* makeCellState(0);
104
178
  yield* Effect.scoped(
105
179
  Effect.gen(function* () {
106
- const sub = yield* PubSub.subscribe(s.pubsub);
180
+ const sub = yield* PubSub.subscribe(
181
+ s[State.RuntimeTypeId].pubsub,
182
+ );
107
183
  assert.strictEqual(yield* PubSub.take(sub), 0);
108
184
 
109
- yield* State.publish(s, 1);
110
- yield* State.publish(s, 2);
185
+ yield* s[State.RuntimeTypeId].publishEffect(
186
+ Effect.succeed(1),
187
+ );
188
+ yield* s[State.RuntimeTypeId].publishEffect(
189
+ Effect.succeed(2),
190
+ );
111
191
  assert.strictEqual(yield* PubSub.take(sub), 1);
112
192
  assert.strictEqual(yield* PubSub.take(sub), 2);
113
193
  }),
@@ -115,6 +195,19 @@ describe("State", () => {
115
195
  }),
116
196
  );
117
197
 
198
+ it.effect("shuts down the backing pubsub when its scope closes", () =>
199
+ Effect.gen(function* () {
200
+ const pubsub = yield* Effect.scoped(
201
+ Effect.gen(function* () {
202
+ const { s } = yield* makeCellState(0);
203
+ return s[State.RuntimeTypeId].pubsub;
204
+ }),
205
+ );
206
+
207
+ assert.isTrue(yield* PubSub.isShutdown(pubsub));
208
+ }),
209
+ );
210
+
118
211
  it.effect("set does NOT auto-publish — the runtime does", () =>
119
212
  Effect.gen(function* () {
120
213
  const { s } = yield* makeCellState(0);
@@ -146,6 +239,61 @@ describe("State", () => {
146
239
 
147
240
  yield* s.pipe(State.update((n) => n * 2));
148
241
  assert.strictEqual(yield* State.get(s), 10);
242
+
243
+ yield* s.pipe(State.updateEffect((n) => Effect.succeed(n + 1)));
244
+ assert.strictEqual(yield* State.get(s), 11);
245
+ }),
246
+ );
247
+
248
+ it.effect("supports instance methods", () =>
249
+ Effect.gen(function* () {
250
+ const { s, cell } = yield* makeCellState(0);
251
+ assert.strictEqual(yield* s.get, 0);
252
+
253
+ yield* s.set(5);
254
+ assert.strictEqual(cell.value, 5);
255
+
256
+ const beforeSet = yield* s.getAndSet(6);
257
+ assert.strictEqual(beforeSet, 5);
258
+ assert.strictEqual(cell.value, 6);
259
+
260
+ const afterSet = yield* s.setAndGet(7);
261
+ assert.strictEqual(afterSet, 7);
262
+ assert.strictEqual(cell.value, 7);
263
+
264
+ yield* s.update((n) => n * 2);
265
+ assert.strictEqual(yield* s.get, 14);
266
+
267
+ yield* s.updateEffect((n) => Effect.succeed(n + 1));
268
+ assert.strictEqual(yield* s.get, 15);
269
+
270
+ const previousUpdate = yield* s.getAndUpdate((n) => n + 1);
271
+ assert.strictEqual(previousUpdate, 15);
272
+ assert.strictEqual(yield* s.get, 16);
273
+
274
+ const previousEffectUpdate = yield* s.getAndUpdateEffect((n) =>
275
+ Effect.succeed(n + 1),
276
+ );
277
+ assert.strictEqual(previousEffectUpdate, 16);
278
+ assert.strictEqual(yield* s.get, 17);
279
+
280
+ const next = yield* s.updateAndGet((n) => n + 1);
281
+ assert.strictEqual(next, 18);
282
+
283
+ const effectNext = yield* s.updateAndGetEffect((n) =>
284
+ Effect.succeed(n + 1),
285
+ );
286
+ assert.strictEqual(effectNext, 19);
287
+
288
+ const previous = yield* s.modify((n) => [n, n + 1] as const);
289
+ assert.strictEqual(previous, 19);
290
+ assert.strictEqual(yield* s.get, 20);
291
+
292
+ const effectPrevious = yield* s.modifyEffect((n) =>
293
+ Effect.succeed([n, n + 1] as const),
294
+ );
295
+ assert.strictEqual(effectPrevious, 20);
296
+ assert.strictEqual(yield* s.get, 21);
149
297
  }),
150
298
  );
151
299
 
@@ -178,4 +326,15 @@ describe("State", () => {
178
326
  assert.isTrue(Exit.isFailure(exit));
179
327
  }),
180
328
  );
329
+
330
+ it.effect("Effectful update failure does not write", () =>
331
+ Effect.gen(function* () {
332
+ const { s, cell } = yield* makeCellState(0);
333
+ const exit = yield* Effect.exit(
334
+ State.updateEffect(s, () => Effect.fail("boom" as const)),
335
+ );
336
+ assert.isTrue(Exit.isFailure(exit));
337
+ assert.strictEqual(cell.value, 0);
338
+ }),
339
+ );
181
340
  });