@silkweave/trpc 3.2.0 → 4.0.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/build/index.d.mts +2 -1
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +74 -41
- package/build/index.mjs.map +1 -1
- package/package.json +3 -3
package/build/index.d.mts
CHANGED
|
@@ -78,7 +78,8 @@ declare function resolveAuth(auth: AuthConfig | undefined, authHeader: string |
|
|
|
78
78
|
declare function mapError(error: unknown): TRPCError;
|
|
79
79
|
//#endregion
|
|
80
80
|
//#region src/lib/inferRouter.d.ts
|
|
81
|
-
type
|
|
81
|
+
type CamelJoin<S extends string> = S extends `${infer A}${'.' | '-' | '_' | ' '}${infer B}` ? `${A}${Capitalize<CamelJoin<B>>}` : S;
|
|
82
|
+
type CamelCase<S extends string> = Uncapitalize<CamelJoin<S>>;
|
|
82
83
|
type ChunkOf<A extends Action> = A['run'] extends ((...args: any[]) => AsyncGenerator<infer C, unknown, unknown>) ? C : never;
|
|
83
84
|
type IsStreaming<A extends Action> = A['run'] extends ((...args: any[]) => AsyncGenerator<unknown, unknown, unknown>) ? true : false;
|
|
84
85
|
type ActionToProcedure<A extends Action> = IsStreaming<A> extends true ? TRPCSubscriptionProcedure<{
|
package/build/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/fetch.ts","../src/adapter/trpc.ts","../src/lib/buildRouter.ts","../src/lib/createContext.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;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/adapter/fetch.ts","../src/adapter/trpc.ts","../src/lib/buildRouter.ts","../src/lib/createContext.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;;;;;;;;iBCK7B,kBAAA,CAAA,GAAsB,MAAA;AAAA,UASrB,gBAAA;EACf,UAAA;EACA,OAAA,EAAS,MAAA;EACT,IAAA;IAAQ,KAAA;IAAe,iBAAA;EAAA;AAAA;AAAA,KAGb,YAAA;EACN,IAAA;EAAY,QAAA,GAAW,QAAA;AAAA;EACvB,IAAA;EAAe,KAAA,EAAO,gBAAA;AAAA;AAAA,iBAEN,WAAA,CACpB,IAAA,EAAM,UAAA,cACN,UAAA,6BACA,OAAA,EAAS,gBAAA,GACR,OAAA,CAAQ,YAAA;;;iBCdK,QAAA,CAAS,KAAA,YAAiB,SAAA;;;KChBrC,SAAA,qBAA8B,CAAA,6DAC5B,CAAA,GAAI,UAAA,CAAW,SAAA,CAAU,CAAA,OAC5B,CAAA;AAAA,KACC,SAAA,qBAA8B,YAAA,CAAa,SAAA,CAAU,CAAA;AAAA,KAGrD,OAAA,WAAkB,MAAA,IAAU,CAAA,qBAAqB,IAAA,YAAgB,cAAA,+BAA4C,CAAA;AAAA,KAG7G,WAAA,WAAsB,MAAA,IAAU,CAAA,qBAAqB,IAAA,YAAgB,cAAA;AAAA,KAErE,iBAAA,WAA4B,MAAA,IAAU,WAAA,CAAY,CAAA,iBACnD,yBAAA;EACA,IAAA;EACA,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,CAAA;EACf,MAAA,EAAQ,OAAA,CAAQ,CAAA;AAAA,sBAEC,WAAA,CAAY,CAAA,YAC3B,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,KAG5B,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;;;ALhCJ;;;;;;;KK2CY,eAAA,MAAqB,CAAA,SAAU,SAAA,kBACvC,eAAA,CAAgB,aAAA,EAAe,qBAAA,CAAsB,OAAA"}
|
package/build/index.mjs
CHANGED
|
@@ -110,6 +110,47 @@ async function resolveAuth(auth, authHeader, context) {
|
|
|
110
110
|
authInfo: result.auth
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Carries the OAuth challenge (status + `WWW-Authenticate`/resource-metadata
|
|
115
|
+
* headers) from a failed `createContext` to `authResponseMeta`. Attached as the
|
|
116
|
+
* `cause` of a `TRPCError` so the tRPC handler owns the whole response
|
|
117
|
+
* lifecycle - the adapter must never write/end the raw response itself (doing so
|
|
118
|
+
* races tRPC's own error write and crashes the process with
|
|
119
|
+
* `ERR_STREAM_WRITE_AFTER_END`).
|
|
120
|
+
*/
|
|
121
|
+
var AuthChallengeError = class extends Error {
|
|
122
|
+
constructor(payload) {
|
|
123
|
+
super(payload.body.error_description || payload.body.error || "Unauthorized");
|
|
124
|
+
this.payload = payload;
|
|
125
|
+
this.name = "AuthChallengeError";
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Throw a resolved auth error as a `TRPCError` so the tRPC handler (standalone
|
|
130
|
+
* or fetch) produces the response. `authResponseMeta` then upgrades that
|
|
131
|
+
* response to the OAuth status + challenge headers via the `AuthChallengeError`
|
|
132
|
+
* cause. Never write to `res` directly from `createContext`.
|
|
133
|
+
*/
|
|
134
|
+
function throwAuthError(payload) {
|
|
135
|
+
throw new TRPCError({
|
|
136
|
+
code: payload.statusCode === 403 ? "FORBIDDEN" : "UNAUTHORIZED",
|
|
137
|
+
message: payload.body.error_description || payload.body.error || "Unauthorized",
|
|
138
|
+
cause: new AuthChallengeError(payload)
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* tRPC `responseMeta` that surfaces an auth challenge: when a request's errors
|
|
143
|
+
* carry an `AuthChallengeError`, override the HTTP status (401/403) and set the
|
|
144
|
+
* OAuth challenge headers so discovery/step-up flows work. Shared by the
|
|
145
|
+
* standalone (`createHTTPHandler`) and fetch (`fetchRequestHandler`) adapters.
|
|
146
|
+
*/
|
|
147
|
+
function authResponseMeta(opts) {
|
|
148
|
+
for (const err of opts.errors) if (err.cause instanceof AuthChallengeError) return {
|
|
149
|
+
status: err.cause.payload.statusCode,
|
|
150
|
+
headers: err.cause.payload.headers
|
|
151
|
+
};
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
113
154
|
//#endregion
|
|
114
155
|
//#region src/adapter/fetch.ts
|
|
115
156
|
/**
|
|
@@ -127,30 +168,28 @@ async function resolveAuth(auth, authHeader, context) {
|
|
|
127
168
|
function trpcFetch(options = {}) {
|
|
128
169
|
const endpoint = (options.endpoint ?? "/trpc").replace(/\/$/, "");
|
|
129
170
|
let resolveReady;
|
|
130
|
-
|
|
171
|
+
let rejectReady;
|
|
172
|
+
const ready = new Promise((resolve, reject) => {
|
|
131
173
|
resolveReady = resolve;
|
|
174
|
+
rejectReady = reject;
|
|
132
175
|
});
|
|
133
|
-
let handler
|
|
134
|
-
error: "not_ready",
|
|
135
|
-
message: "tRPC adapter has not started yet"
|
|
136
|
-
}), {
|
|
137
|
-
status: 503,
|
|
138
|
-
headers: { "Content-Type": "application/json" }
|
|
139
|
-
});
|
|
176
|
+
let handler;
|
|
140
177
|
const adapter = (_silkweaveOptions, baseContext) => {
|
|
141
178
|
const context = baseContext.fork({ adapter: "trpc" });
|
|
142
179
|
return {
|
|
143
180
|
context,
|
|
144
181
|
start: async (actions) => {
|
|
145
|
-
|
|
182
|
+
let router;
|
|
183
|
+
try {
|
|
184
|
+
router = buildRouter(actions);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
rejectReady(error);
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
146
189
|
const logger = createActionLogger();
|
|
147
190
|
const createContext = async (opts) => {
|
|
148
191
|
const resolved = await resolveAuth(options.auth, opts.req.headers.get("authorization"), context.fork({ request: opts.req }));
|
|
149
|
-
if (resolved.kind === "error")
|
|
150
|
-
const err = /* @__PURE__ */ new Error("Unauthorized");
|
|
151
|
-
err.silkweaveAuthError = resolved.error;
|
|
152
|
-
throw err;
|
|
153
|
-
}
|
|
192
|
+
if (resolved.kind === "error") throwAuthError(resolved.error);
|
|
154
193
|
return { silkweaveContext: context.fork({
|
|
155
194
|
logger,
|
|
156
195
|
request: opts.req,
|
|
@@ -158,24 +197,13 @@ function trpcFetch(options = {}) {
|
|
|
158
197
|
}) };
|
|
159
198
|
};
|
|
160
199
|
handler = async (request) => {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
} catch (error) {
|
|
169
|
-
const authError = error.silkweaveAuthError;
|
|
170
|
-
if (authError) return new Response(JSON.stringify(authError.body), {
|
|
171
|
-
status: authError.statusCode,
|
|
172
|
-
headers: {
|
|
173
|
-
"Content-Type": "application/json",
|
|
174
|
-
...authError.headers
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
throw error;
|
|
178
|
-
}
|
|
200
|
+
return fetchRequestHandler({
|
|
201
|
+
endpoint,
|
|
202
|
+
req: request,
|
|
203
|
+
router,
|
|
204
|
+
createContext,
|
|
205
|
+
responseMeta: authResponseMeta
|
|
206
|
+
});
|
|
179
207
|
};
|
|
180
208
|
resolveReady();
|
|
181
209
|
},
|
|
@@ -183,7 +211,17 @@ function trpcFetch(options = {}) {
|
|
|
183
211
|
};
|
|
184
212
|
};
|
|
185
213
|
const dispatch = async (request) => {
|
|
186
|
-
|
|
214
|
+
try {
|
|
215
|
+
await ready;
|
|
216
|
+
} catch {
|
|
217
|
+
return new Response(JSON.stringify({
|
|
218
|
+
error: "not_ready",
|
|
219
|
+
message: "tRPC adapter failed to start"
|
|
220
|
+
}), {
|
|
221
|
+
status: 503,
|
|
222
|
+
headers: { "Content-Type": "application/json" }
|
|
223
|
+
});
|
|
224
|
+
}
|
|
187
225
|
return handler(request);
|
|
188
226
|
};
|
|
189
227
|
return {
|
|
@@ -217,13 +255,7 @@ const trpc = (options) => {
|
|
|
217
255
|
const logger = createActionLogger();
|
|
218
256
|
const createContext = async (opts) => {
|
|
219
257
|
const resolved = await resolveAuth(options.auth, opts.req.headers.authorization, context.fork({ request: opts.req }));
|
|
220
|
-
if (resolved.kind === "error")
|
|
221
|
-
for (const [key, value] of Object.entries(resolved.error.headers)) opts.res.setHeader(key, value);
|
|
222
|
-
opts.res.statusCode = resolved.error.statusCode;
|
|
223
|
-
opts.res.setHeader("Content-Type", "application/json");
|
|
224
|
-
opts.res.end(JSON.stringify(resolved.error.body));
|
|
225
|
-
throw new Error("Unauthorized");
|
|
226
|
-
}
|
|
258
|
+
if (resolved.kind === "error") throwAuthError(resolved.error);
|
|
227
259
|
return { silkweaveContext: context.fork({
|
|
228
260
|
logger,
|
|
229
261
|
request: opts.req,
|
|
@@ -233,7 +265,8 @@ const trpc = (options) => {
|
|
|
233
265
|
const trpcHandler = createHTTPHandler({
|
|
234
266
|
router,
|
|
235
267
|
basePath: endpoint,
|
|
236
|
-
createContext
|
|
268
|
+
createContext,
|
|
269
|
+
responseMeta: authResponseMeta
|
|
237
270
|
});
|
|
238
271
|
const handler = (req, res) => {
|
|
239
272
|
if (!req.url?.startsWith(endpoint)) {
|
package/build/index.mjs.map
CHANGED
|
@@ -1 +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/v4'\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, ActionRun, ActionStreamRun, isStreamingAction, 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 if (isStreamingAction(action)) {\n const streamRun = action.run as ActionStreamRun<object, unknown>\n record[key] = base.subscription(async function* ({ input, ctx, signal }) {\n try {\n const iter = streamRun(input as object, ctx.silkweaveContext)\n for await (const chunk of iter) {\n if (signal?.aborted) { return }\n yield chunk\n }\n } catch (error) {\n throw mapError(error)\n }\n })\n } else {\n const runFn = action.run as ActionRun<object, object>\n const handler = async ({ input, ctx }: { input: object; ctx: TrpcHandlerContext }) => {\n try {\n return await runFn(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 }\n return t.router(record)\n}\n","import { AuthConfig, AuthInfo, validateToken } from '@silkweave/auth'\nimport { buildLogLevels, Logger, LogLevel, SilkweaveContext } from '@silkweave/core'\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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"}
|
|
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/v4'\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, ActionRun, ActionStreamRun, isStreamingAction, 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 if (isStreamingAction(action)) {\n const streamRun = action.run as ActionStreamRun<object, unknown>\n record[key] = base.subscription(async function* ({ input, ctx, signal }) {\n try {\n const iter = streamRun(input as object, ctx.silkweaveContext)\n for await (const chunk of iter) {\n if (signal?.aborted) { return }\n yield chunk\n }\n } catch (error) {\n throw mapError(error)\n }\n })\n } else {\n const runFn = action.run as ActionRun<object, object>\n const handler = async ({ input, ctx }: { input: object; ctx: TrpcHandlerContext }) => {\n try {\n return await runFn(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 }\n return t.router(record)\n}\n","import { AuthConfig, AuthInfo, validateToken } from '@silkweave/auth'\nimport { buildLogLevels, Logger, LogLevel, SilkweaveContext } from '@silkweave/core'\nimport { TRPCError } from '@trpc/server'\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\n/**\n * Carries the OAuth challenge (status + `WWW-Authenticate`/resource-metadata\n * headers) from a failed `createContext` to `authResponseMeta`. Attached as the\n * `cause` of a `TRPCError` so the tRPC handler owns the whole response\n * lifecycle - the adapter must never write/end the raw response itself (doing so\n * races tRPC's own error write and crashes the process with\n * `ERR_STREAM_WRITE_AFTER_END`).\n */\nexport class AuthChallengeError extends Error {\n constructor(public readonly payload: AuthErrorPayload) {\n super(payload.body.error_description || payload.body.error || 'Unauthorized')\n this.name = 'AuthChallengeError'\n }\n}\n\n/**\n * Throw a resolved auth error as a `TRPCError` so the tRPC handler (standalone\n * or fetch) produces the response. `authResponseMeta` then upgrades that\n * response to the OAuth status + challenge headers via the `AuthChallengeError`\n * cause. Never write to `res` directly from `createContext`.\n */\nexport function throwAuthError(payload: AuthErrorPayload): never {\n throw new TRPCError({\n code: payload.statusCode === 403 ? 'FORBIDDEN' : 'UNAUTHORIZED',\n message: payload.body.error_description || payload.body.error || 'Unauthorized',\n cause: new AuthChallengeError(payload)\n })\n}\n\n/**\n * tRPC `responseMeta` that surfaces an auth challenge: when a request's errors\n * carry an `AuthChallengeError`, override the HTTP status (401/403) and set the\n * OAuth challenge headers so discovery/step-up flows work. Shared by the\n * standalone (`createHTTPHandler`) and fetch (`fetchRequestHandler`) adapters.\n */\nexport function authResponseMeta(\n opts: { errors: readonly { cause?: unknown }[] }\n): { status?: number; headers?: Record<string, string> } {\n for (const err of opts.errors) {\n if (err.cause instanceof AuthChallengeError) {\n return { status: err.cause.payload.statusCode, headers: err.cause.payload.headers }\n }\n }\n return {}\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 { authResponseMeta, createActionLogger, resolveAuth, throwAuthError } 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 let rejectReady!: (error: unknown) => void\n const ready = new Promise<void>((resolve, reject) => { resolveReady = resolve; rejectReady = reject })\n\n let handler: FetchHandler | undefined\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 let router\n try {\n router = buildRouter(actions)\n } catch (error) {\n // Reject readiness so requests get a 503 instead of hanging forever,\n // then rethrow so the builder's start() rejects too.\n rejectReady(error)\n throw error\n }\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 // @trpc/server captures createContext errors internally and never\n // rethrows, so a plain throw would surface as a 500. Signal the\n // 401/403 as a TRPCError whose challenge headers authResponseMeta\n // applies to the Response.\n throwAuthError(resolved.error)\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 return fetchRequestHandler({\n endpoint,\n req: request,\n router,\n createContext,\n responseMeta: authResponseMeta\n })\n }\n\n resolveReady()\n },\n stop: async () => { /* no-op for fetch adapter */ }\n }\n }\n\n const dispatch: FetchHandler = async (request) => {\n try {\n await ready\n } catch {\n return new Response(\n JSON.stringify({ error: 'not_ready', message: 'tRPC adapter failed to start' }),\n { status: 503, headers: { 'Content-Type': 'application/json' } }\n )\n }\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 { authResponseMeta, createActionLogger, resolveAuth, throwAuthError } 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 // Signal the 401/403 through tRPC (see throwAuthError). Writing to\n // opts.res here would race tRPC's own error write and crash the\n // process with ERR_STREAM_WRITE_AFTER_END on the finished response.\n throwAuthError(resolved.error)\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, responseMeta: authResponseMeta })\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silkweave/trpc",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Silkweave tRPC Adapter",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://www.silkweave.dev",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"change-case": "^5.4.4",
|
|
31
31
|
"cors": "^2.8.5",
|
|
32
32
|
"zod": "^3.25.0",
|
|
33
|
-
"@silkweave/auth": "
|
|
34
|
-
"@silkweave/core": "
|
|
33
|
+
"@silkweave/auth": "4.0.0",
|
|
34
|
+
"@silkweave/core": "4.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@eslint/js": "^10.0.1",
|