@schemavaults/openapi-operations 0.3.1 → 0.4.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 CHANGED
@@ -105,20 +105,69 @@ Request bodies take two extra flags:
105
105
 
106
106
  An `AuthSchemeDefinition` is a named OpenAPI security scheme plus docs metadata. Built-ins:
107
107
 
108
- | Export | Scheme |
109
- | --- | --- |
110
- | `schemaVaultsAccessTokenBearerScheme` | `Authorization: Bearer <access token>` |
111
- | `schemaVaultsAccessTokenCookieScheme(cookieName)` | first-party access token cookie |
112
- | `schemaVaultsRefreshTokenCookieScheme(cookieName)` | auth-server session cookie |
113
- | `oidcClientSecretBasicScheme` / `oidcClientSecretPostScheme` | OAuth 2.0 client authentication |
114
- | `apiKeyHeaderScheme(name, header)` | static API key |
115
- | `defineAuthScheme({...})` | anything else |
108
+ | Export | Scheme | Principal |
109
+ | --- | --- | --- |
110
+ | `schemaVaultsAccessTokenBearerScheme` | `Authorization: Bearer <access token>` | user |
111
+ | `schemaVaultsAccessTokenCookieScheme(cookieName)` | first-party access token cookie | user |
112
+ | `schemaVaultsRefreshTokenCookieScheme(cookieName)` | auth-server session cookie | user |
113
+ | `oidcClientSecretBasicScheme` / `oidcClientSecretPostScheme` | OAuth 2.0 client authentication | any |
114
+ | `apiKeyHeaderScheme(name, header)` | static API key | any |
115
+ | `defineAuthScheme({...})` | anything else | as declared |
116
116
 
117
117
  Schemes describe *how* credentials are transported; verification is a per-host
118
118
  `AuthResolver` (below). The OpenAPI document carries the standard `security`
119
119
  requirement per operation and an `x-schemavaults-auth` extension with the route guard,
120
120
  required scopes and organization role so docs can display them.
121
121
 
122
+ #### User-bearing schemes: non-nullable `ctx.auth.user`
123
+
124
+ `AuthPrincipal.user` is `TUser | null` because a principal may be a non-user credential
125
+ (client credentials, an API key). A scheme whose resolver *always* identifies a user
126
+ declares `principal: "user"` (the three SchemaVaults token schemes do), and when every
127
+ scheme an operation accepts is such a scheme, its handler gets `ctx.auth.user` typed as
128
+ `TUser`:
129
+
130
+ ```ts
131
+ const userSchemes = [schemaVaultsAccessTokenBearerScheme, accessTokenCookieScheme] as const;
132
+
133
+ export const me = defineOperation({
134
+ auth: requireAuth({ schemes: userSchemes }), // keep the tuple type: `as const`, no widening
135
+ handler: (ctx) => ctx.json(200, { uid: ctx.auth.user.uid }), // UserData, not UserData | null
136
+ // ...
137
+ });
138
+ ```
139
+
140
+ Keep the scheme list's tuple type (a helper returning `RequiredOperationAuth` without
141
+ its type parameter, or a scheme annotated as `AuthSchemeDefinition<"name">` instead of
142
+ `AuthSchemeDefinition<"name", "user">`, widens it back to nullable). The runtime enforces
143
+ the declaration: a `principal: "user"` scheme whose resolver returns a principal without
144
+ a user is refused with 401. For operations that also accept non-user schemes,
145
+ `requireUser(ctx.auth)` narrows and throws a 401 `OperationError` otherwise.
146
+
147
+ Resource servers verifying SchemaVaults access tokens do not write resolvers for these
148
+ schemes themselves: `createSchemaVaultsAuthResolvers()` from
149
+ `@schemavaults/auth-server-sdk/openapi-operations` returns them, keyed by scheme name,
150
+ built on the server SDK's `RouteGuardFactory` and the auth server's JWKS.
151
+
152
+ ### The error envelope
153
+
154
+ Every error the runtime produces, and every `OperationError` a handler throws, is
155
+ `{ success: false, error, message, issues?, details? }`. `OperationErrorBodySchema` is
156
+ that envelope as a zod schema (registered as `components.schemas.OperationError`, with
157
+ `OperationValidationIssueSchema` as `OperationValidationIssue`), for the 404 / 409 / ...
158
+ responses a handler declares:
159
+
160
+ ```ts
161
+ responses: {
162
+ 200: { description: "App", schema: AppSchema },
163
+ 404: { description: "No such app", schema: OperationErrorBodySchema },
164
+ },
165
+ handler: (ctx) => {
166
+ if (!app) throw new OperationError(404, { error: "not_found", message: "No such app" });
167
+ // ...
168
+ },
169
+ ```
170
+
122
171
  ## Generating the OpenAPI document
123
172
 
124
173
  ```ts
@@ -129,9 +178,21 @@ export const openApiDocument = buildOpenApiDocument({
129
178
  servers: [{ url: "https://auth.schemavaults.com" }],
130
179
  tags: [{ name: "apps", description: "Client applications" }],
131
180
  operations: [getApp, health],
181
+ // Also document the responses the runtime produces on its own (below).
182
+ documentRuntimeResponses: true,
132
183
  });
133
184
  ```
134
185
 
186
+ `buildOpenApiDocument` emits the responses each operation declares. The runtime also
187
+ answers on its own with 400 (validation of params / query / headers / body, missing
188
+ organization parameter), 401 (protected operations; with `WWW-Authenticate`), 403 (admin
189
+ route guard, required scopes, organization role), 415 (operations with a validated body)
190
+ and 500, all with the `OperationError` envelope. `documentRuntimeResponses: true` merges
191
+ those into every operation that can produce them; a response the operation declares for
192
+ the same status takes precedence. `runtimeErrorResponses(operation)` returns the set for
193
+ one operation (and `withRuntimeErrorResponses(operation)` a copy with them merged) when
194
+ you assemble responses yourself.
195
+
135
196
  ## Serving with Hono on Vercel / Next.js
136
197
 
137
198
  ```ts
@@ -212,15 +273,42 @@ dynamic segment is parsed by the app itself (Next.js' `params` are never read).
212
273
  for the rest to Next.js. The factory validates the whole catalogue up front (unique
213
274
  operations, a resolver for every scheme) and `api.app()` throws for an operation that
214
275
  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`.
276
+ The reverse (a documented operation with no route file, or one in the wrong folder) is a
277
+ file-layout question; `checkNextAppRouterRoutes()` from
278
+ `@schemavaults/openapi-operations/nextjs/app-router-routes` answers it for a `bun test`
279
+ or a generation script. Given the catalogue and the `app` directory it verifies that every
280
+ `operation.ts` / `operations.ts` under `app/api/**` exports operations that are in the
281
+ catalogue and declare the path its folder serves (`[id]` ↔ `{id}`, `(group)` segments
282
+ ignored, catch-all segments rejected), that a sibling `route.ts` exists, that every
283
+ catalogue entry comes from such a file, and that every `route.ts` serves a catalogued path:
284
+
285
+ ```ts
286
+ // src/lib/api/routes.test.ts
287
+ import { checkNextAppRouterRoutes } from "@schemavaults/openapi-operations/nextjs/app-router-routes";
288
+
289
+ test("route files and the catalogue agree", async () => {
290
+ const report = await checkNextAppRouterRoutes({
291
+ operations, // the catalogue
292
+ appDirectory: path.resolve(import.meta.dir, "../../app"),
293
+ ignoredRoutePaths: ["/api/openapi.json"], // route files that are not operations
294
+ });
295
+ expect(report.problems).toEqual([]);
296
+ });
297
+ ```
298
+
299
+ `assertNextAppRouterRoutes()` throws an `Error` listing every problem instead, for
300
+ scripts. Options: `apiDirectory` (default `api`), `operationFileNames` (default
301
+ `["operation.ts", "operations.ts"]`), `routeFileName`, `ignoredRouteDirectories` (e.g.
302
+ `api/auth/[...nextauth]`), `importModule` (default dynamic `import()`), `catalogueLabel`.
217
303
 
218
304
  Per request the app: resolves the context, tries each accepted scheme's resolver in
219
305
  order (401 + `WWW-Authenticate` if none yields a principal), enforces the route guard
220
306
  (403), required scopes (403 `insufficient_scope`), organization membership (403), then
221
307
  validates params/query/headers/body (400 with zod issues, 415 on media type mismatch)
222
308
  and finally calls the handler. Errors use the `{ success: false, error, message }`
223
- envelope.
309
+ envelope (`OperationErrorBodySchema`). An `OperationError` thrown from a different copy of
310
+ this package (isolated installs can load it twice) is recognised structurally
311
+ (`isOperationError()`), so it still short-circuits with its status and body.
224
312
 
225
313
  ## Scripts
226
314
 
@@ -1,5 +1,19 @@
1
1
  import type { SecuritySchemeObject } from "openapi3-ts/oas31";
2
2
  import type { OrganizationMembershipRoleType } from "@schemavaults/auth-common/organizations";
3
+ /**
4
+ * What kind of principal a scheme's resolver produces:
5
+ *
6
+ * - `"user"`: the credential always identifies a user, so a principal
7
+ * resolved from it carries a non-null `user`. When every scheme an
8
+ * operation accepts is a user scheme, its handler sees `ctx.auth.user`
9
+ * typed as `TUser` (not `TUser | null`), and the runtime fails closed
10
+ * (401) should a resolver ever return a principal without a user.
11
+ * - `"any"` (the default): the principal may or may not be a user (client
12
+ * credentials, API keys, ...); handlers narrow `ctx.auth.user` themselves.
13
+ */
14
+ export type AuthPrincipalKind = "user" | "any";
15
+ export declare const AUTH_PRINCIPAL_KINDS: readonly ["user", "any"];
16
+ export declare function isValidAuthPrincipalKind(value: unknown): value is AuthPrincipalKind;
3
17
  /**
4
18
  * A named OpenAPI security scheme (`components.securitySchemes[name]`) plus
5
19
  * the metadata the SchemaVaults docs UI uses to explain how to authenticate.
@@ -10,9 +24,11 @@ import type { OrganizationMembershipRoleType } from "@schemavaults/auth-common/o
10
24
  * server (which can decrypt its own tokens) and on a third-party resource
11
25
  * server (which verifies them against the auth server's JWKS).
12
26
  */
13
- export interface AuthSchemeDefinition<TName extends string = string> {
27
+ export interface AuthSchemeDefinition<TName extends string = string, TPrincipal extends AuthPrincipalKind = AuthPrincipalKind> {
14
28
  /** Key under `components.securitySchemes` and in `security` requirements. */
15
29
  readonly name: TName;
30
+ /** What the resolver of this scheme produces; see {@link AuthPrincipalKind}. Default `"any"`. */
31
+ readonly principal?: TPrincipal;
16
32
  /** Human readable label for docs. */
17
33
  readonly title: string;
18
34
  /** Longer explanation for docs (where to get the credential, lifetime, ...). */
@@ -26,7 +42,16 @@ export interface AuthSchemeDefinition<TName extends string = string> {
26
42
  */
27
43
  readonly challenge?: string;
28
44
  }
29
- export declare function defineAuthScheme<const TName extends string>(definition: AuthSchemeDefinition<TName>): AuthSchemeDefinition<TName>;
45
+ export declare function defineAuthScheme<const TName extends string, const TPrincipal extends AuthPrincipalKind = "any">(definition: AuthSchemeDefinition<TName, TPrincipal>): AuthSchemeDefinition<TName, TPrincipal>;
46
+ /** Whether a principal resolved from the scheme is guaranteed to carry a user. */
47
+ export declare function schemeResolvesUser(scheme: AuthSchemeDefinition): boolean;
48
+ /** A scheme whose resolver always produces a user principal. */
49
+ export type UserAuthSchemeDefinition<TName extends string = string> = AuthSchemeDefinition<TName, "user">;
50
+ /**
51
+ * `true` when every scheme in the (non-empty) list is a user scheme, so a
52
+ * principal resolved through any of them carries a non-null user.
53
+ */
54
+ export type AllSchemesResolveUser<TSchemes extends readonly AuthSchemeDefinition[]> = TSchemes extends readonly [] ? false : TSchemes[number] extends UserAuthSchemeDefinition ? true : false;
30
55
  /**
31
56
  * Route guard levels mirroring `@schemavaults/auth-server-sdk`'s
32
57
  * `withAuthenticatedApiRouteGuard` / `withAdminApiRouteGuard`.
@@ -54,9 +79,9 @@ export interface OrganizationRoleRequirement {
54
79
  * `x-schemavaults-auth` vendor extension so docs can show the route guard
55
80
  * and organization role in addition to the scopes.
56
81
  */
57
- export interface AuthRequirements {
82
+ export interface AuthRequirements<TSchemes extends readonly AuthSchemeDefinition[] = readonly AuthSchemeDefinition[]> {
58
83
  /** Schemes accepted for this operation (any one of them satisfies it). */
59
- readonly schemes: readonly AuthSchemeDefinition[];
84
+ readonly schemes: TSchemes;
60
85
  /** Who may call once authenticated. Defaults to "authenticated". */
61
86
  readonly routeGuard?: RouteGuardType;
62
87
  /**
@@ -74,28 +99,32 @@ export interface PublicOperationAuth {
74
99
  readonly type: "public";
75
100
  readonly notes?: string;
76
101
  }
77
- export interface RequiredOperationAuth extends AuthRequirements {
102
+ export interface RequiredOperationAuth<TSchemes extends readonly AuthSchemeDefinition[] = readonly AuthSchemeDefinition[]> extends AuthRequirements<TSchemes> {
78
103
  readonly type: "required";
79
104
  }
80
105
  export type OperationAuth = PublicOperationAuth | RequiredOperationAuth;
81
106
  /** Marks an operation as callable without credentials. */
82
107
  export declare function publicAccess(notes?: string): PublicOperationAuth;
83
- /** Marks an operation as requiring one of the given schemes (+ extra checks). */
84
- export declare function requireAuth(requirements: AuthRequirements): RequiredOperationAuth;
108
+ /**
109
+ * Marks an operation as requiring one of the given schemes (+ extra checks).
110
+ * The scheme list type is preserved so that operations accepting only
111
+ * `principal: "user"` schemes get a non-nullable `ctx.auth.user`.
112
+ */
113
+ export declare function requireAuth<const TSchemes extends readonly AuthSchemeDefinition[]>(requirements: AuthRequirements<TSchemes>): RequiredOperationAuth<TSchemes>;
85
114
  export declare function isPublicOperationAuth(auth: OperationAuth): auth is PublicOperationAuth;
86
115
  /** `Authorization: Bearer <access token>` issued by the auth server. */
87
- export declare const schemaVaultsAccessTokenBearerScheme: AuthSchemeDefinition<"schemavaults-access-token">;
116
+ export declare const schemaVaultsAccessTokenBearerScheme: AuthSchemeDefinition<"schemavaults-access-token", "user">;
88
117
  /**
89
118
  * The first-party access-token cookie set by the auth server / resource
90
119
  * server SDK (`AccessTokenCookieName(api_server_id)`). The cookie name is
91
120
  * per API server, so the concrete name is filled in by the caller.
92
121
  */
93
- export declare function schemaVaultsAccessTokenCookieScheme(cookieName: string): AuthSchemeDefinition<"schemavaults-access-token-cookie">;
122
+ export declare function schemaVaultsAccessTokenCookieScheme(cookieName: string): AuthSchemeDefinition<"schemavaults-access-token-cookie", "user">;
94
123
  /** The auth server's own refresh-token cookie (only meaningful ON the auth server). */
95
- export declare function schemaVaultsRefreshTokenCookieScheme(cookieName: string): AuthSchemeDefinition<"schemavaults-refresh-token-cookie">;
124
+ export declare function schemaVaultsRefreshTokenCookieScheme(cookieName: string): AuthSchemeDefinition<"schemavaults-refresh-token-cookie", "user">;
96
125
  /** OAuth 2.0 `client_secret_basic` client authentication (RFC 6749 §2.3.1). */
97
- export declare const oidcClientSecretBasicScheme: AuthSchemeDefinition<"oidc-client-secret-basic">;
126
+ export declare const oidcClientSecretBasicScheme: AuthSchemeDefinition<"oidc-client-secret-basic", "any">;
98
127
  /** OAuth 2.0 `client_secret_post` client authentication (credentials in the form body). */
99
- export declare const oidcClientSecretPostScheme: AuthSchemeDefinition<"oidc-client-secret-post">;
128
+ export declare const oidcClientSecretPostScheme: AuthSchemeDefinition<"oidc-client-secret-post", "any">;
100
129
  /** A static API key in a header (e.g. cron / internal automation). */
101
130
  export declare function apiKeyHeaderScheme<const TName extends string>(name: TName, headerName: string, description?: string): AuthSchemeDefinition<TName>;
@@ -1,3 +1,7 @@
1
+ export const AUTH_PRINCIPAL_KINDS = ["user", "any"];
2
+ export function isValidAuthPrincipalKind(value) {
3
+ return typeof value === "string" && AUTH_PRINCIPAL_KINDS.includes(value);
4
+ }
1
5
  export function defineAuthScheme(definition) {
2
6
  if (typeof definition.name !== "string" || definition.name.length === 0) {
3
7
  throw new TypeError("An auth scheme needs a non-empty name");
@@ -5,8 +9,15 @@ export function defineAuthScheme(definition) {
5
9
  if (!/^[A-Za-z0-9._-]+$/.test(definition.name)) {
6
10
  throw new TypeError(`Auth scheme name "${definition.name}" must match /^[A-Za-z0-9._-]+$/ (it becomes an OpenAPI component key)`);
7
11
  }
12
+ if (definition.principal !== undefined && !isValidAuthPrincipalKind(definition.principal)) {
13
+ throw new TypeError(`Auth scheme "${definition.name}" has an unknown principal kind "${String(definition.principal)}" (expected one of: ${AUTH_PRINCIPAL_KINDS.join(", ")})`);
14
+ }
8
15
  return Object.freeze({ ...definition });
9
16
  }
17
+ /** Whether a principal resolved from the scheme is guaranteed to carry a user. */
18
+ export function schemeResolvesUser(scheme) {
19
+ return scheme.principal === "user";
20
+ }
10
21
  /**
11
22
  * Route guard levels mirroring `@schemavaults/auth-server-sdk`'s
12
23
  * `withAuthenticatedApiRouteGuard` / `withAdminApiRouteGuard`.
@@ -16,7 +27,11 @@ export const ROUTE_GUARD_TYPES = ["authenticated", "admin"];
16
27
  export function publicAccess(notes) {
17
28
  return notes === undefined ? { type: "public" } : { type: "public", notes };
18
29
  }
19
- /** Marks an operation as requiring one of the given schemes (+ extra checks). */
30
+ /**
31
+ * Marks an operation as requiring one of the given schemes (+ extra checks).
32
+ * The scheme list type is preserved so that operations accepting only
33
+ * `principal: "user"` schemes get a non-nullable `ctx.auth.user`.
34
+ */
20
35
  export function requireAuth(requirements) {
21
36
  if (!Array.isArray(requirements.schemes) || requirements.schemes.length === 0) {
22
37
  throw new TypeError("requireAuth() needs at least one auth scheme; use publicAccess() for open operations");
@@ -47,6 +62,7 @@ export function isPublicOperationAuth(auth) {
47
62
  /** `Authorization: Bearer <access token>` issued by the auth server. */
48
63
  export const schemaVaultsAccessTokenBearerScheme = defineAuthScheme({
49
64
  name: "schemavaults-access-token",
65
+ principal: "user",
50
66
  title: "Access token (Bearer)",
51
67
  description: "An access token issued by the auth server for this API server, sent as `Authorization: Bearer <token>`.",
52
68
  securityScheme: {
@@ -65,6 +81,7 @@ export const schemaVaultsAccessTokenBearerScheme = defineAuthScheme({
65
81
  export function schemaVaultsAccessTokenCookieScheme(cookieName) {
66
82
  return defineAuthScheme({
67
83
  name: "schemavaults-access-token-cookie",
84
+ principal: "user",
68
85
  title: "Access token (cookie)",
69
86
  description: `The first-party HTTP-only access token cookie \`${cookieName}\` set after login.`,
70
87
  securityScheme: {
@@ -79,6 +96,7 @@ export function schemaVaultsAccessTokenCookieScheme(cookieName) {
79
96
  export function schemaVaultsRefreshTokenCookieScheme(cookieName) {
80
97
  return defineAuthScheme({
81
98
  name: "schemavaults-refresh-token-cookie",
99
+ principal: "user",
82
100
  title: "Auth server session (refresh token cookie)",
83
101
  description: `The auth server's HTTP-only refresh token cookie \`${cookieName}\`; only the auth server itself can resolve it.`,
84
102
  securityScheme: {
@@ -1 +1 @@
1
- {"version":3,"file":"auth-scheme.js","sourceRoot":"","sources":["../src/auth-scheme.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-scheme.js","sourceRoot":"","sources":["../src/auth-scheme.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,12 +2,13 @@ export { z, withOpenApi } from "./zod-openapi";
2
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
- export { defineAuthScheme, publicAccess, requireAuth, isPublicOperationAuth, ROUTE_GUARD_TYPES, schemaVaultsAccessTokenBearerScheme, schemaVaultsAccessTokenCookieScheme, schemaVaultsRefreshTokenCookieScheme, oidcClientSecretBasicScheme, oidcClientSecretPostScheme, apiKeyHeaderScheme, } from "./auth-scheme";
6
- export type { AuthSchemeDefinition, AuthRequirements, OperationAuth, PublicOperationAuth, RequiredOperationAuth, OrganizationRoleRequirement, RouteGuardType, } from "./auth-scheme";
5
+ export { defineAuthScheme, AUTH_PRINCIPAL_KINDS, isValidAuthPrincipalKind, schemeResolvesUser, publicAccess, requireAuth, isPublicOperationAuth, ROUTE_GUARD_TYPES, schemaVaultsAccessTokenBearerScheme, schemaVaultsAccessTokenCookieScheme, schemaVaultsRefreshTokenCookieScheme, oidcClientSecretBasicScheme, oidcClientSecretPostScheme, apiKeyHeaderScheme, } from "./auth-scheme";
6
+ export type { AllSchemesResolveUser, AuthPrincipalKind, AuthSchemeDefinition, AuthRequirements, UserAuthSchemeDefinition, OperationAuth, PublicOperationAuth, RequiredOperationAuth, OrganizationRoleRequirement, RouteGuardType, } from "./auth-scheme";
7
7
  export { defineOperation, createOperationDefiner, defineOperationGroup, defaultOperationId, assertUniqueOperations, operationHttpMethods, } from "./operation";
8
- export type { AnyOperationDefinition, AuthPrincipal, EmptyResponseStatusOf, InferBody, InferParsed, OperationDefiner, OperationDefinition, OperationGroup, OperationHandlerContext, OperationHandlerResult, OperationInput, OperationRequestDefinition, RequestBodyContentType, RequestBodyDefinition, ResponseBodyOf, ResponseDefinition, ResponseStatusOf, ResponsesDefinition, } from "./operation";
8
+ export type { AnyOperationDefinition, AuthPrincipal, EmptyResponseStatusOf, InferBody, InferParsed, OperationDefiner, OperationDefinition, OperationGroup, OperationHandlerContext, OperationHandlerResult, OperationInput, OperationRequestDefinition, RequestBodyContentType, RequestBodyDefinition, ResolvedAuthPrincipal, ResponseBodyOf, ResponseDefinition, ResponseStatusOf, ResponsesDefinition, UserAuthPrincipal, } 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 { runtimeErrorResponses, withRuntimeErrorResponses } from "./openapi/runtime-responses";
11
12
  export type { OpenAPIObject } from "openapi3-ts/oas31";
12
13
  export { SCHEMAVAULTS_AUTH_EXTENSION, SCHEMAVAULTS_SCHEME_TITLE_EXTENSION, SCHEMAVAULTS_SCHEME_CHALLENGE_EXTENSION, toSchemaVaultsAuthExtension, isSchemaVaultsAuthExtension, } from "./openapi/extensions";
13
14
  export type { SchemaVaultsAuthExtension } from "./openapi/extensions";
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  export { z, withOpenApi } from "./zod-openapi";
2
2
  export { HTTP_METHODS, HTTP_METHODS_WITH_REQUEST_BODY, isHttpMethod } from "./http-method";
3
- export { defineAuthScheme, publicAccess, requireAuth, isPublicOperationAuth, ROUTE_GUARD_TYPES, schemaVaultsAccessTokenBearerScheme, schemaVaultsAccessTokenCookieScheme, schemaVaultsRefreshTokenCookieScheme, oidcClientSecretBasicScheme, oidcClientSecretPostScheme, apiKeyHeaderScheme, } from "./auth-scheme";
3
+ export { defineAuthScheme, AUTH_PRINCIPAL_KINDS, isValidAuthPrincipalKind, schemeResolvesUser, publicAccess, requireAuth, isPublicOperationAuth, ROUTE_GUARD_TYPES, schemaVaultsAccessTokenBearerScheme, schemaVaultsAccessTokenCookieScheme, schemaVaultsRefreshTokenCookieScheme, oidcClientSecretBasicScheme, oidcClientSecretPostScheme, apiKeyHeaderScheme, } from "./auth-scheme";
4
4
  export { defineOperation, createOperationDefiner, defineOperationGroup, defaultOperationId, assertUniqueOperations, operationHttpMethods, } from "./operation";
5
5
  export { buildOpenApiDocument, collectAuthSchemes, toRouteConfig, toSecuritySchemeComponent, } from "./openapi/build-openapi-document";
6
+ export { runtimeErrorResponses, withRuntimeErrorResponses } from "./openapi/runtime-responses";
6
7
  export { SCHEMAVAULTS_AUTH_EXTENSION, SCHEMAVAULTS_SCHEME_TITLE_EXTENSION, SCHEMAVAULTS_SCHEME_CHALLENGE_EXTENSION, toSchemaVaultsAuthExtension, isSchemaVaultsAuthExtension, } from "./openapi/extensions";
7
8
  export { extractPathParameterNames, openApiPathToHonoPath, honoPathToOpenApiPath, isOpenApiPath, } from "./openapi/path-format";
8
9
  export * from "./runtime";
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,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"}
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,oBAAoB,EACpB,wBAAwB,EACxB,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,iBAAiB,EACjB,mCAAmC,EACnC,mCAAmC,EACnC,oCAAoC,EACpC,2BAA2B,EAC3B,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,eAAe,CAAC;AAcvB,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAwBrB,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,EACb,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAG/F,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"}
@@ -0,0 +1,56 @@
1
+ import type { AnyOperationDefinition } from "../operation";
2
+ export interface CheckNextAppRouterRoutesOptions {
3
+ /** The catalogue `buildOpenApiDocument()` / the app factory are given. */
4
+ readonly operations: readonly AnyOperationDefinition[];
5
+ /** The Next.js `app` directory (absolute, or relative to the working directory), e.g. `src/app`. */
6
+ readonly appDirectory: string;
7
+ /** Sub-directory of `appDirectory` holding the API route files. Default `api`. */
8
+ readonly apiDirectory?: string;
9
+ /**
10
+ * File names (beside a `route.ts`) that declare the operations the route
11
+ * serves. Default `["operation.ts", "operations.ts"]`.
12
+ */
13
+ readonly operationFileNames?: readonly string[];
14
+ /** Default `route.ts`. */
15
+ readonly routeFileName?: string;
16
+ /**
17
+ * OpenAPI paths served by route files that are not operations (the route
18
+ * serving the OpenAPI document itself, a webhook with its own handler,
19
+ * ...). Such a route file needs no operation file.
20
+ */
21
+ readonly ignoredRoutePaths?: readonly string[];
22
+ /**
23
+ * Directories (relative to `appDirectory`, `/`-separated, e.g.
24
+ * `api/auth/[...nextauth]`) whose route files are not checked at all.
25
+ */
26
+ readonly ignoredRouteDirectories?: readonly string[];
27
+ /** Loads an operation module; default `import(fileUrl)`. */
28
+ readonly importModule?: (file: string) => Promise<Record<string, unknown>>;
29
+ /** How the catalogue is named in messages. Default `the operations catalogue`. */
30
+ readonly catalogueLabel?: string;
31
+ }
32
+ export interface NextAppRouterRoutesReport {
33
+ readonly ok: boolean;
34
+ /** Human readable problems; empty when `ok`. */
35
+ readonly problems: readonly string[];
36
+ /** Operation files that were checked (absolute paths). */
37
+ readonly operationFiles: readonly string[];
38
+ /** Route files that were checked (absolute paths). */
39
+ readonly routeFiles: readonly string[];
40
+ }
41
+ /**
42
+ * Route directory (relative to the app directory) → OpenAPI path:
43
+ * `(group)` segments dropped, `[param]` → `{param}`. Returns null for
44
+ * catch-all segments (`[...slug]`, `[[...slug]]`), which OpenAPI cannot
45
+ * describe.
46
+ */
47
+ export declare function nextRouteDirectoryToOpenApiPath(routeDirectory: string): string | null;
48
+ /** OpenAPI path → route directory relative to the app directory: `/api/items/{id}` → `api/items/[id]`. */
49
+ export declare function openApiPathToNextRouteDirectory(openApiPath: string): string;
50
+ /**
51
+ * Checks the route files against the catalogue and reports every problem
52
+ * found (never throws for layout problems; see {@link assertNextAppRouterRoutes}).
53
+ */
54
+ export declare function checkNextAppRouterRoutes(options: CheckNextAppRouterRoutesOptions): Promise<NextAppRouterRoutesReport>;
55
+ /** {@link checkNextAppRouterRoutes} that throws an Error listing every problem. */
56
+ export declare function assertNextAppRouterRoutes(options: CheckNextAppRouterRoutesOptions): Promise<NextAppRouterRoutesReport>;
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Consistency check between an operations catalogue and the Next.js App
3
+ * Router files serving it, for the "one `route.ts` per operation" layout
4
+ * (see `createOperationsAppFactory()`):
5
+ *
6
+ * - every `operation.ts` / `operations.ts` under the API directory exports
7
+ * at least one operation, has a sibling `route.ts`, and each exported
8
+ * operation is in the catalogue and declares the path its directory
9
+ * serves (`app/api/items/[id]` ↔ `/api/items/{id}`, route groups
10
+ * `(group)` ignored, catch-all segments rejected);
11
+ * - every catalogue entry comes from such a file;
12
+ * - every `route.ts` under the API directory serves a catalogued path (or
13
+ * one listed in `ignoredRoutePaths`, e.g. the `openapi.json` route).
14
+ *
15
+ * Runs under `bun test` / a `bun` script (it reads the file system and
16
+ * imports the operation modules), not inside a request handler.
17
+ */
18
+ import { existsSync, readdirSync } from "node:fs";
19
+ import { dirname, join, relative, resolve, sep } from "node:path";
20
+ import { pathToFileURL } from "node:url";
21
+ import { assertUniqueOperations } from "../operation";
22
+ const DEFAULT_OPERATION_FILE_NAMES = ["operation.ts", "operations.ts"];
23
+ const DEFAULT_ROUTE_FILE_NAME = "route.ts";
24
+ /**
25
+ * Route directory (relative to the app directory) → OpenAPI path:
26
+ * `(group)` segments dropped, `[param]` → `{param}`. Returns null for
27
+ * catch-all segments (`[...slug]`, `[[...slug]]`), which OpenAPI cannot
28
+ * describe.
29
+ */
30
+ export function nextRouteDirectoryToOpenApiPath(routeDirectory) {
31
+ const out = [];
32
+ for (const segment of routeDirectory.split(/[\\/]/).filter((part) => part.length > 0)) {
33
+ if (segment.startsWith("(") && segment.endsWith(")"))
34
+ continue;
35
+ if (segment.startsWith("[...") || segment.startsWith("[[..."))
36
+ return null;
37
+ const param = /^\[([^\]]+)\]$/.exec(segment);
38
+ out.push(param ? `{${param[1]}}` : segment);
39
+ }
40
+ return `/${out.join("/")}`;
41
+ }
42
+ /** OpenAPI path → route directory relative to the app directory: `/api/items/{id}` → `api/items/[id]`. */
43
+ export function openApiPathToNextRouteDirectory(openApiPath) {
44
+ return openApiPath
45
+ .split("/")
46
+ .filter((segment) => segment.length > 0)
47
+ .map((segment) => segment.replace(/^\{([^}]+)\}$/, "[$1]"))
48
+ .join("/");
49
+ }
50
+ function isOperationDefinition(value) {
51
+ if (typeof value !== "object" || value === null)
52
+ return false;
53
+ const candidate = value;
54
+ return (typeof candidate.method === "string" &&
55
+ typeof candidate.path === "string" &&
56
+ typeof candidate.operationId === "string" &&
57
+ typeof candidate.handler === "function");
58
+ }
59
+ function findFiles(directory, names) {
60
+ if (!existsSync(directory))
61
+ return [];
62
+ const found = [];
63
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
64
+ // `_private` folders and node_modules are never routes.
65
+ if (entry.name.startsWith("_") || entry.name === "node_modules")
66
+ continue;
67
+ const full = join(directory, entry.name);
68
+ if (entry.isDirectory())
69
+ found.push(...findFiles(full, names));
70
+ else if (names.has(entry.name))
71
+ found.push(full);
72
+ }
73
+ return found.sort();
74
+ }
75
+ function toPosix(path) {
76
+ return path.split(sep).join("/");
77
+ }
78
+ async function defaultImportModule(file) {
79
+ return (await import(pathToFileURL(file).href));
80
+ }
81
+ /**
82
+ * Checks the route files against the catalogue and reports every problem
83
+ * found (never throws for layout problems; see {@link assertNextAppRouterRoutes}).
84
+ */
85
+ export async function checkNextAppRouterRoutes(options) {
86
+ const appDirectory = resolve(options.appDirectory);
87
+ const apiDirectory = join(appDirectory, options.apiDirectory ?? "api");
88
+ const operationFileNames = new Set(options.operationFileNames ?? DEFAULT_OPERATION_FILE_NAMES);
89
+ const routeFileName = options.routeFileName ?? DEFAULT_ROUTE_FILE_NAME;
90
+ const ignoredRoutePaths = new Set(options.ignoredRoutePaths ?? []);
91
+ const ignoredRouteDirectories = new Set((options.ignoredRouteDirectories ?? []).map((directory) => toPosix(directory).replace(/^\/+|\/+$/g, "")));
92
+ const importModule = options.importModule ?? defaultImportModule;
93
+ const catalogueLabel = options.catalogueLabel ?? "the operations catalogue";
94
+ const cwd = process.cwd();
95
+ const label = (file) => toPosix(relative(cwd, file));
96
+ const routeDirectoryOf = (file) => toPosix(relative(appDirectory, dirname(file)));
97
+ const isIgnoredDirectory = (file) => ignoredRouteDirectories.has(routeDirectoryOf(file));
98
+ const problems = [];
99
+ try {
100
+ assertUniqueOperations(options.operations);
101
+ }
102
+ catch (error) {
103
+ problems.push(`${catalogueLabel}: ${error instanceof Error ? error.message : String(error)}`);
104
+ }
105
+ const catalogued = new Set(options.operations);
106
+ const cataloguedById = new Map(options.operations.map((operation) => [operation.operationId, operation]));
107
+ const cataloguedPaths = new Set(options.operations.map((operation) => operation.path));
108
+ if (!existsSync(apiDirectory)) {
109
+ problems.push(`API directory ${label(apiDirectory)} does not exist`);
110
+ }
111
+ const operationFiles = findFiles(apiDirectory, operationFileNames).filter((file) => !isIgnoredDirectory(file));
112
+ const routeFiles = findFiles(apiDirectory, new Set([routeFileName])).filter((file) => !isIgnoredDirectory(file));
113
+ const discovered = new Set();
114
+ for (const file of operationFiles) {
115
+ const directory = dirname(file);
116
+ const fileLabel = label(file);
117
+ if (!existsSync(join(directory, routeFileName))) {
118
+ problems.push(`${fileLabel}: no sibling ${routeFileName} serves these operations`);
119
+ }
120
+ const expectedPath = nextRouteDirectoryToOpenApiPath(routeDirectoryOf(file));
121
+ if (expectedPath === null) {
122
+ problems.push(`${fileLabel}: catch-all segments cannot be described in OpenAPI; use explicit [param] segments`);
123
+ continue;
124
+ }
125
+ let mod;
126
+ try {
127
+ mod = await importModule(file);
128
+ }
129
+ catch (error) {
130
+ problems.push(`${fileLabel}: failed to import (${error instanceof Error ? error.message : String(error)})`);
131
+ continue;
132
+ }
133
+ const exported = Object.entries(mod).filter((entry) => isOperationDefinition(entry[1]));
134
+ if (exported.length === 0) {
135
+ problems.push(`${fileLabel}: exports no operations (export the result of defineOperation())`);
136
+ continue;
137
+ }
138
+ for (const [exportName, operation] of exported) {
139
+ const id = operation.operationId;
140
+ if (operation.path !== expectedPath) {
141
+ problems.push(`${fileLabel}: export '${exportName}' (${id}) declares path '${operation.path}' but its directory maps to '${expectedPath}'`);
142
+ }
143
+ if (catalogued.has(operation)) {
144
+ discovered.add(operation);
145
+ }
146
+ else if (cataloguedById.has(id)) {
147
+ problems.push(`${fileLabel}: export '${exportName}' (${id}) is a different object from the '${id}' listed in ${catalogueLabel} (defined twice?)`);
148
+ }
149
+ else {
150
+ problems.push(`${fileLabel}: export '${exportName}' (${id}) is not listed in ${catalogueLabel}`);
151
+ }
152
+ }
153
+ }
154
+ for (const operation of catalogued) {
155
+ if (!discovered.has(operation)) {
156
+ const fileNames = [...operationFileNames].join(" / ");
157
+ problems.push(`${catalogueLabel}: '${operation.operationId}' (${operation.method.toUpperCase()} ${operation.path}) is not exported by any ${fileNames} under ${label(apiDirectory)}; define operations next to the ${routeFileName} that serves them`);
158
+ }
159
+ }
160
+ for (const file of routeFiles) {
161
+ const routePath = nextRouteDirectoryToOpenApiPath(routeDirectoryOf(file));
162
+ if (routePath === null) {
163
+ problems.push(`${label(file)}: catch-all route files cannot serve documented operations; list its directory in ignoredRouteDirectories if it is not one`);
164
+ continue;
165
+ }
166
+ if (cataloguedPaths.has(routePath) || ignoredRoutePaths.has(routePath))
167
+ continue;
168
+ problems.push(`${label(file)}: serves '${routePath}', which is not in ${catalogueLabel} (add it to ignoredRoutePaths if it is not an operation)`);
169
+ }
170
+ return { ok: problems.length === 0, problems, operationFiles, routeFiles };
171
+ }
172
+ /** {@link checkNextAppRouterRoutes} that throws an Error listing every problem. */
173
+ export async function assertNextAppRouterRoutes(options) {
174
+ const report = await checkNextAppRouterRoutes(options);
175
+ if (!report.ok) {
176
+ throw new Error(`Next.js App Router route files and the operations catalogue disagree:\n${report.problems
177
+ .map((problem) => ` - ${problem}`)
178
+ .join("\n")}`);
179
+ }
180
+ return report;
181
+ }
182
+ //# sourceMappingURL=app-router-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-router-routes.js","sourceRoot":"","sources":["../../src/nextjs/app-router-routes.ts"],"names":[],"mappings":"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"}