@silkweave/trpc 1.7.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.
@@ -0,0 +1,16 @@
1
+
2
+ > @silkweave/trpc@1.6.0 build /Users/atomic/projects/ai/silkweave/packages/trpc
3
+ > tsdown
4
+
5
+ ℹ tsdown v0.21.8 powered by rolldown v1.0.0-rc.15
6
+ ℹ config file: /Users/atomic/projects/ai/silkweave/packages/trpc/tsdown.config.ts
7
+ ℹ entry: src/index.ts
8
+ ℹ tsconfig: tsconfig.json
9
+ ℹ Build start
10
+ ℹ Cleaning 4 files
11
+ ℹ build/index.mjs 4.83 kB │ gzip: 1.85 kB
12
+ ℹ build/index.mjs.map 9.85 kB │ gzip: 3.49 kB
13
+ ℹ build/index.d.mts.map 1.11 kB │ gzip: 0.47 kB
14
+ ℹ build/index.d.mts 2.67 kB │ gzip: 1.09 kB
15
+ ℹ 4 files, total: 18.46 kB
16
+ ✔ Build complete in 606ms
@@ -0,0 +1,13 @@
1
+
2
+ 
3
+ > @silkweave/trpc@1.6.0 check /Users/atomic/projects/ai/silkweave/packages/trpc
4
+ > pnpm lint && pnpm typecheck
5
+
6
+
7
+ > @silkweave/trpc@1.6.0 lint /Users/atomic/projects/ai/silkweave/packages/trpc
8
+ > eslint
9
+
10
+
11
+ > @silkweave/trpc@1.6.0 typecheck /Users/atomic/projects/ai/silkweave/packages/trpc
12
+ > tsc --noEmit
13
+
@@ -0,0 +1,24 @@
1
+
2
+ 
3
+ > @silkweave/trpc@1.6.0 prepack /Users/atomic/projects/ai/silkweave/packages/trpc
4
+ > pnpm clean && pnpm build
5
+
6
+
7
+ > @silkweave/trpc@1.6.0 clean /Users/atomic/projects/ai/silkweave/packages/trpc
8
+ > rimraf build
9
+
10
+
11
+ > @silkweave/trpc@1.6.0 build /Users/atomic/projects/ai/silkweave/packages/trpc
12
+ > tsdown
13
+
14
+ ℹ tsdown v0.21.8 powered by rolldown v1.0.0-rc.15
15
+ ℹ config file: /Users/atomic/projects/ai/silkweave/packages/trpc/tsdown.config.ts
16
+ ℹ entry: src/index.ts
17
+ ℹ tsconfig: tsconfig.json
18
+ ℹ Build start
19
+ ℹ build/index.mjs  7.67 kB │ gzip: 2.62 kB
20
+ ℹ build/index.mjs.map 15.99 kB │ gzip: 4.89 kB
21
+ ℹ build/index.d.mts.map  1.40 kB │ gzip: 0.56 kB
22
+ ℹ build/index.d.mts  3.82 kB │ gzip: 1.50 kB
23
+ ℹ 4 files, total: 28.88 kB
24
+ ✔ Build complete in 572ms
package/README.md ADDED
@@ -0,0 +1,184 @@
1
+ # @silkweave/trpc
2
+
3
+ tRPC adapter for [Silkweave](https://github.com/silkweave/silkweave) - expose your actions as fully-typed tRPC procedures with end-to-end TypeScript inference for your client.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @silkweave/core @silkweave/trpc
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ // server.ts
15
+ import { silkweave } from '@silkweave/core'
16
+ import { trpc, type InferTrpcRouter } from '@silkweave/trpc'
17
+ import { GreetAction } from './actions/greet.js'
18
+ import { ListThingsAction } from './actions/list-things.js'
19
+
20
+ const server = silkweave({ name: 'my-api', description: 'My tRPC API', version: '1.0.0' })
21
+ .adapter(trpc({ host: 'localhost', port: 8080 }))
22
+ .action(GreetAction)
23
+ .action(ListThingsAction)
24
+
25
+ export type AppRouter = InferTrpcRouter<typeof server>
26
+
27
+ await server.start()
28
+ ```
29
+
30
+ Client:
31
+
32
+ ```typescript
33
+ // client.ts
34
+ import { createTRPCClient, httpBatchLink } from '@trpc/client'
35
+ import type { AppRouter } from './server.js'
36
+
37
+ const client = createTRPCClient<AppRouter>({
38
+ links: [httpBatchLink({ url: 'http://localhost:8080/trpc' })]
39
+ })
40
+
41
+ // Fully typed: input and return types are inferred from the action's Zod schemas
42
+ const hello = await client.greet.mutate({ name: 'World' })
43
+ // ^? { message: string }
44
+
45
+ const things = await client.listThings.query({ contains: 'saw' })
46
+ // ^? { items: string[] }
47
+ ```
48
+
49
+ ## Type Safety
50
+
51
+ The adapter is paired with the generic `Silkweave<Actions>` builder so that `typeof server` carries the full action map through your `.action()` / `.actions()` calls. `InferTrpcRouter<typeof server>` then produces a `TRPCBuiltRouter` type where:
52
+
53
+ - Each `Action<I, O, N, K>` becomes a `camelCase(N)` procedure
54
+ - `kind: 'query'` dispatches to `.query()` (GET, cacheable); default is `.mutation()` (POST)
55
+ - Input type is `z.infer<typeof action.input>`
56
+ - Output type is `Awaited<ReturnType<typeof action.run>>` (narrower than the optional `action.output` Zod type when `run` returns a subtype)
57
+
58
+ There is **no code generation** - the types flow statically through the TypeScript compiler. Just `export type AppRouter = InferTrpcRouter<typeof server>` and import it on the client.
59
+
60
+ ## Queries vs. Mutations
61
+
62
+ Actions default to `kind: 'mutation'` (tRPC `.mutation()`, HTTP `POST`). Mark side-effect-free read actions with `kind: 'query'` to get GET-cacheable tRPC queries:
63
+
64
+ ```typescript
65
+ createAction({
66
+ name: 'list-things',
67
+ description: 'List things, optionally filtered',
68
+ kind: 'query',
69
+ input: z.object({ contains: z.string().optional() }),
70
+ output: z.object({ items: z.array(z.string()) }),
71
+ run: async ({ contains }) => ({ items: [/* ... */] })
72
+ })
73
+ ```
74
+
75
+ The literal `'query'` is preserved through `createAction` (generic over `K extends 'query' | 'mutation'`), so `InferTrpcRouter` produces the correct `TRPCQueryProcedure` type - calling `.mutate()` on a query (or vice-versa) is a compile-time error.
76
+
77
+ ## Name Conversion
78
+
79
+ Action names are converted to tRPC procedure keys using `camelCase` from `change-case`:
80
+
81
+ | `action.name` | Procedure key |
82
+ |---------------|---------------|
83
+ | `greet` | `client.greet` |
84
+ | `list-things` | `client.listThings` |
85
+ | `get_user_by_id` | `client.getUserById` |
86
+
87
+ ## Options
88
+
89
+ | Option | Type | Default | Description |
90
+ |--------|------|---------|-------------|
91
+ | `host` | `string` | `'0.0.0.0'` | Bind address |
92
+ | `port` | `number` | `8080` | Listen port |
93
+ | `endpoint` | `string` | `'/trpc/'` | URL prefix stripped before tRPC routing (trailing slash is normalized) |
94
+ | `cors` | `CorsOptions \| boolean` | `true` | `false` to disable. `true`/omit for permissive defaults (`origin: '*'`). Or a [cors](https://www.npmjs.com/package/cors) options object. |
95
+ | `auth` | `AuthConfig` | `undefined` | `@silkweave/auth` config. Enables bearer-token validation; `authInfo` is threaded into the action context as `context.get('auth')`. |
96
+
97
+ ## Serverless / Fetch runtimes (`trpcFetch`)
98
+
99
+ `trpc()` binds a Node HTTP server, which isn't compatible with serverless runtimes like Vercel Functions, Cloudflare Workers, or Astro API routes. For those, use `trpcFetch()`. It returns a fetch-compatible `(Request) => Promise<Response>` handler instead of listening on a port.
100
+
101
+ ```typescript
102
+ // server.ts
103
+ import { silkweave } from '@silkweave/core'
104
+ import { trpcFetch, type InferTrpcRouter } from '@silkweave/trpc'
105
+ import { GreetAction } from './actions/greet.js'
106
+
107
+ const { adapter, handler } = trpcFetch({ endpoint: '/api/trpc' })
108
+
109
+ export const server = silkweave({ name: 'my-api', version: '1.0.0' })
110
+ .adapter(adapter)
111
+ .action(GreetAction)
112
+
113
+ export type AppRouter = InferTrpcRouter<typeof server>
114
+ export { handler }
115
+
116
+ await server.start()
117
+ ```
118
+
119
+ ```typescript
120
+ // Astro API route: src/pages/api/trpc/[trpc].ts
121
+ import type { APIRoute } from 'astro'
122
+ import { handler } from '../../../server/silkweave.js'
123
+
124
+ export const GET: APIRoute = ({ request }) => handler(request)
125
+ export const POST: APIRoute = ({ request }) => handler(request)
126
+ ```
127
+
128
+ ```typescript
129
+ // Vercel Edge function: api/trpc/[trpc].ts
130
+ import { handler } from '../../lib/silkweave.js'
131
+ export { handler as GET, handler as POST }
132
+ ```
133
+
134
+ ```typescript
135
+ // Cloudflare Worker
136
+ import { handler } from './silkweave.js'
137
+ export default { fetch: handler }
138
+ ```
139
+
140
+ ### `trpcFetch` options
141
+
142
+ | Option | Type | Default | Description |
143
+ |--------|------|---------|-------------|
144
+ | `endpoint` | `string` | `'/trpc'` | URL prefix stripped before tRPC routing |
145
+ | `auth` | `AuthConfig` | `undefined` | Same semantics as `trpc()`. On auth failure the handler returns a `401 Response` with `WWW-Authenticate` headers |
146
+
147
+ ### Return shape
148
+
149
+ ```ts
150
+ {
151
+ adapter: AdapterGenerator // pass to .adapter()
152
+ handler: (request: Request) => Promise<Response> // primary dispatch
153
+ GET: (request: Request) => Promise<Response> // alias for Astro-style method exports
154
+ POST: (request: Request) => Promise<Response> // alias for Astro-style method exports
155
+ }
156
+ ```
157
+
158
+ ### Notes
159
+
160
+ - **CORS**: not handled by `trpcFetch`. Configure it in your host framework (Astro middleware, `vercel.json` headers, Cloudflare Worker response headers, etc).
161
+ - **Cold-start safety**: an internal `_ready` promise gates the handler until `server.start()` has completed the router build, so the first invocation from a cold serverless function won't race the module-top `await server.start()`.
162
+ - **`server.start()` is still required**. It's what builds the tRPC router from your registered actions. It just doesn't `listen()` in fetch mode.
163
+
164
+ ## Errors
165
+
166
+ Thrown `SilkweaveError` instances are mapped to `TRPCError` by status code:
167
+
168
+ | `SilkweaveError.statusCode` | `TRPCError.code` |
169
+ |-----------------------------|------------------|
170
+ | `400` | `BAD_REQUEST` |
171
+ | `401` | `UNAUTHORIZED` |
172
+ | `403` | `FORBIDDEN` |
173
+ | `404` | `NOT_FOUND` |
174
+ | `409` | `CONFLICT` |
175
+ | `429` | `TOO_MANY_REQUESTS` |
176
+ | `500` (default) | `INTERNAL_SERVER_ERROR` |
177
+
178
+ Zod validation errors from the input schema are automatically returned as `BAD_REQUEST`. Any other thrown value falls through to `INTERNAL_SERVER_ERROR`.
179
+
180
+ ## See Also
181
+
182
+ - [Silkweave README](https://github.com/silkweave/silkweave) - Full documentation
183
+ - [`@silkweave/core`](https://www.npmjs.com/package/@silkweave/core) - Core library
184
+ - [tRPC documentation](https://trpc.io/) - Client setup, React Query integration, subscriptions, and more
@@ -0,0 +1,91 @@
1
+ import * as _$_trpc_server0 from "@trpc/server";
2
+ import { AnyTRPCRootTypes, TRPCBuiltRouter, TRPCError, TRPCMutationProcedure, TRPCQueryProcedure } from "@trpc/server";
3
+ import { Action, AdapterFactory, AdapterGenerator, Silkweave, SilkweaveContext } from "@silkweave/core";
4
+ import { z } from "zod";
5
+ import { AuthConfig } from "@silkweave/auth";
6
+ import { CorsOptions } from "cors";
7
+
8
+ //#region src/adapter/fetch.d.ts
9
+ interface TrpcFetchAdapterOptions {
10
+ /** URL prefix stripped from incoming requests before tRPC routing. Default `/trpc`. */
11
+ endpoint?: string;
12
+ auth?: AuthConfig;
13
+ }
14
+ type FetchHandler = (request: Request) => Promise<Response>;
15
+ interface TrpcFetchAdapter {
16
+ adapter: AdapterGenerator;
17
+ handler: FetchHandler;
18
+ GET: FetchHandler;
19
+ POST: FetchHandler;
20
+ }
21
+ /**
22
+ * Creates a tRPC adapter that exposes a fetch-compatible handler instead of
23
+ * binding its own HTTP server. Use in Astro API routes, Vercel serverless
24
+ * functions, Cloudflare Workers, or any Web Standard runtime.
25
+ *
26
+ * The returned `handler` waits for `server.start()` to complete (via an internal
27
+ * `_ready` promise) before dispatching requests, so it's safe to call from a
28
+ * cold-started serverless invocation.
29
+ *
30
+ * CORS is intentionally not configured here - handle it in your host framework
31
+ * (Astro middleware, `vercel.json` headers, Worker response headers, etc).
32
+ */
33
+ declare function trpcFetch(options?: TrpcFetchAdapterOptions): TrpcFetchAdapter;
34
+ //#endregion
35
+ //#region src/adapter/trpc.d.ts
36
+ interface TrpcAdapterOptions {
37
+ host?: string;
38
+ port?: number;
39
+ /** URL prefix stripped from incoming requests before tRPC routing. Default `/trpc/`. */
40
+ endpoint?: string;
41
+ /** CORS configuration. `false` to disable, `true`/`undefined` for permissive defaults, or a CorsOptions object. */
42
+ cors?: CorsOptions | boolean;
43
+ auth?: AuthConfig;
44
+ }
45
+ declare const trpc: AdapterFactory<TrpcAdapterOptions>;
46
+ //#endregion
47
+ //#region src/lib/buildRouter.d.ts
48
+ interface TrpcHandlerContext {
49
+ silkweaveContext: SilkweaveContext;
50
+ }
51
+ declare function buildRouter(actions: Action[]): _$_trpc_server0.TRPCBuiltRouter<{
52
+ ctx: TrpcHandlerContext;
53
+ meta: object;
54
+ errorShape: _$_trpc_server0.TRPCDefaultErrorShape;
55
+ transformer: false;
56
+ }, _$_trpc_server0.TRPCDecorateCreateRouterOptions<Record<string, any>>>;
57
+ //#endregion
58
+ //#region src/lib/errors.d.ts
59
+ declare function mapError(error: unknown): TRPCError;
60
+ //#endregion
61
+ //#region src/lib/inferRouter.d.ts
62
+ type CamelCase<S extends string> = S extends `${infer A}-${infer B}` ? `${A}${Capitalize<CamelCase<B>>}` : S extends `${infer A}_${infer B}` ? `${A}${Capitalize<CamelCase<B>>}` : S;
63
+ type ActionToProcedure<A extends Action> = 'query' extends NonNullable<A['kind']> ? TRPCQueryProcedure<{
64
+ meta: object;
65
+ input: z.infer<A['input']>;
66
+ output: Awaited<ReturnType<A['run']>>;
67
+ }> : TRPCMutationProcedure<{
68
+ meta: object;
69
+ input: z.infer<A['input']>;
70
+ output: Awaited<ReturnType<A['run']>>;
71
+ }>;
72
+ type ActionsToRouterRecord<Actions extends Record<string, Action>> = { [K in keyof Actions & string as CamelCase<Actions[K]['name']>]: ActionToProcedure<Actions[K]> };
73
+ type TrpcRootTypes = {
74
+ ctx: object;
75
+ meta: object;
76
+ errorShape: unknown;
77
+ transformer: false;
78
+ } & AnyTRPCRootTypes;
79
+ /**
80
+ * Extracts a fully-typed tRPC router from a Silkweave builder instance.
81
+ *
82
+ * Usage:
83
+ * ```ts
84
+ * const server = silkweave(opts).adapter(trpc({ port: 8080 })).action(HelloAction)
85
+ * export type AppRouter = InferTrpcRouter<typeof server>
86
+ * ```
87
+ */
88
+ type InferTrpcRouter<S> = S extends Silkweave<infer Actions> ? TRPCBuiltRouter<TrpcRootTypes, ActionsToRouterRecord<Actions>> : never;
89
+ //#endregion
90
+ export { FetchHandler, type InferTrpcRouter, TrpcAdapterOptions, TrpcFetchAdapter, TrpcFetchAdapterOptions, type TrpcHandlerContext, buildRouter, mapError, trpc, trpcFetch };
91
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/fetch.ts","../src/adapter/trpc.ts","../src/lib/buildRouter.ts","../src/lib/errors.ts","../src/lib/inferRouter.ts"],"mappings":";;;;;;;;UAMiB,uBAAA;;EAEf,QAAA;EACA,IAAA,GAAO,UAAA;AAAA;AAAA,KAGG,YAAA,IAAgB,OAAA,EAAS,OAAA,KAAY,OAAA,CAAQ,QAAA;AAAA,UAExC,gBAAA;EACf,OAAA,EAAS,gBAAA;EACT,OAAA,EAAS,YAAA;EACT,GAAA,EAAK,YAAA;EACL,IAAA,EAAM,YAAA;AAAA;;;;AANR;;;;;;;;;iBAqBgB,SAAA,CAAU,OAAA,GAAS,uBAAA,GAA+B,gBAAA;;;UCzBjD,kBAAA;EACf,IAAA;EACA,IAAA;;EAEA,QAAA;EDNe;ECQf,IAAA,GAAO,WAAA;EACP,IAAA,GAAO,UAAA;AAAA;AAAA,cAWI,IAAA,EAAM,cAAA,CAAe,kBAAA;;;UCpBjB,kBAAA;EACf,gBAAA,EAAkB,gBAAA;AAAA;AAAA,iBAGJ,WAAA,CAAY,OAAA,EAAS,MAAA,qBAAQ,eAAA;;;;;;;;iBCc7B,QAAA,CAAS,KAAA,YAAiB,SAAA;;;KCfrC,SAAA,qBAA8B,CAAA,sCAC5B,CAAA,GAAI,UAAA,CAAW,SAAA,CAAU,CAAA,OAC5B,CAAA,sCACK,CAAA,GAAI,UAAA,CAAW,SAAA,CAAU,CAAA,OAC5B,CAAA;AAAA,KAED,iBAAA,WAA4B,MAAA,oBAA0B,WAAA,CAAY,CAAA,YACnE,kBAAA;EACA,IAAA;EACA,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,CAAA;EACf,MAAA,EAAQ,OAAA,CAAQ,UAAA,CAAW,CAAA;AAAA,KAE3B,qBAAA;EACA,IAAA;EACA,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,CAAA;EACf,MAAA,EAAQ,OAAA,CAAQ,UAAA,CAAW,CAAA;AAAA;AAAA,KAG1B,qBAAA,iBAAsC,MAAA,SAAe,MAAA,mBAC5C,OAAA,aAAoB,SAAA,CAAU,OAAA,CAAQ,CAAA,aAAc,iBAAA,CAAkB,OAAA,CAAQ,CAAA;AAAA,KAGvF,aAAA;EACH,GAAA;EACA,IAAA;EACA,UAAA;EACA,WAAA;AAAA,IACE,gBAAA;;;;;;;;;;KAWQ,eAAA,MAAqB,CAAA,SAAU,SAAA,kBACvC,eAAA,CAAgB,aAAA,EAAe,qBAAA,CAAsB,OAAA"}
@@ -0,0 +1,267 @@
1
+ import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
2
+ import { TRPCError, initTRPC } from "@trpc/server";
3
+ import { camelCase } from "change-case";
4
+ import { SilkweaveError } from "@silkweave/core";
5
+ import { z } from "zod";
6
+ import { validateToken } from "@silkweave/auth";
7
+ import { buildLogLevels } from "@silkweave/logger";
8
+ import { createHTTPHandler } from "@trpc/server/adapters/standalone";
9
+ import cors from "cors";
10
+ import http from "http";
11
+ //#region src/lib/errors.ts
12
+ const CODE_MAP = {
13
+ 400: "BAD_REQUEST",
14
+ 401: "UNAUTHORIZED",
15
+ 403: "FORBIDDEN",
16
+ 404: "NOT_FOUND",
17
+ 408: "TIMEOUT",
18
+ 409: "CONFLICT",
19
+ 412: "PRECONDITION_FAILED",
20
+ 413: "PAYLOAD_TOO_LARGE",
21
+ 429: "TOO_MANY_REQUESTS",
22
+ 499: "CLIENT_CLOSED_REQUEST",
23
+ 500: "INTERNAL_SERVER_ERROR",
24
+ 501: "NOT_IMPLEMENTED",
25
+ 502: "BAD_GATEWAY",
26
+ 503: "SERVICE_UNAVAILABLE",
27
+ 504: "GATEWAY_TIMEOUT"
28
+ };
29
+ function mapError(error) {
30
+ if (error instanceof TRPCError) return error;
31
+ if (error instanceof z.ZodError) return new TRPCError({
32
+ code: "BAD_REQUEST",
33
+ message: error.message,
34
+ cause: error
35
+ });
36
+ if (error instanceof SilkweaveError) return new TRPCError({
37
+ code: CODE_MAP[error.statusCode] ?? "INTERNAL_SERVER_ERROR",
38
+ message: error.message,
39
+ cause: error
40
+ });
41
+ return new TRPCError({
42
+ code: "INTERNAL_SERVER_ERROR",
43
+ message: error instanceof Error ? error.message : "Internal error",
44
+ cause: error instanceof Error ? error : void 0
45
+ });
46
+ }
47
+ //#endregion
48
+ //#region src/lib/buildRouter.ts
49
+ function buildRouter(actions) {
50
+ const t = initTRPC.context().create();
51
+ const record = {};
52
+ for (const action of actions) {
53
+ const key = camelCase(action.name);
54
+ const base = t.procedure.input(action.input);
55
+ const handler = async ({ input, ctx }) => {
56
+ try {
57
+ return await action.run(input, ctx.silkweaveContext);
58
+ } catch (error) {
59
+ throw mapError(error);
60
+ }
61
+ };
62
+ record[key] = action.kind === "query" ? base.query(handler) : base.mutation(handler);
63
+ }
64
+ return t.router(record);
65
+ }
66
+ //#endregion
67
+ //#region src/lib/createContext.ts
68
+ const CONSOLE_LEVEL_MAP = {
69
+ emergency: "error",
70
+ alert: "error",
71
+ critical: "error",
72
+ error: "error",
73
+ warning: "warn",
74
+ notice: "info",
75
+ info: "info",
76
+ debug: "log"
77
+ };
78
+ function createActionLogger() {
79
+ return {
80
+ ...buildLogLevels((level, data) => {
81
+ console[CONSOLE_LEVEL_MAP[level]](data);
82
+ }),
83
+ progress: () => {}
84
+ };
85
+ }
86
+ async function resolveAuth(auth, authHeader, context) {
87
+ if (!auth) return { kind: "ok" };
88
+ const result = await validateToken(authHeader, auth, context);
89
+ if (result.error) return {
90
+ kind: "error",
91
+ error: result.error
92
+ };
93
+ return {
94
+ kind: "ok",
95
+ authInfo: result.auth
96
+ };
97
+ }
98
+ //#endregion
99
+ //#region src/adapter/fetch.ts
100
+ /**
101
+ * Creates a tRPC adapter that exposes a fetch-compatible handler instead of
102
+ * binding its own HTTP server. Use in Astro API routes, Vercel serverless
103
+ * functions, Cloudflare Workers, or any Web Standard runtime.
104
+ *
105
+ * The returned `handler` waits for `server.start()` to complete (via an internal
106
+ * `_ready` promise) before dispatching requests, so it's safe to call from a
107
+ * cold-started serverless invocation.
108
+ *
109
+ * CORS is intentionally not configured here - handle it in your host framework
110
+ * (Astro middleware, `vercel.json` headers, Worker response headers, etc).
111
+ */
112
+ function trpcFetch(options = {}) {
113
+ const endpoint = (options.endpoint ?? "/trpc").replace(/\/$/, "");
114
+ let resolveReady;
115
+ const ready = new Promise((resolve) => {
116
+ resolveReady = resolve;
117
+ });
118
+ let handler = async () => new Response(JSON.stringify({
119
+ error: "not_ready",
120
+ message: "tRPC adapter has not started yet"
121
+ }), {
122
+ status: 503,
123
+ headers: { "Content-Type": "application/json" }
124
+ });
125
+ const adapter = (_silkweaveOptions, baseContext) => {
126
+ const context = baseContext.fork({ adapter: "trpc" });
127
+ return {
128
+ context,
129
+ start: async (actions) => {
130
+ const router = buildRouter(actions);
131
+ const logger = createActionLogger();
132
+ const createContext = async (opts) => {
133
+ const resolved = await resolveAuth(options.auth, opts.req.headers.get("authorization"), context.fork({ request: opts.req }));
134
+ if (resolved.kind === "error") {
135
+ const err = /* @__PURE__ */ new Error("Unauthorized");
136
+ err.silkweaveAuthError = resolved.error;
137
+ throw err;
138
+ }
139
+ return { silkweaveContext: context.fork({
140
+ logger,
141
+ request: opts.req,
142
+ ...resolved.authInfo ? { auth: resolved.authInfo } : {}
143
+ }) };
144
+ };
145
+ handler = async (request) => {
146
+ try {
147
+ return await fetchRequestHandler({
148
+ endpoint,
149
+ req: request,
150
+ router,
151
+ createContext
152
+ });
153
+ } catch (error) {
154
+ const authError = error.silkweaveAuthError;
155
+ if (authError) return new Response(JSON.stringify(authError.body), {
156
+ status: authError.statusCode,
157
+ headers: {
158
+ "Content-Type": "application/json",
159
+ ...authError.headers
160
+ }
161
+ });
162
+ throw error;
163
+ }
164
+ };
165
+ resolveReady();
166
+ },
167
+ stop: async () => {}
168
+ };
169
+ };
170
+ const dispatch = async (request) => {
171
+ await ready;
172
+ return handler(request);
173
+ };
174
+ return {
175
+ adapter,
176
+ handler: dispatch,
177
+ GET: dispatch,
178
+ POST: dispatch
179
+ };
180
+ }
181
+ //#endregion
182
+ //#region src/adapter/trpc.ts
183
+ function resolveCors(corsConfig) {
184
+ if (corsConfig === false) return null;
185
+ return cors({
186
+ origin: "*",
187
+ ...corsConfig === true || corsConfig === void 0 ? {} : corsConfig
188
+ });
189
+ }
190
+ const trpc = (options) => {
191
+ return (_silkweaveOptions, baseContext) => {
192
+ const context = baseContext.fork({ adapter: "trpc" });
193
+ const host = options.host ?? "0.0.0.0";
194
+ const port = options.port ?? 8080;
195
+ const endpoint = (options.endpoint ?? "/trpc/").replace(/\/?$/, "/");
196
+ let server;
197
+ return {
198
+ context,
199
+ start: async (actions) => {
200
+ const router = buildRouter(actions);
201
+ const corsMiddleware = resolveCors(options.cors);
202
+ const logger = createActionLogger();
203
+ const createContext = async (opts) => {
204
+ const resolved = await resolveAuth(options.auth, opts.req.headers.authorization, context.fork({ request: opts.req }));
205
+ if (resolved.kind === "error") {
206
+ for (const [key, value] of Object.entries(resolved.error.headers)) opts.res.setHeader(key, value);
207
+ opts.res.statusCode = resolved.error.statusCode;
208
+ opts.res.setHeader("Content-Type", "application/json");
209
+ opts.res.end(JSON.stringify(resolved.error.body));
210
+ throw new Error("Unauthorized");
211
+ }
212
+ return { silkweaveContext: context.fork({
213
+ logger,
214
+ request: opts.req,
215
+ ...resolved.authInfo ? { auth: resolved.authInfo } : {}
216
+ }) };
217
+ };
218
+ const trpcHandler = createHTTPHandler({
219
+ router,
220
+ basePath: endpoint,
221
+ createContext
222
+ });
223
+ const handler = (req, res) => {
224
+ if (!req.url?.startsWith(endpoint)) {
225
+ res.statusCode = 404;
226
+ res.setHeader("Content-Type", "application/json");
227
+ res.end(JSON.stringify({
228
+ error: "not_found",
229
+ message: `No route for ${req.url}`
230
+ }));
231
+ return;
232
+ }
233
+ trpcHandler(req, res);
234
+ };
235
+ server = http.createServer((req, res) => {
236
+ if (corsMiddleware) corsMiddleware(req, res, () => {
237
+ if (req.method === "OPTIONS") {
238
+ res.statusCode = 204;
239
+ res.end();
240
+ return;
241
+ }
242
+ handler(req, res);
243
+ });
244
+ else handler(req, res);
245
+ });
246
+ await new Promise((resolve) => {
247
+ server.listen(port, host, () => {
248
+ resolve();
249
+ });
250
+ });
251
+ },
252
+ stop: async () => {
253
+ if (!server) return;
254
+ await new Promise((resolve, reject) => {
255
+ server.close((err) => {
256
+ if (err) reject(err);
257
+ else resolve();
258
+ });
259
+ });
260
+ }
261
+ };
262
+ };
263
+ };
264
+ //#endregion
265
+ export { buildRouter, mapError, trpc, trpcFetch };
266
+
267
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/errors.ts","../src/lib/buildRouter.ts","../src/lib/createContext.ts","../src/adapter/fetch.ts","../src/adapter/trpc.ts"],"sourcesContent":["import { SilkweaveError } from '@silkweave/core'\nimport { TRPCError } from '@trpc/server'\nimport { z } from 'zod'\n\ntype TRPCErrorCode = TRPCError['code']\n\nconst CODE_MAP: Record<number, TRPCErrorCode> = {\n 400: 'BAD_REQUEST',\n 401: 'UNAUTHORIZED',\n 403: 'FORBIDDEN',\n 404: 'NOT_FOUND',\n 408: 'TIMEOUT',\n 409: 'CONFLICT',\n 412: 'PRECONDITION_FAILED',\n 413: 'PAYLOAD_TOO_LARGE',\n 429: 'TOO_MANY_REQUESTS',\n 499: 'CLIENT_CLOSED_REQUEST',\n 500: 'INTERNAL_SERVER_ERROR',\n 501: 'NOT_IMPLEMENTED',\n 502: 'BAD_GATEWAY',\n 503: 'SERVICE_UNAVAILABLE',\n 504: 'GATEWAY_TIMEOUT'\n}\n\nexport function mapError(error: unknown): TRPCError {\n if (error instanceof TRPCError) { return error }\n if (error instanceof z.ZodError) {\n return new TRPCError({ code: 'BAD_REQUEST', message: error.message, cause: error })\n }\n if (error instanceof SilkweaveError) {\n return new TRPCError({\n code: CODE_MAP[error.statusCode] ?? 'INTERNAL_SERVER_ERROR',\n message: error.message,\n cause: error\n })\n }\n return new TRPCError({\n code: 'INTERNAL_SERVER_ERROR',\n message: error instanceof Error ? error.message : 'Internal error',\n cause: error instanceof Error ? error : undefined\n })\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Action, SilkweaveContext } from '@silkweave/core'\nimport { initTRPC } from '@trpc/server'\nimport { camelCase } from 'change-case'\nimport { mapError } from './errors.js'\n\nexport interface TrpcHandlerContext {\n silkweaveContext: SilkweaveContext\n}\n\nexport function buildRouter(actions: Action[]) {\n const t = initTRPC.context<TrpcHandlerContext>().create()\n const record: Record<string, any> = {}\n for (const action of actions) {\n const key = camelCase(action.name)\n const base = t.procedure.input(action.input)\n const handler = async ({ input, ctx }: { input: object; ctx: TrpcHandlerContext }) => {\n try {\n return await action.run(input, ctx.silkweaveContext)\n } catch (error) {\n throw mapError(error)\n }\n }\n record[key] = action.kind === 'query' ? base.query(handler) : base.mutation(handler)\n }\n return t.router(record)\n}\n","import { AuthConfig, AuthInfo, validateToken } from '@silkweave/auth'\nimport { SilkweaveContext } from '@silkweave/core'\nimport { buildLogLevels, Logger, LogLevel } from '@silkweave/logger'\n\nconst CONSOLE_LEVEL_MAP: Record<LogLevel, 'log' | 'info' | 'warn' | 'error'> = {\n emergency: 'error',\n alert: 'error',\n critical: 'error',\n error: 'error',\n warning: 'warn',\n notice: 'info',\n info: 'info',\n debug: 'log'\n}\n\nexport function createActionLogger(): Logger {\n return {\n ...buildLogLevels((level, data) => {\n console[CONSOLE_LEVEL_MAP[level]](data)\n }),\n progress: () => { /* progress notifications not supported on tRPC HTTP */ }\n }\n}\n\nexport interface AuthErrorPayload {\n statusCode: number\n headers: Record<string, string>\n body: { error: string; error_description: string }\n}\n\nexport type ResolvedAuth =\n | { kind: 'ok'; authInfo?: AuthInfo }\n | { kind: 'error'; error: AuthErrorPayload }\n\nexport async function resolveAuth(\n auth: AuthConfig | undefined,\n authHeader: string | null | undefined,\n context: SilkweaveContext\n): Promise<ResolvedAuth> {\n if (!auth) { return { kind: 'ok' } }\n const result = await validateToken(authHeader, auth, context)\n if (result.error) { return { kind: 'error', error: result.error } }\n return { kind: 'ok', authInfo: result.auth }\n}\n","import { AuthConfig } from '@silkweave/auth'\nimport { Adapter, AdapterGenerator, SilkweaveOptions } from '@silkweave/core'\nimport { fetchRequestHandler } from '@trpc/server/adapters/fetch'\nimport { buildRouter, TrpcHandlerContext } from '../lib/buildRouter.js'\nimport { createActionLogger, resolveAuth } from '../lib/createContext.js'\n\nexport interface TrpcFetchAdapterOptions {\n /** URL prefix stripped from incoming requests before tRPC routing. Default `/trpc`. */\n endpoint?: string\n auth?: AuthConfig\n}\n\nexport type FetchHandler = (request: Request) => Promise<Response>\n\nexport interface TrpcFetchAdapter {\n adapter: AdapterGenerator\n handler: FetchHandler\n GET: FetchHandler\n POST: FetchHandler\n}\n\n/**\n * Creates a tRPC adapter that exposes a fetch-compatible handler instead of\n * binding its own HTTP server. Use in Astro API routes, Vercel serverless\n * functions, Cloudflare Workers, or any Web Standard runtime.\n *\n * The returned `handler` waits for `server.start()` to complete (via an internal\n * `_ready` promise) before dispatching requests, so it's safe to call from a\n * cold-started serverless invocation.\n *\n * CORS is intentionally not configured here - handle it in your host framework\n * (Astro middleware, `vercel.json` headers, Worker response headers, etc).\n */\nexport function trpcFetch(options: TrpcFetchAdapterOptions = {}): TrpcFetchAdapter {\n const endpoint = (options.endpoint ?? '/trpc').replace(/\\/$/, '')\n\n let resolveReady!: () => void\n const ready = new Promise<void>((resolve) => { resolveReady = resolve })\n\n let handler: FetchHandler = async () => new Response(\n JSON.stringify({ error: 'not_ready', message: 'tRPC adapter has not started yet' }),\n { status: 503, headers: { 'Content-Type': 'application/json' } }\n )\n\n const adapter: AdapterGenerator = (_silkweaveOptions: SilkweaveOptions, baseContext): Adapter => {\n const context = baseContext.fork({ adapter: 'trpc' })\n return {\n context,\n start: async (actions) => {\n const router = buildRouter(actions)\n const logger = createActionLogger()\n\n const createContext = async (\n opts: { req: Request }\n ): Promise<TrpcHandlerContext> => {\n const resolved = await resolveAuth(\n options.auth,\n opts.req.headers.get('authorization'),\n context.fork({ request: opts.req })\n )\n if (resolved.kind === 'error') {\n const err = new Error('Unauthorized') as Error & { silkweaveAuthError?: typeof resolved.error }\n err.silkweaveAuthError = resolved.error\n throw err\n }\n return {\n silkweaveContext: context.fork({\n logger,\n request: opts.req,\n ...(resolved.authInfo ? { auth: resolved.authInfo } : {})\n })\n }\n }\n\n handler = async (request: Request): Promise<Response> => {\n try {\n return await fetchRequestHandler({\n endpoint,\n req: request,\n router,\n createContext\n })\n } catch (error) {\n const authError = (error as { silkweaveAuthError?: { statusCode: number; headers: Record<string, string>; body: object } }).silkweaveAuthError\n if (authError) {\n return new Response(JSON.stringify(authError.body), {\n status: authError.statusCode,\n headers: { 'Content-Type': 'application/json', ...authError.headers }\n })\n }\n throw error\n }\n }\n\n resolveReady()\n },\n stop: async () => { /* no-op for fetch adapter */ }\n }\n }\n\n const dispatch: FetchHandler = async (request) => {\n await ready\n return handler(request)\n }\n\n return {\n adapter,\n handler: dispatch,\n GET: dispatch,\n POST: dispatch\n }\n}\n","import { AuthConfig } from '@silkweave/auth'\nimport { AdapterFactory } from '@silkweave/core'\nimport { createHTTPHandler } from '@trpc/server/adapters/standalone'\nimport cors, { CorsOptions } from 'cors'\nimport http, { IncomingMessage, ServerResponse } from 'http'\nimport { buildRouter, TrpcHandlerContext } from '../lib/buildRouter.js'\nimport { createActionLogger, resolveAuth } from '../lib/createContext.js'\n\nexport interface TrpcAdapterOptions {\n host?: string\n port?: number\n /** URL prefix stripped from incoming requests before tRPC routing. Default `/trpc/`. */\n endpoint?: string\n /** CORS configuration. `false` to disable, `true`/`undefined` for permissive defaults, or a CorsOptions object. */\n cors?: CorsOptions | boolean\n auth?: AuthConfig\n}\n\ntype CorsMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void\n\nfunction resolveCors(corsConfig: CorsOptions | boolean | undefined): CorsMiddleware | null {\n if (corsConfig === false) { return null }\n const userConfig = corsConfig === true || corsConfig === undefined ? {} : corsConfig\n return cors({ origin: '*', ...userConfig }) as CorsMiddleware\n}\n\nexport const trpc: AdapterFactory<TrpcAdapterOptions> = (options) => {\n return (_silkweaveOptions, baseContext) => {\n const context = baseContext.fork({ adapter: 'trpc' })\n const host = options.host ?? '0.0.0.0'\n const port = options.port ?? 8080\n const endpoint = (options.endpoint ?? '/trpc/').replace(/\\/?$/, '/')\n let server: http.Server | undefined\n\n return {\n context,\n start: async (actions) => {\n const router = buildRouter(actions)\n const corsMiddleware = resolveCors(options.cors)\n const logger = createActionLogger()\n\n const createContext = async (\n opts: { req: IncomingMessage; res: ServerResponse }\n ): Promise<TrpcHandlerContext> => {\n const resolved = await resolveAuth(\n options.auth,\n opts.req.headers.authorization,\n context.fork({ request: opts.req })\n )\n if (resolved.kind === 'error') {\n for (const [key, value] of Object.entries(resolved.error.headers)) {\n opts.res.setHeader(key, value)\n }\n opts.res.statusCode = resolved.error.statusCode\n opts.res.setHeader('Content-Type', 'application/json')\n opts.res.end(JSON.stringify(resolved.error.body))\n throw new Error('Unauthorized')\n }\n return {\n silkweaveContext: context.fork({\n logger,\n request: opts.req,\n ...(resolved.authInfo ? { auth: resolved.authInfo } : {})\n })\n }\n }\n\n const trpcHandler = createHTTPHandler({ router, basePath: endpoint, createContext })\n\n const handler = (req: IncomingMessage, res: ServerResponse) => {\n if (!req.url?.startsWith(endpoint)) {\n res.statusCode = 404\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({ error: 'not_found', message: `No route for ${req.url}` }))\n return\n }\n trpcHandler(req, res)\n }\n\n server = http.createServer((req, res) => {\n if (corsMiddleware) {\n corsMiddleware(req, res, () => {\n if (req.method === 'OPTIONS') { res.statusCode = 204; res.end(); return }\n handler(req, res)\n })\n } else {\n handler(req, res)\n }\n })\n\n await new Promise<void>((resolve) => {\n server!.listen(port, host, () => { resolve() })\n })\n },\n stop: async () => {\n if (!server) { return }\n await new Promise<void>((resolve, reject) => {\n server!.close((err) => {\n if (err) { reject(err) } else { resolve() }\n })\n })\n }\n }\n }\n}\n"],"mappings":"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"}