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/src/otel.ts
CHANGED
|
@@ -84,9 +84,15 @@ export type Tracing = {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
export function createTracing(otel: OtelApiLike | undefined): Tracing {
|
|
87
|
-
|
|
87
|
+
let tracer: TracerLike<SpanLike> | undefined
|
|
88
|
+
try {
|
|
89
|
+
tracer = otel?.trace.getTracer('orpc-fn')
|
|
90
|
+
} catch {
|
|
91
|
+
/* Missing telemetry cannot prevent execution. */
|
|
92
|
+
}
|
|
88
93
|
const end: Tracing['end'] = (span, ...error) => {
|
|
89
94
|
if (!span || !otel) return
|
|
95
|
+
span = protectSpan(span)
|
|
90
96
|
if (error.length === 0) {
|
|
91
97
|
span.setStatus({ code: otel.SpanStatusCode.OK })
|
|
92
98
|
} else {
|
|
@@ -103,6 +109,8 @@ export function createTracing(otel: OtelApiLike | undefined): Tracing {
|
|
|
103
109
|
const settle = (span: SpanLike | undefined, ...error: [] | [unknown]) => {
|
|
104
110
|
try {
|
|
105
111
|
onSettled?.(span, ...error)
|
|
112
|
+
} catch {
|
|
113
|
+
/* Telemetry callbacks cannot replace the original result. */
|
|
106
114
|
} finally {
|
|
107
115
|
end(span, ...error)
|
|
108
116
|
}
|
|
@@ -111,6 +119,7 @@ export function createTracing(otel: OtelApiLike | undefined): Tracing {
|
|
|
111
119
|
span: SpanLike | undefined,
|
|
112
120
|
scope?: StreamScope
|
|
113
121
|
): Promise<Awaited<ReturnType<typeof fn>>> => {
|
|
122
|
+
span = span ? protectSpan(span) : undefined
|
|
114
123
|
let result: Awaited<ReturnType<typeof fn>>
|
|
115
124
|
try {
|
|
116
125
|
result = await fn(span)
|
|
@@ -129,18 +138,54 @@ export function createTracing(otel: OtelApiLike | undefined): Tracing {
|
|
|
129
138
|
return result
|
|
130
139
|
}
|
|
131
140
|
if (!tracer || !otel) return run(undefined)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
let started: Promise<Awaited<ReturnType<typeof fn>>> | undefined
|
|
142
|
+
try {
|
|
143
|
+
return tracer.startActiveSpan(
|
|
144
|
+
name,
|
|
145
|
+
{ kind: otel.SpanKind[kind] },
|
|
146
|
+
(span) => {
|
|
147
|
+
let active: unknown
|
|
148
|
+
try {
|
|
149
|
+
active = otel.context.active()
|
|
150
|
+
} catch {
|
|
151
|
+
/* Fall back to the span without scope restoration. */
|
|
152
|
+
}
|
|
153
|
+
started = run(span, (step) => {
|
|
154
|
+
let completed: { value: ReturnType<typeof step> } | undefined
|
|
155
|
+
let failed: { error: unknown } | undefined
|
|
156
|
+
try {
|
|
157
|
+
return otel.context.with(active as never, () => {
|
|
158
|
+
try {
|
|
159
|
+
const value = step()
|
|
160
|
+
completed = { value }
|
|
161
|
+
return value
|
|
162
|
+
} catch (error) {
|
|
163
|
+
failed = { error }
|
|
164
|
+
throw error
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
} catch {
|
|
168
|
+
if (completed) return completed.value
|
|
169
|
+
if (failed) throw failed.error
|
|
170
|
+
return step()
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
return started
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
} catch {
|
|
177
|
+
return started ?? run(undefined)
|
|
178
|
+
}
|
|
140
179
|
},
|
|
141
180
|
startSpan(name, kind, attributes) {
|
|
142
181
|
if (!tracer || !otel) return undefined
|
|
143
|
-
|
|
182
|
+
try {
|
|
183
|
+
return protectSpan(
|
|
184
|
+
tracer.startSpan(name, { kind: otel.SpanKind[kind], attributes })
|
|
185
|
+
)
|
|
186
|
+
} catch {
|
|
187
|
+
return undefined
|
|
188
|
+
}
|
|
144
189
|
},
|
|
145
190
|
end
|
|
146
191
|
}
|
|
@@ -149,3 +194,25 @@ export function createTracing(otel: OtelApiLike | undefined): Tracing {
|
|
|
149
194
|
export function errorMessageOf(error: unknown): string {
|
|
150
195
|
return error instanceof Error ? error.message : String(error)
|
|
151
196
|
}
|
|
197
|
+
|
|
198
|
+
function protectSpan<T extends SpanLike>(span: T): T {
|
|
199
|
+
return new Proxy(span, {
|
|
200
|
+
get(target, key) {
|
|
201
|
+
const value = Reflect.get(target, key, target)
|
|
202
|
+
if (typeof value !== 'function') return value
|
|
203
|
+
if (
|
|
204
|
+
['setAttribute', 'setStatus', 'recordException', 'end'].includes(
|
|
205
|
+
String(key)
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
return (...args: unknown[]) => {
|
|
209
|
+
try {
|
|
210
|
+
return value.apply(target, args)
|
|
211
|
+
} catch {
|
|
212
|
+
/* Ignore exporter failures. */
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return value.bind(target)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnySchema,
|
|
3
|
+
type ErrorMap,
|
|
4
|
+
getEventIteratorSchemaDetails
|
|
5
|
+
} from '@orpc/contract'
|
|
6
|
+
import { ROUTE_OPTION_KEYS } from './types.js'
|
|
7
|
+
|
|
8
|
+
type Resolver = (params: {
|
|
9
|
+
input: unknown
|
|
10
|
+
context: unknown
|
|
11
|
+
signal: AbortSignal | undefined
|
|
12
|
+
}) => unknown
|
|
13
|
+
function schemaOption(schema: unknown, name: string): AnySchema | undefined {
|
|
14
|
+
if (schema === undefined) return undefined
|
|
15
|
+
if (
|
|
16
|
+
typeof schema !== 'object' ||
|
|
17
|
+
schema === null ||
|
|
18
|
+
!('~standard' in schema) ||
|
|
19
|
+
typeof (schema as AnySchema)['~standard']?.validate !== 'function'
|
|
20
|
+
)
|
|
21
|
+
throw new TypeError(`orpc-fn: ${name} must be a Standard Schema`)
|
|
22
|
+
return schema as AnySchema
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Runtime validation for JavaScript callers and spread route options. */
|
|
26
|
+
export function normalizeRoute(
|
|
27
|
+
options: Record<string, unknown>,
|
|
28
|
+
isGuard: (key: string) => boolean
|
|
29
|
+
) {
|
|
30
|
+
const {
|
|
31
|
+
handler,
|
|
32
|
+
input: rawInput,
|
|
33
|
+
output: rawOutput,
|
|
34
|
+
name,
|
|
35
|
+
procedure,
|
|
36
|
+
meta: appMeta = {},
|
|
37
|
+
stream,
|
|
38
|
+
errors,
|
|
39
|
+
guardResolvers = {},
|
|
40
|
+
...rest
|
|
41
|
+
} = options
|
|
42
|
+
if (typeof name !== 'string' || !name || typeof handler !== 'function')
|
|
43
|
+
throw new TypeError('orpc-fn: routes require a name and handler')
|
|
44
|
+
if (procedure !== undefined && typeof procedure !== 'string')
|
|
45
|
+
throw new TypeError('orpc-fn: procedure must be a configured key')
|
|
46
|
+
if (typeof appMeta !== 'object' || appMeta === null || Array.isArray(appMeta))
|
|
47
|
+
throw new TypeError('orpc-fn: meta must be an object')
|
|
48
|
+
if (stream !== undefined && typeof stream !== 'boolean')
|
|
49
|
+
throw new TypeError('orpc-fn: stream must be boolean')
|
|
50
|
+
if (
|
|
51
|
+
typeof guardResolvers !== 'object' ||
|
|
52
|
+
guardResolvers === null ||
|
|
53
|
+
Array.isArray(guardResolvers)
|
|
54
|
+
)
|
|
55
|
+
throw new TypeError('orpc-fn: guardResolvers must be an object')
|
|
56
|
+
const input = schemaOption(rawInput, 'input')
|
|
57
|
+
const output = schemaOption(rawOutput, 'output')
|
|
58
|
+
const route: Record<string, unknown> = {}
|
|
59
|
+
const guardChecks: Array<[string, unknown]> = []
|
|
60
|
+
for (const [option, value] of Object.entries(rest)) {
|
|
61
|
+
if ((ROUTE_OPTION_KEYS as readonly string[]).includes(option))
|
|
62
|
+
route[option] = value
|
|
63
|
+
else if (isGuard(option)) {
|
|
64
|
+
if (value !== undefined) guardChecks.push([option, value])
|
|
65
|
+
} else
|
|
66
|
+
throw new TypeError(
|
|
67
|
+
`orpc-fn: unknown route option "${option}"; put application metadata in meta`
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
const iterator = getEventIteratorSchemaDetails(output) !== undefined
|
|
71
|
+
if (stream === false && iterator)
|
|
72
|
+
throw new TypeError(
|
|
73
|
+
'orpc-fn: an event iterator schema cannot declare stream: false'
|
|
74
|
+
)
|
|
75
|
+
for (const [guard, resolver] of Object.entries(guardResolvers)) {
|
|
76
|
+
if (!isGuard(guard))
|
|
77
|
+
throw new TypeError(`orpc-fn: unknown guard resolver "${guard}"`)
|
|
78
|
+
if (typeof resolver !== 'function')
|
|
79
|
+
throw new TypeError(
|
|
80
|
+
`orpc-fn: guard resolver "${guard}" must be a function`
|
|
81
|
+
)
|
|
82
|
+
if (guardChecks.some(([key]) => key === guard))
|
|
83
|
+
throw new TypeError(
|
|
84
|
+
`orpc-fn: guard "${guard}" has both a value and resolver`
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
handler: handler as (params: Record<PropertyKey, unknown>) => unknown,
|
|
89
|
+
input,
|
|
90
|
+
output,
|
|
91
|
+
name,
|
|
92
|
+
procedure,
|
|
93
|
+
meta: appMeta as Record<string, unknown>,
|
|
94
|
+
stream: stream === true || iterator,
|
|
95
|
+
errors: errors as ErrorMap | undefined,
|
|
96
|
+
guardResolvers: guardResolvers as Record<string, Resolver>,
|
|
97
|
+
route,
|
|
98
|
+
guardChecks
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
type AnyRouter,
|
|
3
|
+
type AnyProcedure,
|
|
4
|
+
resolveContractProcedures,
|
|
5
|
+
traverseContractProcedures
|
|
6
|
+
} from '@orpc/server'
|
|
7
|
+
import { readFnMeta } from './meta.js'
|
|
2
8
|
|
|
3
9
|
/**
|
|
4
10
|
* Creates a router object from routes or nested routers.
|
|
@@ -13,3 +19,54 @@ import type { AnyRouter } from '@orpc/server'
|
|
|
13
19
|
export function createRouter<T extends AnyRouter>(routes: T): Readonly<T> {
|
|
14
20
|
return routes
|
|
15
21
|
}
|
|
22
|
+
|
|
23
|
+
/** Serialize these paths to a frontend module; this helper belongs on the server. */
|
|
24
|
+
export function createStreamManifest(
|
|
25
|
+
router: AnyRouter
|
|
26
|
+
): readonly (readonly string[])[] {
|
|
27
|
+
const paths: string[][] = []
|
|
28
|
+
const unresolved = traverseContractProcedures(
|
|
29
|
+
{ router, path: [] },
|
|
30
|
+
({ contract, path }) => {
|
|
31
|
+
if (readFnMeta(contract as AnyProcedure).stream) paths.push([...path])
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
if (unresolved.length)
|
|
35
|
+
throw new TypeError(
|
|
36
|
+
'orpc-fn: lazy routers require createStreamManifestAsync'
|
|
37
|
+
)
|
|
38
|
+
return paths
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Resolve lazy routers before generating the same serializable path manifest. */
|
|
42
|
+
export async function createStreamManifestAsync(
|
|
43
|
+
router: AnyRouter
|
|
44
|
+
): Promise<readonly (readonly string[])[]> {
|
|
45
|
+
const paths: string[][] = []
|
|
46
|
+
await resolveContractProcedures(
|
|
47
|
+
{ router, path: [] },
|
|
48
|
+
({ contract, path }) => {
|
|
49
|
+
if (readFnMeta(contract as AnyProcedure).stream) paths.push([...path])
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
return paths
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Deterministic module source for a stream manifest: paths sorted, `json`
|
|
57
|
+
* as an array, `ts` as `export const <name>`. Used by `orpc-fn stream-manifest`.
|
|
58
|
+
*/
|
|
59
|
+
export function renderStreamManifest(
|
|
60
|
+
paths: readonly (readonly string[])[],
|
|
61
|
+
options: { format: 'json' | 'ts'; name?: string | undefined }
|
|
62
|
+
): string {
|
|
63
|
+
const lines = paths
|
|
64
|
+
.map(
|
|
65
|
+
(path) =>
|
|
66
|
+
`\t[${path.map((segment) => JSON.stringify(segment)).join(', ')}]`
|
|
67
|
+
)
|
|
68
|
+
.sort()
|
|
69
|
+
const array = lines.length ? `[\n${lines.join(',\n')}\n]` : '[]'
|
|
70
|
+
if (options.format === 'json') return `${array}\n`
|
|
71
|
+
return `// Generated by \`orpc-fn stream-manifest\`. Do not edit.\nexport const ${options.name ?? 'streamPaths'}: readonly (readonly string[])[] = ${array}\n`
|
|
72
|
+
}
|
package/src/timing.ts
ADDED