orpc-fn 0.1.0 → 0.3.0

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 (156) hide show
  1. package/README.md +70 -33
  2. package/dist/assemble-procedure.d.ts +13 -0
  3. package/dist/assemble-procedure.d.ts.map +1 -0
  4. package/dist/assemble-procedure.js +29 -0
  5. package/dist/assemble-procedure.js.map +1 -0
  6. package/dist/bound-call.d.ts +1 -1
  7. package/dist/bound-call.d.ts.map +1 -1
  8. package/dist/bound-call.js.map +1 -1
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +56 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/client.d.ts +4 -2
  14. package/dist/client.d.ts.map +1 -1
  15. package/dist/client.js +4 -2
  16. package/dist/client.js.map +1 -1
  17. package/dist/compatibility.d.ts +16 -0
  18. package/dist/compatibility.d.ts.map +1 -0
  19. package/dist/compatibility.js +4 -0
  20. package/dist/compatibility.js.map +1 -0
  21. package/dist/create-fn.d.ts +30 -34
  22. package/dist/create-fn.d.ts.map +1 -1
  23. package/dist/create-fn.js +177 -121
  24. package/dist/create-fn.js.map +1 -1
  25. package/dist/expo.d.ts +11 -25
  26. package/dist/expo.d.ts.map +1 -1
  27. package/dist/expo.js +25 -30
  28. package/dist/expo.js.map +1 -1
  29. package/dist/hono.d.ts +12 -4
  30. package/dist/hono.d.ts.map +1 -1
  31. package/dist/hono.js +29 -12
  32. package/dist/hono.js.map +1 -1
  33. package/dist/index.d.ts +2 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/json-schema.d.ts +2 -0
  38. package/dist/json-schema.d.ts.map +1 -1
  39. package/dist/json-schema.js +11 -0
  40. package/dist/json-schema.js.map +1 -1
  41. package/dist/live/codec.d.ts +6 -1
  42. package/dist/live/codec.d.ts.map +1 -1
  43. package/dist/live/codec.js +14 -3
  44. package/dist/live/codec.js.map +1 -1
  45. package/dist/live/fn-live.d.ts +67 -22
  46. package/dist/live/fn-live.d.ts.map +1 -1
  47. package/dist/live/fn-live.js +98 -16
  48. package/dist/live/fn-live.js.map +1 -1
  49. package/dist/live/index.d.ts +1 -1
  50. package/dist/live/index.d.ts.map +1 -1
  51. package/dist/live/index.js.map +1 -1
  52. package/dist/live/ioredis.d.ts +4 -1
  53. package/dist/live/ioredis.d.ts.map +1 -1
  54. package/dist/live/ioredis.js +21 -2
  55. package/dist/live/ioredis.js.map +1 -1
  56. package/dist/live/lifecycle.d.ts +7 -0
  57. package/dist/live/lifecycle.d.ts.map +1 -0
  58. package/dist/live/lifecycle.js +36 -0
  59. package/dist/live/lifecycle.js.map +1 -0
  60. package/dist/live/memory.d.ts.map +1 -1
  61. package/dist/live/memory.js +4 -0
  62. package/dist/live/memory.js.map +1 -1
  63. package/dist/live/pub-sub.d.ts +81 -13
  64. package/dist/live/pub-sub.d.ts.map +1 -1
  65. package/dist/live/pub-sub.js +437 -126
  66. package/dist/live/pub-sub.js.map +1 -1
  67. package/dist/live/redis-bun.d.ts +1 -1
  68. package/dist/live/redis-bun.d.ts.map +1 -1
  69. package/dist/live/redis-bun.js +3 -5
  70. package/dist/live/redis-bun.js.map +1 -1
  71. package/dist/live/redis.d.ts +2 -2
  72. package/dist/live/redis.d.ts.map +1 -1
  73. package/dist/live/redis.js +36 -9
  74. package/dist/live/redis.js.map +1 -1
  75. package/dist/live/transport.d.ts +5 -1
  76. package/dist/live/transport.d.ts.map +1 -1
  77. package/dist/live/transport.js.map +1 -1
  78. package/dist/logger.d.ts +2 -0
  79. package/dist/logger.d.ts.map +1 -1
  80. package/dist/logger.js +21 -0
  81. package/dist/logger.js.map +1 -1
  82. package/dist/mastra.d.ts.map +1 -1
  83. package/dist/mastra.js +16 -7
  84. package/dist/mastra.js.map +1 -1
  85. package/dist/mcp/core.d.ts +41 -0
  86. package/dist/mcp/core.d.ts.map +1 -0
  87. package/dist/mcp/core.js +61 -0
  88. package/dist/mcp/core.js.map +1 -0
  89. package/dist/mcp/sdk.d.ts +12 -0
  90. package/dist/mcp/sdk.d.ts.map +1 -0
  91. package/dist/mcp/sdk.js +14 -0
  92. package/dist/mcp/sdk.js.map +1 -0
  93. package/dist/mcp/server.d.ts +11 -0
  94. package/dist/mcp/server.d.ts.map +1 -0
  95. package/dist/mcp/server.js +13 -0
  96. package/dist/mcp/server.js.map +1 -0
  97. package/dist/mcp.d.ts +3 -2
  98. package/dist/mcp.d.ts.map +1 -1
  99. package/dist/mcp.js +22 -7
  100. package/dist/mcp.js.map +1 -1
  101. package/dist/meta.d.ts +15 -7
  102. package/dist/meta.d.ts.map +1 -1
  103. package/dist/meta.js +9 -2
  104. package/dist/meta.js.map +1 -1
  105. package/dist/otel.d.ts.map +1 -1
  106. package/dist/otel.js +77 -6
  107. package/dist/otel.js.map +1 -1
  108. package/dist/route-options.d.ts +22 -0
  109. package/dist/route-options.d.ts.map +1 -0
  110. package/dist/route-options.js +67 -0
  111. package/dist/route-options.js.map +1 -0
  112. package/dist/router.d.ts +13 -1
  113. package/dist/router.d.ts.map +1 -1
  114. package/dist/router.js +35 -0
  115. package/dist/router.js.map +1 -1
  116. package/dist/timing.d.ts +8 -0
  117. package/dist/timing.d.ts.map +1 -0
  118. package/dist/timing.js +4 -0
  119. package/dist/timing.js.map +1 -0
  120. package/dist/types.d.ts +96 -41
  121. package/dist/types.d.ts.map +1 -1
  122. package/dist/types.js +6 -2
  123. package/dist/types.js.map +1 -1
  124. package/package.json +28 -3
  125. package/src/assemble-procedure.ts +42 -0
  126. package/src/bound-call.ts +4 -3
  127. package/src/cli.ts +68 -0
  128. package/src/client.ts +9 -2
  129. package/src/compatibility.ts +39 -0
  130. package/src/create-fn.ts +295 -182
  131. package/src/expo.ts +49 -44
  132. package/src/hono.ts +36 -17
  133. package/src/index.ts +7 -2
  134. package/src/json-schema.ts +16 -0
  135. package/src/live/codec.ts +22 -3
  136. package/src/live/fn-live.ts +248 -69
  137. package/src/live/index.ts +2 -0
  138. package/src/live/ioredis.ts +22 -2
  139. package/src/live/lifecycle.ts +44 -0
  140. package/src/live/memory.ts +4 -0
  141. package/src/live/pub-sub.ts +619 -168
  142. package/src/live/redis-bun.ts +7 -5
  143. package/src/live/redis.ts +37 -11
  144. package/src/live/transport.ts +4 -1
  145. package/src/logger.ts +20 -0
  146. package/src/mastra.ts +15 -7
  147. package/src/mcp/core.ts +102 -0
  148. package/src/mcp/sdk.ts +36 -0
  149. package/src/mcp/server.ts +35 -0
  150. package/src/mcp.ts +48 -20
  151. package/src/meta.ts +40 -24
  152. package/src/otel.ts +77 -10
  153. package/src/route-options.ts +100 -0
  154. package/src/router.ts +58 -1
  155. package/src/timing.ts +5 -0
  156. package/src/types.ts +186 -62
@@ -1,23 +1,12 @@
1
- import type { AnyProcedure } from '@orpc/server';
2
1
  import { type BoundCall } from './bound-call.js';
3
2
  import { type FnLive } from './live/fn-live.js';
4
3
  import { type CreatePublisher, type CreatePubSub, type PubSubRuntimeOptions } from './live/pub-sub.js';
5
4
  import { type FnLogger, type LogAttributes } from './logger.js';
6
- import { type FnMeta } from './meta.js';
7
- import { type AttributeValue, type OtelApiLike, type SpanLike, type SpanOf } from './otel.js';
5
+ import { readFnMeta } from './meta.js';
6
+ import { type AttributeValue, type OtelApiLike, type SpanOf } from './otel.js';
8
7
  import { createRouter } from './router.js';
9
- import { type AnyFnContext, type BuilderLike, type FiniteExtras, type Fn, type FnCompletedEvent, type FnDefinition, type Guard, type GuardParams, type MaybePromise, type RejectKeys, type ReservedOptionKey } from './types.js';
10
- type UnionContext<TProcedures extends Record<string, BuilderLike>> = AnyFnContext<{
11
- procedures: TProcedures;
12
- default: string;
13
- extras: object;
14
- guards: object;
15
- meta: object;
16
- tag: string;
17
- span: SpanLike;
18
- logger: FnLogger;
19
- }>;
20
- export type CreateFnOptions<TProcedures extends Record<string, BuilderLike>, TDefault extends keyof TProcedures & string, TExtras extends object, TGuards, TMeta extends object, TTag extends string, TOtel extends OtelApiLike | undefined, TLogger extends FnLogger> = {
8
+ import { type ProcedureHookParams, type CompletedHookEvent, type CurrentContextOf, type BuilderLike, type FiniteExtras, type ScopedExtrasGuard, type Fn, type FnDefinition, type Guard, type GuardParams, type MaybePromise, type RejectKeys, type ReservedOptionKey } from './types.js';
9
+ export type CreateFnOptions<TProcedures extends Record<string, BuilderLike>, TDefault extends keyof TProcedures & string, TExtras extends object, TGuards, TMeta extends object, TTag extends string, TOtel extends OtelApiLike | undefined, TLogger extends FnLogger, TScoped extends Partial<Record<keyof TProcedures, object>> = Record<never, never>> = {
21
10
  /** Named oRPC builders, e.g. `{ public: os.$context<Ctx>(), protected: authed }`. */
22
11
  procedures: TProcedures;
23
12
  /**
@@ -26,46 +15,52 @@ export type CreateFnOptions<TProcedures extends Record<string, BuilderLike>, TDe
26
15
  */
27
16
  default?: TDefault;
28
17
  /** Per-call values merged into every handler's params; may be async. */
29
- extras?: (params: {
30
- context: UnionContext<TProcedures>;
18
+ extras?: (params: ProcedureHookParams<TProcedures, {
31
19
  span: SpanOf<TOtel> | undefined;
32
20
  signal: AbortSignal | undefined;
33
21
  name: string;
34
- procedure: string;
35
- }) => MaybePromise<TExtras>;
22
+ }>) => MaybePromise<TExtras>;
23
+ extrasByProcedure?: {
24
+ [K in keyof TScoped & keyof TProcedures]: (params: {
25
+ context: CurrentContextOf<TProcedures[K]>;
26
+ span: SpanOf<TOtel> | undefined;
27
+ signal: AbortSignal | undefined;
28
+ name: string;
29
+ procedure: K;
30
+ }) => MaybePromise<TScoped[K] & ScopedExtrasGuard<NoInfer<TScoped[K]>>>;
31
+ };
36
32
  /**
37
33
  * Checks run before the handler; each key becomes a typed `fn()` option.
38
- * Keys must not reuse route options or meta keys.
34
+ * Keys must not reuse route options. False denies, true or void allows.
39
35
  */
40
- guards?: TGuards & Record<string, Guard<never, GuardParams<UnionContext<TProcedures>, TMeta>>> & RejectKeys<NoInfer<TGuards>, ReservedOptionKey | keyof NoInfer<TMeta>, 'orpc-fn: this guard name is a route option or meta key'>;
36
+ guards?: TGuards & Record<string, Guard<never, ProcedureHookParams<TProcedures, Omit<GuardParams<unknown, TMeta>, 'context' | 'procedure'>>>> & RejectKeys<NoInfer<TGuards>, ReservedOptionKey, 'orpc-fn: this guard name is a route option'>;
41
37
  /**
42
- * Typed route metadata: `meta: {} as { readOnly?: boolean }`. Keys become
43
- * `fn()` options and must not reuse route options.
38
+ * Typed route metadata contract: `meta: defineMeta<AppMeta>()`.
39
+ * Route declarations place values inside their own `meta` object.
44
40
  */
45
- meta?: TMeta & RejectKeys<NoInfer<TMeta>, ReservedOptionKey, 'orpc-fn: this meta key is a route option'>;
41
+ meta?: TMeta;
46
42
  /** Allowed route tags: `tags: ['internal', 'external']`. */
47
43
  tags?: readonly TTag[];
48
44
  /** `import * as otel from '@opentelemetry/api'`. Omit for no tracing. */
49
45
  otel?: TOtel;
50
46
  logger?: (scope: string, span: SpanOf<TOtel> | undefined) => TLogger;
51
47
  /** Extra span attributes per call; undefined values are skipped. */
52
- spanAttributes?: (params: {
53
- context: UnionContext<TProcedures>;
48
+ spanAttributes?: (params: ProcedureHookParams<TProcedures, {
54
49
  name: string;
55
- procedure: string;
56
50
  meta: Partial<TMeta>;
57
- }) => Record<string, AttributeValue | undefined>;
58
- /** Called after every handler; returned attributes join the `fn.completed` log. */
59
- onCompleted?: (event: FnCompletedEvent<UnionContext<TProcedures>, SpanOf<TOtel>>) => LogAttributes | undefined | void;
51
+ }>) => Record<string, AttributeValue | undefined>;
52
+ /** Called after the procedure (or stream) settles; returned attributes join the `fn.completed` log. */
53
+ onCompleted?: (event: CompletedHookEvent<TProcedures, SpanOf<TOtel>>) => LogAttributes | undefined | void;
60
54
  /** Errors that are the caller's fault, logged at `warn`. Default: 4xx `ORPCError` or `AbortError`. */
61
55
  isExpectedError?: (error: unknown) => boolean;
62
56
  /** Needed for `fnLive` and `createPubSub`. */
63
57
  pubsub?: PubSubRuntimeOptions;
64
58
  };
65
- type Definition<TProcedures extends Record<string, BuilderLike>, TDefault extends string, TExtras extends object, TGuards, TMeta extends object, TTag extends string, TOtel, TLogger extends FnLogger> = {
59
+ type Definition<TProcedures extends Record<string, BuilderLike>, TDefault extends string, TExtras extends object, TGuards, TMeta extends object, TTag extends string, TOtel, TLogger extends FnLogger, TScoped extends Partial<Record<keyof TProcedures, object>> = Record<never, never>> = {
66
60
  procedures: TProcedures;
67
61
  default: TDefault;
68
62
  extras: TExtras;
63
+ extrasByProcedure: TScoped;
69
64
  guards: TGuards;
70
65
  meta: TMeta;
71
66
  tag: TTag;
@@ -78,13 +73,14 @@ export type FnFactory<TDef extends FnDefinition> = {
78
73
  createPubSub: CreatePubSub<TDef>;
79
74
  createPublisher: CreatePublisher;
80
75
  createRouter: typeof createRouter;
81
- readMeta: (procedure: AnyProcedure) => FnMeta<TDef['meta']>;
76
+ readMeta: typeof readFnMeta;
82
77
  /**
83
78
  * A `call` outside any handler (seeders, scripts, tests), traced with this
84
79
  * instance's OpenTelemetry.
85
80
  */
86
81
  createCall: <TContext>(context: TContext, signal?: AbortSignal) => BoundCall<TContext>;
87
- /** End every open subscription of this instance; returns how many closed. */
82
+ /** Terminal, idempotent shutdown: close streams and await owned resources. */
83
+ shutdown: () => Promise<void>;
88
84
  drainPubSubSubscribers: () => number;
89
85
  activePubSubSubscriberCount: () => number;
90
86
  };
@@ -92,6 +88,6 @@ export type FnFactory<TDef extends FnDefinition> = {
92
88
  * Builds the app's `fn` and friends once. Everything app-specific (auth,
93
89
  * context, database, queues, i18n, permissions) is injected here.
94
90
  */
95
- export declare function createFn<const TProcedures extends Record<string, BuilderLike>, TDefault extends keyof TProcedures & string = never, TExtras extends object = Record<never, never>, TGuards extends object = Record<never, never>, TMeta extends object = Record<never, never>, const TTag extends string = string, TOtel extends OtelApiLike | undefined = undefined, TLogger extends FnLogger = FnLogger>(options: CreateFnOptions<TProcedures, TDefault, TExtras, TGuards, TMeta, TTag, TOtel, TLogger> & FiniteExtras<NoInfer<TExtras>>): FnFactory<Definition<TProcedures, TDefault, TExtras, TGuards, TMeta, TTag, TOtel, TLogger>>;
91
+ export declare function createFn<const TProcedures extends Record<string, BuilderLike>, TDefault extends keyof TProcedures & string = never, TExtras extends object = Record<never, never>, TGuards extends object = Record<never, never>, TMeta extends object = Record<never, never>, const TTag extends string = string, TOtel extends OtelApiLike | undefined = undefined, TLogger extends FnLogger = FnLogger, TScoped extends Partial<Record<keyof TProcedures, object>> = Record<never, never>>(options: CreateFnOptions<TProcedures, TDefault, TExtras, TGuards, TMeta, TTag, TOtel, TLogger, TScoped> & FiniteExtras<NoInfer<TExtras>>): FnFactory<Definition<TProcedures, TDefault, TExtras, TGuards, TMeta, TTag, TOtel, TLogger, TScoped>>;
96
92
  export {};
97
93
  //# sourceMappingURL=create-fn.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-fn.d.ts","sourceRoot":"","sources":["../src/create-fn.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-fn.d.ts","sourceRoot":"","sources":["../src/create-fn.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,iBAAiB,CAAA;AAEjE,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EACN,KAAK,eAAe,EACpB,KAAK,YAAY,EAEjB,KAAK,oBAAoB,EACzB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAGN,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,EACN,KAAK,cAAc,EAGnB,KAAK,WAAW,EAEhB,KAAK,MAAM,EACX,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EACN,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,EAAE,EAEP,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,UAAU,EAEf,KAAK,iBAAiB,EACtB,MAAM,YAAY,CAAA;AA4BnB,MAAM,MAAM,eAAe,CAC1B,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAC/C,QAAQ,SAAS,MAAM,WAAW,GAAG,MAAM,EAC3C,OAAO,SAAS,MAAM,EACtB,OAAO,EACP,KAAK,SAAS,MAAM,EACpB,IAAI,SAAS,MAAM,EACnB,KAAK,SAAS,WAAW,GAAG,SAAS,EACrC,OAAO,SAAS,QAAQ,EACxB,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAClE,KAAK,EACL,KAAK,CACL,IACE;IACH,qFAAqF;IACrF,UAAU,EAAE,WAAW,CAAA;IACvB;;;OAGG;IACH,OAAO,CAAC,EAAE,QAAQ,CAAA;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,CACR,MAAM,EAAE,mBAAmB,CAC1B,WAAW,EACX;QACC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS,CAAA;QAC/B,MAAM,EAAE,WAAW,GAAG,SAAS,CAAA;QAC/B,IAAI,EAAE,MAAM,CAAA;KACZ,CACD,KACG,YAAY,CAAC,OAAO,CAAC,CAAA;IAC1B,iBAAiB,CAAC,EAAE;SAClB,CAAC,IAAI,MAAM,OAAO,GAAG,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE;YAClD,OAAO,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;YACzC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS,CAAA;YAC/B,MAAM,EAAE,WAAW,GAAG,SAAS,CAAA;YAC/B,IAAI,EAAE,MAAM,CAAA;YACZ,SAAS,EAAE,CAAC,CAAA;SACZ,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACvE,CAAA;IACD;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,GACf,MAAM,CACL,MAAM,EACN,KAAK,CACJ,KAAK,EACL,mBAAmB,CAClB,WAAW,EACX,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC,CAC1D,CACD,CACD,GACD,UAAU,CACT,OAAO,CAAC,OAAO,CAAC,EAChB,iBAAiB,EACjB,4CAA4C,CAC5C,CAAA;IACF;;;OAGG;IACH,IAAI,CAAC,EAAE,KAAK,CAAA;IACZ,4DAA4D;IAC5D,IAAI,CAAC,EAAE,SAAS,IAAI,EAAE,CAAA;IACtB,yEAAyE;IACzE,IAAI,CAAC,EAAE,KAAK,CAAA;IACZ,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS,KAAK,OAAO,CAAA;IACpE,oEAAoE;IACpE,cAAc,CAAC,EAAE,CAChB,MAAM,EAAE,mBAAmB,CAC1B,WAAW,EACX;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAA;KAAE,CACtC,KACG,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC,CAAA;IAC/C,uGAAuG;IACvG,WAAW,CAAC,EAAE,CACb,KAAK,EAAE,kBAAkB,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,KAEjD,aAAa,GAAG,SAAS,GAAG,IAAI,CAAA;IACrC,sGAAsG;IACtG,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;IAC7C,8CAA8C;IAC9C,MAAM,CAAC,EAAE,oBAAoB,CAAA;CAC7B,CAAA;AAED,KAAK,UAAU,CACd,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAC/C,QAAQ,SAAS,MAAM,EACvB,OAAO,SAAS,MAAM,EACtB,OAAO,EACP,KAAK,SAAS,MAAM,EACpB,IAAI,SAAS,MAAM,EACnB,KAAK,EACL,OAAO,SAAS,QAAQ,EACxB,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAClE,KAAK,EACL,KAAK,CACL,IACE;IACH,UAAU,EAAE,WAAW,CAAA;IACvB,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,EAAE,OAAO,CAAA;IACf,iBAAiB,EAAE,OAAO,CAAA;IAC1B,MAAM,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,KAAK,CAAA;IACX,GAAG,EAAE,IAAI,CAAA;IACT,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;IACnB,MAAM,EAAE,OAAO,CAAA;CACf,CAAA;AAED,MAAM,MAAM,SAAS,CAAC,IAAI,SAAS,YAAY,IAAI;IAClD,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAA;IACZ,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;IACpB,YAAY,EAAE,YAAY,CAAC,IAAI,CAAC,CAAA;IAChC,eAAe,EAAE,eAAe,CAAA;IAChC,YAAY,EAAE,OAAO,YAAY,CAAA;IACjC,QAAQ,EAAE,OAAO,UAAU,CAAA;IAC3B;;;OAGG;IACH,UAAU,EAAE,CAAC,QAAQ,EACpB,OAAO,EAAE,QAAQ,EACjB,MAAM,CAAC,EAAE,WAAW,KAChB,SAAS,CAAC,QAAQ,CAAC,CAAA;IACxB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7B,sBAAsB,EAAE,MAAM,MAAM,CAAA;IACpC,2BAA2B,EAAE,MAAM,MAAM,CAAA;CACzC,CAAA;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CACvB,KAAK,CAAC,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACrD,QAAQ,SAAS,MAAM,WAAW,GAAG,MAAM,GAAG,KAAK,EACnD,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAC7C,OAAO,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAC7C,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAC3C,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAClC,KAAK,SAAS,WAAW,GAAG,SAAS,GAAG,SAAS,EACjD,OAAO,SAAS,QAAQ,GAAG,QAAQ,EACnC,OAAO,SAAS,OAAO,CAAC,MAAM,CAAC,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAClE,KAAK,EACL,KAAK,CACL,EAED,OAAO,EAAE,eAAe,CACvB,WAAW,EACX,QAAQ,EACR,OAAO,EACP,OAAO,EACP,KAAK,EACL,IAAI,EACJ,KAAK,EACL,OAAO,EACP,OAAO,CACP,GACA,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAC7B,SAAS,CACX,UAAU,CACT,WAAW,EACX,QAAQ,EACR,OAAO,EACP,OAAO,EACP,KAAK,EACL,IAAI,EACJ,KAAK,EACL,OAAO,EACP,OAAO,CACP,CACD,CA8TA"}
package/dist/create-fn.js CHANGED
@@ -1,12 +1,18 @@
1
+ import { createProcedureAssembler } from './assemble-procedure.js';
2
+ import { isAsyncIteratorObject } from './stream.js';
3
+ import { normalizeRoute } from './route-options.js';
4
+ import { ORPCError, os } from '@orpc/server';
5
+ import { procedureDefinition } from './compatibility.js';
1
6
  import { createBoundCall } from './bound-call.js';
2
7
  import { isExpectedClientError } from './expected-client-error.js';
3
8
  import { createFnLive } from './live/fn-live.js';
4
9
  import { createLiveRuntime } from './live/pub-sub.js';
5
- import { createDefaultLogger } from './logger.js';
6
- import { FN_META_KEY, readFnMeta } from './meta.js';
10
+ import { createDefaultLogger, protectLogger } from './logger.js';
11
+ import { readFnMeta } from './meta.js';
7
12
  import { createTracing, errorMessageOf } from './otel.js';
13
+ import { RAW_INPUT, REQUEST_TIMING } from './timing.js';
8
14
  import { createRouter } from './router.js';
9
- import { RESERVED_OPTION_KEYS, ROUTE_OPTION_KEYS } from './types.js';
15
+ import { RESERVED_OPTION_KEYS } from './types.js';
10
16
  /**
11
17
  * Builds the app's `fn` and friends once. Everything app-specific (auth,
12
18
  * context, database, queues, i18n, permissions) is injected here.
@@ -14,19 +20,30 @@ import { RESERVED_OPTION_KEYS, ROUTE_OPTION_KEYS } from './types.js';
14
20
  export function createFn(options) {
15
21
  const runtime = options;
16
22
  const tracing = createTracing(runtime.otel);
17
- const defaultLogger = createDefaultLogger('fn');
18
- const createLogger = runtime.logger ??
19
- ((scope) => scope === 'fn' ? defaultLogger : createDefaultLogger(scope));
23
+ const defaultLogger = protectLogger(createDefaultLogger('fn'));
24
+ const createLogger = (scope, span) => {
25
+ try {
26
+ return protectLogger(runtime.logger?.(scope, span) ??
27
+ (scope === 'fn' ? defaultLogger : createDefaultLogger(scope)));
28
+ }
29
+ catch {
30
+ return defaultLogger;
31
+ }
32
+ };
20
33
  const isExpectedError = runtime.isExpectedError ?? isExpectedClientError;
21
34
  const guards = runtime.guards ?? {};
22
35
  const isGuard = (option) => Object.hasOwn(guards, option);
23
36
  for (const guard of Object.keys(guards)) {
37
+ if (typeof guards[guard] !== 'function')
38
+ throw new TypeError(`orpc-fn: guard ${guard} must be a function`);
24
39
  if (RESERVED_OPTION_KEYS.includes(guard)) {
25
40
  throw new Error(`orpc-fn: guard "${guard}" reuses a route option name; rename it`);
26
41
  }
27
42
  }
43
+ if (runtime.default && !Object.hasOwn(runtime.procedures, runtime.default))
44
+ throw new TypeError(`orpc-fn: unknown default procedure ${runtime.default}`);
28
45
  for (const [key, builder] of Object.entries(runtime.procedures)) {
29
- const def = builder['~orpc'];
46
+ const def = procedureDefinition(builder);
30
47
  if (def?.inputSchema !== undefined || def?.outputSchema !== undefined) {
31
48
  throw new Error(`orpc-fn: procedure "${key}" sets an input or output schema; set schemas on routes instead`);
32
49
  }
@@ -38,138 +55,170 @@ export function createFn(options) {
38
55
  }
39
56
  return key;
40
57
  };
41
- const buildProcedure = (route) => {
42
- const key = resolveKey(route.procedure, route.name);
43
- let builder = runtime.procedures[key];
44
- if (!builder)
45
- throw new Error(`orpc-fn: unknown procedure "${key}"`);
46
- const stored = {
47
- name: route.name,
48
- procedure: key,
49
- meta: route.meta
50
- };
51
- builder = builder
52
- .route({
53
- operationId: route.name,
54
- ...Object.fromEntries(Object.entries(route.route).filter(([, value]) => value !== undefined))
55
- })
56
- .meta({ [FN_META_KEY]: stored });
57
- if (route.input)
58
- builder = builder.input(route.input);
59
- if (route.output)
60
- builder = builder.output(route.output);
61
- return builder.handler(route.handler);
62
- };
63
- // biome-ignore lint/suspicious/noExplicitAny: the overloads in `Fn` carry the types
58
+ const buildProcedure = createProcedureAssembler(runtime.procedures, resolveKey);
59
+ const invocation = Symbol('orpc-fn.invocation');
60
+ // The overload-to-runtime boundary is adapted once at the returned factory.
64
61
  const fn = (routeOptions) => {
65
- const { handler, input, output, name, procedure, ...rest } = routeOptions;
62
+ const { handler, input, output, name, procedure, meta, stream, errors, guardResolvers, route, guardChecks } = normalizeRoute(routeOptions, isGuard);
66
63
  const key = resolveKey(procedure, name);
67
- const route = {};
68
- const guardChecks = [];
69
- const meta = {};
70
- for (const [option, value] of Object.entries(rest)) {
71
- if (value === undefined)
72
- continue;
73
- if (ROUTE_OPTION_KEYS.includes(option))
74
- route[option] = value;
75
- else if (isGuard(option))
76
- guardChecks.push([option, value]);
77
- else
78
- meta[option] = value;
79
- }
80
- return buildProcedure({
64
+ const built = buildProcedure({
81
65
  name,
82
66
  procedure: key,
83
67
  route,
84
68
  meta,
85
69
  input,
86
70
  output,
87
- handler: (params) => {
71
+ stream,
72
+ ...(errors ? { errors } : {}),
73
+ handler: async (params) => {
88
74
  const { context, signal } = params;
89
- const startTime = performance.now();
90
- const elapsedMs = () => Math.round((performance.now() - startTime) * 100) / 100;
91
- let logger = defaultLogger;
92
- return tracing.inSpan(name, 'SERVER', async (span) => {
93
- logger = createLogger('fn', span);
94
- span?.setAttribute('fn.operation', name);
95
- span?.setAttribute('fn.procedure', key);
75
+ const state = context[invocation];
76
+ const { [invocation]: _invocation, ...observedContext } = context;
77
+ state.context = observedContext;
78
+ state.input = params.input;
79
+ state.handlerStarted = true;
80
+ try {
96
81
  const attributes = runtime.spanAttributes?.({
97
82
  context,
98
83
  name,
99
84
  procedure: key,
100
85
  meta
101
86
  });
102
- for (const [attribute, value] of Object.entries(attributes ?? {})) {
87
+ for (const [attribute, value] of Object.entries(attributes ?? {}))
103
88
  if (value !== undefined)
104
- span?.setAttribute(attribute, value);
105
- }
106
- try {
107
- for (const [guard, value] of guardChecks) {
108
- await guards[guard]?.(value, {
109
- context,
110
- input: params.input,
111
- name,
112
- procedure: key,
113
- meta,
114
- signal
115
- });
116
- }
117
- const extras = await runtime.extras?.({
118
- context,
119
- span,
120
- signal,
121
- name,
122
- procedure: key
123
- });
124
- return await handler({
125
- ...extras,
126
- input: params.input,
127
- context,
128
- call: createBoundCall(context, signal, tracing),
129
- signal,
130
- span,
131
- logger
132
- });
133
- }
134
- finally {
135
- // Server-Timing reports the time until the result (or stream) exists.
136
- const timing = context.timing;
137
- if (typeof timing === 'object' && timing !== null) {
138
- ;
139
- timing.handler_ms = elapsedMs();
140
- }
141
- }
142
- },
143
- // After the result, or after a returned stream ends; before the span ends.
144
- (span, ...error) => {
145
- const durationMs = elapsedMs();
146
- span?.setAttribute('fn.duration_ms', durationMs);
147
- logCompleted(logger, {
89
+ state.span?.setAttribute(attribute, value);
90
+ }
91
+ catch (error) {
92
+ state.logger.warn('spanAttributes hook failed', {
93
+ error: errorMessageOf(error)
94
+ });
95
+ }
96
+ const resolvedChecks = [...guardChecks];
97
+ for (const [guard, resolver] of Object.entries(guardResolvers)) {
98
+ const value = await resolver({ input: params.input, context, signal });
99
+ if (value !== undefined)
100
+ resolvedChecks.push([guard, value]);
101
+ }
102
+ for (const [guard, value] of resolvedChecks) {
103
+ const result = await guards[guard]?.(value, {
104
+ context,
105
+ input: params.input,
148
106
  name,
149
107
  procedure: key,
150
- durationMs,
151
- success: error.length === 0,
152
- error: error[0],
153
- input: params.input,
154
- context,
155
- span,
156
- meta
108
+ meta,
109
+ signal
157
110
  });
111
+ if (result === false)
112
+ throw new ORPCError('FORBIDDEN', {
113
+ message: `Guard "${guard}" denied this call`
114
+ });
115
+ if (result !== undefined && result !== true)
116
+ throw new TypeError(`orpc-fn: guard "${guard}" must return void or boolean`);
117
+ }
118
+ const extraParams = {
119
+ context,
120
+ span: state.span,
121
+ signal,
122
+ name,
123
+ procedure: key
124
+ };
125
+ const shared = await runtime.extras?.(extraParams);
126
+ const scoped = await runtime.extrasByProcedure?.[key]?.(extraParams);
127
+ const extras = {
128
+ ...shared,
129
+ ...scoped
130
+ };
131
+ if (extras && typeof extras === 'object') {
132
+ for (const reserved of [
133
+ 'input',
134
+ 'context',
135
+ 'call',
136
+ 'signal',
137
+ 'span',
138
+ 'logger',
139
+ 'errors',
140
+ 'lastEventId'
141
+ ]) {
142
+ if (Object.hasOwn(extras, reserved))
143
+ throw new TypeError(`orpc-fn: extra "${reserved}" shadows a handler parameter`);
144
+ }
145
+ }
146
+ return handler({
147
+ ...extras,
148
+ input: params.input,
149
+ context,
150
+ call: createBoundCall(context, signal, tracing),
151
+ signal,
152
+ span: state.span,
153
+ logger: state.logger,
154
+ errors: params.errors,
155
+ lastEventId: params.lastEventId,
156
+ [RAW_INPUT]: state.rawInput
158
157
  });
159
158
  }
160
159
  });
160
+ // Prepending middleware shifts oRPC's validation indexes, covering auth,
161
+ // input validation, output validation, and the final stream lifetime.
162
+ return os
163
+ .use(async ({ context, next }, rawInput) => {
164
+ const started = performance.now();
165
+ const state = {
166
+ span: undefined,
167
+ logger: defaultLogger,
168
+ input: undefined,
169
+ context,
170
+ handlerStarted: false,
171
+ rawInput
172
+ };
173
+ const result = await tracing.inSpan(name, 'SERVER', async (span) => {
174
+ state.span = span;
175
+ state.logger = createLogger('fn', span);
176
+ span?.setAttribute('fn.operation', name);
177
+ span?.setAttribute('fn.procedure', key);
178
+ try {
179
+ const result = await next({ context: { [invocation]: state } });
180
+ if (isAsyncIteratorObject(result.output) !== stream) {
181
+ if (isAsyncIteratorObject(result.output)) {
182
+ try {
183
+ await result.output.return?.();
184
+ }
185
+ catch {
186
+ /* Release the unexpected stream. */
187
+ }
188
+ }
189
+ throw new TypeError(`orpc-fn: ${name} returned a result inconsistent with stream; declare stream: true for iterators`);
190
+ }
191
+ return result.output;
192
+ }
193
+ finally {
194
+ const timing = context[REQUEST_TIMING];
195
+ if (timing && !context[invocation]) {
196
+ timing.procedureMs += performance.now() - started;
197
+ timing.calls++;
198
+ }
199
+ }
200
+ }, (span, ...error) => {
201
+ const durationMs = Math.round((performance.now() - started) * 100) / 100;
202
+ span?.setAttribute('fn.duration_ms', durationMs);
203
+ logCompleted(state.logger, {
204
+ name,
205
+ procedure: key,
206
+ durationMs,
207
+ success: error.length === 0,
208
+ error: error[0],
209
+ input: state.input,
210
+ context: state.context,
211
+ span,
212
+ meta,
213
+ handlerStarted: state.handlerStarted
214
+ });
215
+ });
216
+ return { output: result, context };
217
+ })
218
+ .router({ route: built }).route;
161
219
  };
162
220
  const logCompleted = (logger, event) => {
163
221
  const { error, success } = event;
164
- const timing = event.context.timing;
165
- const timingFields = {};
166
- if (typeof timing === 'object' && timing !== null) {
167
- for (const [field, value] of Object.entries(timing)) {
168
- if (field !== 'handler_ms' && typeof value === 'number') {
169
- timingFields[field] = value;
170
- }
171
- }
172
- }
173
222
  let extra;
174
223
  try {
175
224
  extra = runtime.onCompleted?.(event);
@@ -179,25 +228,31 @@ export function createFn(options) {
179
228
  error: errorMessageOf(hookError)
180
229
  });
181
230
  }
182
- const level = success ? 'info' : isExpectedError(error) ? 'warn' : 'error';
231
+ let expected = false;
232
+ try {
233
+ expected = isExpectedError(error);
234
+ }
235
+ catch {
236
+ expected = isExpectedClientError(error);
237
+ }
238
+ const level = success ? 'info' : expected ? 'warn' : 'error';
183
239
  logger[level]('fn.completed', {
240
+ ...extra,
184
241
  event: 'fn.completed',
185
242
  operation: event.name,
186
243
  procedure: event.procedure,
187
244
  duration_ms: event.durationMs,
188
245
  status: success ? 'success' : 'failed',
189
- ...timingFields,
190
246
  ...(error === undefined
191
247
  ? {}
192
248
  : {
193
249
  error_type: error instanceof Error ? error.name : typeof error,
194
250
  error_message: errorMessageOf(error)
195
- }),
196
- ...extra
251
+ })
197
252
  });
198
253
  };
199
254
  const live = createLiveRuntime({
200
- buildProcedure,
255
+ fn,
201
256
  tracing,
202
257
  createLogger,
203
258
  pubsub: runtime.pubsub
@@ -205,7 +260,7 @@ export function createFn(options) {
205
260
  // The runtimes are untyped; `Fn`, `FnLive`, `CreatePubSub` and
206
261
  // `CreatePublisher` are their typed surface.
207
262
  return {
208
- fn: fn,
263
+ fn,
209
264
  fnLive: createFnLive({
210
265
  fn,
211
266
  createChannel: live.createChannel,
@@ -215,8 +270,9 @@ export function createFn(options) {
215
270
  createPubSub: live.createPubSub,
216
271
  createPublisher: live.createPublisher,
217
272
  createRouter,
218
- readMeta: (procedure) => readFnMeta(procedure),
273
+ readMeta: readFnMeta,
219
274
  createCall: (context, signal) => createBoundCall(context, signal, tracing),
275
+ shutdown: live.shutdown,
220
276
  drainPubSubSubscribers: live.drainPubSubSubscribers,
221
277
  activePubSubSubscriberCount: live.activePubSubSubscriberCount
222
278
  };