@rivetkit/effect 0.0.0-codex-rivetkit-cli-preview.5e414a8

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 (89) hide show
  1. package/dist/Action.d.ts +104 -0
  2. package/dist/Action.d.ts.map +1 -0
  3. package/dist/Action.js +50 -0
  4. package/dist/Action.js.map +1 -0
  5. package/dist/Actor.d.ts +133 -0
  6. package/dist/Actor.d.ts.map +1 -0
  7. package/dist/Actor.js +104 -0
  8. package/dist/Actor.js.map +1 -0
  9. package/dist/Client.d.ts +31 -0
  10. package/dist/Client.d.ts.map +1 -0
  11. package/dist/Client.js +98 -0
  12. package/dist/Client.js.map +1 -0
  13. package/dist/Logger.d.ts +29 -0
  14. package/dist/Logger.d.ts.map +1 -0
  15. package/dist/Logger.js +31 -0
  16. package/dist/Logger.js.map +1 -0
  17. package/dist/Registry.d.ts +72 -0
  18. package/dist/Registry.d.ts.map +1 -0
  19. package/dist/Registry.js +125 -0
  20. package/dist/Registry.js.map +1 -0
  21. package/dist/RivetError.d.ts +438 -0
  22. package/dist/RivetError.d.ts.map +1 -0
  23. package/dist/RivetError.js +873 -0
  24. package/dist/RivetError.js.map +1 -0
  25. package/dist/State.d.ts +123 -0
  26. package/dist/State.d.ts.map +1 -0
  27. package/dist/State.js +104 -0
  28. package/dist/State.js.map +1 -0
  29. package/dist/internal/ActionDispatcher.d.ts +14 -0
  30. package/dist/internal/ActionDispatcher.d.ts.map +1 -0
  31. package/dist/internal/ActionDispatcher.js +100 -0
  32. package/dist/internal/ActionDispatcher.js.map +1 -0
  33. package/dist/internal/ActionErrorEnvelope.d.ts +11 -0
  34. package/dist/internal/ActionErrorEnvelope.d.ts.map +1 -0
  35. package/dist/internal/ActionErrorEnvelope.js +14 -0
  36. package/dist/internal/ActionErrorEnvelope.js.map +1 -0
  37. package/dist/internal/ActorInstanceManager.d.ts +28 -0
  38. package/dist/internal/ActorInstanceManager.d.ts.map +1 -0
  39. package/dist/internal/ActorInstanceManager.js +51 -0
  40. package/dist/internal/ActorInstanceManager.js.map +1 -0
  41. package/dist/internal/ActorStateAdapter.d.ts +18 -0
  42. package/dist/internal/ActorStateAdapter.d.ts.map +1 -0
  43. package/dist/internal/ActorStateAdapter.js +29 -0
  44. package/dist/internal/ActorStateAdapter.js.map +1 -0
  45. package/dist/internal/StateOptions.d.ts +12 -0
  46. package/dist/internal/StateOptions.d.ts.map +1 -0
  47. package/dist/internal/StateOptions.js +2 -0
  48. package/dist/internal/StateOptions.js.map +1 -0
  49. package/dist/internal/logging.d.ts +23 -0
  50. package/dist/internal/logging.d.ts.map +1 -0
  51. package/dist/internal/logging.js +162 -0
  52. package/dist/internal/logging.js.map +1 -0
  53. package/dist/internal/tracing.d.ts +23 -0
  54. package/dist/internal/tracing.d.ts.map +1 -0
  55. package/dist/internal/tracing.js +30 -0
  56. package/dist/internal/tracing.js.map +1 -0
  57. package/dist/internal/utils.d.ts +7 -0
  58. package/dist/internal/utils.d.ts.map +1 -0
  59. package/dist/internal/utils.js +7 -0
  60. package/dist/internal/utils.js.map +1 -0
  61. package/dist/mod.d.ts +8 -0
  62. package/dist/mod.d.ts.map +1 -0
  63. package/dist/mod.js +8 -0
  64. package/dist/mod.js.map +1 -0
  65. package/package.json +46 -0
  66. package/src/Action.ts +231 -0
  67. package/src/Actor.test-d.ts +603 -0
  68. package/src/Actor.test.ts +206 -0
  69. package/src/Actor.ts +550 -0
  70. package/src/Client.test.ts +210 -0
  71. package/src/Client.ts +216 -0
  72. package/src/Logger.ts +43 -0
  73. package/src/Registry.test-d.ts +126 -0
  74. package/src/Registry.test.ts +411 -0
  75. package/src/Registry.ts +243 -0
  76. package/src/RivetError.test.ts +188 -0
  77. package/src/RivetError.ts +1044 -0
  78. package/src/State.test.ts +181 -0
  79. package/src/State.ts +224 -0
  80. package/src/internal/ActionDispatcher.ts +192 -0
  81. package/src/internal/ActionErrorEnvelope.ts +19 -0
  82. package/src/internal/ActorInstanceManager.ts +143 -0
  83. package/src/internal/ActorStateAdapter.ts +88 -0
  84. package/src/internal/StateOptions.ts +17 -0
  85. package/src/internal/logging.test.ts +288 -0
  86. package/src/internal/logging.ts +237 -0
  87. package/src/internal/tracing.ts +42 -0
  88. package/src/internal/utils.ts +12 -0
  89. package/src/mod.ts +7 -0
@@ -0,0 +1,411 @@
1
+ import { assert, describe, it } from "@effect/vitest";
2
+ import { Action, Actor, Logger, Registry } from "@rivetkit/effect";
3
+ import { Effect, Layer } from "effect";
4
+ import { HttpEffect } from "effect/unstable/http";
5
+ import {
6
+ configureDefaultLogger,
7
+ getBaseLogger,
8
+ type Logger as PinoLogger,
9
+ } from "rivetkit/log";
10
+ import { vi } from "vitest";
11
+
12
+ const TestActor = Actor.make("TestActor", {
13
+ actions: [Action.make("Test")],
14
+ });
15
+
16
+ const TestActorLive = TestActor.toLayer({
17
+ Test: () => Effect.void,
18
+ });
19
+
20
+ const ActorsLayer = Layer.mergeAll(TestActorLive);
21
+
22
+ const RegistryLive = ActorsLayer.pipe(
23
+ Layer.provideMerge(
24
+ Registry.layer({
25
+ endpoint: "http://127.0.0.1:6420",
26
+ noWelcome: true,
27
+ }),
28
+ ),
29
+ );
30
+
31
+ function makeTestLogger(): PinoLogger {
32
+ const logger: Record<string, unknown> = {
33
+ level: "debug",
34
+ child: () => logger,
35
+ };
36
+ for (const level of [
37
+ "trace",
38
+ "debug",
39
+ "info",
40
+ "warn",
41
+ "error",
42
+ "fatal",
43
+ ]) {
44
+ logger[level] = (): void => {};
45
+ }
46
+
47
+ return logger as unknown as PinoLogger;
48
+ }
49
+
50
+ describe("Registry.toWebHandler", () => {
51
+ it("serves registered actors as a Fetch handler", async () => {
52
+ const { handler, dispose } = Registry.toWebHandler(RegistryLive);
53
+
54
+ try {
55
+ const response = await handler(
56
+ new Request("http://runner.test/api/rivet/metadata"),
57
+ );
58
+
59
+ assert.strictEqual(response.status, 200);
60
+ const body = (await response.json()) as {
61
+ readonly actorNames: Record<string, unknown>;
62
+ };
63
+ assert.ok(body.actorNames.TestActor);
64
+ } finally {
65
+ await dispose();
66
+ }
67
+ });
68
+
69
+ it("uses a custom serverless base path", async () => {
70
+ const { handler, dispose } = Registry.toWebHandler(RegistryLive, {
71
+ basePath: "/",
72
+ });
73
+
74
+ try {
75
+ const response = await handler(
76
+ new Request("http://runner.test/metadata"),
77
+ );
78
+
79
+ assert.strictEqual(response.status, 200);
80
+ const body = (await response.json()) as {
81
+ readonly actorNames: Record<string, unknown>;
82
+ };
83
+ assert.ok(body.actorNames.TestActor);
84
+ } finally {
85
+ await dispose();
86
+ }
87
+ });
88
+
89
+ it("uses the custom base path to identify start requests", async () => {
90
+ const { handler, dispose } = Registry.toWebHandler(RegistryLive, {
91
+ basePath: "/custom",
92
+ maxStartPayloadBytes: 1,
93
+ });
94
+
95
+ try {
96
+ const defaultPrefix = await handler(
97
+ new Request("http://runner.test/api/rivet/start", {
98
+ method: "POST",
99
+ body: new Uint8Array([1, 2]),
100
+ }),
101
+ );
102
+ assert.notStrictEqual(defaultPrefix.status, 413);
103
+
104
+ const customPrefix = await handler(
105
+ new Request("http://runner.test/custom/start", {
106
+ method: "POST",
107
+ body: new Uint8Array([1, 2]),
108
+ }),
109
+ );
110
+ assert.strictEqual(customPrefix.status, 413);
111
+ const body = (await customPrefix.json()) as {
112
+ readonly group: string;
113
+ readonly code: string;
114
+ readonly message: string;
115
+ };
116
+ assert.deepStrictEqual(
117
+ { group: body.group, code: body.code },
118
+ { group: "message", code: "incoming_too_long" },
119
+ );
120
+ assert.match(body.message, /limit is 1 bytes/);
121
+ } finally {
122
+ await dispose();
123
+ }
124
+ });
125
+
126
+ it("uses a custom serverless start payload size limit", async () => {
127
+ const { handler, dispose } = Registry.toWebHandler(RegistryLive, {
128
+ maxStartPayloadBytes: 1,
129
+ });
130
+
131
+ try {
132
+ const response = await handler(
133
+ new Request("http://runner.test/api/rivet/start", {
134
+ method: "POST",
135
+ body: new Uint8Array([1, 2]),
136
+ }),
137
+ );
138
+
139
+ assert.strictEqual(response.status, 413);
140
+ const body = (await response.json()) as {
141
+ readonly group: string;
142
+ readonly code: string;
143
+ readonly message: string;
144
+ };
145
+ assert.deepStrictEqual(
146
+ { group: body.group, code: body.code },
147
+ { group: "message", code: "incoming_too_long" },
148
+ );
149
+ assert.match(body.message, /limit is 1 bytes/);
150
+ } finally {
151
+ await dispose();
152
+ }
153
+ });
154
+
155
+ it("does not print the welcome banner when disabled", async () => {
156
+ const log = vi.spyOn(console, "log").mockImplementation(() => {});
157
+ const { handler, dispose } = Registry.toWebHandler(RegistryLive);
158
+
159
+ try {
160
+ const response = await handler(
161
+ new Request("http://runner.test/api/rivet/metadata"),
162
+ );
163
+
164
+ assert.strictEqual(response.status, 200);
165
+ assert.strictEqual(log.mock.calls.length, 0);
166
+ } finally {
167
+ await dispose();
168
+ log.mockRestore();
169
+ }
170
+ });
171
+
172
+ it("builds the registry layer once across requests", async () => {
173
+ let builds = 0;
174
+ const CountingRegistryLive = Layer.mergeAll(
175
+ RegistryLive,
176
+ Layer.effectDiscard(
177
+ Effect.sync(() => {
178
+ builds += 1;
179
+ }),
180
+ ),
181
+ );
182
+ const { handler, dispose } =
183
+ Registry.toWebHandler(CountingRegistryLive);
184
+
185
+ try {
186
+ const first = await handler(
187
+ new Request("http://runner.test/api/rivet/metadata"),
188
+ );
189
+ const second = await handler(
190
+ new Request("http://runner.test/api/rivet/metadata"),
191
+ );
192
+
193
+ assert.strictEqual(first.status, 200);
194
+ assert.strictEqual(second.status, 200);
195
+ assert.strictEqual(builds, 1);
196
+ } finally {
197
+ await dispose();
198
+ }
199
+ });
200
+
201
+ it("initializes the underlying RivetKit registry once across requests", async () => {
202
+ const log = vi.spyOn(console, "log").mockImplementation(() => {});
203
+ const WelcomeRegistryLive = ActorsLayer.pipe(
204
+ Layer.provideMerge(
205
+ Registry.layer({
206
+ endpoint: "http://127.0.0.1:6420",
207
+ }),
208
+ ),
209
+ );
210
+ const { handler, dispose } =
211
+ Registry.toWebHandler(WelcomeRegistryLive);
212
+
213
+ try {
214
+ const first = await handler(
215
+ new Request("http://runner.test/api/rivet/metadata"),
216
+ );
217
+ const callsAfterFirst = log.mock.calls.length;
218
+ const second = await handler(
219
+ new Request("http://runner.test/api/rivet/metadata"),
220
+ );
221
+
222
+ assert.strictEqual(first.status, 200);
223
+ assert.strictEqual(second.status, 200);
224
+ assert.ok(callsAfterFirst > 0);
225
+ assert.strictEqual(log.mock.calls.length, callsAfterFirst);
226
+ } finally {
227
+ await dispose();
228
+ log.mockRestore();
229
+ }
230
+ });
231
+
232
+ it("uses a custom logger layer for the underlying RivetKit registry", async () => {
233
+ const baseLogger = makeTestLogger();
234
+ const CustomLoggerRegistryLive = RegistryLive.pipe(
235
+ Layer.provide(Logger.layerPino(baseLogger)),
236
+ );
237
+ const { handler, dispose } =
238
+ Registry.toWebHandler(CustomLoggerRegistryLive);
239
+
240
+ try {
241
+ const response = await handler(
242
+ new Request("http://runner.test/api/rivet/metadata"),
243
+ );
244
+
245
+ assert.strictEqual(response.status, 200);
246
+ assert.strictEqual(getBaseLogger(), baseLogger);
247
+ } finally {
248
+ await dispose();
249
+ configureDefaultLogger("silent");
250
+ }
251
+ });
252
+
253
+ it("closes registry layer finalizers on dispose", async () => {
254
+ let finalizers = 0;
255
+ const FinalizedRegistryLive = Layer.mergeAll(
256
+ RegistryLive,
257
+ Layer.effectDiscard(
258
+ Effect.addFinalizer(() =>
259
+ Effect.sync(() => {
260
+ finalizers += 1;
261
+ }),
262
+ ),
263
+ ),
264
+ );
265
+ const { handler, dispose } = Registry.toWebHandler(
266
+ FinalizedRegistryLive,
267
+ );
268
+
269
+ try {
270
+ const response = await handler(
271
+ new Request("http://runner.test/api/rivet/metadata"),
272
+ );
273
+
274
+ assert.strictEqual(response.status, 200);
275
+ assert.strictEqual(finalizers, 0);
276
+ } finally {
277
+ await dispose();
278
+ }
279
+ assert.strictEqual(finalizers, 1);
280
+ });
281
+ });
282
+
283
+ describe("Registry.toHttpEffect", () => {
284
+ it.effect("serves registered actors as an Effect HTTP handler", () =>
285
+ Effect.scoped(
286
+ Effect.gen(function* () {
287
+ const httpEffect = yield* Registry.toHttpEffect(RegistryLive);
288
+ const handler = HttpEffect.toWebHandler(httpEffect);
289
+ const response = yield* Effect.promise(() =>
290
+ handler(
291
+ new Request("http://runner.test/api/rivet/metadata"),
292
+ ),
293
+ );
294
+
295
+ yield* Effect.promise(() =>
296
+ (async (response: Response) => {
297
+ assert.strictEqual(response.status, 200);
298
+ const body = (await response.json()) as {
299
+ readonly actorNames: Record<string, unknown>;
300
+ };
301
+ assert.ok(body.actorNames.TestActor);
302
+ })(response),
303
+ );
304
+ }),
305
+ ),
306
+ );
307
+
308
+ it.effect("uses a custom serverless base path", () =>
309
+ Effect.scoped(
310
+ Effect.gen(function* () {
311
+ const httpEffect = yield* Registry.toHttpEffect(RegistryLive, {
312
+ basePath: "/",
313
+ });
314
+ const handler = HttpEffect.toWebHandler(httpEffect);
315
+ const response = yield* Effect.promise(() =>
316
+ handler(new Request("http://runner.test/metadata")),
317
+ );
318
+
319
+ yield* Effect.promise(() =>
320
+ (async (response: Response) => {
321
+ assert.strictEqual(response.status, 200);
322
+ const body = (await response.json()) as {
323
+ readonly actorNames: Record<string, unknown>;
324
+ };
325
+ assert.ok(body.actorNames.TestActor);
326
+ })(response),
327
+ );
328
+ }),
329
+ ),
330
+ );
331
+
332
+ it.effect("uses the custom base path to identify start requests", () =>
333
+ Effect.scoped(
334
+ Effect.gen(function* () {
335
+ const httpEffect = yield* Registry.toHttpEffect(RegistryLive, {
336
+ basePath: "/custom",
337
+ maxStartPayloadBytes: 1,
338
+ });
339
+ const handler = HttpEffect.toWebHandler(httpEffect);
340
+ const defaultPrefix = yield* Effect.promise(() =>
341
+ handler(
342
+ new Request("http://runner.test/api/rivet/start", {
343
+ method: "POST",
344
+ body: new Uint8Array([1, 2]),
345
+ }),
346
+ ),
347
+ );
348
+ assert.notStrictEqual(defaultPrefix.status, 413);
349
+
350
+ const customPrefix = yield* Effect.promise(() =>
351
+ handler(
352
+ new Request("http://runner.test/custom/start", {
353
+ method: "POST",
354
+ body: new Uint8Array([1, 2]),
355
+ }),
356
+ ),
357
+ );
358
+ yield* Effect.promise(() =>
359
+ (async (response: Response) => {
360
+ assert.strictEqual(response.status, 413);
361
+ const body = (await response.json()) as {
362
+ readonly group: string;
363
+ readonly code: string;
364
+ readonly message: string;
365
+ };
366
+ assert.deepStrictEqual(
367
+ { group: body.group, code: body.code },
368
+ { group: "message", code: "incoming_too_long" },
369
+ );
370
+ assert.match(body.message, /limit is 1 bytes/);
371
+ })(customPrefix),
372
+ );
373
+ }),
374
+ ),
375
+ );
376
+
377
+ it.effect("uses a custom serverless start payload size limit", () =>
378
+ Effect.scoped(
379
+ Effect.gen(function* () {
380
+ const httpEffect = yield* Registry.toHttpEffect(RegistryLive, {
381
+ maxStartPayloadBytes: 1,
382
+ });
383
+ const handler = HttpEffect.toWebHandler(httpEffect);
384
+ const response = yield* Effect.promise(() =>
385
+ handler(
386
+ new Request("http://runner.test/api/rivet/start", {
387
+ method: "POST",
388
+ body: new Uint8Array([1, 2]),
389
+ }),
390
+ ),
391
+ );
392
+
393
+ yield* Effect.promise(() =>
394
+ (async (response: Response) => {
395
+ assert.strictEqual(response.status, 413);
396
+ const body = (await response.json()) as {
397
+ readonly group: string;
398
+ readonly code: string;
399
+ readonly message: string;
400
+ };
401
+ assert.deepStrictEqual(
402
+ { group: body.group, code: body.code },
403
+ { group: "message", code: "incoming_too_long" },
404
+ );
405
+ assert.match(body.message, /limit is 1 bytes/);
406
+ })(response),
407
+ );
408
+ }),
409
+ ),
410
+ );
411
+ });
@@ -0,0 +1,243 @@
1
+ import { Context, Effect, Layer, type Scope } from "effect";
2
+ import {
3
+ HttpEffect,
4
+ type HttpMiddleware,
5
+ type HttpServerError,
6
+ type HttpServerRequest,
7
+ type HttpServerResponse,
8
+ } from "effect/unstable/http";
9
+ import * as Rivetkit from "rivetkit";
10
+ import {
11
+ configureBaseLogger,
12
+ type Logger as PinoLogger,
13
+ } from "rivetkit/log";
14
+ import * as Client from "./Client.ts";
15
+ import { BaseLogger, getOrCreateBaseLogger } from "./internal/logging.ts";
16
+ import * as Logger from "./Logger.ts";
17
+
18
+ const TypeId = "~@rivetkit/effect/Registry";
19
+ type ServerlessOptions = NonNullable<
20
+ Rivetkit.RegistryConfigInput<Rivetkit.RegistryActors>["serverless"]
21
+ >;
22
+
23
+ export type Options = Pick<
24
+ Rivetkit.RegistryConfigInput<Rivetkit.RegistryActors>,
25
+ "endpoint" | "token" | "namespace" | "noWelcome"
26
+ >;
27
+
28
+ export interface Registry {
29
+ readonly [TypeId]: typeof TypeId;
30
+
31
+ readonly options: Options;
32
+
33
+ readonly baseLogger: PinoLogger;
34
+
35
+ readonly rivetkitActors: Map<string, Rivetkit.AnyActorDefinition>;
36
+ }
37
+
38
+ export const Registry: Context.Service<Registry, Registry> =
39
+ Context.Service<Registry>("@rivetkit/effect/Registry");
40
+
41
+ const make = (options: Options, baseLogger: PinoLogger): Registry => {
42
+ return Registry.of({
43
+ [TypeId]: TypeId,
44
+ options,
45
+ baseLogger,
46
+ rivetkitActors: new Map(),
47
+ });
48
+ };
49
+
50
+ export const layer = (options: Options = {}): Layer.Layer<Registry> =>
51
+ Layer.effect(
52
+ Registry,
53
+ Effect.map(getOrCreateBaseLogger, (baseLogger) =>
54
+ make(options, baseLogger),
55
+ ),
56
+ );
57
+
58
+ const setupRivetkitRegistry = (
59
+ registry: Registry,
60
+ options?: {
61
+ readonly serverless?: ServerlessOptions | undefined;
62
+ },
63
+ ) => {
64
+ configureBaseLogger(registry.baseLogger);
65
+ return Rivetkit.setup({
66
+ use: Object.fromEntries(registry.rivetkitActors),
67
+ ...registry.options,
68
+ logging: { baseLogger: registry.baseLogger },
69
+ ...(options?.serverless === undefined
70
+ ? {}
71
+ : { serverless: options.serverless }),
72
+ });
73
+ };
74
+
75
+ /**
76
+ * Runs an actor registration layer against the configured engine.
77
+ *
78
+ * The actor layer is built in the server layer scope. Registered Rivet Actors
79
+ * are collected from `Registry`, materialized into a single underlying RivetKit
80
+ * registry, and started.
81
+ */
82
+ export const serve = <E, R>(
83
+ actorsLayer: Layer.Layer<never, E, R>,
84
+ ): Layer.Layer<never, E, R | Registry> =>
85
+ Layer.effectDiscard(
86
+ Effect.gen(function* () {
87
+ const registry = yield* Registry;
88
+ const baseLogger = registry.baseLogger;
89
+ yield* Layer.build(
90
+ actorsLayer.pipe(
91
+ Layer.provideMerge(Logger.layerPino(baseLogger)),
92
+ ),
93
+ );
94
+ const rivetkitRegistry = setupRivetkitRegistry(registry);
95
+ yield* Effect.sync(() => rivetkitRegistry.start());
96
+ }),
97
+ );
98
+
99
+ /**
100
+ * In-process test runtime. Boots the rivetkit registry against the
101
+ * configured engine, waits for `/health` to answer, and provides
102
+ * `Client` from the same Layer so consumers don't need to wire
103
+ * `Client.layer` separately. Mirrors `Registry.start` plus test-mode
104
+ * flags and a scoped client dispose. The registry itself is leaked
105
+ * to process exit because the public rivetkit `Registry` doesn't
106
+ * expose a public `shutdown()` today; only the SIGINT handler can
107
+ * drive `#runShutdown`. This matches `setupTest`'s existing behavior.
108
+ */
109
+ export const test: Layer.Layer<Client.Client, never, Registry> = Layer.effect(
110
+ Client.Client,
111
+ Effect.gen(function* () {
112
+ const registry = yield* Registry;
113
+ const rivetkitRegistry = setupRivetkitRegistry(registry);
114
+ rivetkitRegistry.config.test = {
115
+ ...rivetkitRegistry.config.test,
116
+ enabled: true,
117
+ };
118
+ rivetkitRegistry.config.noWelcome = true;
119
+ // Auto-spawn the engine when no endpoint was provided, so
120
+ // `Registry.test` works out of the box without requiring the
121
+ // caller to start an engine externally. If the user wired an
122
+ // explicit endpoint via `Registry.layer({ endpoint: ... })`,
123
+ // honor it and skip the local spawn.
124
+ if (registry.options.endpoint === undefined) {
125
+ rivetkitRegistry.config.startEngine = true;
126
+ }
127
+ yield* Effect.sync(() => rivetkitRegistry.start());
128
+
129
+ // The rivetkitRegistry itself is leaked until process exit (matches
130
+ // setupTest's behavior). The public Rivetkit.Registry doesn't
131
+ // expose a shutdown method; only the SIGINT handler can drive the
132
+ // inner .shutdown(). Disposing the client is the only cleanup we
133
+ // can do cleanly today.
134
+ //
135
+ // When the engine was auto-spawned, propagate its resolved
136
+ // endpoint to the client so `createClient` doesn't fall back
137
+ // to its (warning-emitting) default.
138
+ const resolvedEndpoint = rivetkitRegistry.parseConfig().endpoint;
139
+
140
+ return yield* Client.make({
141
+ ...registry.options,
142
+ endpoint: registry.options.endpoint ?? resolvedEndpoint,
143
+ }).pipe(
144
+ Effect.provideService(BaseLogger, registry.baseLogger),
145
+ );
146
+ }),
147
+ );
148
+
149
+ const makeHttpEffect = (
150
+ registry: Registry,
151
+ options?: ToHttpEffectOptions,
152
+ ): Effect.Effect<
153
+ HttpServerResponse.HttpServerResponse,
154
+ HttpServerError.HttpServerError,
155
+ HttpServerRequest.HttpServerRequest
156
+ > => {
157
+ const rivetkitRegistry = setupRivetkitRegistry(registry, {
158
+ serverless: options,
159
+ });
160
+ return HttpEffect.fromWebHandler((request) =>
161
+ rivetkitRegistry.handler(request),
162
+ );
163
+ };
164
+
165
+ export type ToHttpEffectOptions = ServerlessOptions;
166
+
167
+ /**
168
+ * Builds a scoped Effect HTTP handler from a registry layer.
169
+ *
170
+ * The registry layer is built once in the surrounding scope. Registered Rivet
171
+ * Actors are materialized into a single underlying RivetKit registry, and each
172
+ * request is delegated to that registry's serverless handler.
173
+ */
174
+ export const toHttpEffect = Effect.fnUntraced(function* <E>(
175
+ registryLayer: Layer.Layer<Registry, E>,
176
+ options?: ToHttpEffectOptions,
177
+ ): Effect.fn.Return<
178
+ Effect.Effect<
179
+ HttpServerResponse.HttpServerResponse,
180
+ HttpServerError.HttpServerError,
181
+ HttpServerRequest.HttpServerRequest
182
+ >,
183
+ E,
184
+ Scope.Scope
185
+ > {
186
+ const context = yield* Layer.build(
187
+ registryLayer.pipe(Layer.provideMerge(Logger.layer)),
188
+ );
189
+ // @effect-diagnostics-next-line returnEffectInGen:off
190
+ return makeHttpEffect(Context.get(context, Registry), options).pipe(
191
+ Effect.provide(context),
192
+ );
193
+ });
194
+
195
+ export type ToWebHandlerOptions = ServerlessOptions & {
196
+ /**
197
+ * Effect HTTP middleware applied around the generated handler.
198
+ */
199
+ readonly middleware?: HttpMiddleware.HttpMiddleware | undefined;
200
+ /**
201
+ * Memo map used while building the registry layer.
202
+ */
203
+ readonly memoMap?: Layer.MemoMap | undefined;
204
+ };
205
+
206
+ /**
207
+ * Builds a Fetch-compatible request handler from a registry layer.
208
+ *
209
+ * This is the serverless entrypoint for the Effect SDK. The registry layer must
210
+ * provide `Registry`, usually by composing actor layers with `Registry.layer`
211
+ * via `Layer.provideMerge`.
212
+ */
213
+ export const toWebHandler = <E>(
214
+ registryLayer: Layer.Layer<Registry, E>,
215
+ options?: ToWebHandlerOptions,
216
+ ) => {
217
+ const { middleware, memoMap } = options ?? {};
218
+ let serverlessOptions: ServerlessOptions | undefined;
219
+ if (options !== undefined) {
220
+ const {
221
+ middleware: _middleware,
222
+ memoMap: _memoMap,
223
+ ...handlerOptions
224
+ } = options;
225
+ serverlessOptions = handlerOptions;
226
+ }
227
+
228
+ const registryLayerWithLogging = registryLayer.pipe(
229
+ Layer.provideMerge(Logger.layer),
230
+ );
231
+
232
+ return HttpEffect.toWebHandlerLayerWith(registryLayerWithLogging, {
233
+ toHandler: (context) =>
234
+ Effect.succeed(
235
+ makeHttpEffect(
236
+ Context.get(context, Registry),
237
+ serverlessOptions,
238
+ ).pipe(Effect.provide(context)),
239
+ ),
240
+ middleware,
241
+ memoMap,
242
+ });
243
+ };