effect-orpc 0.1.4 → 0.2.1

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.
@@ -1,22 +1,11 @@
1
1
  import type { ClientContext } from "@orpc/client";
2
- import type {
3
- AnySchema,
4
- InferSchemaInput,
5
- InferSchemaOutput,
6
- Meta,
7
- Route,
8
- } from "@orpc/contract";
2
+ import type { AnySchema, Meta, Route } from "@orpc/contract";
9
3
  import { mergeMeta, mergeRoute } from "@orpc/contract";
10
4
  import type {
11
5
  AnyMiddleware,
12
6
  Context,
13
7
  CreateProcedureClientOptions,
14
8
  MapInputMiddleware,
15
- MergedCurrentContext,
16
- MergedInitialContext,
17
- Middleware,
18
- ProcedureActionableClient,
19
- ProcedureClient,
20
9
  ProcedureDef,
21
10
  } from "@orpc/server";
22
11
  import {
@@ -26,16 +15,238 @@ import {
26
15
  decorateMiddleware,
27
16
  Procedure,
28
17
  } from "@orpc/server";
29
- import type { IntersectPick, MaybeOptionalOptions } from "@orpc/shared";
18
+ import type { MaybeOptionalOptions } from "@orpc/shared";
30
19
 
31
- import type {
32
- EffectErrorConstructorMap,
33
- EffectErrorMap,
34
- MergedEffectErrorMap,
35
- } from "./tagged-error";
20
+ import { composeSurfaceProxy } from "./extension/compose-surfaces";
21
+ import {
22
+ createNodeProxy,
23
+ unhandled,
24
+ type NodeProxyContext,
25
+ } from "./extension/create-node-proxy";
26
+ import {
27
+ assertEffectState,
28
+ attachEffectState,
29
+ type EffectProxyTarget,
30
+ } from "./extension/state";
31
+ import type { EffectErrorMap, MergedEffectErrorMap } from "./tagged-error";
36
32
  import { effectErrorMapToErrorMap } from "./tagged-error";
37
33
  import type { EffectErrorMapToErrorMap, EffectProcedureDef } from "./types";
34
+ import type { EffectDecoratedProcedureSurface } from "./types/effect-procedure-surface";
35
+
36
+ type AnyProcedureLike = Procedure<any, any, any, any, any, any>;
37
+ type AnyEffectProcedure = EffectProcedure<
38
+ any,
39
+ any,
40
+ any,
41
+ any,
42
+ any,
43
+ any,
44
+ any,
45
+ any
46
+ >;
47
+ type AnyEffectDecoratedProcedure = EffectDecoratedProcedure<
48
+ any,
49
+ any,
50
+ any,
51
+ any,
52
+ any,
53
+ any,
54
+ any,
55
+ any
56
+ >;
57
+ type EffectProcedureTarget<
58
+ T extends AnyEffectProcedure | AnyEffectDecoratedProcedure =
59
+ | AnyEffectProcedure
60
+ | AnyEffectDecoratedProcedure,
61
+ > = T & EffectProxyTarget<AnyProcedureLike>;
62
+
63
+ const procedureVirtualDescriptors = {
64
+ "~effect": { enumerable: true },
65
+ actionable: { enumerable: false },
66
+ callable: { enumerable: false },
67
+ errors: { enumerable: false },
68
+ meta: { enumerable: false },
69
+ route: { enumerable: false },
70
+ use: { enumerable: false },
71
+ } as const;
72
+
73
+ const baseProcedureVirtualKeys = ["~effect"] as const;
74
+ const decoratedProcedureVirtualKeys = [
75
+ ...baseProcedureVirtualKeys,
76
+ "errors",
77
+ "meta",
78
+ "route",
79
+ "use",
80
+ "callable",
81
+ "actionable",
82
+ ] as const;
83
+
84
+ function getOrCreateVirtualMethod<T>(
85
+ context: NodeProxyContext<EffectProcedureTarget, AnyProcedureLike>,
86
+ prop: PropertyKey,
87
+ factory: () => T,
88
+ ): T {
89
+ const cache = context.methodCache;
90
+ if (cache.has(prop)) {
91
+ return cache.get(prop) as T;
92
+ }
93
+
94
+ const value = factory();
95
+ cache.set(prop, value);
96
+ return value;
97
+ }
98
+
99
+ function getEffectProcedureDef(
100
+ context: NodeProxyContext<EffectProcedureTarget, AnyProcedureLike>,
101
+ ): EffectProcedureDef<any, any, any, any, any, any, any, any> {
102
+ return {
103
+ ...context.upstream["~orpc"],
104
+ effectErrorMap: context.state.effectErrorMap,
105
+ runtime: context.state.runtime,
106
+ };
107
+ }
38
108
 
109
+ function createEffectProcedureProxy<
110
+ T extends AnyEffectProcedure | AnyEffectDecoratedProcedure,
111
+ >(
112
+ target: EffectProcedureTarget<T>,
113
+ decorated: boolean,
114
+ ): EffectProcedureTarget<T> {
115
+ return createNodeProxy<EffectProcedureTarget<T>, AnyProcedureLike>(target, {
116
+ getVirtual(context, prop, receiver) {
117
+ if (prop === "~effect") {
118
+ return getEffectProcedureDef(context);
119
+ }
120
+
121
+ if (!decorated) {
122
+ return unhandled();
123
+ }
124
+
125
+ const state = context.state;
126
+
127
+ switch (prop) {
128
+ case "errors":
129
+ return getOrCreateVirtualMethod(context, prop, () => {
130
+ return <U extends EffectErrorMap>(errors: U) => {
131
+ const nextEffectErrorMap: MergedEffectErrorMap<
132
+ typeof state.effectErrorMap,
133
+ U
134
+ > = {
135
+ ...state.effectErrorMap,
136
+ ...errors,
137
+ };
138
+ return new EffectDecoratedProcedure({
139
+ ...getEffectProcedureDef(context),
140
+ effectErrorMap: nextEffectErrorMap,
141
+ errorMap: effectErrorMapToErrorMap(nextEffectErrorMap),
142
+ });
143
+ };
144
+ });
145
+ case "meta":
146
+ return getOrCreateVirtualMethod(context, prop, () => {
147
+ return (meta: Meta) =>
148
+ new EffectDecoratedProcedure({
149
+ ...getEffectProcedureDef(context),
150
+ meta: mergeMeta(getEffectProcedureDef(context).meta, meta),
151
+ });
152
+ });
153
+ case "route":
154
+ return getOrCreateVirtualMethod(context, prop, () => {
155
+ return (route: Route) =>
156
+ new EffectDecoratedProcedure({
157
+ ...getEffectProcedureDef(context),
158
+ route: mergeRoute(getEffectProcedureDef(context).route, route),
159
+ });
160
+ });
161
+ case "use":
162
+ return getOrCreateVirtualMethod(context, prop, () => {
163
+ return (
164
+ middleware: AnyMiddleware,
165
+ mapInput?: MapInputMiddleware<any, any>,
166
+ ) => {
167
+ const mapped = mapInput
168
+ ? decorateMiddleware(middleware).mapInput(mapInput)
169
+ : middleware;
170
+
171
+ return new EffectDecoratedProcedure({
172
+ ...getEffectProcedureDef(context),
173
+ middlewares: addMiddleware(
174
+ getEffectProcedureDef(context).middlewares,
175
+ mapped,
176
+ ),
177
+ });
178
+ };
179
+ });
180
+ case "callable":
181
+ return <TClientContext extends ClientContext>(
182
+ ...rest: MaybeOptionalOptions<
183
+ CreateProcedureClientOptions<any, any, any, any, TClientContext>
184
+ >
185
+ ) => {
186
+ const client = createProcedureClient(
187
+ receiver as AnyProcedureLike,
188
+ ...rest,
189
+ );
190
+ return composeSurfaceProxy(
191
+ receiver as EffectDecoratedProcedure<
192
+ any,
193
+ any,
194
+ any,
195
+ any,
196
+ any,
197
+ any,
198
+ any,
199
+ any
200
+ >,
201
+ client,
202
+ );
203
+ };
204
+ case "actionable":
205
+ return (
206
+ ...rest: MaybeOptionalOptions<
207
+ CreateProcedureClientOptions<
208
+ any,
209
+ any,
210
+ any,
211
+ any,
212
+ Record<never, never>
213
+ >
214
+ >
215
+ ) => {
216
+ const client = createProcedureClient(
217
+ receiver as AnyProcedureLike,
218
+ ...rest,
219
+ );
220
+ const action = createActionableClient(client);
221
+ return composeSurfaceProxy(
222
+ receiver as EffectDecoratedProcedure<
223
+ any,
224
+ any,
225
+ any,
226
+ any,
227
+ any,
228
+ any,
229
+ any,
230
+ any
231
+ >,
232
+ action,
233
+ );
234
+ };
235
+ default:
236
+ return unhandled();
237
+ }
238
+ },
239
+ virtualDescriptors: procedureVirtualDescriptors,
240
+ virtualKeys: decorated
241
+ ? decoratedProcedureVirtualKeys
242
+ : baseProcedureVirtualKeys,
243
+ });
244
+ }
245
+
246
+ /**
247
+ * Effect-aware base procedure that carries the upstream procedure definition
248
+ * together with Effect runtime and error metadata.
249
+ */
39
250
  export class EffectProcedure<
40
251
  TInitialContext extends Context,
41
252
  TCurrentContext extends Context,
@@ -53,9 +264,6 @@ export class EffectProcedure<
53
264
  EffectErrorMapToErrorMap<TEffectErrorMap>,
54
265
  TMeta
55
266
  > {
56
- /**
57
- * This property holds the defined options and the effect-specific properties.
58
- */
59
267
  declare "~effect": EffectProcedureDef<
60
268
  TInitialContext,
61
269
  TCurrentContext,
@@ -66,9 +274,6 @@ export class EffectProcedure<
66
274
  TRequirementsProvided,
67
275
  TRuntimeError
68
276
  >;
69
- /**
70
- * This property holds the defined options.
71
- */
72
277
  declare "~orpc": ProcedureDef<
73
278
  TInitialContext,
74
279
  TCurrentContext,
@@ -89,9 +294,17 @@ export class EffectProcedure<
89
294
  TRequirementsProvided,
90
295
  TRuntimeError
91
296
  >,
297
+ procedure?: AnyProcedureLike,
92
298
  ) {
93
299
  super(def);
94
- this["~effect"] = def;
300
+ attachEffectState(this, procedure ?? new Procedure(def), {
301
+ effectErrorMap: def.effectErrorMap,
302
+ runtime: def.runtime,
303
+ });
304
+
305
+ if (new.target === EffectProcedure) {
306
+ return createEffectProcedureProxy(this, false);
307
+ }
95
308
  }
96
309
  }
97
310
 
@@ -110,20 +323,8 @@ export class EffectDecoratedProcedure<
110
323
  TMeta extends Meta,
111
324
  TRequirementsProvided,
112
325
  TRuntimeError,
113
- > extends EffectProcedure<
114
- TInitialContext,
115
- TCurrentContext,
116
- TInputSchema,
117
- TOutputSchema,
118
- TEffectErrorMap,
119
- TMeta,
120
- TRequirementsProvided,
121
- TRuntimeError
122
- > {
123
- /**
124
- * This property holds the defined options and the effect-specific properties.
125
- */
126
- declare "~effect": EffectProcedureDef<
326
+ >
327
+ extends EffectProcedure<
127
328
  TInitialContext,
128
329
  TCurrentContext,
129
330
  TInputSchema,
@@ -132,18 +333,9 @@ export class EffectDecoratedProcedure<
132
333
  TMeta,
133
334
  TRequirementsProvided,
134
335
  TRuntimeError
135
- >;
136
- declare "~orpc": ProcedureDef<
137
- TInitialContext,
138
- TCurrentContext,
139
- TInputSchema,
140
- TOutputSchema,
141
- EffectErrorMapToErrorMap<TEffectErrorMap>,
142
- TMeta
143
- >;
144
-
145
- constructor(
146
- def: EffectProcedureDef<
336
+ >
337
+ implements
338
+ EffectDecoratedProcedureSurface<
147
339
  TInitialContext,
148
340
  TCurrentContext,
149
341
  TInputSchema,
@@ -152,50 +344,19 @@ export class EffectDecoratedProcedure<
152
344
  TMeta,
153
345
  TRequirementsProvided,
154
346
  TRuntimeError
155
- >,
156
- ) {
157
- super(def);
158
- this["~effect"] = def;
159
- }
160
-
161
- /**
162
- * Adds type-safe custom errors.
163
- * Supports both traditional oRPC error definitions and ORPCTaggedError classes.
164
- *
165
- * @see {@link https://orpc.dev/docs/error-handling#type%E2%80%90safe-error-handling Type-Safe Error Handling Docs}
166
- */
167
- errors<U extends EffectErrorMap>(
168
- errors: U,
169
- ): EffectDecoratedProcedure<
347
+ >
348
+ {
349
+ declare errors: EffectDecoratedProcedureSurface<
170
350
  TInitialContext,
171
351
  TCurrentContext,
172
352
  TInputSchema,
173
353
  TOutputSchema,
174
- MergedEffectErrorMap<TEffectErrorMap, U>,
354
+ TEffectErrorMap,
175
355
  TMeta,
176
356
  TRequirementsProvided,
177
357
  TRuntimeError
178
- > {
179
- const newEffectErrorMap: MergedEffectErrorMap<TEffectErrorMap, U> = {
180
- ...this["~effect"].effectErrorMap,
181
- ...errors,
182
- };
183
- return new EffectDecoratedProcedure({
184
- ...this["~effect"],
185
- effectErrorMap: newEffectErrorMap,
186
- errorMap: effectErrorMapToErrorMap(newEffectErrorMap),
187
- });
188
- }
189
-
190
- /**
191
- * Sets or updates the metadata.
192
- * The provided metadata is spared-merged with any existing metadata.
193
- *
194
- * @see {@link https://orpc.dev/docs/metadata Metadata Docs}
195
- */
196
- meta(
197
- meta: TMeta,
198
- ): EffectDecoratedProcedure<
358
+ >["errors"];
359
+ declare meta: EffectDecoratedProcedureSurface<
199
360
  TInitialContext,
200
361
  TCurrentContext,
201
362
  TInputSchema,
@@ -204,24 +365,8 @@ export class EffectDecoratedProcedure<
204
365
  TMeta,
205
366
  TRequirementsProvided,
206
367
  TRuntimeError
207
- > {
208
- return new EffectDecoratedProcedure({
209
- ...this["~effect"],
210
- meta: mergeMeta(this["~effect"].meta, meta),
211
- });
212
- }
213
-
214
- /**
215
- * Sets or updates the route definition.
216
- * The provided route is spared-merged with any existing route.
217
- * This option is typically relevant when integrating with OpenAPI.
218
- *
219
- * @see {@link https://orpc.dev/docs/openapi/routing OpenAPI Routing Docs}
220
- * @see {@link https://orpc.dev/docs/openapi/input-output-structure OpenAPI Input/Output Structure Docs}
221
- */
222
- route(
223
- route: Route,
224
- ): EffectDecoratedProcedure<
368
+ >["meta"];
369
+ declare route: EffectDecoratedProcedureSurface<
225
370
  TInitialContext,
226
371
  TCurrentContext,
227
372
  TInputSchema,
@@ -230,107 +375,18 @@ export class EffectDecoratedProcedure<
230
375
  TMeta,
231
376
  TRequirementsProvided,
232
377
  TRuntimeError
233
- > {
234
- return new EffectDecoratedProcedure({
235
- ...this["~effect"],
236
- route: mergeRoute(this["~effect"].route, route),
237
- });
238
- }
239
-
240
- /**
241
- * Uses a middleware to modify the context or improve the pipeline.
242
- *
243
- * @info Supports both normal middleware and inline middleware implementations.
244
- * @info Pass second argument to map the input.
245
- * @note The current context must be satisfy middleware dependent-context
246
- * @see {@link https://orpc.dev/docs/middleware Middleware Docs}
247
- */
248
- use<
249
- UOutContext extends IntersectPick<TCurrentContext, UOutContext>,
250
- UInContext extends Context = TCurrentContext,
251
- >(
252
- middleware: Middleware<
253
- UInContext | TCurrentContext,
254
- UOutContext,
255
- InferSchemaOutput<TInputSchema>,
256
- InferSchemaInput<TOutputSchema>,
257
- EffectErrorConstructorMap<TEffectErrorMap>,
258
- TMeta
259
- >,
260
- ): EffectDecoratedProcedure<
261
- MergedInitialContext<TInitialContext, UInContext, TCurrentContext>,
262
- MergedCurrentContext<TCurrentContext, UOutContext>,
263
- TInputSchema,
264
- TOutputSchema,
265
- TEffectErrorMap,
266
- TMeta,
267
- TRequirementsProvided,
268
- TRuntimeError
269
- >;
270
-
271
- /**
272
- * Uses a middleware to modify the context or improve the pipeline.
273
- *
274
- * @info Supports both normal middleware and inline middleware implementations.
275
- * @info Pass second argument to map the input.
276
- * @note The current context must be satisfy middleware dependent-context
277
- * @see {@link https://orpc.dev/docs/middleware Middleware Docs}
278
- */
279
- use<
280
- UOutContext extends IntersectPick<TCurrentContext, UOutContext>,
281
- UInput,
282
- UInContext extends Context = TCurrentContext,
283
- >(
284
- middleware: Middleware<
285
- UInContext | TCurrentContext,
286
- UOutContext,
287
- UInput,
288
- InferSchemaInput<TOutputSchema>,
289
- EffectErrorConstructorMap<TEffectErrorMap>,
290
- TMeta
291
- >,
292
- mapInput: MapInputMiddleware<InferSchemaOutput<TInputSchema>, UInput>,
293
- ): EffectDecoratedProcedure<
294
- MergedInitialContext<TInitialContext, UInContext, TCurrentContext>,
295
- MergedCurrentContext<TCurrentContext, UOutContext>,
378
+ >["route"];
379
+ declare use: EffectDecoratedProcedureSurface<
380
+ TInitialContext,
381
+ TCurrentContext,
296
382
  TInputSchema,
297
383
  TOutputSchema,
298
384
  TEffectErrorMap,
299
385
  TMeta,
300
386
  TRequirementsProvided,
301
387
  TRuntimeError
302
- >;
303
-
304
- use(
305
- middleware: AnyMiddleware,
306
- mapInput?: MapInputMiddleware<any, any>,
307
- ): EffectDecoratedProcedure<any, any, any, any, any, any, any, any> {
308
- const mapped = mapInput
309
- ? decorateMiddleware(middleware).mapInput(mapInput)
310
- : middleware;
311
-
312
- return new EffectDecoratedProcedure({
313
- ...this["~effect"],
314
- middlewares: addMiddleware(this["~effect"].middlewares, mapped),
315
- });
316
- }
317
-
318
- /**
319
- * Make this procedure callable (works like a function while still being a procedure).
320
- *
321
- * @see {@link https://orpc.dev/docs/client/server-side Server-side Client Docs}
322
- */
323
- callable<TClientContext extends ClientContext>(
324
- ...rest: MaybeOptionalOptions<
325
- CreateProcedureClientOptions<
326
- TInitialContext,
327
- TOutputSchema,
328
- EffectErrorMapToErrorMap<TEffectErrorMap>,
329
- TMeta,
330
- TClientContext
331
- >
332
- >
333
- ): EffectDecoratedProcedure<
388
+ >["use"];
389
+ declare callable: EffectDecoratedProcedureSurface<
334
390
  TInitialContext,
335
391
  TCurrentContext,
336
392
  TInputSchema,
@@ -339,48 +395,8 @@ export class EffectDecoratedProcedure<
339
395
  TMeta,
340
396
  TRequirementsProvided,
341
397
  TRuntimeError
342
- > &
343
- ProcedureClient<
344
- TClientContext,
345
- TInputSchema,
346
- TOutputSchema,
347
- EffectErrorMapToErrorMap<TEffectErrorMap>
348
- > {
349
- const client: ProcedureClient<
350
- TClientContext,
351
- TInputSchema,
352
- TOutputSchema,
353
- EffectErrorMapToErrorMap<TEffectErrorMap>
354
- > = createProcedureClient(this, ...rest);
355
-
356
- return new Proxy(client, {
357
- get: (target, key) => {
358
- return Reflect.has(this, key)
359
- ? Reflect.get(this, key)
360
- : Reflect.get(target, key);
361
- },
362
- has: (target, key) => {
363
- return Reflect.has(this, key) || Reflect.has(target, key);
364
- },
365
- }) as any;
366
- }
367
-
368
- /**
369
- * Make this procedure compatible with server action.
370
- *
371
- * @see {@link https://orpc.dev/docs/server-action Server Action Docs}
372
- */
373
- actionable(
374
- ...rest: MaybeOptionalOptions<
375
- CreateProcedureClientOptions<
376
- TInitialContext,
377
- TOutputSchema,
378
- EffectErrorMapToErrorMap<TEffectErrorMap>,
379
- TMeta,
380
- Record<never, never>
381
- >
382
- >
383
- ): EffectDecoratedProcedure<
398
+ >["callable"];
399
+ declare actionable: EffectDecoratedProcedureSurface<
384
400
  TInitialContext,
385
401
  TCurrentContext,
386
402
  TInputSchema,
@@ -389,27 +405,22 @@ export class EffectDecoratedProcedure<
389
405
  TMeta,
390
406
  TRequirementsProvided,
391
407
  TRuntimeError
392
- > &
393
- ProcedureActionableClient<
394
- TInputSchema,
395
- TOutputSchema,
396
- EffectErrorMapToErrorMap<TEffectErrorMap>
397
- > {
398
- const action: ProcedureActionableClient<
408
+ >["actionable"];
409
+ constructor(
410
+ def: EffectProcedureDef<
411
+ TInitialContext,
412
+ TCurrentContext,
399
413
  TInputSchema,
400
414
  TOutputSchema,
401
- EffectErrorMapToErrorMap<TEffectErrorMap>
402
- > = createActionableClient(createProcedureClient(this, ...rest));
403
-
404
- return new Proxy(action, {
405
- get: (target, key) => {
406
- return Reflect.has(this, key)
407
- ? Reflect.get(this, key)
408
- : Reflect.get(target, key);
409
- },
410
- has: (target, key) => {
411
- return Reflect.has(this, key) || Reflect.has(target, key);
412
- },
413
- }) as any;
415
+ TEffectErrorMap,
416
+ TMeta,
417
+ TRequirementsProvided,
418
+ TRuntimeError
419
+ >,
420
+ procedure?: AnyProcedureLike,
421
+ ) {
422
+ super(def, procedure);
423
+ assertEffectState<AnyProcedureLike>(this);
424
+ return createEffectProcedureProxy(this, true);
414
425
  }
415
426
  }