@veloxts/router 0.7.5 → 0.7.7
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/CHANGELOG.md +18 -0
- package/GUIDE.md +11 -36
- package/dist/index.d.ts +7 -7
- package/dist/index.js +8 -6
- package/dist/openapi/generator.js +9 -4
- package/dist/openapi/index.d.ts +3 -4
- package/dist/openapi/index.js +3 -4
- package/dist/openapi/plugin.d.ts +3 -47
- package/dist/openapi/plugin.js +3 -51
- package/dist/openapi/schema-converter.d.ts +13 -0
- package/dist/openapi/schema-converter.js +51 -0
- package/dist/procedure/builder.js +33 -21
- package/dist/procedure/types.d.ts +67 -28
- package/dist/resource/index.d.ts +7 -5
- package/dist/resource/index.js +3 -2
- package/dist/resource/instance.d.ts +38 -20
- package/dist/resource/instance.js +51 -44
- package/dist/resource/levels.d.ts +71 -0
- package/dist/resource/levels.js +109 -0
- package/dist/resource/schema.d.ts +151 -159
- package/dist/resource/schema.js +132 -124
- package/dist/resource/tags.d.ts +35 -15
- package/dist/resource/tags.js +1 -1
- package/dist/resource/types.d.ts +10 -8
- package/dist/resource/visibility.d.ts +16 -3
- package/dist/resource/visibility.js +16 -0
- package/dist/types.d.ts +28 -5
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @veloxts/router
|
|
2
2
|
|
|
3
|
+
## 0.7.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- refactor(router): rename swaggerUIPlugin → swaggerPlugin, remove redundant exports
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @veloxts/core@0.7.7
|
|
10
|
+
- @veloxts/validation@0.7.7
|
|
11
|
+
|
|
12
|
+
## 0.7.6
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- feat(router): custom access levels for the Resource API + advanced Architectural Patterns
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
- @veloxts/core@0.7.6
|
|
19
|
+
- @veloxts/validation@0.7.6
|
|
20
|
+
|
|
3
21
|
## 0.7.5
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/GUIDE.md
CHANGED
|
@@ -28,8 +28,8 @@ export const userProcedures = procedures('users', {
|
|
|
28
28
|
## Procedure API
|
|
29
29
|
|
|
30
30
|
- `.input(schema)` - Validate input with Zod
|
|
31
|
-
- `.output(schema)` - Validate output with Zod
|
|
32
|
-
- `.
|
|
31
|
+
- `.output(schema)` - Validate output with Zod
|
|
32
|
+
- `.expose(schema)` - Context-dependent output with resource schema
|
|
33
33
|
- `.use(middleware)` - Add middleware
|
|
34
34
|
- `.guard(guard)` - Add authorization guard
|
|
35
35
|
- `.guardNarrow(guard)` - Add guard with TypeScript type narrowing
|
|
@@ -198,9 +198,9 @@ fs.writeFileSync('openapi.json', JSON.stringify(spec, null, 2));
|
|
|
198
198
|
Serve interactive API documentation with Swagger UI:
|
|
199
199
|
|
|
200
200
|
```typescript
|
|
201
|
-
import {
|
|
201
|
+
import { swaggerPlugin } from '@veloxts/router';
|
|
202
202
|
|
|
203
|
-
app.server.register(
|
|
203
|
+
app.server.register(swaggerPlugin, {
|
|
204
204
|
routePrefix: '/docs',
|
|
205
205
|
collections: [userProcedures, postProcedures],
|
|
206
206
|
openapi: {
|
|
@@ -215,31 +215,6 @@ app.server.register(swaggerUIPlugin, {
|
|
|
215
215
|
// - /docs/openapi.json - Raw OpenAPI spec
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
### Factory Functions
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
import { createSwaggerUI, getOpenApiSpec, registerDocs } from '@veloxts/router';
|
|
222
|
-
|
|
223
|
-
// Create pre-configured plugin
|
|
224
|
-
const docs = createSwaggerUI({
|
|
225
|
-
collections: [userProcedures],
|
|
226
|
-
openapi: { info: { title: 'My API', version: '1.0.0' } },
|
|
227
|
-
});
|
|
228
|
-
app.server.register(docs);
|
|
229
|
-
|
|
230
|
-
// Get spec without registering routes
|
|
231
|
-
const spec = getOpenApiSpec({
|
|
232
|
-
collections: [userProcedures],
|
|
233
|
-
openapi: { info: { title: 'My API', version: '1.0.0' } },
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Register docs with one call
|
|
237
|
-
await registerDocs(app.server, {
|
|
238
|
-
collections: [userProcedures],
|
|
239
|
-
openapi: { info: { title: 'My API', version: '1.0.0' } },
|
|
240
|
-
});
|
|
241
|
-
```
|
|
242
|
-
|
|
243
218
|
### CLI Commands
|
|
244
219
|
|
|
245
220
|
Generate and serve OpenAPI specs from the command line:
|
|
@@ -430,7 +405,7 @@ console.table(routes);
|
|
|
430
405
|
|
|
431
406
|
## Resource API (Context-Dependent Outputs)
|
|
432
407
|
|
|
433
|
-
The Resource API provides context-dependent output types using phantom types.
|
|
408
|
+
The Resource API provides context-dependent output types using phantom types. Pass a resource schema to `.output()` instead of a Zod schema to define field visibility per access level.
|
|
434
409
|
|
|
435
410
|
### Defining a Resource Schema
|
|
436
411
|
|
|
@@ -450,7 +425,7 @@ const UserSchema = resourceSchema()
|
|
|
450
425
|
|
|
451
426
|
### Automatic Projection (Simple Cases)
|
|
452
427
|
|
|
453
|
-
The most elegant approach is to chain `.
|
|
428
|
+
The most elegant approach is to chain `.expose()` with a narrowing guard. The procedure executor automatically projects fields based on the guard's access level:
|
|
454
429
|
|
|
455
430
|
```typescript
|
|
456
431
|
import { authenticatedNarrow, adminNarrow } from '@veloxts/auth';
|
|
@@ -459,7 +434,7 @@ export const userProcedures = procedures('users', {
|
|
|
459
434
|
// Authenticated endpoint - auto-projects { id, name, email, createdAt }
|
|
460
435
|
getProfile: procedure()
|
|
461
436
|
.guardNarrow(authenticatedNarrow)
|
|
462
|
-
.
|
|
437
|
+
.expose(UserSchema)
|
|
463
438
|
.input(z.object({ id: z.string().uuid() }))
|
|
464
439
|
.query(async ({ input, ctx }) => {
|
|
465
440
|
// Just return the full data - projection is automatic!
|
|
@@ -469,7 +444,7 @@ export const userProcedures = procedures('users', {
|
|
|
469
444
|
// Admin endpoint - auto-projects all fields
|
|
470
445
|
getFullProfile: procedure()
|
|
471
446
|
.guardNarrow(adminNarrow)
|
|
472
|
-
.
|
|
447
|
+
.expose(UserSchema)
|
|
473
448
|
.input(z.object({ id: z.string().uuid() }))
|
|
474
449
|
.query(async ({ input, ctx }) => {
|
|
475
450
|
return ctx.db.user.findUniqueOrThrow({ where: { id: input.id } });
|
|
@@ -544,7 +519,7 @@ For arrays of items, use `resourceCollection()`:
|
|
|
544
519
|
// Automatic projection (simple cases)
|
|
545
520
|
const listUsers = procedure()
|
|
546
521
|
.guardNarrow(authenticatedNarrow)
|
|
547
|
-
.
|
|
522
|
+
.expose(UserSchema)
|
|
548
523
|
.query(async ({ ctx }) => {
|
|
549
524
|
return ctx.db.user.findMany({ take: 50 });
|
|
550
525
|
});
|
|
@@ -590,7 +565,7 @@ The Resource API provides compile-time type safety:
|
|
|
590
565
|
// Automatic projection - type inferred from guard
|
|
591
566
|
const getProfile = procedure()
|
|
592
567
|
.guardNarrow(authenticatedNarrow)
|
|
593
|
-
.
|
|
568
|
+
.expose(UserSchema)
|
|
594
569
|
.query(async ({ ctx }) => {
|
|
595
570
|
return ctx.db.user.findFirst();
|
|
596
571
|
});
|
|
@@ -611,7 +586,7 @@ const adminResult = resource(user, UserSchema.admin);
|
|
|
611
586
|
|
|
612
587
|
| Scenario | Approach |
|
|
613
588
|
|----------|----------|
|
|
614
|
-
| Guard determines access level | **Automatic** (`.guardNarrow().
|
|
589
|
+
| Guard determines access level | **Automatic** (`.guardNarrow().output()`) |
|
|
615
590
|
| Public endpoints (no guard) | Tagged view (`UserSchema.public`) |
|
|
616
591
|
| Conditional/dynamic projection | Tagged view or `.for(ctx)` in handler |
|
|
617
592
|
| Simple, declarative code | **Automatic** |
|
package/dist/index.d.ts
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
/** Router package version */
|
|
41
41
|
export declare const ROUTER_VERSION: string;
|
|
42
|
-
export type { CompiledProcedure, ContextExtensions, ContextFactory, ExtendedContext, GuardLike, HttpMethod, InferProcedureContext, InferProcedureInput, InferProcedureOutput, MiddlewareArgs, MiddlewareFunction, MiddlewareNext, MiddlewareResult, ParentResourceChain, ParentResourceConfig, ProcedureCollection, ProcedureHandler, ProcedureHandlerArgs, ProcedureRecord, ProcedureType, RestRouteOverride, } from './types.js';
|
|
42
|
+
export type { CompiledProcedure, ContextExtensions, ContextFactory, ExtendedContext, GuardLike, HttpMethod, InferProcedureContext, InferProcedureInput, InferProcedureOutput, Middleware, MiddlewareArgs, MiddlewareFunction, MiddlewareNext, MiddlewareResult, ParentResourceChain, ParentResourceConfig, ProcedureCollection, ProcedureHandler, ProcedureHandlerArgs, ProcedureRecord, ProcedureType, RestRouteOverride, } from './types.js';
|
|
43
43
|
export { PROCEDURE_METHOD_MAP, } from './types.js';
|
|
44
44
|
export type { GuardErrorResponse, RouterErrorCode } from './errors.js';
|
|
45
45
|
export { GuardError, isGuardError } from './errors.js';
|
|
@@ -66,7 +66,7 @@ export { serve } from './expose.js';
|
|
|
66
66
|
*
|
|
67
67
|
* @example
|
|
68
68
|
* ```typescript
|
|
69
|
-
* import { generateOpenApiSpec,
|
|
69
|
+
* import { generateOpenApiSpec, swaggerPlugin } from '@veloxts/router';
|
|
70
70
|
*
|
|
71
71
|
* // Generate spec programmatically
|
|
72
72
|
* const spec = generateOpenApiSpec([userProcedures], {
|
|
@@ -74,7 +74,7 @@ export { serve } from './expose.js';
|
|
|
74
74
|
* });
|
|
75
75
|
*
|
|
76
76
|
* // Or serve Swagger UI
|
|
77
|
-
* app.register(
|
|
77
|
+
* app.register(swaggerPlugin, {
|
|
78
78
|
* routePrefix: '/docs',
|
|
79
79
|
* collections: [userProcedures],
|
|
80
80
|
* openapi: { info: { title: 'My API', version: '1.0.0' } },
|
|
@@ -82,8 +82,8 @@ export { serve } from './expose.js';
|
|
|
82
82
|
* ```
|
|
83
83
|
*/
|
|
84
84
|
export type { BuildParametersOptions, BuildParametersResult, GuardMappingOptions, JSONSchema, OpenAPIComponents, OpenAPIContact, OpenAPIEncoding, OpenAPIExample, OpenAPIExternalDocs, OpenAPIGeneratorOptions, OpenAPIHeader, OpenAPIHttpMethod, OpenAPIInfo, OpenAPILicense, OpenAPILink, OpenAPIMediaType, OpenAPIOAuthFlow, OpenAPIOAuthFlows, OpenAPIOperation, OpenAPIParameter, OpenAPIPathItem, OpenAPIRequestBody, OpenAPIResponse, OpenAPISecurityRequirement, OpenAPISecurityScheme, OpenAPIServer, OpenAPISpec, OpenAPITag, ParameterIn, QueryParamExtractionOptions, RouteInfo, SchemaConversionOptions, SecuritySchemeType, SwaggerUIConfig, SwaggerUIHtmlOptions, SwaggerUIPluginOptions, } from './openapi/index.js';
|
|
85
|
-
export { buildParameters, convertFromOpenAPIPath, convertToOpenAPIPath, createSecurityRequirement, createStringSchema,
|
|
86
|
-
export type { AccessLevel, ADMIN, AdminOutput, AdminTaggedContext, ANONYMOUS, AnonymousOutput, AnonymousTaggedContext, AnyResourceOutput, AUTHENTICATED, AuthenticatedOutput, AuthenticatedTaggedContext, BuilderField, ContextTag, ExtractTag, HasTag, IfAdmin, IfAuthenticated, InferResourceData, InferResourceOutput, IsVisibleToTag, OutputForTag, RelationField, ResourceField, ResourceSchema, RuntimeField, TaggedContext, VisibilityLevel, WithTag, } from './resource/index.js';
|
|
85
|
+
export { buildParameters, convertFromOpenAPIPath, convertToOpenAPIPath, createSecurityRequirement, createStringSchema, DEFAULT_GUARD_MAPPINGS, DEFAULT_SECURITY_SCHEMES, DEFAULT_UI_CONFIG, escapeHtml, extractGuardScopes, extractPathParamNames, extractQueryParameters, extractResourceFromPath, extractSchemaProperties, extractUsedSecuritySchemes, filterUsedSecuritySchemes, generateOpenApiSpec, generateSwaggerUIHtml, getOpenApiRouteSummary, getOpenApiSpec, guardsRequireAuth, guardsToSecurity, hasPathParameters, joinPaths, mapGuardToSecurity, mergeSchemas, mergeSecuritySchemes, normalizePath, parsePathParameters, removeSchemaProperties, SWAGGER_UI_CDN, schemaHasProperties, swaggerPlugin, validateOpenApiSpec, zodSchemaToJsonSchema, } from './openapi/index.js';
|
|
86
|
+
export type { AccessLevel, AccessLevelConfig, ADMIN, AdminOutput, AdminTaggedContext, ANONYMOUS, AnonymousOutput, AnonymousTaggedContext, AnyResourceOutput, AUTHENTICATED, AuthenticatedOutput, AuthenticatedTaggedContext, BuilderField, ContextTag, CustomResourceSchemaWithViews, CustomSchemaBuilder, ExtractTag, FilterFieldsByLevel, HasTag, IfAdmin, IfAuthenticated, InferResourceData, InferResourceOutput, IsVisibleToTag, LevelToTag, OutputForLevel, OutputForTag, PUBLIC, PublicOutput, PublicTaggedContext, RelationField, ResourceField, ResourceSchema, ResourceSchemaWithViews, RuntimeField, TaggedContext, TagToLevel, VisibilityLevel, WithTag, } from './resource/index.js';
|
|
87
87
|
/**
|
|
88
88
|
* Resource API for context-dependent output types using phantom types.
|
|
89
89
|
*
|
|
@@ -109,9 +109,9 @@ export type { AccessLevel, ADMIN, AdminOutput, AdminTaggedContext, ANONYMOUS, An
|
|
|
109
109
|
* .input(z.object({ id: z.string() }))
|
|
110
110
|
* .query(async ({ input, ctx }) => {
|
|
111
111
|
* const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
112
|
-
* return resource(user, UserSchema).
|
|
112
|
+
* return resource(user, UserSchema).forPublic();
|
|
113
113
|
* }),
|
|
114
114
|
* });
|
|
115
115
|
* ```
|
|
116
116
|
*/
|
|
117
|
-
export { getAccessibleLevels, getVisibilityForTag, isResourceSchema, isVisibleAtLevel, Resource, ResourceCollection, ResourceSchemaBuilder, resource, resourceCollection, resourceSchema, } from './resource/index.js';
|
|
117
|
+
export { defineAccessLevels, getAccessibleLevels, getVisibilityForTag, isFieldVisibleToLevel, isResourceSchema, isVisibleAtLevel, Resource, ResourceCollection, ResourceSchemaBuilder, resource, resourceCollection, resourceSchema, } from './resource/index.js';
|
package/dist/index.js
CHANGED
|
@@ -71,13 +71,13 @@ buildParameters, convertFromOpenAPIPath, convertToOpenAPIPath,
|
|
|
71
71
|
// Security mapper
|
|
72
72
|
createSecurityRequirement,
|
|
73
73
|
// Schema converter
|
|
74
|
-
createStringSchema,
|
|
75
|
-
// Plugin
|
|
76
|
-
createSwaggerUI, DEFAULT_GUARD_MAPPINGS, DEFAULT_SECURITY_SCHEMES, DEFAULT_UI_CONFIG, escapeHtml, extractGuardScopes, extractPathParamNames, extractQueryParameters, extractResourceFromPath, extractSchemaProperties, extractUsedSecuritySchemes, filterUsedSecuritySchemes,
|
|
74
|
+
createStringSchema, DEFAULT_GUARD_MAPPINGS, DEFAULT_SECURITY_SCHEMES, DEFAULT_UI_CONFIG, escapeHtml, extractGuardScopes, extractPathParamNames, extractQueryParameters, extractResourceFromPath, extractSchemaProperties, extractUsedSecuritySchemes, filterUsedSecuritySchemes,
|
|
77
75
|
// Generator
|
|
78
76
|
generateOpenApiSpec,
|
|
79
77
|
// HTML Generator
|
|
80
|
-
generateSwaggerUIHtml, getOpenApiRouteSummary, getOpenApiSpec, guardsRequireAuth, guardsToSecurity, hasPathParameters, joinPaths, mapGuardToSecurity, mergeSchemas, mergeSecuritySchemes, normalizePath, parsePathParameters,
|
|
78
|
+
generateSwaggerUIHtml, getOpenApiRouteSummary, getOpenApiSpec, guardsRequireAuth, guardsToSecurity, hasPathParameters, joinPaths, mapGuardToSecurity, mergeSchemas, mergeSecuritySchemes, normalizePath, parsePathParameters, removeSchemaProperties, SWAGGER_UI_CDN, schemaHasProperties,
|
|
79
|
+
// Plugin
|
|
80
|
+
swaggerPlugin, validateOpenApiSpec, zodSchemaToJsonSchema, } from './openapi/index.js';
|
|
81
81
|
/**
|
|
82
82
|
* Resource API for context-dependent output types using phantom types.
|
|
83
83
|
*
|
|
@@ -103,12 +103,14 @@ generateSwaggerUIHtml, getOpenApiRouteSummary, getOpenApiSpec, guardsRequireAuth
|
|
|
103
103
|
* .input(z.object({ id: z.string() }))
|
|
104
104
|
* .query(async ({ input, ctx }) => {
|
|
105
105
|
* const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
106
|
-
* return resource(user, UserSchema).
|
|
106
|
+
* return resource(user, UserSchema).forPublic();
|
|
107
107
|
* }),
|
|
108
108
|
* });
|
|
109
109
|
* ```
|
|
110
110
|
*/
|
|
111
|
-
export {
|
|
111
|
+
export {
|
|
112
|
+
// Access level configuration
|
|
113
|
+
defineAccessLevels, getAccessibleLevels, getVisibilityForTag, isFieldVisibleToLevel, isResourceSchema,
|
|
112
114
|
// Visibility
|
|
113
115
|
isVisibleAtLevel, Resource, ResourceCollection, ResourceSchemaBuilder,
|
|
114
116
|
// Resource instances
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { generateRestRoutes } from '../rest/adapter.js';
|
|
9
9
|
import { buildParameters, convertToOpenAPIPath, joinPaths } from './path-extractor.js';
|
|
10
|
-
import { removeSchemaProperties, zodSchemaToJsonSchema } from './schema-converter.js';
|
|
10
|
+
import { removeSchemaProperties, resourceSchemaToJsonSchema, zodSchemaToJsonSchema, } from './schema-converter.js';
|
|
11
11
|
import { extractUsedSecuritySchemes, filterUsedSecuritySchemes, guardsToSecurity, mergeSecuritySchemes, } from './security-mapper.js';
|
|
12
12
|
// ============================================================================
|
|
13
13
|
// Main Generator
|
|
@@ -110,9 +110,14 @@ function generateOperation(route, namespace, options) {
|
|
|
110
110
|
const inputSchema = procedure.inputSchema
|
|
111
111
|
? zodSchemaToJsonSchema(procedure.inputSchema)
|
|
112
112
|
: undefined;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
let outputSchema;
|
|
114
|
+
if (procedure.outputSchema) {
|
|
115
|
+
outputSchema = zodSchemaToJsonSchema(procedure.outputSchema);
|
|
116
|
+
}
|
|
117
|
+
else if (procedure._resourceSchema) {
|
|
118
|
+
const level = procedure._resourceLevel ?? 'public';
|
|
119
|
+
outputSchema = resourceSchemaToJsonSchema(procedure._resourceSchema, level);
|
|
120
|
+
}
|
|
116
121
|
// Build parameters
|
|
117
122
|
const { pathParams, queryParams, pathParamNames } = buildParameters({
|
|
118
123
|
path,
|
package/dist/openapi/index.d.ts
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
* ```typescript
|
|
10
10
|
* import {
|
|
11
11
|
* generateOpenApiSpec,
|
|
12
|
-
*
|
|
13
|
-
* createSwaggerUI,
|
|
12
|
+
* swaggerPlugin,
|
|
14
13
|
* } from '@veloxts/router';
|
|
15
14
|
*
|
|
16
15
|
* // Generate spec programmatically
|
|
@@ -20,7 +19,7 @@
|
|
|
20
19
|
* });
|
|
21
20
|
*
|
|
22
21
|
* // Or register Swagger UI plugin
|
|
23
|
-
* app.register(
|
|
22
|
+
* app.register(swaggerPlugin, {
|
|
24
23
|
* routePrefix: '/docs',
|
|
25
24
|
* collections: [userProcedures],
|
|
26
25
|
* openapi: {
|
|
@@ -30,7 +29,7 @@
|
|
|
30
29
|
* ```
|
|
31
30
|
*/
|
|
32
31
|
export { generateOpenApiSpec, getOpenApiRouteSummary, validateOpenApiSpec, } from './generator.js';
|
|
33
|
-
export {
|
|
32
|
+
export { getOpenApiSpec, swaggerPlugin, } from './plugin.js';
|
|
34
33
|
export { DEFAULT_UI_CONFIG, escapeHtml, generateSwaggerUIHtml, SWAGGER_UI_CDN, type SwaggerUIHtmlOptions, } from './html-generator.js';
|
|
35
34
|
export { createStringSchema, extractSchemaProperties, mergeSchemas, removeSchemaProperties, type SchemaConversionOptions, schemaHasProperties, zodSchemaToJsonSchema, } from './schema-converter.js';
|
|
36
35
|
export { type BuildParametersOptions, type BuildParametersResult, buildParameters, convertFromOpenAPIPath, convertToOpenAPIPath, extractPathParamNames, extractQueryParameters, extractResourceFromPath, hasPathParameters, joinPaths, normalizePath, parsePathParameters, type QueryParamExtractionOptions, } from './path-extractor.js';
|
package/dist/openapi/index.js
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
* ```typescript
|
|
10
10
|
* import {
|
|
11
11
|
* generateOpenApiSpec,
|
|
12
|
-
*
|
|
13
|
-
* createSwaggerUI,
|
|
12
|
+
* swaggerPlugin,
|
|
14
13
|
* } from '@veloxts/router';
|
|
15
14
|
*
|
|
16
15
|
* // Generate spec programmatically
|
|
@@ -20,7 +19,7 @@
|
|
|
20
19
|
* });
|
|
21
20
|
*
|
|
22
21
|
* // Or register Swagger UI plugin
|
|
23
|
-
* app.register(
|
|
22
|
+
* app.register(swaggerPlugin, {
|
|
24
23
|
* routePrefix: '/docs',
|
|
25
24
|
* collections: [userProcedures],
|
|
26
25
|
* openapi: {
|
|
@@ -36,7 +35,7 @@ export { generateOpenApiSpec, getOpenApiRouteSummary, validateOpenApiSpec, } fro
|
|
|
36
35
|
// ============================================================================
|
|
37
36
|
// Plugin
|
|
38
37
|
// ============================================================================
|
|
39
|
-
export {
|
|
38
|
+
export { getOpenApiSpec, swaggerPlugin, } from './plugin.js';
|
|
40
39
|
// ============================================================================
|
|
41
40
|
// HTML Generator
|
|
42
41
|
// ============================================================================
|
package/dist/openapi/plugin.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ import type { OpenAPISpec, SwaggerUIPluginOptions } from './types.js';
|
|
|
14
14
|
*
|
|
15
15
|
* @example
|
|
16
16
|
* ```typescript
|
|
17
|
-
* import {
|
|
17
|
+
* import { swaggerPlugin } from '@veloxts/router';
|
|
18
18
|
*
|
|
19
|
-
* app.register(
|
|
19
|
+
* app.register(swaggerPlugin, {
|
|
20
20
|
* routePrefix: '/docs',
|
|
21
21
|
* collections: [userProcedures, postProcedures],
|
|
22
22
|
* openapi: {
|
|
@@ -30,51 +30,7 @@ import type { OpenAPISpec, SwaggerUIPluginOptions } from './types.js';
|
|
|
30
30
|
* });
|
|
31
31
|
* ```
|
|
32
32
|
*/
|
|
33
|
-
export declare const
|
|
34
|
-
/**
|
|
35
|
-
* Creates a Swagger UI plugin with pre-configured options
|
|
36
|
-
*
|
|
37
|
-
* @param options - Plugin options
|
|
38
|
-
* @returns Configured plugin
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```typescript
|
|
42
|
-
* import { createSwaggerUI } from '@veloxts/router';
|
|
43
|
-
*
|
|
44
|
-
* const docs = createSwaggerUI({
|
|
45
|
-
* collections: [userProcedures],
|
|
46
|
-
* openapi: {
|
|
47
|
-
* info: { title: 'My API', version: '1.0.0' },
|
|
48
|
-
* },
|
|
49
|
-
* });
|
|
50
|
-
*
|
|
51
|
-
* app.register(docs);
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
export declare function createSwaggerUI(options: SwaggerUIPluginOptions): FastifyPluginAsync<SwaggerUIPluginOptions>;
|
|
55
|
-
/**
|
|
56
|
-
* Registers multiple procedure collections with Swagger UI
|
|
57
|
-
*
|
|
58
|
-
* Convenience function that sets up both REST routes and documentation.
|
|
59
|
-
*
|
|
60
|
-
* @param fastify - Fastify instance
|
|
61
|
-
* @param options - Documentation options
|
|
62
|
-
*
|
|
63
|
-
* @example
|
|
64
|
-
* ```typescript
|
|
65
|
-
* import { registerDocs } from '@veloxts/router';
|
|
66
|
-
*
|
|
67
|
-
* await registerDocs(app, {
|
|
68
|
-
* collections: [userProcedures, postProcedures],
|
|
69
|
-
* openapi: {
|
|
70
|
-
* info: { title: 'My API', version: '1.0.0' },
|
|
71
|
-
* },
|
|
72
|
-
* });
|
|
73
|
-
* ```
|
|
74
|
-
*/
|
|
75
|
-
export declare function registerDocs(fastify: {
|
|
76
|
-
register: (plugin: FastifyPluginAsync<SwaggerUIPluginOptions>, options: SwaggerUIPluginOptions) => Promise<void>;
|
|
77
|
-
}, options: SwaggerUIPluginOptions): Promise<void>;
|
|
33
|
+
export declare const swaggerPlugin: FastifyPluginAsync<SwaggerUIPluginOptions>;
|
|
78
34
|
/**
|
|
79
35
|
* Gets the generated OpenAPI specification without registering routes
|
|
80
36
|
*
|
package/dist/openapi/plugin.js
CHANGED
|
@@ -17,9 +17,9 @@ import { generateSwaggerUIHtml } from './html-generator.js';
|
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
19
|
* ```typescript
|
|
20
|
-
* import {
|
|
20
|
+
* import { swaggerPlugin } from '@veloxts/router';
|
|
21
21
|
*
|
|
22
|
-
* app.register(
|
|
22
|
+
* app.register(swaggerPlugin, {
|
|
23
23
|
* routePrefix: '/docs',
|
|
24
24
|
* collections: [userProcedures, postProcedures],
|
|
25
25
|
* openapi: {
|
|
@@ -33,7 +33,7 @@ import { generateSwaggerUIHtml } from './html-generator.js';
|
|
|
33
33
|
* });
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
|
-
export const
|
|
36
|
+
export const swaggerPlugin = async (fastify, options) => {
|
|
37
37
|
const { routePrefix = '/docs', specRoute = `${routePrefix}/openapi.json`, uiConfig = {}, openapi, collections, title = 'API Documentation', favicon, } = options;
|
|
38
38
|
// Generate the OpenAPI specification
|
|
39
39
|
let spec;
|
|
@@ -69,54 +69,6 @@ export const swaggerUIPlugin = async (fastify, options) => {
|
|
|
69
69
|
// ============================================================================
|
|
70
70
|
// Utility Functions
|
|
71
71
|
// ============================================================================
|
|
72
|
-
/**
|
|
73
|
-
* Creates a Swagger UI plugin with pre-configured options
|
|
74
|
-
*
|
|
75
|
-
* @param options - Plugin options
|
|
76
|
-
* @returns Configured plugin
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* ```typescript
|
|
80
|
-
* import { createSwaggerUI } from '@veloxts/router';
|
|
81
|
-
*
|
|
82
|
-
* const docs = createSwaggerUI({
|
|
83
|
-
* collections: [userProcedures],
|
|
84
|
-
* openapi: {
|
|
85
|
-
* info: { title: 'My API', version: '1.0.0' },
|
|
86
|
-
* },
|
|
87
|
-
* });
|
|
88
|
-
*
|
|
89
|
-
* app.register(docs);
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
export function createSwaggerUI(options) {
|
|
93
|
-
return async (fastify) => {
|
|
94
|
-
await swaggerUIPlugin(fastify, options);
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Registers multiple procedure collections with Swagger UI
|
|
99
|
-
*
|
|
100
|
-
* Convenience function that sets up both REST routes and documentation.
|
|
101
|
-
*
|
|
102
|
-
* @param fastify - Fastify instance
|
|
103
|
-
* @param options - Documentation options
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* ```typescript
|
|
107
|
-
* import { registerDocs } from '@veloxts/router';
|
|
108
|
-
*
|
|
109
|
-
* await registerDocs(app, {
|
|
110
|
-
* collections: [userProcedures, postProcedures],
|
|
111
|
-
* openapi: {
|
|
112
|
-
* info: { title: 'My API', version: '1.0.0' },
|
|
113
|
-
* },
|
|
114
|
-
* });
|
|
115
|
-
* ```
|
|
116
|
-
*/
|
|
117
|
-
export async function registerDocs(fastify, options) {
|
|
118
|
-
await fastify.register(swaggerUIPlugin, options);
|
|
119
|
-
}
|
|
120
72
|
/**
|
|
121
73
|
* Gets the generated OpenAPI specification without registering routes
|
|
122
74
|
*
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* @module @veloxts/router/openapi/schema-converter
|
|
7
7
|
*/
|
|
8
8
|
import { type ZodType } from 'zod';
|
|
9
|
+
import type { ResourceSchema } from '../resource/schema.js';
|
|
9
10
|
import type { JSONSchema } from './types.js';
|
|
10
11
|
/**
|
|
11
12
|
* Options for Zod to JSON Schema conversion
|
|
@@ -100,3 +101,15 @@ export declare function createStringSchema(format?: string): JSONSchema;
|
|
|
100
101
|
* Checks if a schema has any properties
|
|
101
102
|
*/
|
|
102
103
|
export declare function schemaHasProperties(schema: JSONSchema | undefined): boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Converts a ResourceSchema to JSON Schema for OpenAPI, filtered by visibility level
|
|
106
|
+
*
|
|
107
|
+
* Iterates the resource's field definitions and includes only fields
|
|
108
|
+
* visible at the given access level. Nested resource schemas are
|
|
109
|
+
* converted recursively.
|
|
110
|
+
*
|
|
111
|
+
* @param schema - The resource schema
|
|
112
|
+
* @param level - The access level to generate documentation for (defaults to 'public')
|
|
113
|
+
* @returns JSON Schema with only the fields visible at the given level
|
|
114
|
+
*/
|
|
115
|
+
export declare function resourceSchemaToJsonSchema(schema: ResourceSchema, level?: string): JSONSchema;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { createLogger } from '@veloxts/core';
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
+
import { isFieldVisibleToLevel } from '../resource/visibility.js';
|
|
10
11
|
const log = createLogger('router');
|
|
11
12
|
/**
|
|
12
13
|
* Maps our target names to Zod 4's native `z.toJSONSchema()` target names.
|
|
@@ -257,3 +258,53 @@ export function schemaHasProperties(schema) {
|
|
|
257
258
|
return false;
|
|
258
259
|
return Object.keys(schema.properties).length > 0;
|
|
259
260
|
}
|
|
261
|
+
// ============================================================================
|
|
262
|
+
// Resource Schema to JSON Schema
|
|
263
|
+
// ============================================================================
|
|
264
|
+
/**
|
|
265
|
+
* Converts a ResourceSchema to JSON Schema for OpenAPI, filtered by visibility level
|
|
266
|
+
*
|
|
267
|
+
* Iterates the resource's field definitions and includes only fields
|
|
268
|
+
* visible at the given access level. Nested resource schemas are
|
|
269
|
+
* converted recursively.
|
|
270
|
+
*
|
|
271
|
+
* @param schema - The resource schema
|
|
272
|
+
* @param level - The access level to generate documentation for (defaults to 'public')
|
|
273
|
+
* @returns JSON Schema with only the fields visible at the given level
|
|
274
|
+
*/
|
|
275
|
+
export function resourceSchemaToJsonSchema(schema, level = 'public') {
|
|
276
|
+
const properties = {};
|
|
277
|
+
const required = [];
|
|
278
|
+
for (const field of schema.fields) {
|
|
279
|
+
if (!isFieldVisibleToLevel(field, level)) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (field.nestedSchema) {
|
|
283
|
+
// Nested relation — recurse
|
|
284
|
+
const nestedJsonSchema = resourceSchemaToJsonSchema(field.nestedSchema, level);
|
|
285
|
+
if (field.cardinality === 'many') {
|
|
286
|
+
properties[field.name] = { type: 'array', items: nestedJsonSchema };
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
properties[field.name] = { ...nestedJsonSchema, nullable: true };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else if (field.schema) {
|
|
293
|
+
// Scalar field with Zod schema
|
|
294
|
+
const fieldJsonSchema = zodSchemaToJsonSchema(field.schema);
|
|
295
|
+
if (fieldJsonSchema) {
|
|
296
|
+
properties[field.name] = fieldJsonSchema;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
// Field without schema — generic
|
|
301
|
+
properties[field.name] = {};
|
|
302
|
+
}
|
|
303
|
+
required.push(field.name);
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
type: 'object',
|
|
307
|
+
properties,
|
|
308
|
+
...(required.length > 0 ? { required } : {}),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
@@ -85,15 +85,26 @@ function createBuilder(state) {
|
|
|
85
85
|
});
|
|
86
86
|
},
|
|
87
87
|
/**
|
|
88
|
-
* Sets the output validation schema
|
|
88
|
+
* Sets the output validation schema (Zod-only)
|
|
89
89
|
*/
|
|
90
90
|
output(schema) {
|
|
91
|
-
// Return new builder with updated output schema
|
|
92
91
|
return createBuilder({
|
|
93
92
|
...state,
|
|
94
93
|
outputSchema: schema,
|
|
95
94
|
});
|
|
96
95
|
},
|
|
96
|
+
/**
|
|
97
|
+
* Sets field-level visibility via a resource schema
|
|
98
|
+
*/
|
|
99
|
+
expose(schema) {
|
|
100
|
+
const level = isTaggedResourceSchema(schema) ? schema._level : undefined;
|
|
101
|
+
return createBuilder({
|
|
102
|
+
...state,
|
|
103
|
+
resourceSchema: schema,
|
|
104
|
+
resourceLevel: level,
|
|
105
|
+
outputSchema: undefined,
|
|
106
|
+
});
|
|
107
|
+
},
|
|
97
108
|
/**
|
|
98
109
|
* Adds middleware to the chain
|
|
99
110
|
*/
|
|
@@ -151,6 +162,16 @@ function createBuilder(state) {
|
|
|
151
162
|
restOverride: config,
|
|
152
163
|
});
|
|
153
164
|
},
|
|
165
|
+
/**
|
|
166
|
+
* Configures the procedure as a webhook endpoint
|
|
167
|
+
*/
|
|
168
|
+
webhook(path) {
|
|
169
|
+
return createBuilder({
|
|
170
|
+
...state,
|
|
171
|
+
restOverride: { method: 'POST', path },
|
|
172
|
+
isWebhook: true,
|
|
173
|
+
});
|
|
174
|
+
},
|
|
154
175
|
/**
|
|
155
176
|
* Marks the procedure as deprecated
|
|
156
177
|
*/
|
|
@@ -200,10 +221,7 @@ function createBuilder(state) {
|
|
|
200
221
|
return compileProcedure('mutation', handler, state);
|
|
201
222
|
},
|
|
202
223
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* Accepts either a plain `ResourceSchema` or a tagged schema
|
|
206
|
-
* (e.g., `UserSchema.authenticated`) for declarative auto-projection.
|
|
224
|
+
* @deprecated Use `.expose()` instead. `.resource()` will be removed in v1.0.
|
|
207
225
|
*/
|
|
208
226
|
resource(schema) {
|
|
209
227
|
const level = isTaggedResourceSchema(schema) ? schema._level : undefined;
|
|
@@ -211,6 +229,7 @@ function createBuilder(state) {
|
|
|
211
229
|
...state,
|
|
212
230
|
resourceSchema: schema,
|
|
213
231
|
resourceLevel: level,
|
|
232
|
+
outputSchema: undefined,
|
|
214
233
|
});
|
|
215
234
|
},
|
|
216
235
|
};
|
|
@@ -240,6 +259,7 @@ function compileProcedure(type, handler, state) {
|
|
|
240
259
|
restOverride: state.restOverride,
|
|
241
260
|
deprecated: state.deprecated,
|
|
242
261
|
deprecationMessage: state.deprecationMessage,
|
|
262
|
+
isWebhook: state.isWebhook,
|
|
243
263
|
parentResource: state.parentResource,
|
|
244
264
|
parentResources: state.parentResources,
|
|
245
265
|
// Store pre-compiled executor for performance
|
|
@@ -413,14 +433,14 @@ export async function executeProcedure(procedure, rawInput, ctx) {
|
|
|
413
433
|
const message = guard.message ?? `Guard "${guard.name}" check failed`;
|
|
414
434
|
throw new GuardError(guard.name, message, statusCode);
|
|
415
435
|
}
|
|
416
|
-
// Track
|
|
436
|
+
// Track access level from narrowing guards.
|
|
437
|
+
// IMPORTANT: last guard's accessLevel wins. With custom levels that
|
|
438
|
+
// have no inherent hierarchy, ordering of guards matters.
|
|
439
|
+
// Guards without accessLevel (e.g. plain `authenticated`) do NOT
|
|
440
|
+
// update the level — it stays at 'public' for .expose() projection.
|
|
417
441
|
const guardWithLevel = guard;
|
|
418
442
|
if (guardWithLevel.accessLevel) {
|
|
419
|
-
|
|
420
|
-
if (guardWithLevel.accessLevel === 'admin' ||
|
|
421
|
-
(guardWithLevel.accessLevel === 'authenticated' && accessLevel === 'public')) {
|
|
422
|
-
accessLevel = guardWithLevel.accessLevel;
|
|
423
|
-
}
|
|
443
|
+
accessLevel = guardWithLevel.accessLevel;
|
|
424
444
|
}
|
|
425
445
|
}
|
|
426
446
|
}
|
|
@@ -451,15 +471,7 @@ export async function executeProcedure(procedure, rawInput, ctx) {
|
|
|
451
471
|
// Prefer explicit level from tagged schema over guard-derived level
|
|
452
472
|
const finalLevel = procedure._resourceLevel ?? accessLevel;
|
|
453
473
|
const projectOne = (item) => {
|
|
454
|
-
|
|
455
|
-
switch (finalLevel) {
|
|
456
|
-
case 'admin':
|
|
457
|
-
return r.forAdmin();
|
|
458
|
-
case 'authenticated':
|
|
459
|
-
return r.forAuthenticated();
|
|
460
|
-
default:
|
|
461
|
-
return r.forAnonymous();
|
|
462
|
-
}
|
|
474
|
+
return new Resource(item, schema).forLevel(finalLevel);
|
|
463
475
|
};
|
|
464
476
|
if (Array.isArray(result)) {
|
|
465
477
|
result = result.map((item) => projectOne(item));
|