@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.
- package/.turbo/turbo-build.log +16 -0
- package/.turbo/turbo-check.log +13 -0
- package/.turbo/turbo-prepack.log +24 -0
- package/README.md +184 -0
- package/build/index.d.mts +91 -0
- package/build/index.d.mts.map +1 -0
- package/build/index.mjs +267 -0
- package/build/index.mjs.map +1 -0
- package/eslint.config.mjs +93 -0
- package/package.json +45 -0
- package/src/adapter/fetch.ts +112 -0
- package/src/adapter/trpc.ts +105 -0
- package/src/index.ts +6 -0
- package/src/lib/buildRouter.ts +27 -0
- package/src/lib/createContext.ts +44 -0
- package/src/lib/errors.ts +42 -0
- package/src/lib/inferRouter.ts +50 -0
- package/tsconfig.json +22 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsdown.config.ts +7 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
> @silkweave/trpc@1.6.0 build /Users/atomic/projects/ai/silkweave/packages/trpc
|
|
3
|
+
> tsdown
|
|
4
|
+
|
|
5
|
+
[34mℹ[39m [34mtsdown v0.21.8[39m powered by [38;2;255;126;23mrolldown v1.0.0-rc.15[39m
|
|
6
|
+
[34mℹ[39m config file: [4m/Users/atomic/projects/ai/silkweave/packages/trpc/tsdown.config.ts[24m
|
|
7
|
+
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
8
|
+
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
9
|
+
[34mℹ[39m Build start
|
|
10
|
+
[34mℹ[39m Cleaning 4 files
|
|
11
|
+
[34mℹ[39m [2mbuild/[22m[1mindex.mjs[22m [2m4.83 kB[22m [2m│ gzip: 1.85 kB[22m
|
|
12
|
+
[34mℹ[39m [2mbuild/[22mindex.mjs.map [2m9.85 kB[22m [2m│ gzip: 3.49 kB[22m
|
|
13
|
+
[34mℹ[39m [2mbuild/[22mindex.d.mts.map [2m1.11 kB[22m [2m│ gzip: 0.47 kB[22m
|
|
14
|
+
[34mℹ[39m [2mbuild/[22m[32m[1mindex.d.mts[22m[39m [2m2.67 kB[22m [2m│ gzip: 1.09 kB[22m
|
|
15
|
+
[34mℹ[39m 4 files, total: 18.46 kB
|
|
16
|
+
[32m✔[39m Build complete in [32m606ms[39m
|
|
@@ -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
|
+
[34mℹ[39m [34mtsdown v0.21.8[39m powered by [38;2;255;126;23mrolldown v1.0.0-rc.15[39m
|
|
15
|
+
[34mℹ[39m config file: [4m/Users/atomic/projects/ai/silkweave/packages/trpc/tsdown.config.ts[24m
|
|
16
|
+
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
17
|
+
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
18
|
+
[34mℹ[39m Build start
|
|
19
|
+
[34mℹ[39m [2mbuild/[22m[1mindex.mjs[22m [2m 7.67 kB[22m [2m│ gzip: 2.62 kB[22m
|
|
20
|
+
[34mℹ[39m [2mbuild/[22mindex.mjs.map [2m15.99 kB[22m [2m│ gzip: 4.89 kB[22m
|
|
21
|
+
[34mℹ[39m [2mbuild/[22mindex.d.mts.map [2m 1.40 kB[22m [2m│ gzip: 0.56 kB[22m
|
|
22
|
+
[34mℹ[39m [2mbuild/[22m[32m[1mindex.d.mts[22m[39m [2m 3.82 kB[22m [2m│ gzip: 1.50 kB[22m
|
|
23
|
+
[34mℹ[39m 4 files, total: 28.88 kB
|
|
24
|
+
[32m✔[39m Build complete in [32m572ms[39m
|
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"}
|
package/build/index.mjs
ADDED
|
@@ -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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"}
|