@veloxts/router 0.7.8 → 0.8.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/CHANGELOG.md +21 -0
- package/GUIDE.md +30 -48
- package/README.md +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/openapi/generator.d.ts +25 -0
- package/dist/openapi/generator.js +49 -9
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +1 -1
- package/dist/openapi/plugin.d.ts +11 -5
- package/dist/openapi/plugin.js +33 -9
- package/dist/openapi/schema-converter.d.ts +11 -0
- package/dist/openapi/schema-converter.js +47 -0
- package/dist/openapi/types.d.ts +5 -2
- package/dist/procedure/builder.js +184 -43
- package/dist/procedure/types.d.ts +33 -88
- package/dist/resource/index.d.ts +4 -4
- package/dist/resource/index.js +3 -3
- package/dist/resource/instance.d.ts +1 -1
- package/dist/resource/instance.js +1 -1
- package/dist/resource/levels.d.ts +93 -11
- package/dist/resource/levels.js +78 -3
- package/dist/resource/schema.d.ts +2 -0
- package/dist/resource/schema.js +4 -4
- package/dist/resource/tags.d.ts +5 -7
- package/dist/resource/tags.js +1 -1
- package/dist/rest/adapter.js +6 -0
- package/dist/rest/index.d.ts +2 -0
- package/dist/rest/index.js +1 -0
- package/dist/rest/registry.d.ts +39 -0
- package/dist/rest/registry.js +43 -0
- package/dist/trpc/adapter.js +5 -0
- package/dist/types.d.ts +16 -8
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @veloxts/router
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- feat(router): new simplified procedure builder
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @veloxts/core@0.8.0
|
|
13
|
+
- @veloxts/validation@0.8.0
|
|
14
|
+
|
|
15
|
+
## 0.7.9
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- feat(router): swagger auto-discovery of module collections
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- @veloxts/core@0.7.9
|
|
22
|
+
- @veloxts/validation@0.7.9
|
|
23
|
+
|
|
3
24
|
## 0.7.8
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/GUIDE.md
CHANGED
|
@@ -28,11 +28,9 @@ 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
|
-
- `.expose(schema)` - Context-dependent output with resource schema
|
|
31
|
+
- `.output(schema)` - Validate output with Zod schema or tagged resource view
|
|
33
32
|
- `.use(middleware)` - Add middleware
|
|
34
33
|
- `.guard(guard)` - Add authorization guard
|
|
35
|
-
- `.guardNarrow(guard)` - Add guard with TypeScript type narrowing
|
|
36
34
|
- `.rest({ method, path })` - Override REST path
|
|
37
35
|
- `.query(handler)` - Finalize as read operation
|
|
38
36
|
- `.mutation(handler)` - Finalize as write operation
|
|
@@ -405,7 +403,7 @@ console.table(routes);
|
|
|
405
403
|
|
|
406
404
|
## Resource API (Context-Dependent Outputs)
|
|
407
405
|
|
|
408
|
-
The Resource API provides context-dependent output types using phantom types. Pass a resource
|
|
406
|
+
The Resource API provides context-dependent output types using phantom types. Pass a tagged resource view to `.output()` instead of a plain Zod schema to define field visibility per access level.
|
|
409
407
|
|
|
410
408
|
### Defining a Resource Schema
|
|
411
409
|
|
|
@@ -423,28 +421,27 @@ const UserSchema = resourceSchema()
|
|
|
423
421
|
.build();
|
|
424
422
|
```
|
|
425
423
|
|
|
426
|
-
### Automatic Projection (
|
|
424
|
+
### Automatic Projection with `.output()` and Guards
|
|
427
425
|
|
|
428
|
-
The most elegant approach is to chain `.
|
|
426
|
+
The most elegant approach is to chain `.guard()` with `.output()` using a tagged resource view. The procedure builder uses the guard's access level to determine which fields to include:
|
|
429
427
|
|
|
430
428
|
```typescript
|
|
431
|
-
import {
|
|
429
|
+
import { authenticated, hasRole } from '@veloxts/auth';
|
|
432
430
|
|
|
433
431
|
export const userProcedures = procedures('users', {
|
|
434
|
-
// Authenticated endpoint -
|
|
432
|
+
// Authenticated endpoint - projects { id, name, email, createdAt }
|
|
435
433
|
getProfile: procedure()
|
|
436
|
-
.
|
|
437
|
-
.
|
|
434
|
+
.guard(authenticated)
|
|
435
|
+
.output(UserSchema.authenticated)
|
|
438
436
|
.input(z.object({ id: z.string().uuid() }))
|
|
439
437
|
.query(async ({ input, ctx }) => {
|
|
440
|
-
// Just return the full data - projection is automatic!
|
|
441
438
|
return ctx.db.user.findUniqueOrThrow({ where: { id: input.id } });
|
|
442
439
|
}),
|
|
443
440
|
|
|
444
|
-
// Admin endpoint -
|
|
441
|
+
// Admin endpoint - projects all fields
|
|
445
442
|
getFullProfile: procedure()
|
|
446
|
-
.
|
|
447
|
-
.
|
|
443
|
+
.guard(hasRole('admin'))
|
|
444
|
+
.output(UserSchema.admin)
|
|
448
445
|
.input(z.object({ id: z.string().uuid() }))
|
|
449
446
|
.query(async ({ input, ctx }) => {
|
|
450
447
|
return ctx.db.user.findUniqueOrThrow({ where: { id: input.id } });
|
|
@@ -454,14 +451,13 @@ export const userProcedures = procedures('users', {
|
|
|
454
451
|
|
|
455
452
|
**How it works:**
|
|
456
453
|
|
|
457
|
-
1. The
|
|
458
|
-
2.
|
|
459
|
-
3.
|
|
460
|
-
4. No manual `.forX()` calls needed - the output type is inferred at compile time
|
|
454
|
+
1. The `.output()` method accepts both plain Zod schemas and tagged resource views (e.g., `UserSchema.authenticated`)
|
|
455
|
+
2. When a tagged view is passed, the procedure executor automatically projects the handler's return value
|
|
456
|
+
3. The output type is inferred at compile time from the tagged view
|
|
461
457
|
|
|
462
458
|
**Benefits:**
|
|
463
459
|
- Clean, declarative code - no projection logic in handlers
|
|
464
|
-
- Type-safe - return types are inferred from
|
|
460
|
+
- Type-safe - return types are inferred from the tagged view
|
|
465
461
|
- Less boilerplate - handlers just return data
|
|
466
462
|
- Consistent - impossible to forget projection
|
|
467
463
|
|
|
@@ -516,10 +512,10 @@ export const userProcedures = procedures('users', {
|
|
|
516
512
|
For arrays of items, use `resourceCollection()`:
|
|
517
513
|
|
|
518
514
|
```typescript
|
|
519
|
-
// Automatic projection (
|
|
515
|
+
// Automatic projection with .output()
|
|
520
516
|
const listUsers = procedure()
|
|
521
|
-
.
|
|
522
|
-
.
|
|
517
|
+
.guard(authenticated)
|
|
518
|
+
.output(UserSchema.authenticated)
|
|
523
519
|
.query(async ({ ctx }) => {
|
|
524
520
|
return ctx.db.user.findMany({ take: 50 });
|
|
525
521
|
});
|
|
@@ -539,7 +535,7 @@ For manual projection with dynamic access level, use `.for(ctx)`:
|
|
|
539
535
|
|
|
540
536
|
```typescript
|
|
541
537
|
const getUser = procedure()
|
|
542
|
-
.
|
|
538
|
+
.guard(authenticated)
|
|
543
539
|
.input(z.object({ id: z.string().uuid() }))
|
|
544
540
|
.query(async ({ input, ctx }) => {
|
|
545
541
|
const user = await ctx.db.user.findUniqueOrThrow({ where: { id: input.id } });
|
|
@@ -562,10 +558,10 @@ const getUser = procedure()
|
|
|
562
558
|
The Resource API provides compile-time type safety:
|
|
563
559
|
|
|
564
560
|
```typescript
|
|
565
|
-
// Automatic projection - type inferred from
|
|
561
|
+
// Automatic projection - type inferred from tagged view
|
|
566
562
|
const getProfile = procedure()
|
|
567
|
-
.
|
|
568
|
-
.
|
|
563
|
+
.guard(authenticated)
|
|
564
|
+
.output(UserSchema.authenticated)
|
|
569
565
|
.query(async ({ ctx }) => {
|
|
570
566
|
return ctx.db.user.findFirst();
|
|
571
567
|
});
|
|
@@ -586,7 +582,7 @@ const adminResult = resource(user, UserSchema.admin);
|
|
|
586
582
|
|
|
587
583
|
| Scenario | Approach |
|
|
588
584
|
|----------|----------|
|
|
589
|
-
| Guard determines access level | **Automatic** (`.
|
|
585
|
+
| Guard determines access level | **Automatic** (`.guard().output(Schema.level)`) |
|
|
590
586
|
| Public endpoints (no guard) | Tagged view (`UserSchema.public`) |
|
|
591
587
|
| Conditional/dynamic projection | Tagged view or `.for(ctx)` in handler |
|
|
592
588
|
| Simple, declarative code | **Automatic** |
|
|
@@ -603,43 +599,29 @@ const getUser = procedure()
|
|
|
603
599
|
.query(handler);
|
|
604
600
|
```
|
|
605
601
|
|
|
606
|
-
## Guard Type Narrowing
|
|
602
|
+
## Guard Type Narrowing
|
|
607
603
|
|
|
608
|
-
|
|
604
|
+
Guards like `authenticated` and `hasRole()` both enforce authorization and narrow the context type. After a guard passes, TypeScript knows `ctx.user` is non-null:
|
|
609
605
|
|
|
610
606
|
```typescript
|
|
611
|
-
import {
|
|
607
|
+
import { authenticated, hasRole } from '@veloxts/auth';
|
|
612
608
|
|
|
613
609
|
// ctx.user is guaranteed non-null after guard passes
|
|
614
610
|
const getProfile = procedure()
|
|
615
|
-
.
|
|
611
|
+
.guard(authenticated)
|
|
616
612
|
.query(({ ctx }) => {
|
|
617
613
|
return { email: ctx.user.email }; // No null check needed!
|
|
618
614
|
});
|
|
619
615
|
|
|
620
|
-
// Chain multiple
|
|
616
|
+
// Chain multiple guards
|
|
621
617
|
const adminAction = procedure()
|
|
622
|
-
.
|
|
623
|
-
.
|
|
618
|
+
.guard(authenticated)
|
|
619
|
+
.guard(hasRole('admin'))
|
|
624
620
|
.mutation(({ ctx }) => {
|
|
625
621
|
// ctx.user is non-null with roles
|
|
626
622
|
});
|
|
627
623
|
```
|
|
628
624
|
|
|
629
|
-
**Note**: This API is experimental. The current stable alternative is to use middleware for context extension:
|
|
630
|
-
|
|
631
|
-
```typescript
|
|
632
|
-
const getProfile = procedure()
|
|
633
|
-
.guard(authenticated)
|
|
634
|
-
.use(async ({ ctx, next }) => {
|
|
635
|
-
if (!ctx.user) throw new Error('Unreachable');
|
|
636
|
-
return next({ ctx: { user: ctx.user } });
|
|
637
|
-
})
|
|
638
|
-
.query(({ ctx }) => {
|
|
639
|
-
// ctx.user is non-null via middleware
|
|
640
|
-
});
|
|
641
|
-
```
|
|
642
|
-
|
|
643
625
|
## Schema Browser-Safety
|
|
644
626
|
|
|
645
627
|
When building full-stack apps, schemas may be imported on both server and client. Avoid importing server-only dependencies in schema files:
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @veloxts/router
|
|
2
2
|
|
|
3
|
-
> **Early Access (v0.
|
|
3
|
+
> **Early Access (v0.8.x)**
|
|
4
4
|
|
|
5
5
|
Procedure-based routing for VeloxTS Framework - provides hybrid tRPC and REST adapters with naming convention-based HTTP method inference. Learn more at [@veloxts/velox](https://www.npmjs.com/package/@veloxts/velox).
|
|
6
6
|
|
package/dist/index.d.ts
CHANGED
|
@@ -46,13 +46,13 @@ export { GuardError, isGuardError } from './errors.js';
|
|
|
46
46
|
export type { DefineProceduresOptions, } from './procedure/builder.js';
|
|
47
47
|
export { defineProcedures, executeProcedure, isCompiledProcedure, isProcedureCollection, procedure, procedures, } from './procedure/builder.js';
|
|
48
48
|
export { createProcedure, typedProcedure } from './procedure/factory.js';
|
|
49
|
-
export type { BuilderRuntimeState, InferProcedures, InferSchemaOutput, ProcedureBuilder, ProcedureBuilderState, ProcedureDefinitions, ValidSchema, } from './procedure/types.js';
|
|
49
|
+
export type { BuilderRuntimeState, InferOutputSchema, InferProcedures, InferSchemaOutput, ProcedureBuilder, ProcedureBuilderState, ProcedureDefinitions, ValidOutputSchema, ValidSchema, } from './procedure/types.js';
|
|
50
50
|
export type { RouterResult } from './router-utils.js';
|
|
51
51
|
export { createRouter, toRouter } from './router-utils.js';
|
|
52
52
|
export type { NamingWarning, NamingWarningType, WarningConfig, WarningOption } from './warnings.js';
|
|
53
53
|
export { analyzeNamingConvention, isDevelopment, normalizeWarningOption } from './warnings.js';
|
|
54
|
-
export type { ExtractRoutesType, GenerateRestRoutesOptions, RestAdapterOptions, RestMapping, RestRoute, RouteMap, } from './rest/index.js';
|
|
55
|
-
export { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, extractRoutes, followsNamingConvention, generateRestRoutes, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
|
|
54
|
+
export type { ExtractRoutesType, GenerateRestRoutesOptions, RegisteredCollection, RestAdapterOptions, RestMapping, RestRoute, RouteMap, } from './rest/index.js';
|
|
55
|
+
export { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, clearCollectionRegistry, extractRoutes, followsNamingConvention, generateRestRoutes, getRegisteredCollections, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
|
|
56
56
|
export type { AnyRouter, CollectionsToRouterRecord, ExtractNamespace, ExtractProcedures, InferRouterFromCollections, MapProcedureRecordToTRPC, MapProcedureToTRPC, TRPCInstance, TRPCPluginOptions, TRPCRouter, } from './trpc/index.js';
|
|
57
57
|
export { appRouter, buildTRPCRouter, createTRPCContextFactory, registerTRPCPlugin, trpc, veloxErrorToTRPCError, } from './trpc/index.js';
|
|
58
58
|
export type { RpcOptions, RpcResult } from './rpc.js';
|
|
@@ -82,7 +82,7 @@ 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, 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';
|
|
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, generateOpenApiSpecFromRegistry, 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
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.
|
|
@@ -114,4 +114,4 @@ export type { AccessLevel, AccessLevelConfig, ADMIN, AdminOutput, AdminTaggedCon
|
|
|
114
114
|
* });
|
|
115
115
|
* ```
|
|
116
116
|
*/
|
|
117
|
-
export { defineAccessLevels, getAccessibleLevels, getVisibilityForTag, isFieldVisibleToLevel, isResourceSchema, isVisibleAtLevel, Resource, ResourceCollection, ResourceSchemaBuilder, resource, resourceCollection, resourceSchema, } from './resource/index.js';
|
|
117
|
+
export { defaultAccess, defineAccessLevels, getAccessibleLevels, getVisibilityForTag, isFieldVisibleToLevel, isResourceSchema, isVisibleAtLevel, Resource, ResourceCollection, ResourceSchemaBuilder, resource, resourceCollection, resourceSchema, } from './resource/index.js';
|
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ defineProcedures, executeProcedure, isCompiledProcedure, isProcedureCollection,
|
|
|
58
58
|
export { createProcedure, typedProcedure } from './procedure/factory.js';
|
|
59
59
|
export { createRouter, toRouter } from './router-utils.js';
|
|
60
60
|
export { analyzeNamingConvention, isDevelopment, normalizeWarningOption } from './warnings.js';
|
|
61
|
-
export { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, extractRoutes, followsNamingConvention, generateRestRoutes, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
|
|
61
|
+
export { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, clearCollectionRegistry, extractRoutes, followsNamingConvention, generateRestRoutes, getRegisteredCollections, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
|
|
62
62
|
export {
|
|
63
63
|
// tRPC utilities
|
|
64
64
|
appRouter, buildTRPCRouter, createTRPCContextFactory, registerTRPCPlugin, trpc, veloxErrorToTRPCError, } from './trpc/index.js';
|
|
@@ -73,7 +73,7 @@ createSecurityRequirement,
|
|
|
73
73
|
// Schema converter
|
|
74
74
|
createStringSchema, DEFAULT_GUARD_MAPPINGS, DEFAULT_SECURITY_SCHEMES, DEFAULT_UI_CONFIG, escapeHtml, extractGuardScopes, extractPathParamNames, extractQueryParameters, extractResourceFromPath, extractSchemaProperties, extractUsedSecuritySchemes, filterUsedSecuritySchemes,
|
|
75
75
|
// Generator
|
|
76
|
-
generateOpenApiSpec,
|
|
76
|
+
generateOpenApiSpec, generateOpenApiSpecFromRegistry,
|
|
77
77
|
// HTML Generator
|
|
78
78
|
generateSwaggerUIHtml, getOpenApiRouteSummary, getOpenApiSpec, guardsRequireAuth, guardsToSecurity, hasPathParameters, joinPaths, mapGuardToSecurity, mergeSchemas, mergeSecuritySchemes, normalizePath, parsePathParameters, removeSchemaProperties, SWAGGER_UI_CDN, schemaHasProperties,
|
|
79
79
|
// Plugin
|
|
@@ -110,7 +110,7 @@ swaggerPlugin, validateOpenApiSpec, zodSchemaToJsonSchema, } from './openapi/ind
|
|
|
110
110
|
*/
|
|
111
111
|
export {
|
|
112
112
|
// Access level configuration
|
|
113
|
-
defineAccessLevels, getAccessibleLevels, getVisibilityForTag, isFieldVisibleToLevel, isResourceSchema,
|
|
113
|
+
defaultAccess, defineAccessLevels, getAccessibleLevels, getVisibilityForTag, isFieldVisibleToLevel, isResourceSchema,
|
|
114
114
|
// Visibility
|
|
115
115
|
isVisibleAtLevel, Resource, ResourceCollection, ResourceSchemaBuilder,
|
|
116
116
|
// Resource instances
|
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module @veloxts/router/openapi/generator
|
|
7
7
|
*/
|
|
8
|
+
import type { RegisteredCollection } from '../rest/registry.js';
|
|
8
9
|
import type { ProcedureCollection } from '../types.js';
|
|
9
10
|
import type { OpenAPIGeneratorOptions, OpenAPISpec } from './types.js';
|
|
10
11
|
/**
|
|
11
12
|
* Generates an OpenAPI 3.0.3 specification from procedure collections
|
|
12
13
|
*
|
|
14
|
+
* All collections share the same prefix from `options.prefix` (default '/api').
|
|
15
|
+
* For collections with different prefixes, use `generateOpenApiSpecFromRegistry()`.
|
|
16
|
+
*
|
|
13
17
|
* @param collections - Array of procedure collections to document
|
|
14
18
|
* @param options - Generator options
|
|
15
19
|
* @returns Complete OpenAPI specification
|
|
@@ -30,6 +34,27 @@ import type { OpenAPIGeneratorOptions, OpenAPISpec } from './types.js';
|
|
|
30
34
|
* ```
|
|
31
35
|
*/
|
|
32
36
|
export declare function generateOpenApiSpec(collections: ProcedureCollection[], options: OpenAPIGeneratorOptions): OpenAPISpec;
|
|
37
|
+
/**
|
|
38
|
+
* Generates an OpenAPI 3.0.3 specification from registered collections
|
|
39
|
+
*
|
|
40
|
+
* Each entry carries its own prefix, allowing collections registered under
|
|
41
|
+
* different Fastify prefixes (e.g., `/api` and `/loterie`) to produce
|
|
42
|
+
* correct paths in a single spec.
|
|
43
|
+
*
|
|
44
|
+
* @param entries - Registered collections with per-entry prefixes
|
|
45
|
+
* @param options - Generator options (prefix field is ignored — per-entry prefix is used)
|
|
46
|
+
* @returns Complete OpenAPI specification
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { generateOpenApiSpecFromRegistry, getRegisteredCollections } from '@veloxts/router';
|
|
51
|
+
*
|
|
52
|
+
* const spec = generateOpenApiSpecFromRegistry(getRegisteredCollections(), {
|
|
53
|
+
* info: { title: 'My API', version: '1.0.0' },
|
|
54
|
+
* });
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function generateOpenApiSpecFromRegistry(entries: readonly RegisteredCollection[], options: OpenAPIGeneratorOptions): OpenAPISpec;
|
|
33
58
|
/**
|
|
34
59
|
* Gets route summary information for debugging/logging
|
|
35
60
|
*
|
|
@@ -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, resourceSchemaToJsonSchema, zodSchemaToJsonSchema, } from './schema-converter.js';
|
|
10
|
+
import { removeSchemaProperties, resourceSchemaToJsonSchema, resourceSchemaToJsonSchemaForBranching, zodSchemaToJsonSchema, } from './schema-converter.js';
|
|
11
11
|
import { extractUsedSecuritySchemes, filterUsedSecuritySchemes, guardsToSecurity, mergeSecuritySchemes, } from './security-mapper.js';
|
|
12
12
|
// ============================================================================
|
|
13
13
|
// Main Generator
|
|
@@ -15,6 +15,9 @@ import { extractUsedSecuritySchemes, filterUsedSecuritySchemes, guardsToSecurity
|
|
|
15
15
|
/**
|
|
16
16
|
* Generates an OpenAPI 3.0.3 specification from procedure collections
|
|
17
17
|
*
|
|
18
|
+
* All collections share the same prefix from `options.prefix` (default '/api').
|
|
19
|
+
* For collections with different prefixes, use `generateOpenApiSpecFromRegistry()`.
|
|
20
|
+
*
|
|
18
21
|
* @param collections - Array of procedure collections to document
|
|
19
22
|
* @param options - Generator options
|
|
20
23
|
* @returns Complete OpenAPI specification
|
|
@@ -36,20 +39,53 @@ import { extractUsedSecuritySchemes, filterUsedSecuritySchemes, guardsToSecurity
|
|
|
36
39
|
*/
|
|
37
40
|
export function generateOpenApiSpec(collections, options) {
|
|
38
41
|
const prefix = options.prefix ?? '/api';
|
|
42
|
+
// Convert to RegisteredCollection entries with the shared prefix
|
|
43
|
+
const entries = collections.map((collection) => ({
|
|
44
|
+
collection,
|
|
45
|
+
prefix,
|
|
46
|
+
}));
|
|
47
|
+
return generateOpenApiSpecFromRegistry(entries, options);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generates an OpenAPI 3.0.3 specification from registered collections
|
|
51
|
+
*
|
|
52
|
+
* Each entry carries its own prefix, allowing collections registered under
|
|
53
|
+
* different Fastify prefixes (e.g., `/api` and `/loterie`) to produce
|
|
54
|
+
* correct paths in a single spec.
|
|
55
|
+
*
|
|
56
|
+
* @param entries - Registered collections with per-entry prefixes
|
|
57
|
+
* @param options - Generator options (prefix field is ignored — per-entry prefix is used)
|
|
58
|
+
* @returns Complete OpenAPI specification
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* import { generateOpenApiSpecFromRegistry, getRegisteredCollections } from '@veloxts/router';
|
|
63
|
+
*
|
|
64
|
+
* const spec = generateOpenApiSpecFromRegistry(getRegisteredCollections(), {
|
|
65
|
+
* info: { title: 'My API', version: '1.0.0' },
|
|
66
|
+
* });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function generateOpenApiSpecFromRegistry(entries, options) {
|
|
39
70
|
const paths = {};
|
|
40
71
|
const tags = [];
|
|
41
72
|
const allGuards = [];
|
|
42
|
-
|
|
43
|
-
|
|
73
|
+
const seenTagNames = new Set();
|
|
74
|
+
// Process each registered entry
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const { collection, prefix } = entry;
|
|
44
77
|
const routes = generateRestRoutes(collection);
|
|
45
|
-
// Add tag for this namespace
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
78
|
+
// Add tag for this namespace (deduplicate across entries)
|
|
79
|
+
if (!seenTagNames.has(collection.namespace)) {
|
|
80
|
+
seenTagNames.add(collection.namespace);
|
|
81
|
+
tags.push({
|
|
82
|
+
name: collection.namespace,
|
|
83
|
+
description: options.tagDescriptions?.[collection.namespace],
|
|
84
|
+
});
|
|
85
|
+
}
|
|
50
86
|
// Process each route
|
|
51
87
|
for (const route of routes) {
|
|
52
|
-
// Build full path with prefix
|
|
88
|
+
// Build full path with this entry's prefix
|
|
53
89
|
const fullPath = joinPaths(prefix, route.path);
|
|
54
90
|
const openApiPath = convertToOpenAPIPath(fullPath);
|
|
55
91
|
// Initialize path item if not exists
|
|
@@ -114,6 +150,10 @@ function generateOperation(route, namespace, options) {
|
|
|
114
150
|
if (procedure.outputSchema) {
|
|
115
151
|
outputSchema = zodSchemaToJsonSchema(procedure.outputSchema);
|
|
116
152
|
}
|
|
153
|
+
else if (procedure._handlerMap && procedure._resourceSchema) {
|
|
154
|
+
// Level 3: branched procedure — include all fields, non-public fields optional
|
|
155
|
+
outputSchema = resourceSchemaToJsonSchemaForBranching(procedure._resourceSchema);
|
|
156
|
+
}
|
|
117
157
|
else if (procedure._resourceSchema) {
|
|
118
158
|
const level = procedure._resourceLevel ?? 'public';
|
|
119
159
|
outputSchema = resourceSchemaToJsonSchema(procedure._resourceSchema, level);
|
package/dist/openapi/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* });
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
export { generateOpenApiSpec, getOpenApiRouteSummary, validateOpenApiSpec, } from './generator.js';
|
|
31
|
+
export { generateOpenApiSpec, generateOpenApiSpecFromRegistry, getOpenApiRouteSummary, validateOpenApiSpec, } from './generator.js';
|
|
32
32
|
export { getOpenApiSpec, swaggerPlugin, } from './plugin.js';
|
|
33
33
|
export { DEFAULT_UI_CONFIG, escapeHtml, generateSwaggerUIHtml, SWAGGER_UI_CDN, type SwaggerUIHtmlOptions, } from './html-generator.js';
|
|
34
34
|
export { createStringSchema, extractSchemaProperties, mergeSchemas, removeSchemaProperties, type SchemaConversionOptions, schemaHasProperties, zodSchemaToJsonSchema, } from './schema-converter.js';
|
package/dist/openapi/index.js
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// ============================================================================
|
|
32
32
|
// Generator
|
|
33
33
|
// ============================================================================
|
|
34
|
-
export { generateOpenApiSpec, getOpenApiRouteSummary, validateOpenApiSpec, } from './generator.js';
|
|
34
|
+
export { generateOpenApiSpec, generateOpenApiSpecFromRegistry, getOpenApiRouteSummary, validateOpenApiSpec, } from './generator.js';
|
|
35
35
|
// ============================================================================
|
|
36
36
|
// Plugin
|
|
37
37
|
// ============================================================================
|
package/dist/openapi/plugin.d.ts
CHANGED
|
@@ -12,22 +12,27 @@ import type { OpenAPISpec, SwaggerUIPluginOptions } from './types.js';
|
|
|
12
12
|
*
|
|
13
13
|
* Registers routes for serving Swagger UI and the OpenAPI specification.
|
|
14
14
|
*
|
|
15
|
+
* When `collections` is omitted, the plugin auto-discovers all collections
|
|
16
|
+
* previously registered via `rest()`, using each collection's effective prefix.
|
|
17
|
+
*
|
|
15
18
|
* @example
|
|
16
19
|
* ```typescript
|
|
17
20
|
* import { swaggerPlugin } from '@veloxts/router';
|
|
18
21
|
*
|
|
22
|
+
* // Explicit collections (backward compatible)
|
|
19
23
|
* app.register(swaggerPlugin, {
|
|
20
24
|
* routePrefix: '/docs',
|
|
21
25
|
* collections: [userProcedures, postProcedures],
|
|
22
26
|
* openapi: {
|
|
23
|
-
* info: {
|
|
24
|
-
* title: 'My API',
|
|
25
|
-
* version: '1.0.0',
|
|
26
|
-
* description: 'A VeloxTS-powered API',
|
|
27
|
-
* },
|
|
27
|
+
* info: { title: 'My API', version: '1.0.0' },
|
|
28
28
|
* servers: [{ url: 'http://localhost:3030' }],
|
|
29
29
|
* },
|
|
30
30
|
* });
|
|
31
|
+
*
|
|
32
|
+
* // Auto-discovery — no collections needed
|
|
33
|
+
* app.register(swaggerPlugin, {
|
|
34
|
+
* openapi: { info: { title: 'My API', version: '1.0.0' } },
|
|
35
|
+
* });
|
|
31
36
|
* ```
|
|
32
37
|
*/
|
|
33
38
|
export declare const swaggerPlugin: FastifyPluginAsync<SwaggerUIPluginOptions>;
|
|
@@ -35,6 +40,7 @@ export declare const swaggerPlugin: FastifyPluginAsync<SwaggerUIPluginOptions>;
|
|
|
35
40
|
* Gets the generated OpenAPI specification without registering routes
|
|
36
41
|
*
|
|
37
42
|
* Useful for testing or exporting the spec programmatically.
|
|
43
|
+
* Supports auto-discovery when `collections` is omitted.
|
|
38
44
|
*
|
|
39
45
|
* @param options - Plugin options
|
|
40
46
|
* @returns Generated OpenAPI specification
|
package/dist/openapi/plugin.js
CHANGED
|
@@ -5,9 +5,27 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module @veloxts/router/openapi/plugin
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { getRegisteredCollections } from '../rest/registry.js';
|
|
9
|
+
import { generateOpenApiSpec, generateOpenApiSpecFromRegistry } from './generator.js';
|
|
9
10
|
import { generateSwaggerUIHtml } from './html-generator.js';
|
|
10
11
|
// ============================================================================
|
|
12
|
+
// Internal Helpers
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Build an OpenAPI spec from plugin options.
|
|
16
|
+
*
|
|
17
|
+
* - Explicit `collections` → use `generateOpenApiSpec` (single global prefix)
|
|
18
|
+
* - No collections → auto-discover from the registry (per-entry prefix)
|
|
19
|
+
*/
|
|
20
|
+
function buildSpec(options) {
|
|
21
|
+
const { collections, openapi } = options;
|
|
22
|
+
if (collections && collections.length > 0) {
|
|
23
|
+
return generateOpenApiSpec(collections, openapi);
|
|
24
|
+
}
|
|
25
|
+
const registered = getRegisteredCollections();
|
|
26
|
+
return generateOpenApiSpecFromRegistry(registered, openapi);
|
|
27
|
+
}
|
|
28
|
+
// ============================================================================
|
|
11
29
|
// Fastify Plugin
|
|
12
30
|
// ============================================================================
|
|
13
31
|
/**
|
|
@@ -15,30 +33,35 @@ import { generateSwaggerUIHtml } from './html-generator.js';
|
|
|
15
33
|
*
|
|
16
34
|
* Registers routes for serving Swagger UI and the OpenAPI specification.
|
|
17
35
|
*
|
|
36
|
+
* When `collections` is omitted, the plugin auto-discovers all collections
|
|
37
|
+
* previously registered via `rest()`, using each collection's effective prefix.
|
|
38
|
+
*
|
|
18
39
|
* @example
|
|
19
40
|
* ```typescript
|
|
20
41
|
* import { swaggerPlugin } from '@veloxts/router';
|
|
21
42
|
*
|
|
43
|
+
* // Explicit collections (backward compatible)
|
|
22
44
|
* app.register(swaggerPlugin, {
|
|
23
45
|
* routePrefix: '/docs',
|
|
24
46
|
* collections: [userProcedures, postProcedures],
|
|
25
47
|
* openapi: {
|
|
26
|
-
* info: {
|
|
27
|
-
* title: 'My API',
|
|
28
|
-
* version: '1.0.0',
|
|
29
|
-
* description: 'A VeloxTS-powered API',
|
|
30
|
-
* },
|
|
48
|
+
* info: { title: 'My API', version: '1.0.0' },
|
|
31
49
|
* servers: [{ url: 'http://localhost:3030' }],
|
|
32
50
|
* },
|
|
33
51
|
* });
|
|
52
|
+
*
|
|
53
|
+
* // Auto-discovery — no collections needed
|
|
54
|
+
* app.register(swaggerPlugin, {
|
|
55
|
+
* openapi: { info: { title: 'My API', version: '1.0.0' } },
|
|
56
|
+
* });
|
|
34
57
|
* ```
|
|
35
58
|
*/
|
|
36
59
|
export const swaggerPlugin = async (fastify, options) => {
|
|
37
|
-
const { routePrefix = '/docs', specRoute = `${routePrefix}/openapi.json`, uiConfig = {},
|
|
60
|
+
const { routePrefix = '/docs', specRoute = `${routePrefix}/openapi.json`, uiConfig = {}, title = 'API Documentation', favicon, } = options;
|
|
38
61
|
// Generate the OpenAPI specification
|
|
39
62
|
let spec;
|
|
40
63
|
try {
|
|
41
|
-
spec =
|
|
64
|
+
spec = buildSpec(options);
|
|
42
65
|
}
|
|
43
66
|
catch (error) {
|
|
44
67
|
fastify.log.error(error, '[VeloxTS] Failed to generate OpenAPI specification');
|
|
@@ -73,6 +96,7 @@ export const swaggerPlugin = async (fastify, options) => {
|
|
|
73
96
|
* Gets the generated OpenAPI specification without registering routes
|
|
74
97
|
*
|
|
75
98
|
* Useful for testing or exporting the spec programmatically.
|
|
99
|
+
* Supports auto-discovery when `collections` is omitted.
|
|
76
100
|
*
|
|
77
101
|
* @param options - Plugin options
|
|
78
102
|
* @returns Generated OpenAPI specification
|
|
@@ -93,5 +117,5 @@ export const swaggerPlugin = async (fastify, options) => {
|
|
|
93
117
|
* ```
|
|
94
118
|
*/
|
|
95
119
|
export function getOpenApiSpec(options) {
|
|
96
|
-
return
|
|
120
|
+
return buildSpec(options);
|
|
97
121
|
}
|
|
@@ -113,3 +113,14 @@ export declare function schemaHasProperties(schema: JSONSchema | undefined): boo
|
|
|
113
113
|
* @returns JSON Schema with only the fields visible at the given level
|
|
114
114
|
*/
|
|
115
115
|
export declare function resourceSchemaToJsonSchema(schema: ResourceSchema, level?: string): JSONSchema;
|
|
116
|
+
/**
|
|
117
|
+
* Converts a resource schema to JSON Schema for Level 3 branched procedures
|
|
118
|
+
*
|
|
119
|
+
* Includes ALL fields from the resource schema. Fields visible at the
|
|
120
|
+
* lowest level (first in the levels array) are marked as required;
|
|
121
|
+
* higher-level-only fields are optional.
|
|
122
|
+
*
|
|
123
|
+
* @param schema - The resource schema (must have _levelConfig for custom levels)
|
|
124
|
+
* @returns JSON Schema with all fields, non-public fields optional
|
|
125
|
+
*/
|
|
126
|
+
export declare function resourceSchemaToJsonSchemaForBranching(schema: ResourceSchema): JSONSchema;
|
|
@@ -308,3 +308,50 @@ export function resourceSchemaToJsonSchema(schema, level = 'public') {
|
|
|
308
308
|
...(required.length > 0 ? { required } : {}),
|
|
309
309
|
};
|
|
310
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* Converts a resource schema to JSON Schema for Level 3 branched procedures
|
|
313
|
+
*
|
|
314
|
+
* Includes ALL fields from the resource schema. Fields visible at the
|
|
315
|
+
* lowest level (first in the levels array) are marked as required;
|
|
316
|
+
* higher-level-only fields are optional.
|
|
317
|
+
*
|
|
318
|
+
* @param schema - The resource schema (must have _levelConfig for custom levels)
|
|
319
|
+
* @returns JSON Schema with all fields, non-public fields optional
|
|
320
|
+
*/
|
|
321
|
+
export function resourceSchemaToJsonSchemaForBranching(schema) {
|
|
322
|
+
const properties = {};
|
|
323
|
+
const required = [];
|
|
324
|
+
// Determine the lowest (most accessible) level
|
|
325
|
+
const schemaWithConfig = schema;
|
|
326
|
+
const lowestLevel = schemaWithConfig._levelConfig?.levels[0] ?? 'public';
|
|
327
|
+
for (const field of schema.fields) {
|
|
328
|
+
const runtimeField = field;
|
|
329
|
+
if (field.nestedSchema) {
|
|
330
|
+
const nestedJsonSchema = resourceSchemaToJsonSchemaForBranching(field.nestedSchema);
|
|
331
|
+
if (field.cardinality === 'many') {
|
|
332
|
+
properties[field.name] = { type: 'array', items: nestedJsonSchema };
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
properties[field.name] = { ...nestedJsonSchema, nullable: true };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else if (field.schema) {
|
|
339
|
+
const fieldJsonSchema = zodSchemaToJsonSchema(field.schema);
|
|
340
|
+
if (fieldJsonSchema) {
|
|
341
|
+
properties[field.name] = fieldJsonSchema;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
properties[field.name] = {};
|
|
346
|
+
}
|
|
347
|
+
// Only public/lowest-level fields are required
|
|
348
|
+
if (isFieldVisibleToLevel(runtimeField, lowestLevel)) {
|
|
349
|
+
required.push(field.name);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
type: 'object',
|
|
354
|
+
properties,
|
|
355
|
+
...(required.length > 0 ? { required } : {}),
|
|
356
|
+
};
|
|
357
|
+
}
|
package/dist/openapi/types.d.ts
CHANGED
|
@@ -404,9 +404,12 @@ export interface SwaggerUIPluginOptions {
|
|
|
404
404
|
*/
|
|
405
405
|
openapi: OpenAPIGeneratorOptions;
|
|
406
406
|
/**
|
|
407
|
-
* Procedure collections to document
|
|
407
|
+
* Procedure collections to document.
|
|
408
|
+
*
|
|
409
|
+
* When omitted, the plugin auto-discovers collections registered via `rest()`
|
|
410
|
+
* along with their effective prefixes.
|
|
408
411
|
*/
|
|
409
|
-
collections
|
|
412
|
+
collections?: ProcedureCollection[];
|
|
410
413
|
/**
|
|
411
414
|
* Custom page title
|
|
412
415
|
* @default 'API Documentation'
|