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.
- package/README.md +70 -33
- package/dist/assemble-procedure.d.ts +13 -0
- package/dist/assemble-procedure.d.ts.map +1 -0
- package/dist/assemble-procedure.js +29 -0
- package/dist/assemble-procedure.js.map +1 -0
- package/dist/bound-call.d.ts +1 -1
- package/dist/bound-call.d.ts.map +1 -1
- package/dist/bound-call.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +56 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +4 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -2
- package/dist/client.js.map +1 -1
- package/dist/compatibility.d.ts +16 -0
- package/dist/compatibility.d.ts.map +1 -0
- package/dist/compatibility.js +4 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/create-fn.d.ts +30 -34
- package/dist/create-fn.d.ts.map +1 -1
- package/dist/create-fn.js +177 -121
- package/dist/create-fn.js.map +1 -1
- package/dist/expo.d.ts +11 -25
- package/dist/expo.d.ts.map +1 -1
- package/dist/expo.js +25 -30
- package/dist/expo.js.map +1 -1
- package/dist/hono.d.ts +12 -4
- package/dist/hono.d.ts.map +1 -1
- package/dist/hono.js +29 -12
- package/dist/hono.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/json-schema.d.ts +2 -0
- package/dist/json-schema.d.ts.map +1 -1
- package/dist/json-schema.js +11 -0
- package/dist/json-schema.js.map +1 -1
- package/dist/live/codec.d.ts +6 -1
- package/dist/live/codec.d.ts.map +1 -1
- package/dist/live/codec.js +14 -3
- package/dist/live/codec.js.map +1 -1
- package/dist/live/fn-live.d.ts +67 -22
- package/dist/live/fn-live.d.ts.map +1 -1
- package/dist/live/fn-live.js +98 -16
- package/dist/live/fn-live.js.map +1 -1
- package/dist/live/index.d.ts +1 -1
- package/dist/live/index.d.ts.map +1 -1
- package/dist/live/index.js.map +1 -1
- package/dist/live/ioredis.d.ts +4 -1
- package/dist/live/ioredis.d.ts.map +1 -1
- package/dist/live/ioredis.js +21 -2
- package/dist/live/ioredis.js.map +1 -1
- package/dist/live/lifecycle.d.ts +7 -0
- package/dist/live/lifecycle.d.ts.map +1 -0
- package/dist/live/lifecycle.js +36 -0
- package/dist/live/lifecycle.js.map +1 -0
- package/dist/live/memory.d.ts.map +1 -1
- package/dist/live/memory.js +4 -0
- package/dist/live/memory.js.map +1 -1
- package/dist/live/pub-sub.d.ts +81 -13
- package/dist/live/pub-sub.d.ts.map +1 -1
- package/dist/live/pub-sub.js +437 -126
- package/dist/live/pub-sub.js.map +1 -1
- package/dist/live/redis-bun.d.ts +1 -1
- package/dist/live/redis-bun.d.ts.map +1 -1
- package/dist/live/redis-bun.js +3 -5
- package/dist/live/redis-bun.js.map +1 -1
- package/dist/live/redis.d.ts +2 -2
- package/dist/live/redis.d.ts.map +1 -1
- package/dist/live/redis.js +36 -9
- package/dist/live/redis.js.map +1 -1
- package/dist/live/transport.d.ts +5 -1
- package/dist/live/transport.d.ts.map +1 -1
- package/dist/live/transport.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +21 -0
- package/dist/logger.js.map +1 -1
- package/dist/mastra.d.ts.map +1 -1
- package/dist/mastra.js +16 -7
- package/dist/mastra.js.map +1 -1
- package/dist/mcp/core.d.ts +41 -0
- package/dist/mcp/core.d.ts.map +1 -0
- package/dist/mcp/core.js +61 -0
- package/dist/mcp/core.js.map +1 -0
- package/dist/mcp/sdk.d.ts +12 -0
- package/dist/mcp/sdk.d.ts.map +1 -0
- package/dist/mcp/sdk.js +14 -0
- package/dist/mcp/sdk.js.map +1 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +13 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp.d.ts +3 -2
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +22 -7
- package/dist/mcp.js.map +1 -1
- package/dist/meta.d.ts +15 -7
- package/dist/meta.d.ts.map +1 -1
- package/dist/meta.js +9 -2
- package/dist/meta.js.map +1 -1
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +77 -6
- package/dist/otel.js.map +1 -1
- package/dist/route-options.d.ts +22 -0
- package/dist/route-options.d.ts.map +1 -0
- package/dist/route-options.js +67 -0
- package/dist/route-options.js.map +1 -0
- package/dist/router.d.ts +13 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +35 -0
- package/dist/router.js.map +1 -1
- package/dist/timing.d.ts +8 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +4 -0
- package/dist/timing.js.map +1 -0
- package/dist/types.d.ts +96 -41
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -2
- package/dist/types.js.map +1 -1
- package/package.json +28 -3
- package/src/assemble-procedure.ts +42 -0
- package/src/bound-call.ts +4 -3
- package/src/cli.ts +68 -0
- package/src/client.ts +9 -2
- package/src/compatibility.ts +39 -0
- package/src/create-fn.ts +295 -182
- package/src/expo.ts +49 -44
- package/src/hono.ts +36 -17
- package/src/index.ts +7 -2
- package/src/json-schema.ts +16 -0
- package/src/live/codec.ts +22 -3
- package/src/live/fn-live.ts +248 -69
- package/src/live/index.ts +2 -0
- package/src/live/ioredis.ts +22 -2
- package/src/live/lifecycle.ts +44 -0
- package/src/live/memory.ts +4 -0
- package/src/live/pub-sub.ts +619 -168
- package/src/live/redis-bun.ts +7 -5
- package/src/live/redis.ts +37 -11
- package/src/live/transport.ts +4 -1
- package/src/logger.ts +20 -0
- package/src/mastra.ts +15 -7
- package/src/mcp/core.ts +102 -0
- package/src/mcp/sdk.ts +36 -0
- package/src/mcp/server.ts +35 -0
- package/src/mcp.ts +48 -20
- package/src/meta.ts +40 -24
- package/src/otel.ts +77 -10
- package/src/route-options.ts +100 -0
- package/src/router.ts +58 -1
- package/src/timing.ts +5 -0
- package/src/types.ts +186 -62
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ export const { fn, fnLive, createPubSub, createRouter } = createFn({
|
|
|
29
29
|
default: 'protected',
|
|
30
30
|
extras: () => ({ db }),
|
|
31
31
|
guards: {
|
|
32
|
-
permission: (required: Permission, { context }) => context.user?.can(required)
|
|
32
|
+
permission: (required: Permission, { context }) => context.user?.can(required) ?? false
|
|
33
33
|
},
|
|
34
34
|
otel
|
|
35
35
|
})
|
|
@@ -52,7 +52,7 @@ export const getOrder = fn({
|
|
|
52
52
|
})
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Every call runs in an OpenTelemetry span named `order.get` (for a streaming handler, until the stream ends), runs the `permission` guard,
|
|
55
|
+
Every call runs in an OpenTelemetry span named `order.get` (for a streaming handler, until the stream ends), runs the `permission` guard, observes input/output validation, and logs one `fn.completed` line at `info`, `warn` (4xx) or `error` (defects).
|
|
56
56
|
|
|
57
57
|
## `orpc-fn`: `createFn`
|
|
58
58
|
|
|
@@ -61,31 +61,34 @@ createFn({
|
|
|
61
61
|
procedures, // named oRPC builders; the handler context is the builder's context
|
|
62
62
|
default, // builder used when a route omits `procedure`; leave out to require `procedure` on every route
|
|
63
63
|
extras, // ({ context, span, signal, name, procedure }) => values (or a promise) merged into handler params
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
extrasByProcedure, // e.g. { protected: ({ context }) => ({ t: translator(context.user) }) }
|
|
65
|
+
guards, // { key: (value, params) => void | boolean | Promise<void | boolean> } — each key is a typed fn() option
|
|
66
|
+
meta, // defineMeta<AppMeta>() — allowed values of each route's meta object
|
|
66
67
|
tags, // ['internal', 'external'] — allowed route tags
|
|
67
68
|
otel, // `import * as otel from '@opentelemetry/api'`; omit for no tracing
|
|
68
69
|
logger, // (scope, span) => your logger (debug/info/warn/error)
|
|
69
70
|
spanAttributes, // ({ context, name, procedure, meta }) => extra span attributes
|
|
70
71
|
onCompleted, // (event) => extra attributes for the fn.completed log
|
|
71
72
|
isExpectedError, // default: 4xx ORPCError or AbortError
|
|
72
|
-
pubsub // { transport, onDrop?,
|
|
73
|
+
pubsub // { transport (or () => transport, created on first use), namespace?, ownsTransport?, maxQueueSize?, maxIngressSize?, maxReplaySize?, initializationTimeoutMs?, onDrop?, onMetric? } for fnLive / createPubSub
|
|
73
74
|
})
|
|
74
75
|
```
|
|
75
76
|
|
|
76
|
-
It returns `fn`, `fnLive`, `createPubSub`, `createPublisher`, `createRouter`, `readMeta`, `createCall`, `drainPubSubSubscribers` and `activePubSubSubscriberCount`.
|
|
77
|
+
It returns `fn`, `fnLive`, `createPubSub`, `createPublisher`, `createRouter`, `readMeta`, `createCall`, `shutdown`, `drainPubSubSubscribers` and `activePubSubSubscriberCount`.
|
|
77
78
|
|
|
78
|
-
Procedure builders must not set an input or output schema (`os.input(...)`); put schemas on routes. Guard
|
|
79
|
+
Procedure builders must not set an input or output schema (`os.input(...)`); put schemas on routes. Guard keys cannot reuse route option names. Application metadata lives in a typed `meta` object and may share names with guards or route options. Unknown top-level route options and extras that shadow built-in handler params are rejected. Scoped extras replace matching shared keys, with handler types reflecting that precedence.
|
|
79
80
|
|
|
80
|
-
**`fn(options)`** has four overloads (input and output schema, each given or inferred). Route options: `name` (required; operationId and span name), `procedure`, `path`, `method`, `summary`, `description`, `tags`, `deprecated`, `successStatus`, `successDescription`, `inputStructure`, `outputStructure`,
|
|
81
|
+
**`fn(options)`** has four overloads (input and output schema, each given or inferred). Route options: `name` (required; operationId and span name), `procedure`, `path`, `method`, `summary`, `description`, `tags`, `deprecated`, `successStatus`, `successDescription`, `inputStructure`, `outputStructure`, configured guard keys, `guardResolvers`, `meta`, `stream` and native `errors` declarations. Core schemas use Standard Schema; Zod 4 remains the default for tool/live adapters. The handler receives `input`, `context`, `call`, `signal`, `span`, `logger`, native typed `errors`, `lastEventId` and your extras. With an output schema, the handler returns the schema's input type and callers receive its output type, so `output: z.string().transform(Number)` means the handler returns a string and callers get a number.
|
|
81
82
|
|
|
82
|
-
**Guards** run inside the span, before the handler, and only on routes that set their option.
|
|
83
|
+
**Guards** run inside the span, before the handler, and only on routes that set their option. Return `false` or throw an `ORPCError` to refuse; `true` and `undefined` allow. Static guards run sequentially in route declaration order, followed by resolved guards in resolver order. Unsupported return values fail types and runtime. For input-dependent checks use `guardResolvers: { permission: ({ input, context }) => requirement }`; these see parsed input. A guard cannot set both a static value and a resolver.
|
|
83
84
|
|
|
84
85
|
**`call(procedure, input)`** calls another procedure with the caller's context and abort signal, inside a `call: <name>` child span. Input and output are typed from the callee. Calling a procedure whose context the caller's context does not satisfy is a type error. Outside a handler (seeders, scripts, tests), use `createCall(context, signal?)` from `createFn`.
|
|
85
86
|
|
|
86
|
-
**Meta** is stored in oRPC's own procedure meta. It survives `os.router()`, prefixes and lazy routers.
|
|
87
|
+
**Meta** is stored in oRPC's own procedure meta. It survives `os.router()`, prefixes and lazy routers. Declare the contract with `defineMeta<AppMeta>()` (no runtime defaults), then use `fn({ meta: { readOnly: true }, ... })`. Both `readMeta(procedure)` and `readFnMeta(procedure)` infer metadata from the passed procedure, including procedures from another factory.
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
Completion hooks discriminate by `procedure` and `handlerStarted`. Before handler context exists (auth/input failure), they receive the initial context. Logging, tracing, metrics and completion-hook failures preserve procedure results. Custom log fields cannot overwrite canonical completion fields. Stream completion runs once when the stream ends.
|
|
90
|
+
|
|
91
|
+
Also exported: `defineMeta`, `createStreamManifest`, `createStreamManifestAsync`, `renderStreamManifest`, `isExpectedClientError`, `createBoundedEventQueue`, `createRouter`, `createBoundCall`, and the types `HandlerParams`, `RouteConfig`, `BoundCall`, `FnMeta`, `FnContext`, `FnCompletedEvent`, `ProcedureInput`, `ProcedureOutput`, `ProcedureContext`, `InitialContextOf`, `CurrentContextOf` and `GuardOptions`.
|
|
89
92
|
|
|
90
93
|
## `orpc-fn/live`: pub/sub and live queries
|
|
91
94
|
|
|
@@ -93,10 +96,14 @@ Also exported: `isExpectedClientError`, `createBoundedEventQueue`, `createRouter
|
|
|
93
96
|
import { createFn } from 'orpc-fn'
|
|
94
97
|
import { bunRedisTransport } from 'orpc-fn/live/redis-bun' // or ioredis / memory
|
|
95
98
|
|
|
96
|
-
const transport = bunRedisTransport(new RedisClient(process.env.REDIS_URL))
|
|
97
99
|
export const { fnLive, createPubSub, drainPubSubSubscribers } = createFn({
|
|
98
100
|
/* … */,
|
|
99
|
-
pubsub: {
|
|
101
|
+
pubsub: {
|
|
102
|
+
// A function defers the connection until the first publish or subscribe.
|
|
103
|
+
transport: () => bunRedisTransport(new RedisClient(process.env.REDIS_URL)),
|
|
104
|
+
ownsTransport: true,
|
|
105
|
+
onDrop: (count) => dropCounter.add(count)
|
|
106
|
+
}
|
|
100
107
|
})
|
|
101
108
|
|
|
102
109
|
export const listOrders = fnLive({
|
|
@@ -113,20 +120,23 @@ export const listOrders = fnLive({
|
|
|
113
120
|
await listOrders.publish({ organizationId, orderId }) // after a write
|
|
114
121
|
```
|
|
115
122
|
|
|
116
|
-
The `subscribe` route
|
|
123
|
+
The `subscribe` route runs builder middleware and the same fn guards/extras/meta/completion policy, then `authFn` and subscribes to the channel, then streams the initial snapshot, then reloads when invalidated (once per coalescing batch). Queue bounds (`maxQueueSize`, `maxIngressSize`, `maxReplaySize`) can be overridden per pub/sub definition or live config. `maxBatchSize` bounds a live coalescing batch (default 1000). `pubsub.onMetric` reports typed queue depths, drops, successful reconnects and parsing failures; keep this synchronous callback fast. Startup waits for a successful broker subscription, bounded by `initializationTimeoutMs` (10000ms by default) and cancellation. A refused subscriber receives nothing, and events published while the snapshot loads are not lost. Snapshots go through the output schema, like the route's own result. Options:
|
|
117
124
|
|
|
118
125
|
- `shouldUpdate` skips an event.
|
|
119
|
-
- `transformerFn` folds an event into `previous`
|
|
120
|
-
- `coalesceMs`
|
|
126
|
+
- `transformerFn` folds an event into initialized `previous`. It requires a `stateSchema` that validates parsed state. Patches returned by `fnLivePatch(state, emit)` additionally require `emitSchema`. These schemas should not transform already-parsed values. `rerun()` rechecks the current input through the procedure; it cannot change subscriber input.
|
|
127
|
+
- `coalesceMs` performs one rerun for an invalidation batch. Reducers process every event but emit their final full state; ordered patch payloads remain ordered. Batches are bounded (1000 events by default in `streamLiveSnapshots`).
|
|
121
128
|
- `authFn` and `useBacklog`/`backlogSize`/`backlogTtl` work as on `createPubSub`.
|
|
122
|
-
-
|
|
123
|
-
- `
|
|
129
|
+
- Live queries reload a full snapshot after overflow or reconnect. Raw pub/sub streams fail with `SERVICE_UNAVAILABLE` after detected loss, or emit an application-provided `overflowMarker` that must trigger recovery.
|
|
130
|
+
- `reauthorize` can check access before each live update. Initial authorization covers idle streams; apps that need immediate revocation should close those streams.
|
|
131
|
+
- `safePublish` logs publish errors instead of throwing them; it does not retry or guarantee delivery.
|
|
124
132
|
|
|
125
|
-
**`createPubSub`** returns a typed `subscribe` route plus `publish`, `publishMany` (one atomic round-trip across channels) and `getChannelName
|
|
133
|
+
**`createPubSub`** returns a typed `subscribe` route plus `publish`, `publishMany` (one atomic round-trip across channels) and typed `getSubscriptionChannelName({ input, context })` / `getPublishChannelName(event)` methods. The shared partial-input `getChannelName` method is deprecated. Publishers pass the event schema's input type; subscribers, filters and channel resolvers get its output type. The raw event travels in oRPC's JSON format, so `Date`, `Map`, `Set` and `BigInt` survive, and each receiver parses it once. Options include `filterFn`, `authFn`, `mirrorChannel`, `overflowMarker`, `procedure`, configured guards and `meta`. Channels may be static or use `channel: { subscribe: ({ input, context }) => key, publish: (parsedEvent) => key }`. Carry publishing tenant keys in the event. A factory `namespace` prefixes channels, mirrors and backlog keys. Each pub/sub definition subscribes to each channel once and parses each payload once for all local subscribers. Each subscriber then runs its filter in arrival order as it reads; its bounded queue holds events before filtering, so `onDrop` counts unfiltered events. Abort and drain release a subscription at once, even one that is not being read. A lost subscription is retried with exponential backoff and jitter. **`createPublisher`** is the publish-only half.
|
|
126
134
|
|
|
127
|
-
**Graceful shutdown:**
|
|
135
|
+
**Graceful shutdown:** `await shutdown()` stops new subscriptions/publication, cancels initialization/retries, ends existing streams and awaits cleanup. It closes a supplied transport only with `ownsTransport: true`, and a lazy `transport: () => …` only if it was ever created; it is never created during or after shutdown. A custom transport whose pending `subscribe` cannot be cancelled may resolve later; that late subscription is immediately released. `drainPubSubSubscribers()` remains a synchronous, nonterminal operation. Both are per factory.
|
|
128
136
|
|
|
129
|
-
|
|
137
|
+
Recent event IDs deduplicate replay/live overlap; legacy payloads remain readable. Replay is bounded and TTL-limited, so it is not durable resume. A snapshot may already include a queued delta: reducers must use revisions or idempotence. See [the revision example](examples/adoption/revisions.ts). Parsing ingress, replay staging and subscriber queues are bounded; positive queue/TTL/time options are validated.
|
|
138
|
+
|
|
139
|
+
**Transports** implement `PubSubTransport` (`publish(messages, backlog?)`, `readBacklog(channel)`, `subscribe(channel, listener, onLost, { onReconnect }?)`):
|
|
130
140
|
|
|
131
141
|
| Subpath | Client | Notes |
|
|
132
142
|
|---|---|---|
|
|
@@ -134,7 +144,7 @@ The `subscribe` route first runs `authFn` and subscribes to the channel, then st
|
|
|
134
144
|
| `orpc-fn/live/ioredis` | ioredis 5 | Dedicated subscriber via `duplicate()`; ioredis re-subscribes itself |
|
|
135
145
|
| `orpc-fn/live/memory` | none | Tests and single-process dev |
|
|
136
146
|
|
|
137
|
-
A subscriber connection that is lost, or cannot restore its channels after a reconnect, is closed and replaced; nothing it still emits is delivered. The Redis transports publish and maintain backlogs with one Lua script, sent by SHA, with a fallback to `EVAL` when Redis returns `NOSCRIPT`.
|
|
147
|
+
A subscriber connection that is lost, or cannot restore its channels after a reconnect, is closed and replaced; nothing it still emits is delivered. The Redis transports publish and maintain backlogs with one Lua script, sent by SHA, with a fallback to `EVAL` when Redis returns `NOSCRIPT`. Await `transport.close()` when the app owns its lifecycle. The Redis Lua batching adapter supports standalone Redis; Redis Cluster requires compatible key slots and is not promised by this adapter.
|
|
138
148
|
|
|
139
149
|
`streamLiveSnapshots`, `fnLivePatch` and `throwInitialSnapshotError` are exported for custom streams.
|
|
140
150
|
|
|
@@ -151,21 +161,40 @@ const tool = createMastraTool(getOrder, { requireApproval: true })
|
|
|
151
161
|
|
|
152
162
|
Options: `id`, `description`, `requireApproval`, `allowMissingInputSchema`, `onExecuteFinish`, `contextKey` (default `'orpcContext'`). Execution goes through oRPC `call`, so middleware and guards apply.
|
|
153
163
|
|
|
154
|
-
The tool takes the procedure's raw input and returns its parsed output, and `InferToolInput`/`InferToolOutput`/`InferUITools` see exactly those types. Mastra validates the input (coercing date strings) but passes the raw value on, and the procedure parses that raw value:
|
|
164
|
+
The tool takes the procedure's raw input and returns its parsed output, and `InferToolInput`/`InferToolOutput`/`InferUITools` see exactly those types. Mastra validates the input (coercing date strings) but passes the raw value on, and the procedure parses that raw value: transforms/refinements must be pure: SDK validation and procedure execution may run them more than once, always on raw input. Agent tools accept objects and unions of object shapes. Other inputs and streams are rejected. Finish-hook failures preserve execution outcomes.
|
|
155
165
|
|
|
156
166
|
## `orpc-fn/mcp`
|
|
157
167
|
|
|
158
168
|
Optional peers: `@orpc/zod`, `@orpc/json-schema`.
|
|
159
169
|
|
|
170
|
+
Inspection definitions from `listTools` use Standard Schema and are not `McpServer.registerTool` configs. Executable registration is a separate optional entry point per SDK major: `orpc-fn/mcp/sdk` for `@modelcontextprotocol/sdk` ^1.32.0 and `orpc-fn/mcp/server` for `@modelcontextprotocol/server` ^2.0.0:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
174
|
+
import { hasTag } from 'orpc-fn/mcp'
|
|
175
|
+
import { registerMcpTools } from 'orpc-fn/mcp/sdk'
|
|
176
|
+
|
|
177
|
+
const server = new Server({ name: 'api', version: '1.0.0' }, { capabilities: { tools: {} } })
|
|
178
|
+
registerMcpTools(server, router, {
|
|
179
|
+
filter: hasTag('external'),
|
|
180
|
+
context: ({ signal }) => authenticateToolRequest(signal)
|
|
181
|
+
})
|
|
182
|
+
// Connect your SDK transport; see examples/adoption/mcp.ts.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
For MCP SDK v2 (`@modelcontextprotocol/server` ^2.0.0), import the same `registerMcpTools` from `orpc-fn/mcp/server` instead; options and behaviour are identical:
|
|
186
|
+
|
|
160
187
|
```ts
|
|
161
|
-
import {
|
|
188
|
+
import { McpServer } from '@modelcontextprotocol/server'
|
|
189
|
+
import { registerMcpTools } from 'orpc-fn/mcp/server'
|
|
162
190
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
191
|
+
const mcp = new McpServer({ name: 'api', version: '1.0.0' }, { capabilities: { tools: {} } })
|
|
192
|
+
registerMcpTools(mcp.server, router, { filter: hasTag('external'), context })
|
|
166
193
|
```
|
|
167
194
|
|
|
168
|
-
|
|
195
|
+
The adapter installs the SDK list/call handlers on a low-level `Server` (also accessible as `McpServer.server`). Use one registration per server; these handlers own its tool list. It forwards cancellation, validates/coerces arguments, applies procedure authorization, and formats results as JSON/text MCP content. Override `formatResult` for images, structured content or other presentation. Tool selection is required. Object unions are supported; streams are rejected. Resolve lazy routers first with oRPC's `unlazyRouter`.
|
|
196
|
+
|
|
197
|
+
Sanitized tool-name collisions between distinct procedures throw; identical aliases are deduplicated. Override `name` to resolve deliberate collisions. Tool names are the sanitized `fn` names (`user.me` becomes `user-me`). Input schemas carry the same JSON Schema as your OpenAPI docs (dates become `string`/`date-time`), and validation turns them back into `Date`s. Validation returns the raw arguments, and `call(procedure, args, { context })` parses those: transforms/refinements must be pure: SDK validation and procedure execution may run them more than once, always on raw input. `readOnlyHint` defaults to GET routes; pass `readOnly` to change it. `hasTag(tag)` also works as an oRPC `OpenAPIHandler` filter.
|
|
169
198
|
|
|
170
199
|
## `orpc-fn/hono`
|
|
171
200
|
|
|
@@ -186,7 +215,7 @@ mountOrpc(app, {
|
|
|
186
215
|
|
|
187
216
|
Behaviour:
|
|
188
217
|
|
|
189
|
-
- The response gets a `Server-Timing` header built from
|
|
218
|
+
- The response gets a `Server-Timing` header built from the request timing collector: app-recorded queue/auth/context metrics plus library-owned procedure time. Nested calls do not overwrite parent timing; batches aggregate their top-level procedure durations (which may overlap). `fn` does not mutate an arbitrary `context.timing` field. `formatServerTiming` and `finishOrpcHeaders` are available for composed Fetch adapters.
|
|
190
219
|
- SSE responses get keep-alive comments (15 s) and anti-buffering headers.
|
|
191
220
|
- RPC calls are batched (`BatchHandlerPlugin`).
|
|
192
221
|
- Scalar docs are served at the OpenAPI prefix.
|
|
@@ -215,7 +244,14 @@ export const orpc = createTanstackQueryUtils(client) // Vue Query or React Query
|
|
|
215
244
|
if (hasOrpcErrorCode(error, 'NOT_FOUND')) { /* … */ }
|
|
216
245
|
```
|
|
217
246
|
|
|
218
|
-
`createRpcLink` takes every `RPCLink` option. It also batches parallel calls into one request, matching `mountOrpc`'s server-side batching (`batch: { maxSize, exclude, groups }`, or `false` to turn it off). A batch is one request with one client context, so by default only calls without a client context are batched; pass `groups` to batch calls that share one. A streaming response cannot be batched, and the request doesn't say whether the response will stream, so
|
|
247
|
+
`createRpcLink` takes every `RPCLink` option. It also batches parallel calls into one request, matching `mountOrpc`'s server-side batching (`batch: { maxSize, exclude, groups }`, or `false` to turn it off). A batch is one request with one client context, so by default only calls without a client context are batched; pass `groups` to batch calls that share one. A streaming response cannot be batched, and the request doesn't say whether the response will stream, so pass `streamPaths` generated from the actual router with `createStreamManifest` (or `createStreamManifestAsync` for lazy routers). Serialize it to a frontend module; importing that data needs no server runtime. The `orpc-fn` CLI does that, resolving lazy routers, sorting paths and writing only on change:
|
|
248
|
+
|
|
249
|
+
```sh
|
|
250
|
+
orpc-fn stream-manifest src/router.ts#appRouter --out ../web/src/stream-paths.ts # export const streamPaths = [...]
|
|
251
|
+
orpc-fn stream-manifest src/router.ts#appRouter --out ../web/src/stream-paths.ts --check # CI: exit 1 when stale
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`--out x.json` writes JSON instead, and `--name` renames the export. The CLI imports the router, so that module's side effects (database clients, SDKs) still run; it exits when done rather than waiting on their open handles. Run TypeScript routers with `bun`, `tsx` or Node ≥ 22.18. Mark custom iterator routes with `stream: true` or use oRPC's `eventIterator` output schema. Generated live/pubsub routes are marked automatically. `batch.exclude` remains an escape hatch, and the legacy `subscribe` naming heuristic remains as a fallback.
|
|
219
255
|
|
|
220
256
|
## `orpc-fn/expo`
|
|
221
257
|
|
|
@@ -224,9 +260,9 @@ Expo itself is passed in, not imported. The fetch must resolve to a response wit
|
|
|
224
260
|
```ts
|
|
225
261
|
import { fetch } from 'expo/fetch'
|
|
226
262
|
import { Platform } from 'react-native'
|
|
227
|
-
import {
|
|
263
|
+
import { createBetterAuthExpoLink } from 'orpc-fn/expo'
|
|
228
264
|
|
|
229
|
-
const link =
|
|
265
|
+
const link = createBetterAuthExpoLink({
|
|
230
266
|
url: `${baseUrl}/api/rpc`,
|
|
231
267
|
fetch,
|
|
232
268
|
native: Platform.OS !== 'web',
|
|
@@ -236,13 +272,14 @@ const link = createExpoLink({
|
|
|
236
272
|
})
|
|
237
273
|
```
|
|
238
274
|
|
|
239
|
-
On native, calls go through `expo/fetch`, the only fetch that streams there. The bridge forwards the request body and the abort signal. Without the signal, cancelled subscriptions keep their sockets open, the device's HTTP pool runs out, and later requests hang. Cookies are sent in a header (`credentials: 'omit'`), together with `expo-origin` and `x-skip-oauth-proxy`, the same headers `@better-auth/expo` sends. On the server, `mountOrpc({ normalizeHeaders: normalizeExpoOrigin })` copies `expo-origin` to `origin`. On web, the link uses the platform fetch with cookies. `createExpoFetch` is exported for a custom link.
|
|
275
|
+
On native, calls go through `expo/fetch`, the only fetch that streams there. The bridge forwards the request body and the abort signal. Without the signal, cancelled subscriptions keep their sockets open, the device's HTTP pool runs out, and later requests hang. Cookies are sent in a header (`credentials: 'omit'`), together with `expo-origin` and `x-skip-oauth-proxy`, the same headers `@better-auth/expo` sends. On the server, `mountOrpc({ normalizeHeaders: normalizeExpoOrigin })` copies `expo-origin` to `origin`. The generic `createExpoLink` forwards upstream headers (including async/context-aware resolvers) and adds no provider-specific headers. `createBetterAuthExpoLink` supplies the cookie/origin preset above. On web, the link uses the platform fetch with cookies, or your explicit `webFetch` override. `createExpoFetch` is exported for a custom link.
|
|
240
276
|
|
|
241
277
|
## Docs
|
|
242
278
|
|
|
243
279
|
- [Migrating an app from an in-repo `lib/fn`](docs/migration.md)
|
|
244
280
|
- [Decisions: what oRPC already ships and why this exists](docs/decisions.md)
|
|
245
281
|
- [Releasing](docs/releasing.md)
|
|
282
|
+
- [Independent core, tenant, revision, MCP and Expo examples](examples/adoption/README.md)
|
|
246
283
|
- [Example: Hono, in-memory pub/sub, two routes and a live query](examples/basic/server.ts)
|
|
247
284
|
|
|
248
285
|
## License
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AnySchema } from '@orpc/contract';
|
|
2
|
+
import type { AnyProcedure } from '@orpc/server';
|
|
3
|
+
import type { BuildProcedure } from './types.js';
|
|
4
|
+
export type BuilderChain = {
|
|
5
|
+
route(route: Record<string, unknown>): BuilderChain;
|
|
6
|
+
meta(meta: Record<string, unknown>): BuilderChain;
|
|
7
|
+
input(schema: AnySchema): BuilderChain;
|
|
8
|
+
output(schema: AnySchema): BuilderChain;
|
|
9
|
+
errors(errors: import('@orpc/contract').ErrorMap): BuilderChain;
|
|
10
|
+
handler(handler: Parameters<BuildProcedure>[0]['handler']): AnyProcedure;
|
|
11
|
+
};
|
|
12
|
+
export declare function createProcedureAssembler(procedures: Record<string, BuilderChain>, resolveKey: (key: string | undefined, name: string) => string): BuildProcedure;
|
|
13
|
+
//# sourceMappingURL=assemble-procedure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble-procedure.d.ts","sourceRoot":"","sources":["../src/assemble-procedure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,MAAM,MAAM,YAAY,GAAG;IAC1B,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAAA;IACnD,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAAA;IACjD,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG,YAAY,CAAA;IACtC,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,YAAY,CAAA;IACvC,MAAM,CAAC,MAAM,EAAE,OAAO,gBAAgB,EAAE,QAAQ,GAAG,YAAY,CAAA;IAC/D,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,YAAY,CAAA;CACxE,CAAA;AAED,wBAAgB,wBAAwB,CACvC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACxC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,GAC3D,cAAc,CAwBhB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FN_META_KEY } from './meta.js';
|
|
2
|
+
export function createProcedureAssembler(procedures, resolveKey) {
|
|
3
|
+
return (route) => {
|
|
4
|
+
const key = resolveKey(route.procedure, route.name);
|
|
5
|
+
let builder = procedures[key];
|
|
6
|
+
if (!builder)
|
|
7
|
+
throw new Error(`orpc-fn: unknown procedure "${key}"`);
|
|
8
|
+
const stored = {
|
|
9
|
+
name: route.name,
|
|
10
|
+
procedure: key,
|
|
11
|
+
meta: route.meta,
|
|
12
|
+
stream: route.stream ?? false
|
|
13
|
+
};
|
|
14
|
+
builder = builder
|
|
15
|
+
.route({
|
|
16
|
+
operationId: route.name,
|
|
17
|
+
...Object.fromEntries(Object.entries(route.route).filter(([, value]) => value !== undefined))
|
|
18
|
+
})
|
|
19
|
+
.meta({ [FN_META_KEY]: stored });
|
|
20
|
+
if (route.errors)
|
|
21
|
+
builder = builder.errors(route.errors);
|
|
22
|
+
if (route.input)
|
|
23
|
+
builder = builder.input(route.input);
|
|
24
|
+
if (route.output)
|
|
25
|
+
builder = builder.output(route.output);
|
|
26
|
+
return builder.handler(route.handler);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=assemble-procedure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble-procedure.js","sourceRoot":"","sources":["../src/assemble-procedure.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAqB,MAAM,WAAW,CAAA;AAY1D,MAAM,UAAU,wBAAwB,CACvC,UAAwC,EACxC,UAA6D;IAE7D,OAAO,CAAC,KAAK,EAAE,EAAE;QAChB,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QACnD,IAAI,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,GAAG,GAAG,CAAC,CAAA;QACpE,MAAM,MAAM,GAAiB;YAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,SAAS,EAAE,GAAG;YACd,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK;SAC7B,CAAA;QACD,OAAO,GAAG,OAAO;aACf,KAAK,CAAC;YACN,WAAW,EAAE,KAAK,CAAC,IAAI;YACvB,GAAG,MAAM,CAAC,WAAW,CACpB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CACtE;SACD,CAAC;aACD,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;QACjC,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACxD,IAAI,KAAK,CAAC,KAAK;YAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACrD,IAAI,KAAK,CAAC,MAAM;YAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACxD,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACtC,CAAC,CAAA;AACF,CAAC","sourcesContent":["import type { AnySchema } from '@orpc/contract'\nimport type { AnyProcedure } from '@orpc/server'\nimport { FN_META_KEY, type StoredFnMeta } from './meta.js'\nimport type { BuildProcedure } from './types.js'\n\nexport type BuilderChain = {\n\troute(route: Record<string, unknown>): BuilderChain\n\tmeta(meta: Record<string, unknown>): BuilderChain\n\tinput(schema: AnySchema): BuilderChain\n\toutput(schema: AnySchema): BuilderChain\n\terrors(errors: import('@orpc/contract').ErrorMap): BuilderChain\n\thandler(handler: Parameters<BuildProcedure>[0]['handler']): AnyProcedure\n}\n\nexport function createProcedureAssembler(\n\tprocedures: Record<string, BuilderChain>,\n\tresolveKey: (key: string | undefined, name: string) => string\n): BuildProcedure {\n\treturn (route) => {\n\t\tconst key = resolveKey(route.procedure, route.name)\n\t\tlet builder = procedures[key]\n\t\tif (!builder) throw new Error(`orpc-fn: unknown procedure \"${key}\"`)\n\t\tconst stored: StoredFnMeta = {\n\t\t\tname: route.name,\n\t\t\tprocedure: key,\n\t\t\tmeta: route.meta,\n\t\t\tstream: route.stream ?? false\n\t\t}\n\t\tbuilder = builder\n\t\t\t.route({\n\t\t\t\toperationId: route.name,\n\t\t\t\t...Object.fromEntries(\n\t\t\t\t\tObject.entries(route.route).filter(([, value]) => value !== undefined)\n\t\t\t\t)\n\t\t\t})\n\t\t\t.meta({ [FN_META_KEY]: stored })\n\t\tif (route.errors) builder = builder.errors(route.errors)\n\t\tif (route.input) builder = builder.input(route.input)\n\t\tif (route.output) builder = builder.output(route.output)\n\t\treturn builder.handler(route.handler)\n\t}\n}\n"]}
|
package/dist/bound-call.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ type ContextCheck<TContext, T> = [TContext] extends [ProcedureContext<T>] ? unkn
|
|
|
9
9
|
* Calls another procedure with the caller's context and signal. `TContext` is
|
|
10
10
|
* the caller's context; the callee must accept it.
|
|
11
11
|
*/
|
|
12
|
-
export type BoundCall<TContext
|
|
12
|
+
export type BoundCall<TContext> = <T extends AnyProcedure>(procedure: T & ContextCheck<TContext, T>, ...args: undefined extends ProcedureInput<T> ? [input?: ProcedureInput<T>] : [input: ProcedureInput<T>]) => Promise<ProcedureOutput<T>>;
|
|
13
13
|
/**
|
|
14
14
|
* Creates a bound call: nested calls keep the caller's context and abort
|
|
15
15
|
* signal and run inside a `call: <name>` child span, so the trace shows the
|
package/dist/bound-call.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bound-call.d.ts","sourceRoot":"","sources":["../src/bound-call.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,cAAc,CAAA;AAEtD,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,WAAW,CAAA;AACvD,OAAO,KAAK,EACX,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,MAAM,YAAY,CAAA;AAEnB,+EAA+E;AAC/E,KAAK,YAAY,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,GACtE,OAAO,GACP;IACA,8DAA8D,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAA;CACnF,CAAA;AAEH;;;GAGG;
|
|
1
|
+
{"version":3,"file":"bound-call.d.ts","sourceRoot":"","sources":["../src/bound-call.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,cAAc,CAAA;AAEtD,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,WAAW,CAAA;AACvD,OAAO,KAAK,EACX,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,MAAM,YAAY,CAAA;AAEnB,+EAA+E;AAC/E,KAAK,YAAY,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,GACtE,OAAO,GACP;IACA,8DAA8D,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAA;CACnF,CAAA;AAEH;;;GAGG;AACH,MAAM,MAAM,SAAS,CAAC,QAAQ,IAAI,CAAC,CAAC,SAAS,YAAY,EACxD,SAAS,EAAE,CAAC,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,CAAC,EACxC,GAAG,IAAI,EAAE,SAAS,SAAS,cAAc,CAAC,CAAC,CAAC,GACzC,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,GAC3B,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,KACzB,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAA;AAIhC;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EACvC,OAAO,EAAE,QAAQ,EACjB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,OAAmB,GAC1B,SAAS,CAAC,QAAQ,CAAC,CASrB"}
|
package/dist/bound-call.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bound-call.js","sourceRoot":"","sources":["../src/bound-call.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,IAAI,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,EAAE,aAAa,EAAgB,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"bound-call.js","sourceRoot":"","sources":["../src/bound-call.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,IAAI,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,EAAE,aAAa,EAAgB,MAAM,WAAW,CAAA;AAyBvD,MAAM,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC9B,OAAiB,EACjB,MAAoB,EACpB,OAAO,GAAY,SAAS;IAE5B,OAAO,CAAC,CAAC,SAAuB,EAAE,KAAc,EAAE,EAAE;QACnD,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,mBAAmB,CAAA;QAC9D,OAAO,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YACjE,MAAM,EAAE,cAAc,EAAE,CAAA;YACxB,IAAI,EAAE,YAAY,CAAC,YAAY,EAAE,IAAI,CAAC,CAAA;YACtC,OAAO,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;QAC1E,CAAC,CAAC,CAAA;IACH,CAAC,CAAwB,CAAA;AAC1B,CAAC","sourcesContent":["import { type AnyProcedure, call } from '@orpc/server'\nimport { readFnMeta } from './meta.js'\nimport { createTracing, type Tracing } from './otel.js'\nimport type {\n\tProcedureContext,\n\tProcedureInput,\n\tProcedureOutput\n} from './types.js'\n\n/** Rejects a procedure whose context the caller's context does not satisfy. */\ntype ContextCheck<TContext, T> = [TContext] extends [ProcedureContext<T>]\n\t? unknown\n\t: {\n\t\t\t'orpc-fn: the calling context does not satisfy this procedure': ProcedureContext<T>\n\t\t}\n\n/**\n * Calls another procedure with the caller's context and signal. `TContext` is\n * the caller's context; the callee must accept it.\n */\nexport type BoundCall<TContext> = <T extends AnyProcedure>(\n\tprocedure: T & ContextCheck<TContext, T>,\n\t...args: undefined extends ProcedureInput<T>\n\t\t? [input?: ProcedureInput<T>]\n\t\t: [input: ProcedureInput<T>]\n) => Promise<ProcedureOutput<T>>\n\nconst noTracing = createTracing(undefined)\n\n/**\n * Creates a bound call: nested calls keep the caller's context and abort\n * signal and run inside a `call: <name>` child span, so the trace shows the\n * whole call tree. oRPC's `call` runs the callee's own middleware and guards.\n *\n * Handlers get one as `call`. Outside a handler (seeders, tests) use\n * `createCall` from `createFn`, which traces with the instance's OpenTelemetry.\n */\nexport function createBoundCall<TContext>(\n\tcontext: TContext,\n\tsignal?: AbortSignal,\n\ttracing: Tracing = noTracing\n): BoundCall<TContext> {\n\treturn ((procedure: AnyProcedure, input: unknown) => {\n\t\tconst name = readFnMeta(procedure).name ?? 'unknown_procedure'\n\t\treturn tracing.inSpan(`call: ${name}`, 'INTERNAL', async (span) => {\n\t\t\tsignal?.throwIfAborted()\n\t\t\tspan?.setAttribute('rpc.method', name)\n\t\t\treturn call(procedure, input, signal ? { context, signal } : { context })\n\t\t})\n\t}) as BoundCall<TContext>\n}\n"]}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// biome-ignore-all lint/suspicious/noConsole: a CLI reports on the console
|
|
3
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { parseArgs } from 'node:util';
|
|
7
|
+
import { createStreamManifestAsync, renderStreamManifest } from './router.js';
|
|
8
|
+
const usage = `Usage: orpc-fn stream-manifest <module>[#export] --out <file.ts|file.json> [--name streamPaths] [--check]
|
|
9
|
+
|
|
10
|
+
Imports the router (export "default" unless #export is given) and writes the
|
|
11
|
+
paths of its streaming routes for createRpcLink({ streamPaths }). The file is
|
|
12
|
+
only written when its content changes. --check writes nothing and exits 1 when
|
|
13
|
+
the file is missing or stale. Importing the router runs its module code; run
|
|
14
|
+
TypeScript routers with a runtime that loads .ts (bun, tsx, node >= 22.18).`;
|
|
15
|
+
async function run(argv) {
|
|
16
|
+
const { positionals, values } = parseArgs({
|
|
17
|
+
args: [...argv],
|
|
18
|
+
allowPositionals: true,
|
|
19
|
+
options: {
|
|
20
|
+
out: { type: 'string' },
|
|
21
|
+
name: { type: 'string' },
|
|
22
|
+
check: { type: 'boolean' },
|
|
23
|
+
help: { type: 'boolean', short: 'h' }
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const [command, target] = positionals;
|
|
27
|
+
if (values.help || command !== 'stream-manifest' || !target || !values.out) {
|
|
28
|
+
console.error(usage);
|
|
29
|
+
return values.help ? 0 : 2;
|
|
30
|
+
}
|
|
31
|
+
const [modulePath = '', exportName = 'default'] = target.split('#');
|
|
32
|
+
const loaded = await import(pathToFileURL(resolve(modulePath)).href);
|
|
33
|
+
const router = loaded[exportName];
|
|
34
|
+
if (!router || typeof router !== 'object') {
|
|
35
|
+
console.error(`orpc-fn: ${modulePath} has no router export "${exportName}"`);
|
|
36
|
+
return 2;
|
|
37
|
+
}
|
|
38
|
+
const out = resolve(values.out);
|
|
39
|
+
const source = renderStreamManifest(await createStreamManifestAsync(router), { format: out.endsWith('.json') ? 'json' : 'ts', name: values.name });
|
|
40
|
+
const current = await readFile(out, 'utf8').catch(() => undefined);
|
|
41
|
+
if (current === source)
|
|
42
|
+
return 0;
|
|
43
|
+
if (values.check) {
|
|
44
|
+
console.error(`orpc-fn: ${values.out} is stale; rerun without --check to update it`);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
await writeFile(out, source);
|
|
48
|
+
console.log(`orpc-fn: wrote ${values.out}`);
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
// Exit explicitly: the imported router may hold open database or broker handles.
|
|
52
|
+
run(process.argv.slice(2)).then((code) => process.exit(code), (error) => {
|
|
53
|
+
console.error(error);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,2EAA2E;AAC3E,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAE7E,MAAM,KAAK,GAAG;;;;;;4EAM8D,CAAA;AAE5E,KAAK,UAAU,GAAG,CAAC,IAAuB;IACzC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;QACzC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;QACf,gBAAgB,EAAE,IAAI;QACtB,OAAO,EAAE;YACR,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACvB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACxB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;YAC1B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;SACrC;KACD,CAAC,CAAA;IACF,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,WAAW,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,IAAI,OAAO,KAAK,iBAAiB,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAC5E,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACpB,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3B,CAAC;IACD,MAAM,CAAC,UAAU,GAAG,EAAE,EAAE,UAAU,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACnE,MAAM,MAAM,GAA4B,MAAM,MAAM,CACnD,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CACvC,CAAA;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAA;IACjC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC3C,OAAO,CAAC,KAAK,CAAC,YAAY,UAAU,0BAA0B,UAAU,GAAG,CAAC,CAAA;QAC5E,OAAO,CAAC,CAAA;IACT,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC/B,MAAM,MAAM,GAAG,oBAAoB,CAClC,MAAM,yBAAyB,CAAC,MAAmB,CAAC,EACpD,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CACpE,CAAA;IACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAClE,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,CAAC,CAAA;IAChC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CACZ,YAAY,MAAM,CAAC,GAAG,+CAA+C,CACrE,CAAA;QACD,OAAO,CAAC,CAAA;IACT,CAAC;IACD,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAC5B,OAAO,CAAC,GAAG,CAAC,kBAAkB,MAAM,CAAC,GAAG,EAAE,CAAC,CAAA;IAC3C,OAAO,CAAC,CAAA;AACT,CAAC;AAED,iFAAiF;AACjF,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC9B,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAC5B,CAAC,KAAK,EAAE,EAAE;IACT,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAChB,CAAC,CACD,CAAA","sourcesContent":["#!/usr/bin/env node\n// biome-ignore-all lint/suspicious/noConsole: a CLI reports on the console\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { parseArgs } from 'node:util'\nimport type { AnyRouter } from '@orpc/server'\nimport { createStreamManifestAsync, renderStreamManifest } from './router.js'\n\nconst usage = `Usage: orpc-fn stream-manifest <module>[#export] --out <file.ts|file.json> [--name streamPaths] [--check]\n\nImports the router (export \"default\" unless #export is given) and writes the\npaths of its streaming routes for createRpcLink({ streamPaths }). The file is\nonly written when its content changes. --check writes nothing and exits 1 when\nthe file is missing or stale. Importing the router runs its module code; run\nTypeScript routers with a runtime that loads .ts (bun, tsx, node >= 22.18).`\n\nasync function run(argv: readonly string[]): Promise<number> {\n\tconst { positionals, values } = parseArgs({\n\t\targs: [...argv],\n\t\tallowPositionals: true,\n\t\toptions: {\n\t\t\tout: { type: 'string' },\n\t\t\tname: { type: 'string' },\n\t\t\tcheck: { type: 'boolean' },\n\t\t\thelp: { type: 'boolean', short: 'h' }\n\t\t}\n\t})\n\tconst [command, target] = positionals\n\tif (values.help || command !== 'stream-manifest' || !target || !values.out) {\n\t\tconsole.error(usage)\n\t\treturn values.help ? 0 : 2\n\t}\n\tconst [modulePath = '', exportName = 'default'] = target.split('#')\n\tconst loaded: Record<string, unknown> = await import(\n\t\tpathToFileURL(resolve(modulePath)).href\n\t)\n\tconst router = loaded[exportName]\n\tif (!router || typeof router !== 'object') {\n\t\tconsole.error(`orpc-fn: ${modulePath} has no router export \"${exportName}\"`)\n\t\treturn 2\n\t}\n\tconst out = resolve(values.out)\n\tconst source = renderStreamManifest(\n\t\tawait createStreamManifestAsync(router as AnyRouter),\n\t\t{ format: out.endsWith('.json') ? 'json' : 'ts', name: values.name }\n\t)\n\tconst current = await readFile(out, 'utf8').catch(() => undefined)\n\tif (current === source) return 0\n\tif (values.check) {\n\t\tconsole.error(\n\t\t\t`orpc-fn: ${values.out} is stale; rerun without --check to update it`\n\t\t)\n\t\treturn 1\n\t}\n\tawait writeFile(out, source)\n\tconsole.log(`orpc-fn: wrote ${values.out}`)\n\treturn 0\n}\n\n// Exit explicitly: the imported router may hold open database or broker handles.\nrun(process.argv.slice(2)).then(\n\t(code) => process.exit(code),\n\t(error) => {\n\t\tconsole.error(error)\n\t\tprocess.exit(1)\n\t}\n)\n"]}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ClientContext, type ORPCErrorCode } from '@orpc/client';
|
|
1
|
+
import { type ClientContext, ORPCError, type ORPCErrorCode } from '@orpc/client';
|
|
2
2
|
import { RPCLink, type RPCLinkOptions } from '@orpc/client/fetch';
|
|
3
3
|
import { BatchLinkPlugin } from '@orpc/client/plugins';
|
|
4
4
|
type BatchOptions<T extends ClientContext> = ConstructorParameters<typeof BatchLinkPlugin<T>>[0];
|
|
@@ -11,6 +11,8 @@ export type CreateRpcLinkOptions<T extends ClientContext> = RPCLinkOptions<T> &
|
|
|
11
11
|
* default only calls without a client context are batched; pass
|
|
12
12
|
* `groups` to batch calls that share a context (e.g. a token).
|
|
13
13
|
*/
|
|
14
|
+
/** Client-safe manifest generated by createStreamManifest(router). */
|
|
15
|
+
streamPaths?: readonly (readonly string[])[];
|
|
14
16
|
batch?: boolean | {
|
|
15
17
|
maxSize?: number;
|
|
16
18
|
/** Calls that must not be batched. Default: {@link isSubscriptionPath}. */
|
|
@@ -39,6 +41,6 @@ export declare function createRpcLink<T extends ClientContext = ClientContext>(o
|
|
|
39
41
|
* Typed check for a route error on the client. The RPC link rebuilds thrown
|
|
40
42
|
* route errors as `ORPCError`, so `instanceof` plus `code` is the contract.
|
|
41
43
|
*/
|
|
42
|
-
export declare function hasOrpcErrorCode(error: unknown, code:
|
|
44
|
+
export declare function hasOrpcErrorCode<TCode extends ORPCErrorCode>(error: unknown, code: TCode): error is ORPCError<TCode, unknown>;
|
|
43
45
|
export {};
|
|
44
46
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD,KAAK,YAAY,CAAC,CAAC,SAAS,aAAa,IAAI,qBAAqB,CACjE,OAAO,eAAe,CAAC,CAAC,CAAC,CACzB,CAAC,CAAC,CAAC,CAAA;AAEJ,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,aAAa,IACvD,cAAc,CAAC,CAAC,CAAC,GAAG;IACnB;;;;;;;OAOG;IACH,sEAAsE;IACtE,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,CAAA;IAC5C,KAAK,CAAC,EACH,OAAO,GACP;QACA,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,2EAA2E;QAC3E,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAA;QAC9C,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;KACjC,CAAA;CACJ,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,SAAU,SAAS,MAAM,EAAE,YACU,CAAA;AAEpE;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa,EACpE,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAC9B,OAAO,CAAC,CAAC,CAAC,CA2BZ;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,aAAa,EAC3D,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,KAAK,GACT,KAAK,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAEpC"}
|
package/dist/client.js
CHANGED
|
@@ -18,7 +18,8 @@ export const isSubscriptionPath = (path) => path.some((segment) => segment.toLow
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
export function createRpcLink(options) {
|
|
21
|
-
const { batch = true, plugins = [], ...rest } = options;
|
|
21
|
+
const { batch = true, plugins = [], streamPaths, ...rest } = options;
|
|
22
|
+
const streams = new Set(streamPaths?.map((path) => JSON.stringify(path)));
|
|
22
23
|
if (batch === false)
|
|
23
24
|
return new RPCLink({ ...rest, plugins });
|
|
24
25
|
const { maxSize = 25, exclude = isSubscriptionPath, groups } = batch === true ? {} : batch;
|
|
@@ -32,7 +33,8 @@ export function createRpcLink(options) {
|
|
|
32
33
|
// so the batch's empty context is exactly theirs.
|
|
33
34
|
{ condition: () => true, context: {} }
|
|
34
35
|
],
|
|
35
|
-
exclude: ({ path, context }) =>
|
|
36
|
+
exclude: ({ path, context }) => streams.has(JSON.stringify(path)) ||
|
|
37
|
+
exclude(path) ||
|
|
36
38
|
(groups === undefined && Object.keys(context).length > 0)
|
|
37
39
|
}),
|
|
38
40
|
...plugins
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,SAAS,EAAsB,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,OAAO,EAAuB,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,SAAS,EAAsB,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,OAAO,EAAuB,MAAM,oBAAoB,CAAA;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AA4BtD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAuB,EAAE,EAAE,CAC7D,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAA;AAEpE;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAC5B,OAAgC;IAEhC,MAAM,EAAE,KAAK,GAAG,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAA;IACpE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACzE,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,IAAI,OAAO,CAAI,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAA;IAChE,MAAM,EACL,OAAO,GAAG,EAAE,EACZ,OAAO,GAAG,kBAAkB,EAC5B,MAAM,EACN,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;IAC/B,OAAO,IAAI,OAAO,CAAI;QACrB,GAAG,IAAI;QACP,OAAO,EAAE;YACR,IAAI,eAAe,CAAI;gBACtB,OAAO;gBACP,MAAM,EAAE,MAAM,IAAI;oBACjB,kEAAkE;oBAClE,kDAAkD;oBAClD,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAO,EAAE;iBAC3C;gBACD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAC9B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;oBACjC,OAAO,CAAC,IAAI,CAAC;oBACb,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;aAC1D,CAAC;YACF,GAAG,OAAO;SACV;KACD,CAAC,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC/B,KAAc,EACd,IAAW;IAEX,OAAO,KAAK,YAAY,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAA;AACzD,CAAC","sourcesContent":["import { type ClientContext, ORPCError, type ORPCErrorCode } from '@orpc/client'\nimport { RPCLink, type RPCLinkOptions } from '@orpc/client/fetch'\nimport { BatchLinkPlugin } from '@orpc/client/plugins'\n\ntype BatchOptions<T extends ClientContext> = ConstructorParameters<\n\ttypeof BatchLinkPlugin<T>\n>[0]\n\nexport type CreateRpcLinkOptions<T extends ClientContext> =\n\tRPCLinkOptions<T> & {\n\t\t/**\n\t\t * Collapse parallel calls into one HTTP request, matching `mountOrpc`'s\n\t\t * server-side batching. Default: true (25 calls). Pass false to turn off.\n\t\t *\n\t\t * A batch is one HTTP request, so it carries one client context. By\n\t\t * default only calls without a client context are batched; pass\n\t\t * `groups` to batch calls that share a context (e.g. a token).\n\t\t */\n\t\t/** Client-safe manifest generated by createStreamManifest(router). */\n\t\tstreamPaths?: readonly (readonly string[])[]\n\t\tbatch?:\n\t\t\t| boolean\n\t\t\t| {\n\t\t\t\t\tmaxSize?: number\n\t\t\t\t\t/** Calls that must not be batched. Default: {@link isSubscriptionPath}. */\n\t\t\t\t\texclude?: (path: readonly string[]) => boolean\n\t\t\t\t\tgroups?: BatchOptions<T>['groups']\n\t\t\t }\n\t}\n\n/**\n * Streaming responses cannot be batched, and the request does not say a\n * response will stream - so streaming routes are recognised by name. The\n * default treats any path segment containing `subscribe` as one (the\n * convention `fnLive` routers use: `{ list, subscribe }`).\n */\nexport const isSubscriptionPath = (path: readonly string[]) =>\n\tpath.some((segment) => segment.toLowerCase().includes('subscribe'))\n\n/**\n * `RPCLink` with request batching: a page-load burst becomes one request\n * instead of many queued on the server's event loop.\n *\n * ```ts\n * const client: RouterClient<typeof router> = createORPCClient(createRpcLink({ url }))\n * export const orpc = createTanstackQueryUtils(client)\n * ```\n */\nexport function createRpcLink<T extends ClientContext = ClientContext>(\n\toptions: CreateRpcLinkOptions<T>\n): RPCLink<T> {\n\tconst { batch = true, plugins = [], streamPaths, ...rest } = options\n\tconst streams = new Set(streamPaths?.map((path) => JSON.stringify(path)))\n\tif (batch === false) return new RPCLink<T>({ ...rest, plugins })\n\tconst {\n\t\tmaxSize = 25,\n\t\texclude = isSubscriptionPath,\n\t\tgroups\n\t} = batch === true ? {} : batch\n\treturn new RPCLink<T>({\n\t\t...rest,\n\t\tplugins: [\n\t\t\tnew BatchLinkPlugin<T>({\n\t\t\t\tmaxSize,\n\t\t\t\tgroups: groups ?? [\n\t\t\t\t\t// Only context-free calls reach this group (see `exclude` below),\n\t\t\t\t\t// so the batch's empty context is exactly theirs.\n\t\t\t\t\t{ condition: () => true, context: {} as T }\n\t\t\t\t],\n\t\t\t\texclude: ({ path, context }) =>\n\t\t\t\t\tstreams.has(JSON.stringify(path)) ||\n\t\t\t\t\texclude(path) ||\n\t\t\t\t\t(groups === undefined && Object.keys(context).length > 0)\n\t\t\t}),\n\t\t\t...plugins\n\t\t]\n\t})\n}\n\n/**\n * Typed check for a route error on the client. The RPC link rebuilds thrown\n * route errors as `ORPCError`, so `instanceof` plus `code` is the contract.\n */\nexport function hasOrpcErrorCode<TCode extends ORPCErrorCode>(\n\terror: unknown,\n\tcode: TCode\n): error is ORPCError<TCode, unknown> {\n\treturn error instanceof ORPCError && error.code === code\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AnySchema, ErrorMap, Meta } from '@orpc/contract';
|
|
2
|
+
import type { Procedure, Route } from '@orpc/server';
|
|
3
|
+
/** All runtime access to oRPC's implementation namespace is audited here. */
|
|
4
|
+
export type ProcedureDefinition = {
|
|
5
|
+
meta?: Record<string, unknown>;
|
|
6
|
+
route?: Route;
|
|
7
|
+
inputSchema?: AnySchema;
|
|
8
|
+
outputSchema?: AnySchema;
|
|
9
|
+
};
|
|
10
|
+
export declare function procedureDefinition(procedure: object): ProcedureDefinition;
|
|
11
|
+
export type NativeMeta<T> = T extends Procedure<infer _I, infer _C, infer _In, infer _Out, infer _E, infer M extends Meta> ? M : Meta;
|
|
12
|
+
export type NativeErrors<T> = T extends Procedure<infer _I, infer _C, infer _In, infer _Out, infer E extends ErrorMap, infer _M> ? E : ErrorMap;
|
|
13
|
+
export type LibraryMeta<T> = NativeMeta<T> extends {
|
|
14
|
+
'orpc-fn': infer M;
|
|
15
|
+
} ? M : never;
|
|
16
|
+
//# sourceMappingURL=compatibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compatibility.d.ts","sourceRoot":"","sources":["../src/compatibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAA;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AAEpD,6EAA6E;AAC7E,MAAM,MAAM,mBAAmB,GAAG;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,YAAY,CAAC,EAAE,SAAS,CAAA;CACxB,CAAA;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CAE1E;AAED,MAAM,MAAM,UAAU,CAAC,CAAC,IACvB,CAAC,SAAS,SAAS,CAClB,MAAM,EAAE,EACR,MAAM,EAAE,EACR,MAAM,GAAG,EACT,MAAM,IAAI,EACV,MAAM,EAAE,EACR,MAAM,CAAC,SAAS,IAAI,CACpB,GACE,CAAC,GACD,IAAI,CAAA;AACR,MAAM,MAAM,YAAY,CAAC,CAAC,IACzB,CAAC,SAAS,SAAS,CAClB,MAAM,EAAE,EACR,MAAM,EAAE,EACR,MAAM,GAAG,EACT,MAAM,IAAI,EACV,MAAM,CAAC,SAAS,QAAQ,EACxB,MAAM,EAAE,CACR,GACE,CAAC,GACD,QAAQ,CAAA;AACZ,MAAM,MAAM,WAAW,CAAC,CAAC,IACxB,UAAU,CAAC,CAAC,CAAC,SAAS;IAAE,SAAS,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,KAAK,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compatibility.js","sourceRoot":"","sources":["../src/compatibility.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACpD,OAAQ,SAA+C,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;AACvE,CAAC","sourcesContent":["import type { AnySchema, ErrorMap, Meta } from '@orpc/contract'\nimport type { Procedure, Route } from '@orpc/server'\n\n/** All runtime access to oRPC's implementation namespace is audited here. */\nexport type ProcedureDefinition = {\n\tmeta?: Record<string, unknown>\n\troute?: Route\n\tinputSchema?: AnySchema\n\toutputSchema?: AnySchema\n}\n\nexport function procedureDefinition(procedure: object): ProcedureDefinition {\n\treturn (procedure as { '~orpc'?: ProcedureDefinition })['~orpc'] ?? {}\n}\n\nexport type NativeMeta<T> =\n\tT extends Procedure<\n\t\tinfer _I,\n\t\tinfer _C,\n\t\tinfer _In,\n\t\tinfer _Out,\n\t\tinfer _E,\n\t\tinfer M extends Meta\n\t>\n\t\t? M\n\t\t: Meta\nexport type NativeErrors<T> =\n\tT extends Procedure<\n\t\tinfer _I,\n\t\tinfer _C,\n\t\tinfer _In,\n\t\tinfer _Out,\n\t\tinfer E extends ErrorMap,\n\t\tinfer _M\n\t>\n\t\t? E\n\t\t: ErrorMap\nexport type LibraryMeta<T> =\n\tNativeMeta<T> extends { 'orpc-fn': infer M } ? M : never\n"]}
|