@schemavaults/openapi-operations 0.1.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -2
- package/dist/adapters/nextjs.d.ts +18 -4
- package/dist/adapters/nextjs.js +18 -4
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/operation.d.ts +27 -2
- package/dist/operation.js +8 -0
- package/dist/operation.js.map +1 -1
- package/dist/runtime/create-operations-app-factory.d.ts +69 -0
- package/dist/runtime/create-operations-app-factory.js +68 -0
- package/dist/runtime/create-operations-app-factory.js.map +1 -0
- package/dist/runtime/create-operations-app.d.ts +22 -4
- package/dist/runtime/create-operations-app.js +31 -13
- package/dist/runtime/create-operations-app.js.map +1 -1
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/resolve-auth.d.ts +7 -5
- package/dist/runtime/resolve-auth.js +5 -3
- package/dist/runtime/resolve-auth.js.map +1 -1
- package/dist/runtime/validate-request.js +16 -4
- package/dist/runtime/validate-request.js.map +1 -1
- package/dist/zod-openapi.d.ts +22 -1
- package/dist/zod-openapi.js +25 -1
- package/dist/zod-openapi.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,6 +80,27 @@ export const health = defineOperation({
|
|
|
80
80
|
`defineOperationGroup({ pathPrefix, tags, operations })` applies a shared prefix and tags.
|
|
81
81
|
Throw `new OperationError(status, { error, message })` from a handler to short-circuit.
|
|
82
82
|
|
|
83
|
+
Schemas built by OTHER packages (e.g. `@schemavaults/auth-common`) may have been constructed
|
|
84
|
+
before this package installed the `.openapi()` extension (zod v4 copies prototype methods onto
|
|
85
|
+
each instance at construction time), in which case `schema.openapi(...)` throws at module load
|
|
86
|
+
depending on import order. Use `withOpenApi(schema, refId?, metadata)` for them:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { withOpenApi } from "@schemavaults/openapi-operations";
|
|
90
|
+
export const Organization = withOpenApi(organizationDefinitionSchema, "Organization", { description: "..." });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Request bodies take two extra flags:
|
|
94
|
+
|
|
95
|
+
- `lenientContentType: true` also parses bodies labelled `text/plain` (or carrying no
|
|
96
|
+
`Content-Type` at all) as the declared media type. Browsers send
|
|
97
|
+
`text/plain;charset=UTF-8` for `fetch(url, { body: JSON.stringify(x) })` without an
|
|
98
|
+
explicit header, so JSON endpoints with such callers need it.
|
|
99
|
+
- `documentOnly: true` describes the body in the OpenAPI document but leaves the
|
|
100
|
+
request untouched: `ctx.body` is `undefined` and the handler reads `ctx.request`
|
|
101
|
+
itself. For endpoints whose parsing / error format is mandated by a protocol (the
|
|
102
|
+
OAuth 2.0 token endpoint's `{ error, error_description }`, ...).
|
|
103
|
+
|
|
83
104
|
### Auth schemes
|
|
84
105
|
|
|
85
106
|
An `AuthSchemeDefinition` is a named OpenAPI security scheme plus docs metadata. Built-ins:
|
|
@@ -118,10 +139,14 @@ import { createOperationsApp, toNextRouteHandlers } from "@schemavaults/openapi-
|
|
|
118
139
|
|
|
119
140
|
const app = createOperationsApp<{ dbh: Kysely<AuthDatabase> }, UserData>({
|
|
120
141
|
operations: [getApp, health],
|
|
142
|
+
// Built per request BEFORE credentials are resolved (keep it cheap, open
|
|
143
|
+
// expensive resources lazily); released by disposeContext afterwards.
|
|
121
144
|
context: async () => ({ dbh: await getDbh() }),
|
|
145
|
+
disposeContext: async ({ dbh }) => dbh.destroy(),
|
|
122
146
|
authResolvers: {
|
|
123
|
-
// keyed by scheme name; return null when no credential for that scheme is
|
|
124
|
-
|
|
147
|
+
// keyed by scheme name; return null when no credential for that scheme is
|
|
148
|
+
// present. The third argument is the per-request context.
|
|
149
|
+
"schemavaults-access-token": async (c, _scheme, { dbh }) => {
|
|
125
150
|
const token = c.req.header("authorization")?.replace(/^Bearer /, "");
|
|
126
151
|
if (!token) return null;
|
|
127
152
|
const user = await verifyAccessToken(token); // throw OperationError(401, ...) if invalid
|
|
@@ -137,6 +162,8 @@ const app = createOperationsApp<{ dbh: Kysely<AuthDatabase> }, UserData>({
|
|
|
137
162
|
// Served at GET /openapi.json. Pass a function to build it per request,
|
|
138
163
|
// e.g. to set `servers` from the incoming Host / X-Forwarded-* headers.
|
|
139
164
|
openapi: { document: openApiDocument },
|
|
165
|
+
// Unexpected failures: log / persist them before the generic 500 goes out.
|
|
166
|
+
onError: (error, c, { operation, context }) => reportException(error, operation.operationId),
|
|
140
167
|
});
|
|
141
168
|
|
|
142
169
|
// app/api/[[...route]]/route.ts
|
|
@@ -147,6 +174,47 @@ export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS } = toNextRouteHandl
|
|
|
147
174
|
export default toVercelHandler(app);
|
|
148
175
|
```
|
|
149
176
|
|
|
177
|
+
### One route file per operation
|
|
178
|
+
|
|
179
|
+
A catch-all is not required. To give every operation its own Next.js `route.ts`
|
|
180
|
+
(or Vercel function file) while still serving one OpenAPI document that lists all of
|
|
181
|
+
them, bind the shared runtime configuration and the full operation catalogue once with
|
|
182
|
+
`createOperationsAppFactory()` and build a small app per route file from it:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
// src/lib/api/app.ts
|
|
186
|
+
import { createOperationsAppFactory, operationHttpMethods, toNextRouteHandlers } from "@schemavaults/openapi-operations";
|
|
187
|
+
|
|
188
|
+
export const operations = [getApp, health]; // the catalogue buildOpenApiDocument() is given
|
|
189
|
+
export const api = createOperationsAppFactory<{ dbh: Kysely<AuthDatabase> }, UserData>({
|
|
190
|
+
operations,
|
|
191
|
+
authResolvers,
|
|
192
|
+
context: async () => ({ dbh: await getDbh() }),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// app/api/apps/[app_id]/route.ts (Next.js segment [app_id] ↔ OpenAPI {app_id})
|
|
196
|
+
export const runtime = "nodejs";
|
|
197
|
+
export const { GET } = toNextRouteHandlers(api.app([getApp]), operationHttpMethods([getApp]));
|
|
198
|
+
|
|
199
|
+
// app/api/health/route.ts
|
|
200
|
+
export const { GET } = toNextRouteHandlers(api.app([health]), operationHttpMethods([health]));
|
|
201
|
+
|
|
202
|
+
// app/api/openapi.json/route.ts
|
|
203
|
+
export const { GET } = toNextRouteHandlers(
|
|
204
|
+
api.openApiDocumentApp({ path: "/api/openapi.json", document: openApiDocument }),
|
|
205
|
+
["get"],
|
|
206
|
+
);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Each app routes on the full request pathname, so no `basePath` is needed and the
|
|
210
|
+
dynamic segment is parsed by the app itself (Next.js' `params` are never read).
|
|
211
|
+
`operationHttpMethods()` exports only the methods the operations declare, leaving 405s
|
|
212
|
+
for the rest to Next.js. The factory validates the whole catalogue up front (unique
|
|
213
|
+
operations, a resolver for every scheme) and `api.app()` throws for an operation that
|
|
214
|
+
is not in it, so a route file cannot serve something the document does not describe.
|
|
215
|
+
The reverse (a documented operation with no route file) is a file-layout question;
|
|
216
|
+
the example resource server in this repository checks it with a small `bun test`.
|
|
217
|
+
|
|
150
218
|
Per request the app: resolves the context, tries each accepted scheme's resolver in
|
|
151
219
|
order (401 + `WWW-Authenticate` if none yields a principal), enforces the route guard
|
|
152
220
|
(403), required scopes (403 `insufficient_scope`), organization membership (403), then
|
|
@@ -5,8 +5,8 @@ export type NextRouteHandlers<TMethods extends HttpMethod = HttpMethod> = {
|
|
|
5
5
|
readonly [M in TMethods as Uppercase<M>]: NextRouteHandler;
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
|
-
* Next.js App Router route-handler exports for the operations app.
|
|
9
|
-
*
|
|
8
|
+
* Next.js App Router route-handler exports for the operations app. Either
|
|
9
|
+
* mount one app from a catch-all route so it serves every operation:
|
|
10
10
|
*
|
|
11
11
|
* ```ts
|
|
12
12
|
* // app/api/[[...route]]/route.ts
|
|
@@ -14,7 +14,21 @@ export type NextRouteHandlers<TMethods extends HttpMethod = HttpMethod> = {
|
|
|
14
14
|
* export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS } = toNextRouteHandlers(app);
|
|
15
15
|
* ```
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* or give each operation its own `route.ts` at the matching Next.js path
|
|
18
|
+
* (`app/api/apps/[app_id]/route.ts` for `/api/apps/{app_id}`) and mount an
|
|
19
|
+
* app built for just that operation, via `createOperationsAppFactory()`,
|
|
20
|
+
* exporting only the methods it declares so Next.js answers 405 for the
|
|
21
|
+
* rest:
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* // app/api/apps/[app_id]/route.ts
|
|
25
|
+
* export const { GET } = toNextRouteHandlers(api.app([getApp]), operationHttpMethods([getApp]));
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* The app receives the raw request, so it routes on the full pathname
|
|
29
|
+
* regardless of which route file it is exported from; no `basePath` is
|
|
30
|
+
* needed for per-route mounting. Pass `methods` to only export a subset
|
|
31
|
+
* (e.g. when a route file coexists with hand-written handlers for other
|
|
32
|
+
* methods).
|
|
19
33
|
*/
|
|
20
34
|
export declare function toNextRouteHandlers<const TMethods extends readonly HttpMethod[] = typeof HTTP_METHODS>(app: Hono, methods?: TMethods): NextRouteHandlers<TMethods[number]>;
|
package/dist/adapters/nextjs.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { handle } from "hono/vercel";
|
|
2
2
|
import { HTTP_METHODS } from "../http-method";
|
|
3
3
|
/**
|
|
4
|
-
* Next.js App Router route-handler exports for the operations app.
|
|
5
|
-
*
|
|
4
|
+
* Next.js App Router route-handler exports for the operations app. Either
|
|
5
|
+
* mount one app from a catch-all route so it serves every operation:
|
|
6
6
|
*
|
|
7
7
|
* ```ts
|
|
8
8
|
* // app/api/[[...route]]/route.ts
|
|
@@ -10,8 +10,22 @@ import { HTTP_METHODS } from "../http-method";
|
|
|
10
10
|
* export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS } = toNextRouteHandlers(app);
|
|
11
11
|
* ```
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* or give each operation its own `route.ts` at the matching Next.js path
|
|
14
|
+
* (`app/api/apps/[app_id]/route.ts` for `/api/apps/{app_id}`) and mount an
|
|
15
|
+
* app built for just that operation, via `createOperationsAppFactory()`,
|
|
16
|
+
* exporting only the methods it declares so Next.js answers 405 for the
|
|
17
|
+
* rest:
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* // app/api/apps/[app_id]/route.ts
|
|
21
|
+
* export const { GET } = toNextRouteHandlers(api.app([getApp]), operationHttpMethods([getApp]));
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* The app receives the raw request, so it routes on the full pathname
|
|
25
|
+
* regardless of which route file it is exported from; no `basePath` is
|
|
26
|
+
* needed for per-route mounting. Pass `methods` to only export a subset
|
|
27
|
+
* (e.g. when a route file coexists with hand-written handlers for other
|
|
28
|
+
* methods).
|
|
15
29
|
*/
|
|
16
30
|
export function toNextRouteHandlers(app, methods = HTTP_METHODS) {
|
|
17
31
|
const handler = handle(app);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nextjs.js","sourceRoot":"","sources":["../../src/adapters/nextjs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAmB,MAAM,gBAAgB,CAAC;AAQ/D
|
|
1
|
+
{"version":3,"file":"nextjs.js","sourceRoot":"","sources":["../../src/adapters/nextjs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAmB,MAAM,gBAAgB,CAAC;AAQ/D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAAS,EACT,UAAoB,YAAmC;IAEvD,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,QAAQ,GAAqC,EAAE,CAAC;IACtD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC;IAC3C,CAAC;IACD,OAAO,QAA+C,CAAC;AACzD,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
export { z } from "./zod-openapi";
|
|
2
|
-
export type { ZodType, ZodObject } from "./zod-openapi";
|
|
1
|
+
export { z, withOpenApi } from "./zod-openapi";
|
|
2
|
+
export type { ZodType, ZodObject, OpenApiSchemaMetadata } from "./zod-openapi";
|
|
3
3
|
export { HTTP_METHODS, HTTP_METHODS_WITH_REQUEST_BODY, isHttpMethod } from "./http-method";
|
|
4
4
|
export type { HttpMethod } from "./http-method";
|
|
5
5
|
export { defineAuthScheme, publicAccess, requireAuth, isPublicOperationAuth, ROUTE_GUARD_TYPES, schemaVaultsAccessTokenBearerScheme, schemaVaultsAccessTokenCookieScheme, schemaVaultsRefreshTokenCookieScheme, oidcClientSecretBasicScheme, oidcClientSecretPostScheme, apiKeyHeaderScheme, } from "./auth-scheme";
|
|
6
6
|
export type { AuthSchemeDefinition, AuthRequirements, OperationAuth, PublicOperationAuth, RequiredOperationAuth, OrganizationRoleRequirement, RouteGuardType, } from "./auth-scheme";
|
|
7
|
-
export { defineOperation, createOperationDefiner, defineOperationGroup, defaultOperationId, assertUniqueOperations, } from "./operation";
|
|
7
|
+
export { defineOperation, createOperationDefiner, defineOperationGroup, defaultOperationId, assertUniqueOperations, operationHttpMethods, } from "./operation";
|
|
8
8
|
export type { AnyOperationDefinition, AuthPrincipal, EmptyResponseStatusOf, InferBody, InferParsed, OperationDefiner, OperationDefinition, OperationGroup, OperationHandlerContext, OperationHandlerResult, OperationInput, OperationRequestDefinition, RequestBodyContentType, RequestBodyDefinition, ResponseBodyOf, ResponseDefinition, ResponseStatusOf, ResponsesDefinition, } from "./operation";
|
|
9
9
|
export { buildOpenApiDocument, collectAuthSchemes, toRouteConfig, toSecuritySchemeComponent, } from "./openapi/build-openapi-document";
|
|
10
10
|
export type { BuildOpenApiDocumentOptions } from "./openapi/build-openapi-document";
|
|
11
|
+
export type { OpenAPIObject } from "openapi3-ts/oas31";
|
|
11
12
|
export { SCHEMAVAULTS_AUTH_EXTENSION, SCHEMAVAULTS_SCHEME_TITLE_EXTENSION, SCHEMAVAULTS_SCHEME_CHALLENGE_EXTENSION, toSchemaVaultsAuthExtension, isSchemaVaultsAuthExtension, } from "./openapi/extensions";
|
|
12
13
|
export type { SchemaVaultsAuthExtension } from "./openapi/extensions";
|
|
13
14
|
export { extractPathParameterNames, openApiPathToHonoPath, honoPathToOpenApiPath, isOpenApiPath, } from "./openapi/path-format";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { z } from "./zod-openapi";
|
|
1
|
+
export { z, withOpenApi } from "./zod-openapi";
|
|
2
2
|
export { HTTP_METHODS, HTTP_METHODS_WITH_REQUEST_BODY, isHttpMethod } from "./http-method";
|
|
3
3
|
export { defineAuthScheme, publicAccess, requireAuth, isPublicOperationAuth, ROUTE_GUARD_TYPES, schemaVaultsAccessTokenBearerScheme, schemaVaultsAccessTokenCookieScheme, schemaVaultsRefreshTokenCookieScheme, oidcClientSecretBasicScheme, oidcClientSecretPostScheme, apiKeyHeaderScheme, } from "./auth-scheme";
|
|
4
|
-
export { defineOperation, createOperationDefiner, defineOperationGroup, defaultOperationId, assertUniqueOperations, } from "./operation";
|
|
4
|
+
export { defineOperation, createOperationDefiner, defineOperationGroup, defaultOperationId, assertUniqueOperations, operationHttpMethods, } from "./operation";
|
|
5
5
|
export { buildOpenApiDocument, collectAuthSchemes, toRouteConfig, toSecuritySchemeComponent, } from "./openapi/build-openapi-document";
|
|
6
6
|
export { SCHEMAVAULTS_AUTH_EXTENSION, SCHEMAVAULTS_SCHEME_TITLE_EXTENSION, SCHEMAVAULTS_SCHEME_CHALLENGE_EXTENSION, toSchemaVaultsAuthExtension, isSchemaVaultsAuthExtension, } from "./openapi/extensions";
|
|
7
7
|
export { extractPathParameterNames, openApiPathToHonoPath, honoPathToOpenApiPath, isOpenApiPath, } from "./openapi/path-format";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAE,8BAA8B,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG3F,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,mCAAmC,EACnC,mCAAmC,EACnC,oCAAoC,EACpC,2BAA2B,EAC3B,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,eAAe,CAAC;AAWvB,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAsBrB,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,EACb,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC;AAI1C,OAAO,EACL,2BAA2B,EAC3B,mCAAmC,EACnC,uCAAuC,EACvC,2BAA2B,EAC3B,2BAA2B,GAC5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,EACrB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,cAAc,WAAW,CAAC;AAI1B,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/operation.d.ts
CHANGED
|
@@ -3,13 +3,30 @@ import type { OrganizationMembershipRoleType } from "@schemavaults/auth-common/o
|
|
|
3
3
|
import { type HttpMethod } from "./http-method";
|
|
4
4
|
import type { OperationAuth, PublicOperationAuth } from "./auth-scheme";
|
|
5
5
|
export type RequestBodyContentType = "application/json" | "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain" | (string & {});
|
|
6
|
-
export interface RequestBodyDefinition<TSchema extends ZodType = ZodType> {
|
|
6
|
+
export interface RequestBodyDefinition<TSchema extends ZodType = ZodType, TDocumentOnly extends boolean = boolean> {
|
|
7
7
|
/** Media type the body is parsed as (default `application/json`). */
|
|
8
8
|
readonly contentType?: RequestBodyContentType;
|
|
9
9
|
readonly schema: TSchema;
|
|
10
10
|
readonly description?: string;
|
|
11
11
|
/** Default true. */
|
|
12
12
|
readonly required?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Also parse the body as `contentType` when the request labels it
|
|
15
|
+
* `text/plain` or carries no Content-Type header at all. Browsers send
|
|
16
|
+
* `text/plain;charset=UTF-8` for `fetch(url, { body: JSON.stringify(x) })`
|
|
17
|
+
* without an explicit header, so JSON APIs with such callers need this.
|
|
18
|
+
* Default false (415 on a media type mismatch).
|
|
19
|
+
*/
|
|
20
|
+
readonly lenientContentType?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Describe the body in the OpenAPI document but leave the request
|
|
23
|
+
* untouched: the runtime neither reads nor validates it and `ctx.body`
|
|
24
|
+
* is `undefined`, so the handler reads `ctx.request` itself. For
|
|
25
|
+
* endpoints whose error format is mandated by a protocol (the OAuth 2.0
|
|
26
|
+
* token endpoint's `{ error, error_description }`, ...) and endpoints
|
|
27
|
+
* with bespoke parsing that must keep its exact responses.
|
|
28
|
+
*/
|
|
29
|
+
readonly documentOnly?: TDocumentOnly;
|
|
13
30
|
}
|
|
14
31
|
export interface ResponseDefinition<TSchema extends ZodType | undefined = ZodType | undefined> {
|
|
15
32
|
readonly description: string;
|
|
@@ -31,7 +48,9 @@ export interface OperationRequestDefinition<TParams extends ZodObject | undefine
|
|
|
31
48
|
readonly body?: TBody;
|
|
32
49
|
}
|
|
33
50
|
export type InferParsed<T> = T extends ZodType ? z.output<T> : Readonly<Record<string, never>>;
|
|
34
|
-
export type InferBody<T> = T extends
|
|
51
|
+
export type InferBody<T> = T extends {
|
|
52
|
+
readonly documentOnly: true;
|
|
53
|
+
} ? undefined : T extends RequestBodyDefinition<infer S> ? S extends ZodType ? z.output<S> : undefined : undefined;
|
|
35
54
|
export type ResponseStatusOf<TResponses extends ResponsesDefinition> = Extract<keyof TResponses, number>;
|
|
36
55
|
export type ResponseBodyOf<TResponses extends ResponsesDefinition, S extends keyof TResponses> = TResponses[S] extends {
|
|
37
56
|
readonly schema: infer TSchema;
|
|
@@ -165,4 +184,10 @@ export interface OperationGroup {
|
|
|
165
184
|
* `createOperationsApp` like any other operation list.
|
|
166
185
|
*/
|
|
167
186
|
export declare function defineOperationGroup(group: OperationGroup): AnyOperationDefinition[];
|
|
187
|
+
/**
|
|
188
|
+
* Distinct HTTP methods declared by the given operations, in declaration
|
|
189
|
+
* order. Hands `toNextRouteHandlers()` exactly the methods a route file
|
|
190
|
+
* should export, so Next.js answers 405 for the others itself.
|
|
191
|
+
*/
|
|
192
|
+
export declare function operationHttpMethods(operations: readonly AnyOperationDefinition[]): HttpMethod[];
|
|
168
193
|
export declare function assertUniqueOperations(operations: readonly AnyOperationDefinition[]): void;
|
package/dist/operation.js
CHANGED
|
@@ -93,6 +93,14 @@ export function defineOperationGroup(group) {
|
|
|
93
93
|
return Object.freeze({ ...operation, path, tags });
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Distinct HTTP methods declared by the given operations, in declaration
|
|
98
|
+
* order. Hands `toNextRouteHandlers()` exactly the methods a route file
|
|
99
|
+
* should export, so Next.js answers 405 for the others itself.
|
|
100
|
+
*/
|
|
101
|
+
export function operationHttpMethods(operations) {
|
|
102
|
+
return Array.from(new Set(operations.map((operation) => operation.method)));
|
|
103
|
+
}
|
|
96
104
|
export function assertUniqueOperations(operations) {
|
|
97
105
|
const ids = new Set();
|
|
98
106
|
const routes = new Set();
|
package/dist/operation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation.js","sourceRoot":"","sources":["../src/operation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,8BAA8B,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE9F,OAAO,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"operation.js","sourceRoot":"","sources":["../src/operation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Hono } from "hono";
|
|
2
|
+
import type { AnyOperationDefinition } from "../operation";
|
|
3
|
+
import { type CreateOperationsAppOptions, type OpenApiDocumentRouteOptions } from "./create-operations-app";
|
|
4
|
+
import { type AuthResolvers } from "./resolve-auth";
|
|
5
|
+
export interface CreateOperationsAppFactoryOptions<TContext = unknown, TUser = unknown> {
|
|
6
|
+
/**
|
|
7
|
+
* Every operation the API exposes: the catalogue `buildOpenApiDocument()`
|
|
8
|
+
* is given. Apps built by the factory may only serve operations from it,
|
|
9
|
+
* so a route file cannot mount an operation the document does not list.
|
|
10
|
+
*/
|
|
11
|
+
readonly operations: readonly AnyOperationDefinition[];
|
|
12
|
+
/** Credential resolvers keyed by auth scheme name, shared by every app. */
|
|
13
|
+
readonly authResolvers?: AuthResolvers<TUser, TContext>;
|
|
14
|
+
/** Builds the per-request context handed to handlers as `ctx.context`. */
|
|
15
|
+
readonly context?: CreateOperationsAppOptions<TContext, TUser>["context"];
|
|
16
|
+
/** Releases the per-request context once the response was produced. */
|
|
17
|
+
readonly disposeContext?: CreateOperationsAppOptions<TContext, TUser>["disposeContext"];
|
|
18
|
+
/** Called for unexpected (non-OperationError) failures before the 500 is sent. */
|
|
19
|
+
readonly onError?: CreateOperationsAppOptions<TContext, TUser>["onError"];
|
|
20
|
+
}
|
|
21
|
+
/** Per-app options a factory caller may still set. */
|
|
22
|
+
export type OperationsAppFactoryAppOptions<TContext = unknown, TUser = unknown> = Pick<CreateOperationsAppOptions<TContext, TUser>, "basePath" | "configure" | "openapi">;
|
|
23
|
+
export interface OperationsAppFactory<TContext = unknown, TUser = unknown> {
|
|
24
|
+
/** The full catalogue the factory was created with. */
|
|
25
|
+
readonly operations: readonly AnyOperationDefinition[];
|
|
26
|
+
/** The shared resolvers every app built by the factory authenticates with. */
|
|
27
|
+
readonly authResolvers: AuthResolvers<TUser, TContext>;
|
|
28
|
+
/**
|
|
29
|
+
* Builds a Hono app serving the given operations (default: the whole
|
|
30
|
+
* catalogue) with the shared resolvers / context / error reporting.
|
|
31
|
+
* Throws when an operation is not part of the catalogue.
|
|
32
|
+
*/
|
|
33
|
+
app(operations?: readonly AnyOperationDefinition[], options?: OperationsAppFactoryAppOptions<TContext, TUser>): Hono;
|
|
34
|
+
/** Builds a Hono app that only serves the OpenAPI document. */
|
|
35
|
+
openApiDocumentApp(openapi: OpenApiDocumentRouteOptions, options?: Omit<OperationsAppFactoryAppOptions<TContext, TUser>, "openapi">): Hono;
|
|
36
|
+
/** Throws unless every given operation is part of the catalogue. */
|
|
37
|
+
assertRegistered(operations: readonly AnyOperationDefinition[]): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Binds the shared runtime configuration (auth resolvers, per-request
|
|
41
|
+
* context, error reporting) and the full operation catalogue once, so many
|
|
42
|
+
* small apps can be built from it: one per Next.js `route.ts` / Vercel
|
|
43
|
+
* function file, each serving only the operation(s) at its path, while a
|
|
44
|
+
* single OpenAPI document generated from the same catalogue still describes
|
|
45
|
+
* all of them.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* // src/lib/api/app.ts
|
|
49
|
+
* export const api = createOperationsAppFactory<Ctx, UserData>({
|
|
50
|
+
* operations, // the same list buildOpenApiDocument() is given
|
|
51
|
+
* authResolvers,
|
|
52
|
+
* context: () => ({ ... }),
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // app/api/health/route.ts
|
|
56
|
+
* export const { GET } = toNextRouteHandlers(api.app([health]), operationHttpMethods([health]));
|
|
57
|
+
*
|
|
58
|
+
* // app/api/openapi.json/route.ts
|
|
59
|
+
* export const { GET } = toNextRouteHandlers(
|
|
60
|
+
* api.openApiDocumentApp({ path: "/api/openapi.json", document }),
|
|
61
|
+
* ["get"],
|
|
62
|
+
* );
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* Every app validates against the whole catalogue up front: duplicate
|
|
66
|
+
* operations or a scheme without a resolver fail at module load of the
|
|
67
|
+
* first route rather than when the affected route is first hit.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createOperationsAppFactory<TContext = unknown, TUser = unknown>(options: CreateOperationsAppFactoryOptions<TContext, TUser>): OperationsAppFactory<TContext, TUser>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { assertUniqueOperations } from "../operation";
|
|
2
|
+
import { createOperationsApp, } from "./create-operations-app";
|
|
3
|
+
import { assertResolversForOperations } from "./resolve-auth";
|
|
4
|
+
/**
|
|
5
|
+
* Binds the shared runtime configuration (auth resolvers, per-request
|
|
6
|
+
* context, error reporting) and the full operation catalogue once, so many
|
|
7
|
+
* small apps can be built from it: one per Next.js `route.ts` / Vercel
|
|
8
|
+
* function file, each serving only the operation(s) at its path, while a
|
|
9
|
+
* single OpenAPI document generated from the same catalogue still describes
|
|
10
|
+
* all of them.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* // src/lib/api/app.ts
|
|
14
|
+
* export const api = createOperationsAppFactory<Ctx, UserData>({
|
|
15
|
+
* operations, // the same list buildOpenApiDocument() is given
|
|
16
|
+
* authResolvers,
|
|
17
|
+
* context: () => ({ ... }),
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* // app/api/health/route.ts
|
|
21
|
+
* export const { GET } = toNextRouteHandlers(api.app([health]), operationHttpMethods([health]));
|
|
22
|
+
*
|
|
23
|
+
* // app/api/openapi.json/route.ts
|
|
24
|
+
* export const { GET } = toNextRouteHandlers(
|
|
25
|
+
* api.openApiDocumentApp({ path: "/api/openapi.json", document }),
|
|
26
|
+
* ["get"],
|
|
27
|
+
* );
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Every app validates against the whole catalogue up front: duplicate
|
|
31
|
+
* operations or a scheme without a resolver fail at module load of the
|
|
32
|
+
* first route rather than when the affected route is first hit.
|
|
33
|
+
*/
|
|
34
|
+
export function createOperationsAppFactory(options) {
|
|
35
|
+
const catalogue = Object.freeze([...options.operations]);
|
|
36
|
+
assertUniqueOperations(catalogue);
|
|
37
|
+
const resolvers = options.authResolvers ?? {};
|
|
38
|
+
assertResolversForOperations(catalogue, resolvers);
|
|
39
|
+
const registered = new Set(catalogue);
|
|
40
|
+
const assertRegistered = (operations) => {
|
|
41
|
+
for (const operation of operations) {
|
|
42
|
+
if (!registered.has(operation)) {
|
|
43
|
+
throw new TypeError(`${operation.method.toUpperCase()} ${operation.path} (${operation.operationId}) is not part of the operations catalogue this factory was created with, so it would be served without appearing in the OpenAPI document`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const app = (operations = catalogue, appOptions = {}) => {
|
|
48
|
+
assertRegistered(operations);
|
|
49
|
+
return createOperationsApp({
|
|
50
|
+
operations,
|
|
51
|
+
authResolvers: resolvers,
|
|
52
|
+
context: options.context,
|
|
53
|
+
disposeContext: options.disposeContext,
|
|
54
|
+
onError: options.onError,
|
|
55
|
+
basePath: appOptions.basePath,
|
|
56
|
+
configure: appOptions.configure,
|
|
57
|
+
openapi: appOptions.openapi,
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
operations: catalogue,
|
|
62
|
+
authResolvers: resolvers,
|
|
63
|
+
app,
|
|
64
|
+
openApiDocumentApp: (openapi, appOptions = {}) => app([], { ...appOptions, openapi }),
|
|
65
|
+
assertRegistered,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=create-operations-app-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-operations-app-factory.js","sourceRoot":"","sources":["../../src/runtime/create-operations-app-factory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EACL,mBAAmB,GAGpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,4BAA4B,EAAsB,MAAM,gBAAgB,CAAC;AAgDlF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAA2D;IAE3D,MAAM,SAAS,GAAsC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IAC5F,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,SAAS,GAAmC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;IAC9E,4BAA4B,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAyB,SAAS,CAAC,CAAC;IAE9D,MAAM,gBAAgB,GAAG,CAAC,UAA6C,EAAQ,EAAE;QAC/E,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC/B,MAAM,IAAI,SAAS,CACjB,GAAG,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,WAAW,0IAA0I,CACxN,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,GAAG,GAAiD,CACxD,UAAU,GAAG,SAAS,EACtB,UAAU,GAAG,EAAE,EACf,EAAE;QACF,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAC7B,OAAO,mBAAmB,CAAkB;YAC1C,UAAU;YACV,aAAa,EAAE,SAAS;YACxB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,QAAQ,EAAE,UAAU,CAAC,QAAQ;YAC7B,SAAS,EAAE,UAAU,CAAC,SAAS;YAC/B,OAAO,EAAE,UAAU,CAAC,OAAO;SAC5B,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,OAAO;QACL,UAAU,EAAE,SAAS;QACrB,aAAa,EAAE,SAAS;QACxB,GAAG;QACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,UAAU,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,UAAU,EAAE,OAAO,EAAE,CAAC;QACrF,gBAAgB;KACjB,CAAC;AACJ,CAAC"}
|
|
@@ -12,23 +12,41 @@ export interface OpenApiDocumentRouteOptions {
|
|
|
12
12
|
*/
|
|
13
13
|
readonly document: OpenAPIObject | ((c: Context) => OpenAPIObject | Promise<OpenAPIObject>);
|
|
14
14
|
}
|
|
15
|
+
/** What `onError` learns about the failed request besides the error itself. */
|
|
16
|
+
export interface OperationFailureInfo<TContext = unknown> {
|
|
17
|
+
readonly operation: AnyOperationDefinition;
|
|
18
|
+
/** The per-request context, when it had been built before the failure. */
|
|
19
|
+
readonly context: TContext | undefined;
|
|
20
|
+
}
|
|
15
21
|
export interface CreateOperationsAppOptions<TContext = unknown, TUser = unknown> {
|
|
16
22
|
readonly operations: readonly AnyOperationDefinition[];
|
|
17
23
|
/**
|
|
18
24
|
* Prefix stripped by the deployment before routing (e.g. `/api` when the
|
|
19
25
|
* app is mounted from `app/api/[[...route]]/route.ts`). Operation paths
|
|
20
26
|
* stay absolute in the OpenAPI document; leave unset to route on them
|
|
21
|
-
* verbatim.
|
|
27
|
+
* verbatim (Next.js route handlers and Vercel functions receive the full
|
|
28
|
+
* request URL, so they never need it).
|
|
22
29
|
*/
|
|
23
30
|
readonly basePath?: string;
|
|
24
31
|
/** Credential resolvers keyed by auth scheme name. */
|
|
25
|
-
readonly authResolvers?: AuthResolvers<TUser>;
|
|
26
|
-
/**
|
|
32
|
+
readonly authResolvers?: AuthResolvers<TUser, TContext>;
|
|
33
|
+
/**
|
|
34
|
+
* Builds the per-request context handed to auth resolvers and to handlers
|
|
35
|
+
* as `ctx.context`. Built before credentials are resolved, so it should
|
|
36
|
+
* be cheap (open expensive resources lazily) and is released through
|
|
37
|
+
* `disposeContext` once the response has been produced.
|
|
38
|
+
*/
|
|
27
39
|
readonly context?: (c: Context) => Promise<TContext> | TContext;
|
|
40
|
+
/**
|
|
41
|
+
* Releases the per-request context (database handles, cache connections)
|
|
42
|
+
* after the handler returned or failed. Errors thrown here are reported
|
|
43
|
+
* to `onError` (or the console) and never change the response.
|
|
44
|
+
*/
|
|
45
|
+
readonly disposeContext?: (context: TContext, c: Context) => void | Promise<void>;
|
|
28
46
|
/** Serve the OpenAPI document from the app; omit to not expose it. */
|
|
29
47
|
readonly openapi?: OpenApiDocumentRouteOptions;
|
|
30
48
|
/** Called for unexpected (non-OperationError) failures before the 500 is sent. */
|
|
31
|
-
readonly onError?: (error: unknown, c: Context) => void | Promise<void>;
|
|
49
|
+
readonly onError?: (error: unknown, c: Context, info: OperationFailureInfo<TContext>) => void | Promise<void>;
|
|
32
50
|
/** Extra middleware / routes registered before the operations (CORS, logging, ...). */
|
|
33
51
|
readonly configure?: (app: Hono) => void;
|
|
34
52
|
}
|
|
@@ -52,11 +52,29 @@ export function createOperationsApp(options) {
|
|
|
52
52
|
return c.json(resolved);
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
+
const report = async (error, c, info) => {
|
|
56
|
+
if (options.onError) {
|
|
57
|
+
try {
|
|
58
|
+
await options.onError(error, c, info);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// never let error reporting mask the response
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.error(`[openapi-operations] ${info.operation.method.toUpperCase()} ${info.operation.path} failed:`, error);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
55
68
|
for (const operation of options.operations) {
|
|
56
69
|
app.on(operation.method.toUpperCase(), openApiPathToHonoPath(operation.path), async (c) => {
|
|
70
|
+
let context = undefined;
|
|
71
|
+
let built = false;
|
|
57
72
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
if (options.context) {
|
|
74
|
+
context = await options.context(c);
|
|
75
|
+
built = true;
|
|
76
|
+
}
|
|
77
|
+
const auth = await resolveAuth(c, operation.auth, resolvers, context);
|
|
60
78
|
const validated = await validateRequest(c, operation);
|
|
61
79
|
const ctx = buildHandlerContext(c, operation, validated, auth, context);
|
|
62
80
|
const result = await operation.handler(ctx);
|
|
@@ -68,23 +86,23 @@ export function createOperationsApp(options) {
|
|
|
68
86
|
catch (error) {
|
|
69
87
|
if (error instanceof OperationError)
|
|
70
88
|
return error.toResponse();
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
await options.onError(error, c);
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
// never let error reporting mask the response
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
console.error(`[openapi-operations] ${operation.method.toUpperCase()} ${operation.path} failed:`, error);
|
|
81
|
-
}
|
|
89
|
+
await report(error, c, { operation, context });
|
|
82
90
|
return jsonResponse(500, {
|
|
83
91
|
success: false,
|
|
84
92
|
error: OPERATION_ERROR_CODES.internal,
|
|
85
93
|
message: "Internal Server Error",
|
|
86
94
|
});
|
|
87
95
|
}
|
|
96
|
+
finally {
|
|
97
|
+
if (built && options.disposeContext) {
|
|
98
|
+
try {
|
|
99
|
+
await options.disposeContext(context, c);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
await report(error, c, { operation, context });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
88
106
|
});
|
|
89
107
|
}
|
|
90
108
|
return root;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-operations-app.js","sourceRoot":"","sources":["../../src/runtime/create-operations-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;AAG1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAsB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-operations-app.js","sourceRoot":"","sources":["../../src/runtime/create-operations-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;AAG1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,4BAA4B,EAAE,WAAW,EAAsB,MAAM,gBAAgB,CAAC;AAqE/F,SAAS,mBAAmB,CAC1B,CAAU,EACV,SAAiC,EACjC,SAA+E,EAC/E,IAAa,EACb,OAAgB;IAEhB,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/E,MAAM,cAAc,GAAG,CAAC,MAAc,EAAQ,EAAE;QAC9C,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,SAAS,CACjB,GAAG,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,SAAS,CAAC,IAAI,qCAAqC,MAAM,EAAE,CACjG,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;IACF,OAAO;QACL,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,KAAK,EAAE,SAAS,CAAC,KAAK;QACtB,OAAO,EAAE,SAAS,CAAC,OAAO;QAC1B,IAAI,EAAE,SAAS,CAAC,IAAI;QACpB,IAAI;QACJ,OAAO;QACP,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG;QAClB,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;QACvB,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI;YACrB,cAAc,CAAC,MAAM,CAAC,CAAC;YACvB,OAAO,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACnD,CAAC;QACD,KAAK,CAAC,MAAM,EAAE,IAAI;YAChB,cAAc,CAAC,MAAM,CAAC,CAAC;YACvB,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAChE,CAAC;QACD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG;YAC7B,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QACzE,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAoD;IAEpD,sBAAsB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAmC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;IAC9E,4BAA4B,CAAC,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAE5D,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IACxB,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtE,OAAO,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC;IAEzB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,eAAe,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;QAC7D,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YACxB,MAAM,QAAQ,GAAG,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;YAC/E,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,EAClB,KAAc,EACd,CAAU,EACV,IAAoC,EACrB,EAAE;QACjB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,8CAA8C;YAChD,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CACX,wBAAwB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,UAAU,EAC5F,KAAK,CACN,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QAC3C,GAAG,CAAC,EAAE,CACJ,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,EAC9B,qBAAqB,CAAC,SAAS,CAAC,IAAI,CAAC,EACrC,KAAK,EAAE,CAAU,EAAqB,EAAE;YACtC,IAAI,OAAO,GAAyB,SAAS,CAAC;YAC9C,IAAI,KAAK,GAAG,KAAK,CAAC;YAClB,IAAI,CAAC;gBACH,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;oBACpB,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;oBACnC,KAAK,GAAG,IAAI,CAAC;gBACf,CAAC;gBACD,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,OAAmB,CAAC,CAAC;gBAClF,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;gBACtD,MAAM,GAAG,GAAG,mBAAmB,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;gBACxE,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAC5C,IAAI,CAAC,CAAC,MAAM,YAAY,QAAQ,CAAC,EAAE,CAAC;oBAClC,MAAM,IAAI,SAAS,CACjB,GAAG,SAAS,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,SAAS,CAAC,IAAI,4DAA4D,CAChH,CAAC;gBACJ,CAAC;gBACD,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,KAAc,EAAE,CAAC;gBACxB,IAAI,KAAK,YAAY,cAAc;oBAAE,OAAO,KAAK,CAAC,UAAU,EAAE,CAAC;gBAC/D,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC/C,OAAO,YAAY,CAAC,GAAG,EAAE;oBACvB,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,qBAAqB,CAAC,QAAQ;oBACrC,OAAO,EAAE,uBAAuB;iBACjC,CAAC,CAAC;YACL,CAAC;oBAAS,CAAC;gBACT,IAAI,KAAK,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;oBACpC,IAAI,CAAC;wBACH,MAAM,OAAO,CAAC,cAAc,CAAC,OAAmB,EAAE,CAAC,CAAC,CAAC;oBACvD,CAAC;oBAAC,OAAO,KAAc,EAAE,CAAC;wBACxB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;oBACjD,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { createOperationsApp } from "./create-operations-app";
|
|
2
|
-
export type { CreateOperationsAppOptions, OpenApiDocumentRouteOptions, } from "./create-operations-app";
|
|
2
|
+
export type { CreateOperationsAppOptions, OpenApiDocumentRouteOptions, OperationFailureInfo, } from "./create-operations-app";
|
|
3
|
+
export { createOperationsAppFactory } from "./create-operations-app-factory";
|
|
4
|
+
export type { CreateOperationsAppFactoryOptions, OperationsAppFactory, OperationsAppFactoryAppOptions, } from "./create-operations-app-factory";
|
|
3
5
|
export { OperationError, OPERATION_ERROR_CODES, jsonResponse } from "./errors";
|
|
4
6
|
export type { OperationErrorBody, OperationValidationIssue } from "./errors";
|
|
5
7
|
export { resolveAuth, grantedScopes, missingScopes } from "./resolve-auth";
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createOperationsApp } from "./create-operations-app";
|
|
2
|
+
export { createOperationsAppFactory } from "./create-operations-app-factory";
|
|
2
3
|
export { OperationError, OPERATION_ERROR_CODES, jsonResponse } from "./errors";
|
|
3
4
|
export { resolveAuth, grantedScopes, missingScopes } from "./resolve-auth";
|
|
4
5
|
export { validateRequest, queryToObject, headersToObject, readRequestBody } from "./validate-request";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAM9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAM7E,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE/E,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE3E,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -5,11 +5,13 @@ import type { AnyOperationDefinition, AuthPrincipal } from "../operation";
|
|
|
5
5
|
* Verifies the credential transported by one auth scheme. Returns null when
|
|
6
6
|
* the request carries no credential for the scheme (so the next accepted
|
|
7
7
|
* scheme is tried); throws {@link OperationError} to reject outright (e.g.
|
|
8
|
-
* a credential that IS present but invalid).
|
|
8
|
+
* a credential that IS present but invalid). Receives the per-request
|
|
9
|
+
* context built by `createOperationsApp({ context })` so it can share the
|
|
10
|
+
* request's database handle / caches with the handler.
|
|
9
11
|
*/
|
|
10
|
-
export type AuthResolver<TUser = unknown> = (c: Context, scheme: AuthSchemeDefinition) => Promise<AuthPrincipal<TUser> | null> | AuthPrincipal<TUser> | null;
|
|
11
|
-
export type AuthResolvers<TUser = unknown> = Readonly<Record<string, AuthResolver<TUser>>>;
|
|
12
|
-
export declare function assertResolversForOperations(operations: readonly AnyOperationDefinition[], resolvers: AuthResolvers): void;
|
|
12
|
+
export type AuthResolver<TUser = unknown, TContext = unknown> = (c: Context, scheme: AuthSchemeDefinition, context: TContext) => Promise<AuthPrincipal<TUser> | null> | AuthPrincipal<TUser> | null;
|
|
13
|
+
export type AuthResolvers<TUser = unknown, TContext = unknown> = Readonly<Record<string, AuthResolver<TUser, TContext>>>;
|
|
14
|
+
export declare function assertResolversForOperations(operations: readonly AnyOperationDefinition[], resolvers: AuthResolvers<any, any>): void;
|
|
13
15
|
export declare function grantedScopes(principal: AuthPrincipal): string[];
|
|
14
16
|
export declare function missingScopes(principal: AuthPrincipal, required: readonly string[]): string[];
|
|
15
17
|
/**
|
|
@@ -18,4 +20,4 @@ export declare function missingScopes(principal: AuthPrincipal, required: readon
|
|
|
18
20
|
* required scopes, and organization membership. Returns the principal, or
|
|
19
21
|
* null for public operations.
|
|
20
22
|
*/
|
|
21
|
-
export declare function resolveAuth<TUser>(c: Context, auth: OperationAuth, resolvers: AuthResolvers<TUser
|
|
23
|
+
export declare function resolveAuth<TUser, TContext = unknown>(c: Context, auth: OperationAuth, resolvers: AuthResolvers<TUser, TContext>, context?: TContext): Promise<AuthPrincipal<TUser> | null>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { OPERATION_ERROR_CODES, OperationError } from "./errors";
|
|
2
|
-
export function assertResolversForOperations(operations,
|
|
2
|
+
export function assertResolversForOperations(operations,
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
resolvers) {
|
|
3
5
|
for (const operation of operations) {
|
|
4
6
|
if (operation.auth.type !== "required")
|
|
5
7
|
continue;
|
|
@@ -31,7 +33,7 @@ export function missingScopes(principal, required) {
|
|
|
31
33
|
* required scopes, and organization membership. Returns the principal, or
|
|
32
34
|
* null for public operations.
|
|
33
35
|
*/
|
|
34
|
-
export async function resolveAuth(c, auth, resolvers) {
|
|
36
|
+
export async function resolveAuth(c, auth, resolvers, context = undefined) {
|
|
35
37
|
if (auth.type === "public")
|
|
36
38
|
return null;
|
|
37
39
|
let principal = null;
|
|
@@ -39,7 +41,7 @@ export async function resolveAuth(c, auth, resolvers) {
|
|
|
39
41
|
const resolver = resolvers[scheme.name];
|
|
40
42
|
if (!resolver)
|
|
41
43
|
continue;
|
|
42
|
-
principal = await resolver(c, scheme);
|
|
44
|
+
principal = await resolver(c, scheme, context);
|
|
43
45
|
if (principal)
|
|
44
46
|
break;
|
|
45
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-auth.js","sourceRoot":"","sources":["../../src/runtime/resolve-auth.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"resolve-auth.js","sourceRoot":"","sources":["../../src/runtime/resolve-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,6 +50,10 @@ export async function readRequestBody(c, definition) {
|
|
|
50
50
|
const actual = mediaTypeOf(c);
|
|
51
51
|
const contentLength = c.req.header("content-length");
|
|
52
52
|
const looksEmpty = actual === null && (contentLength === undefined || contentLength === "0");
|
|
53
|
+
const lenient = definition.lenientContentType === true;
|
|
54
|
+
// A body labelled text/plain (or not labelled at all) is re-parsed as the
|
|
55
|
+
// expected media type when the operation opted into leniency.
|
|
56
|
+
const relabelled = lenient && (actual === null || actual === "text/plain");
|
|
53
57
|
if (looksEmpty) {
|
|
54
58
|
if (!required)
|
|
55
59
|
return undefined;
|
|
@@ -61,7 +65,9 @@ export async function readRequestBody(c, definition) {
|
|
|
61
65
|
],
|
|
62
66
|
});
|
|
63
67
|
}
|
|
64
|
-
if (actual !== expected &&
|
|
68
|
+
if (actual !== expected &&
|
|
69
|
+
!relabelled &&
|
|
70
|
+
!(expected === "text/plain" && actual?.startsWith("text/"))) {
|
|
65
71
|
throw new OperationError(415, {
|
|
66
72
|
error: OPERATION_ERROR_CODES.unsupportedMediaType,
|
|
67
73
|
message: `Expected a ${expected} request body but received ${actual ?? "none"}`,
|
|
@@ -70,15 +76,21 @@ export async function readRequestBody(c, definition) {
|
|
|
70
76
|
try {
|
|
71
77
|
switch (expected) {
|
|
72
78
|
case "application/json":
|
|
73
|
-
|
|
79
|
+
// c.req.json() trusts the declared media type; parse the raw text
|
|
80
|
+
// ourselves so relabelled text/plain bodies work too.
|
|
81
|
+
return JSON.parse(await c.req.text());
|
|
74
82
|
case "application/x-www-form-urlencoded":
|
|
83
|
+
if (relabelled) {
|
|
84
|
+
return Object.fromEntries(new URLSearchParams(await c.req.text()));
|
|
85
|
+
}
|
|
86
|
+
return await c.req.parseBody({ all: true });
|
|
75
87
|
case "multipart/form-data":
|
|
76
88
|
return await c.req.parseBody({ all: true });
|
|
77
89
|
case "text/plain":
|
|
78
90
|
return await c.req.text();
|
|
79
91
|
default:
|
|
80
92
|
if (expected.endsWith("+json"))
|
|
81
|
-
return await c.req.
|
|
93
|
+
return JSON.parse(await c.req.text());
|
|
82
94
|
return await c.req.text();
|
|
83
95
|
}
|
|
84
96
|
}
|
|
@@ -110,7 +122,7 @@ export async function validateRequest(c, operation) {
|
|
|
110
122
|
? (await parseWith(headers, headersToObject(c), "headers"))
|
|
111
123
|
: EMPTY;
|
|
112
124
|
let parsedBody = undefined;
|
|
113
|
-
if (body) {
|
|
125
|
+
if (body && body.documentOnly !== true) {
|
|
114
126
|
const raw = await readRequestBody(c, body);
|
|
115
127
|
if (raw !== undefined || (body.required ?? true)) {
|
|
116
128
|
parsedBody = await parseWith(body.schema, raw, "body");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-request.js","sourceRoot":"","sources":["../../src/runtime/validate-request.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"validate-request.js","sourceRoot":"","sources":["../../src/runtime/validate-request.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,qBAAqB,EACrB,cAAc,GAEf,MAAM,UAAU,CAAC;AAElB,KAAK,UAAU,SAAS,CACtB,MAAe,EACf,KAAc,EACd,QAA8C;IAE9C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAClD,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC;IACvC,MAAM,MAAM,GAA+B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7E,QAAQ;QACR,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAC5D,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,CAAC,CAAC;IACJ,MAAM,IAAI,cAAc,CAAC,GAAG,EAAE;QAC5B,KAAK,EAAE,qBAAqB,CAAC,UAAU;QACvC,OAAO,EAAE,mBAAmB,QAAQ,EAAE;QACtC,MAAM;KACP,CAAC,CAAC;AACL,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,aAAa,CAAC,CAAU;IACtC,MAAM,MAAM,GAAsC,EAAE,CAAC;IACrD,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAC5D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;YACxB,IAAI,OAAO,MAAM,KAAK,QAAQ;gBAAE,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;QACvD,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,CAAU;IACxC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC;IACpC,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,CAAU;IAC7B,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAC5C,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC5C,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,OAAO,IAAI,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,CAAU,EACV,UAAiC;IAEjC,MAAM,QAAQ,GAAG,CAAC,UAAU,CAAC,WAAW,IAAI,kBAAkB,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9E,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,IAAI,IAAI,CAAC;IAC7C,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;IAC9B,MAAM,aAAa,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACrD,MAAM,UAAU,GACd,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,IAAI,aAAa,KAAK,GAAG,CAAC,CAAC;IAC5E,MAAM,OAAO,GAAG,UAAU,CAAC,kBAAkB,KAAK,IAAI,CAAC;IACvD,0EAA0E;IAC1E,8DAA8D;IAC9D,MAAM,UAAU,GAAG,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,YAAY,CAAC,CAAC;IAE3E,IAAI,UAAU,EAAE,CAAC;QACf,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAChC,MAAM,IAAI,cAAc,CAAC,GAAG,EAAE;YAC5B,KAAK,EAAE,qBAAqB,CAAC,UAAU;YACvC,OAAO,EAAE,KAAK,QAAQ,2BAA2B;YACjD,MAAM,EAAE;gBACN,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,0BAA0B,EAAE,IAAI,EAAE,UAAU,EAAE;aACtF;SACF,CAAC,CAAC;IACL,CAAC;IAED,IACE,MAAM,KAAK,QAAQ;QACnB,CAAC,UAAU;QACX,CAAC,CAAC,QAAQ,KAAK,YAAY,IAAI,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,EAC3D,CAAC;QACD,MAAM,IAAI,cAAc,CAAC,GAAG,EAAE;YAC5B,KAAK,EAAE,qBAAqB,CAAC,oBAAoB;YACjD,OAAO,EAAE,cAAc,QAAQ,8BAA8B,MAAM,IAAI,MAAM,EAAE;SAChF,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC;QACH,QAAQ,QAAQ,EAAE,CAAC;YACjB,KAAK,kBAAkB;gBACrB,kEAAkE;gBAClE,sDAAsD;gBACtD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YACxC,KAAK,mCAAmC;gBACtC,IAAI,UAAU,EAAE,CAAC;oBACf,OAAO,MAAM,CAAC,WAAW,CAAC,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBACrE,CAAC;gBACD,OAAO,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9C,KAAK,qBAAqB;gBACxB,OAAO,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9C,KAAK,YAAY;gBACf,OAAO,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YAC5B;gBACE,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;oBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;gBACtE,OAAO,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,CAAC;IACH,CAAC;IAAC,OAAO,CAAU,EAAE,CAAC;QACpB,MAAM,IAAI,cAAc,CAAC,GAAG,EAAE;YAC5B,KAAK,EAAE,qBAAqB,CAAC,UAAU;YACvC,OAAO,EAAE,aAAa,QAAQ,eAAe;YAC7C,MAAM,EAAE;gBACN;oBACE,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,EAAE;oBACR,OAAO,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB;oBAChE,IAAI,EAAE,WAAW;iBAClB;aACF;SACF,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AASD,MAAM,KAAK,GAAoC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAEjE,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,CAAU,EACV,SAAiC;IAEjC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC;IAC3D,MAAM,YAAY,GAAG,MAAM;QACzB,CAAC,CAAE,CAAC,MAAM,SAAS,CAAC,MAAmB,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,QAAQ,CAAC,CAA6B;QAC9F,CAAC,CAAC,KAAK,CAAC;IACV,MAAM,WAAW,GAAG,KAAK;QACvB,CAAC,CAAE,CAAC,MAAM,SAAS,CAAC,KAAkB,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAA6B;QAC/F,CAAC,CAAC,KAAK,CAAC;IACV,MAAM,aAAa,GAAG,OAAO;QAC3B,CAAC,CAAE,CAAC,MAAM,SAAS,CAAC,OAAoB,EAAE,eAAe,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAA6B;QACrG,CAAC,CAAC,KAAK,CAAC;IACV,IAAI,UAAU,GAAY,SAAS,CAAC;IACpC,IAAI,IAAI,IAAI,IAAI,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAC3C,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,EAAE,CAAC;YACjD,UAAU,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAChG,CAAC"}
|
package/dist/zod-openapi.d.ts
CHANGED
|
@@ -8,6 +8,27 @@
|
|
|
8
8
|
* `z` from here (or call `extendZodWithOpenApi` themselves on the same zod
|
|
9
9
|
* instance) instead of importing zod directly.
|
|
10
10
|
*/
|
|
11
|
-
import { z } from "zod";
|
|
11
|
+
import { z, type ZodType } from "zod";
|
|
12
|
+
import { type ZodOpenAPIMetadata } from "@asteasolutions/zod-to-openapi";
|
|
12
13
|
export { z };
|
|
13
14
|
export type { ZodType, ZodObject } from "zod";
|
|
15
|
+
export type OpenApiSchemaMetadata = Partial<ZodOpenAPIMetadata>;
|
|
16
|
+
/**
|
|
17
|
+
* `schema.openapi(...)` for schemas built BEFORE this package was evaluated.
|
|
18
|
+
*
|
|
19
|
+
* zod v4 copies `ZodType.prototype` methods onto each schema instance when it
|
|
20
|
+
* is constructed, so a schema exported by another package (e.g.
|
|
21
|
+
* `@schemavaults/auth-common`) only has its own `.openapi` when that package
|
|
22
|
+
* happened to be evaluated after this one — which depends on the import order
|
|
23
|
+
* of whichever bundle loads first and throws `... .openapi is not a function`
|
|
24
|
+
* at module load otherwise. Invoking the prototype method explicitly sidesteps
|
|
25
|
+
* that: it returns a fresh copy of the schema carrying the metadata, exactly
|
|
26
|
+
* like a direct call would. Use it for every schema you did not build with the
|
|
27
|
+
* `z` exported from here.
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* export const App = withOpenApi(schemaVaultsAppDefinitionSchema, "App", { description: "..." });
|
|
31
|
+
* const params = z.object({ app_id: withOpenApi(appIdSchema, { example: "my-app" }) });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function withOpenApi<T extends ZodType>(schema: T, refIdOrMetadata: string | OpenApiSchemaMetadata, metadata?: OpenApiSchemaMetadata): T;
|
package/dist/zod-openapi.js
CHANGED
|
@@ -9,7 +9,31 @@
|
|
|
9
9
|
* instance) instead of importing zod directly.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from "zod";
|
|
12
|
-
import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
|
|
12
|
+
import { extendZodWithOpenApi, } from "@asteasolutions/zod-to-openapi";
|
|
13
13
|
extendZodWithOpenApi(z);
|
|
14
14
|
export { z };
|
|
15
|
+
/**
|
|
16
|
+
* `schema.openapi(...)` for schemas built BEFORE this package was evaluated.
|
|
17
|
+
*
|
|
18
|
+
* zod v4 copies `ZodType.prototype` methods onto each schema instance when it
|
|
19
|
+
* is constructed, so a schema exported by another package (e.g.
|
|
20
|
+
* `@schemavaults/auth-common`) only has its own `.openapi` when that package
|
|
21
|
+
* happened to be evaluated after this one — which depends on the import order
|
|
22
|
+
* of whichever bundle loads first and throws `... .openapi is not a function`
|
|
23
|
+
* at module load otherwise. Invoking the prototype method explicitly sidesteps
|
|
24
|
+
* that: it returns a fresh copy of the schema carrying the metadata, exactly
|
|
25
|
+
* like a direct call would. Use it for every schema you did not build with the
|
|
26
|
+
* `z` exported from here.
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* export const App = withOpenApi(schemaVaultsAppDefinitionSchema, "App", { description: "..." });
|
|
30
|
+
* const params = z.object({ app_id: withOpenApi(appIdSchema, { example: "my-app" }) });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function withOpenApi(schema, refIdOrMetadata, metadata) {
|
|
34
|
+
const openapi = z.ZodType.prototype.openapi;
|
|
35
|
+
return typeof refIdOrMetadata === "string"
|
|
36
|
+
? openapi.call(schema, refIdOrMetadata, metadata)
|
|
37
|
+
: openapi.call(schema, refIdOrMetadata);
|
|
38
|
+
}
|
|
15
39
|
//# sourceMappingURL=zod-openapi.js.map
|
package/dist/zod-openapi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod-openapi.js","sourceRoot":"","sources":["../src/zod-openapi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"zod-openapi.js","sourceRoot":"","sources":["../src/zod-openapi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,CAAC,EAAgB,MAAM,KAAK,CAAC;AACtC,OAAO,EACL,oBAAoB,GAErB,MAAM,gCAAgC,CAAC;AAExC,oBAAoB,CAAC,CAAC,CAAC,CAAC;AAExB,OAAO,EAAE,CAAC,EAAE,CAAC;AAOb;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,WAAW,CACzB,MAAS,EACT,eAA+C,EAC/C,QAAgC;IAEhC,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,OAAgE,CAAC;IACrG,OAAO,OAAO,eAAe,KAAK,QAAQ;QACxC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,QAAQ,CAAC;QACjD,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAC5C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schemavaults/openapi-operations",
|
|
3
3
|
"description": "Define OpenAPI-representable HTTP operations (zod schemas + auth schemes + handlers) and serve them as Hono apps on Vercel functions / Next.js route handlers",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
7
7
|
"repository": {
|