@veloxts/router 0.6.87 → 0.6.88
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 +9 -0
- package/dist/middleware/chain.d.ts +25 -1
- package/dist/middleware/chain.js +33 -3
- package/dist/procedure/builder.js +1 -64
- package/dist/types.d.ts +9 -2
- package/dist/types.js +5 -12
- package/dist/utils/pluralization.d.ts +50 -0
- package/dist/utils/pluralization.js +111 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -20,9 +20,33 @@ export interface MiddlewareResult<TOutput> {
|
|
|
20
20
|
* Builds the chain from end to start and executes it, allowing each
|
|
21
21
|
* middleware to extend the context before calling the next middleware.
|
|
22
22
|
*
|
|
23
|
+
* ## Implementation Notes: Closure Capture Pattern
|
|
24
|
+
*
|
|
25
|
+
* This function uses a deliberate closure capture pattern in the loop:
|
|
26
|
+
*
|
|
27
|
+
* ```typescript
|
|
28
|
+
* for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
29
|
+
* const middleware = middlewares[i]; // Captured in closure
|
|
30
|
+
* const currentNext = next; // Captured in closure
|
|
31
|
+
* next = async () => { ... }; // Creates new closure
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* **Why closures in a loop?**
|
|
36
|
+
* Each iteration creates a new closure that captures:
|
|
37
|
+
* 1. `middleware` - The specific middleware function for that position
|
|
38
|
+
* 2. `currentNext` - The accumulated chain from previous iterations
|
|
39
|
+
*
|
|
40
|
+
* This builds the chain backwards (handler → last middleware → ... → first middleware)
|
|
41
|
+
* so that when executed, it runs forwards (first middleware → ... → handler).
|
|
42
|
+
*
|
|
43
|
+
* **Why not use Array.reduceRight?**
|
|
44
|
+
* The closure pattern is more explicit and easier to debug. Each closure
|
|
45
|
+
* captures exactly what it needs, and the chain structure is visible.
|
|
46
|
+
*
|
|
23
47
|
* @param middlewares - Array of middleware functions to execute
|
|
24
48
|
* @param input - The input to pass to each middleware
|
|
25
|
-
* @param ctx - The context object (will be mutated by middleware)
|
|
49
|
+
* @param ctx - The context object (will be mutated by middleware via Object.assign)
|
|
26
50
|
* @param handler - The final handler to execute after all middleware
|
|
27
51
|
* @returns The output from the handler
|
|
28
52
|
*
|
package/dist/middleware/chain.js
CHANGED
|
@@ -12,9 +12,33 @@
|
|
|
12
12
|
* Builds the chain from end to start and executes it, allowing each
|
|
13
13
|
* middleware to extend the context before calling the next middleware.
|
|
14
14
|
*
|
|
15
|
+
* ## Implementation Notes: Closure Capture Pattern
|
|
16
|
+
*
|
|
17
|
+
* This function uses a deliberate closure capture pattern in the loop:
|
|
18
|
+
*
|
|
19
|
+
* ```typescript
|
|
20
|
+
* for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
21
|
+
* const middleware = middlewares[i]; // Captured in closure
|
|
22
|
+
* const currentNext = next; // Captured in closure
|
|
23
|
+
* next = async () => { ... }; // Creates new closure
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* **Why closures in a loop?**
|
|
28
|
+
* Each iteration creates a new closure that captures:
|
|
29
|
+
* 1. `middleware` - The specific middleware function for that position
|
|
30
|
+
* 2. `currentNext` - The accumulated chain from previous iterations
|
|
31
|
+
*
|
|
32
|
+
* This builds the chain backwards (handler → last middleware → ... → first middleware)
|
|
33
|
+
* so that when executed, it runs forwards (first middleware → ... → handler).
|
|
34
|
+
*
|
|
35
|
+
* **Why not use Array.reduceRight?**
|
|
36
|
+
* The closure pattern is more explicit and easier to debug. Each closure
|
|
37
|
+
* captures exactly what it needs, and the chain structure is visible.
|
|
38
|
+
*
|
|
15
39
|
* @param middlewares - Array of middleware functions to execute
|
|
16
40
|
* @param input - The input to pass to each middleware
|
|
17
|
-
* @param ctx - The context object (will be mutated by middleware)
|
|
41
|
+
* @param ctx - The context object (will be mutated by middleware via Object.assign)
|
|
18
42
|
* @param handler - The final handler to execute after all middleware
|
|
19
43
|
* @returns The output from the handler
|
|
20
44
|
*
|
|
@@ -30,20 +54,26 @@
|
|
|
30
54
|
*/
|
|
31
55
|
export async function executeMiddlewareChain(middlewares, input, ctx, handler) {
|
|
32
56
|
// Build the chain from the end (handler) back to the start
|
|
57
|
+
// Start with the handler wrapped in a MiddlewareResult
|
|
33
58
|
let next = async () => {
|
|
34
59
|
const output = await handler();
|
|
35
60
|
return { output };
|
|
36
61
|
};
|
|
37
62
|
// Wrap each middleware from last to first
|
|
63
|
+
// Each iteration captures `middleware` and `currentNext` in a new closure
|
|
64
|
+
// This builds: handler <- MW[n] <- MW[n-1] <- ... <- MW[0]
|
|
65
|
+
// So execution flows: MW[0] -> MW[1] -> ... -> MW[n] -> handler
|
|
38
66
|
for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
39
67
|
const middleware = middlewares[i];
|
|
40
|
-
const currentNext = next;
|
|
68
|
+
const currentNext = next; // Capture the accumulated chain so far
|
|
69
|
+
// Create a new closure that wraps the middleware with the chain
|
|
41
70
|
next = async () => {
|
|
42
71
|
return middleware({
|
|
43
72
|
input,
|
|
44
73
|
ctx,
|
|
45
74
|
next: async (opts) => {
|
|
46
|
-
// Allow middleware to extend context
|
|
75
|
+
// Allow middleware to extend context via Object.assign
|
|
76
|
+
// This mutates ctx in place, which is intentional
|
|
47
77
|
if (opts?.ctx) {
|
|
48
78
|
Object.assign(ctx, opts.ctx);
|
|
49
79
|
}
|
|
@@ -10,72 +10,9 @@
|
|
|
10
10
|
import { ConfigurationError, logWarning } from '@veloxts/core';
|
|
11
11
|
import { GuardError } from '../errors.js';
|
|
12
12
|
import { createMiddlewareExecutor, executeMiddlewareChain } from '../middleware/chain.js';
|
|
13
|
+
import { deriveParentParamName } from '../utils/pluralization.js';
|
|
13
14
|
import { analyzeNamingConvention, isDevelopment, normalizeWarningOption, } from '../warnings.js';
|
|
14
15
|
// ============================================================================
|
|
15
|
-
// Utility Functions
|
|
16
|
-
// ============================================================================
|
|
17
|
-
/**
|
|
18
|
-
* Derives the default parent parameter name from a namespace
|
|
19
|
-
*
|
|
20
|
-
* Converts a plural namespace to a singular form and appends 'Id'.
|
|
21
|
-
* Uses simple heuristics for common English pluralization patterns.
|
|
22
|
-
*
|
|
23
|
-
* @param namespace - The parent resource namespace (e.g., 'posts', 'users')
|
|
24
|
-
* @returns The parameter name (e.g., 'postId', 'userId')
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```typescript
|
|
28
|
-
* deriveParentParamName('posts') // 'postId'
|
|
29
|
-
* deriveParentParamName('users') // 'userId'
|
|
30
|
-
* deriveParentParamName('categories') // 'categoryId'
|
|
31
|
-
* deriveParentParamName('data') // 'dataId' (no change for non-plural)
|
|
32
|
-
* ```
|
|
33
|
-
*
|
|
34
|
-
* @internal
|
|
35
|
-
*/
|
|
36
|
-
function deriveParentParamName(namespace) {
|
|
37
|
-
// Handle common irregular plurals
|
|
38
|
-
const irregulars = {
|
|
39
|
-
people: 'person',
|
|
40
|
-
children: 'child',
|
|
41
|
-
men: 'man',
|
|
42
|
-
women: 'woman',
|
|
43
|
-
mice: 'mouse',
|
|
44
|
-
geese: 'goose',
|
|
45
|
-
teeth: 'tooth',
|
|
46
|
-
feet: 'foot',
|
|
47
|
-
data: 'datum',
|
|
48
|
-
criteria: 'criterion',
|
|
49
|
-
phenomena: 'phenomenon',
|
|
50
|
-
};
|
|
51
|
-
const lower = namespace.toLowerCase();
|
|
52
|
-
if (irregulars[lower]) {
|
|
53
|
-
return `${irregulars[lower]}Id`;
|
|
54
|
-
}
|
|
55
|
-
// Handle common English pluralization patterns
|
|
56
|
-
let singular = namespace;
|
|
57
|
-
if (namespace.endsWith('ies') && namespace.length > 3) {
|
|
58
|
-
// categories -> category
|
|
59
|
-
singular = `${namespace.slice(0, -3)}y`;
|
|
60
|
-
}
|
|
61
|
-
else if (namespace.endsWith('es') && namespace.length > 2) {
|
|
62
|
-
// Check for -shes, -ches, -xes, -zes, -sses patterns
|
|
63
|
-
const beforeEs = namespace.slice(-4, -2);
|
|
64
|
-
if (['sh', 'ch'].includes(beforeEs) || ['x', 'z', 's'].includes(namespace.slice(-3, -2))) {
|
|
65
|
-
singular = namespace.slice(0, -2);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
// classes -> class (double s + es)
|
|
69
|
-
singular = namespace.slice(0, -1);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
else if (namespace.endsWith('s') && namespace.length > 1 && !namespace.endsWith('ss')) {
|
|
73
|
-
// Simple plural: users -> user, posts -> post
|
|
74
|
-
singular = namespace.slice(0, -1);
|
|
75
|
-
}
|
|
76
|
-
return `${singular}Id`;
|
|
77
|
-
}
|
|
78
|
-
// ============================================================================
|
|
79
16
|
// Builder Factory
|
|
80
17
|
// ============================================================================
|
|
81
18
|
/**
|
package/dist/types.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @module types
|
|
8
8
|
*/
|
|
9
9
|
import type { BaseContext } from '@veloxts/core';
|
|
10
|
+
import type { HttpMethod } from '@veloxts/validation';
|
|
10
11
|
/**
|
|
11
12
|
* Procedure operation types
|
|
12
13
|
*
|
|
@@ -18,11 +19,17 @@ export type ProcedureType = 'query' | 'mutation';
|
|
|
18
19
|
* HTTP methods supported by the REST adapter
|
|
19
20
|
*
|
|
20
21
|
* Full REST support: GET, POST, PUT, PATCH, DELETE
|
|
22
|
+
*
|
|
23
|
+
* @see {@link PROCEDURE_METHOD_MAP} for naming convention mapping
|
|
21
24
|
*/
|
|
22
|
-
export type HttpMethod
|
|
25
|
+
export type { HttpMethod };
|
|
23
26
|
/**
|
|
24
27
|
* Maps procedure naming conventions to HTTP methods
|
|
25
28
|
*
|
|
29
|
+
* Re-exported from @veloxts/validation for consistency across router and client.
|
|
30
|
+
*
|
|
31
|
+
* @see {@link @veloxts/validation!PROCEDURE_METHOD_MAP} for the canonical definition
|
|
32
|
+
*
|
|
26
33
|
* @example
|
|
27
34
|
* - getUser -> GET
|
|
28
35
|
* - listUsers -> GET
|
|
@@ -31,7 +38,7 @@ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
|
31
38
|
* - patchUser -> PATCH
|
|
32
39
|
* - deleteUser -> DELETE
|
|
33
40
|
*/
|
|
34
|
-
export
|
|
41
|
+
export { PROCEDURE_METHOD_MAP } from '@veloxts/validation';
|
|
35
42
|
/**
|
|
36
43
|
* Extended context type that can be augmented by middleware
|
|
37
44
|
*
|
package/dist/types.js
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
/**
|
|
10
10
|
* Maps procedure naming conventions to HTTP methods
|
|
11
11
|
*
|
|
12
|
+
* Re-exported from @veloxts/validation for consistency across router and client.
|
|
13
|
+
*
|
|
14
|
+
* @see {@link @veloxts/validation!PROCEDURE_METHOD_MAP} for the canonical definition
|
|
15
|
+
*
|
|
12
16
|
* @example
|
|
13
17
|
* - getUser -> GET
|
|
14
18
|
* - listUsers -> GET
|
|
@@ -17,15 +21,4 @@
|
|
|
17
21
|
* - patchUser -> PATCH
|
|
18
22
|
* - deleteUser -> DELETE
|
|
19
23
|
*/
|
|
20
|
-
export
|
|
21
|
-
get: 'GET',
|
|
22
|
-
list: 'GET',
|
|
23
|
-
find: 'GET',
|
|
24
|
-
create: 'POST',
|
|
25
|
-
add: 'POST',
|
|
26
|
-
update: 'PUT',
|
|
27
|
-
edit: 'PUT',
|
|
28
|
-
patch: 'PATCH',
|
|
29
|
-
delete: 'DELETE',
|
|
30
|
-
remove: 'DELETE',
|
|
31
|
-
};
|
|
24
|
+
export { PROCEDURE_METHOD_MAP } from '@veloxts/validation';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluralization utilities for REST naming conventions
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to convert between singular and plural forms
|
|
5
|
+
* for resource names. Used primarily by the procedure builder to
|
|
6
|
+
* derive parent parameter names from namespaces.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/pluralization
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Converts a plural word to its singular form
|
|
12
|
+
*
|
|
13
|
+
* Uses heuristics for common English pluralization patterns:
|
|
14
|
+
* - Irregular plurals (people -> person, children -> child)
|
|
15
|
+
* - -ies endings (categories -> category)
|
|
16
|
+
* - -es endings (boxes -> box, classes -> class)
|
|
17
|
+
* - Simple -s endings (users -> user)
|
|
18
|
+
*
|
|
19
|
+
* @param word - The plural word to singularize
|
|
20
|
+
* @returns The singular form of the word
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* singularize('users') // 'user'
|
|
25
|
+
* singularize('categories') // 'category'
|
|
26
|
+
* singularize('boxes') // 'box'
|
|
27
|
+
* singularize('people') // 'person'
|
|
28
|
+
* singularize('data') // 'datum'
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function singularize(word: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Derives a parameter name from a resource namespace
|
|
34
|
+
*
|
|
35
|
+
* Converts a plural namespace to a singular form and appends 'Id'.
|
|
36
|
+
* This is used to derive parent parameter names for nested routes.
|
|
37
|
+
*
|
|
38
|
+
* @param namespace - The parent resource namespace (e.g., 'posts', 'users')
|
|
39
|
+
* @returns The parameter name (e.g., 'postId', 'userId')
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* deriveParentParamName('posts') // 'postId'
|
|
44
|
+
* deriveParentParamName('users') // 'userId'
|
|
45
|
+
* deriveParentParamName('categories') // 'categoryId'
|
|
46
|
+
* deriveParentParamName('people') // 'personId'
|
|
47
|
+
* deriveParentParamName('data') // 'datumId'
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function deriveParentParamName(namespace: string): string;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluralization utilities for REST naming conventions
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to convert between singular and plural forms
|
|
5
|
+
* for resource names. Used primarily by the procedure builder to
|
|
6
|
+
* derive parent parameter names from namespaces.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/pluralization
|
|
9
|
+
*/
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Irregular Plurals
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* Common irregular English plurals
|
|
15
|
+
*
|
|
16
|
+
* Maps plural forms to their singular equivalents.
|
|
17
|
+
* Used for accurate parameter name derivation.
|
|
18
|
+
*/
|
|
19
|
+
const IRREGULAR_PLURALS = {
|
|
20
|
+
people: 'person',
|
|
21
|
+
children: 'child',
|
|
22
|
+
men: 'man',
|
|
23
|
+
women: 'woman',
|
|
24
|
+
mice: 'mouse',
|
|
25
|
+
geese: 'goose',
|
|
26
|
+
teeth: 'tooth',
|
|
27
|
+
feet: 'foot',
|
|
28
|
+
data: 'datum',
|
|
29
|
+
criteria: 'criterion',
|
|
30
|
+
phenomena: 'phenomenon',
|
|
31
|
+
};
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Singularization
|
|
34
|
+
// ============================================================================
|
|
35
|
+
/**
|
|
36
|
+
* Converts a plural word to its singular form
|
|
37
|
+
*
|
|
38
|
+
* Uses heuristics for common English pluralization patterns:
|
|
39
|
+
* - Irregular plurals (people -> person, children -> child)
|
|
40
|
+
* - -ies endings (categories -> category)
|
|
41
|
+
* - -es endings (boxes -> box, classes -> class)
|
|
42
|
+
* - Simple -s endings (users -> user)
|
|
43
|
+
*
|
|
44
|
+
* @param word - The plural word to singularize
|
|
45
|
+
* @returns The singular form of the word
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* singularize('users') // 'user'
|
|
50
|
+
* singularize('categories') // 'category'
|
|
51
|
+
* singularize('boxes') // 'box'
|
|
52
|
+
* singularize('people') // 'person'
|
|
53
|
+
* singularize('data') // 'datum'
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function singularize(word) {
|
|
57
|
+
// Check irregular plurals first
|
|
58
|
+
const lower = word.toLowerCase();
|
|
59
|
+
if (IRREGULAR_PLURALS[lower]) {
|
|
60
|
+
// Preserve original casing for first letter
|
|
61
|
+
const singular = IRREGULAR_PLURALS[lower];
|
|
62
|
+
if (word[0] === word[0].toUpperCase()) {
|
|
63
|
+
return singular.charAt(0).toUpperCase() + singular.slice(1);
|
|
64
|
+
}
|
|
65
|
+
return singular;
|
|
66
|
+
}
|
|
67
|
+
// Handle common English pluralization patterns
|
|
68
|
+
if (word.endsWith('ies') && word.length > 3) {
|
|
69
|
+
// categories -> category
|
|
70
|
+
return `${word.slice(0, -3)}y`;
|
|
71
|
+
}
|
|
72
|
+
if (word.endsWith('es') && word.length > 2) {
|
|
73
|
+
// Check for -shes, -ches, -xes, -zes, -sses patterns
|
|
74
|
+
const beforeEs = word.slice(-4, -2);
|
|
75
|
+
if (['sh', 'ch'].includes(beforeEs) || ['x', 'z', 's'].includes(word.slice(-3, -2))) {
|
|
76
|
+
return word.slice(0, -2);
|
|
77
|
+
}
|
|
78
|
+
// Default: remove just the 's' (e.g., "types" -> "type")
|
|
79
|
+
return word.slice(0, -1);
|
|
80
|
+
}
|
|
81
|
+
if (word.endsWith('s') && word.length > 1 && !word.endsWith('ss')) {
|
|
82
|
+
// Simple plural: users -> user, posts -> post
|
|
83
|
+
return word.slice(0, -1);
|
|
84
|
+
}
|
|
85
|
+
// Word doesn't appear to be plural, return as-is
|
|
86
|
+
return word;
|
|
87
|
+
}
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// Parameter Name Derivation
|
|
90
|
+
// ============================================================================
|
|
91
|
+
/**
|
|
92
|
+
* Derives a parameter name from a resource namespace
|
|
93
|
+
*
|
|
94
|
+
* Converts a plural namespace to a singular form and appends 'Id'.
|
|
95
|
+
* This is used to derive parent parameter names for nested routes.
|
|
96
|
+
*
|
|
97
|
+
* @param namespace - The parent resource namespace (e.g., 'posts', 'users')
|
|
98
|
+
* @returns The parameter name (e.g., 'postId', 'userId')
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* deriveParentParamName('posts') // 'postId'
|
|
103
|
+
* deriveParentParamName('users') // 'userId'
|
|
104
|
+
* deriveParentParamName('categories') // 'categoryId'
|
|
105
|
+
* deriveParentParamName('people') // 'personId'
|
|
106
|
+
* deriveParentParamName('data') // 'datumId'
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function deriveParentParamName(namespace) {
|
|
110
|
+
return `${singularize(namespace)}Id`;
|
|
111
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veloxts/router",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.88",
|
|
4
4
|
"description": "Procedure definitions with tRPC and REST routing for VeloxTS framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"@trpc/server": "11.8.0",
|
|
41
41
|
"fastify": "5.6.2",
|
|
42
42
|
"zod-to-json-schema": "3.24.5",
|
|
43
|
-
"@veloxts/validation": "0.6.
|
|
44
|
-
"@veloxts/core": "0.6.
|
|
43
|
+
"@veloxts/validation": "0.6.88",
|
|
44
|
+
"@veloxts/core": "0.6.88"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@vitest/coverage-v8": "4.0.16",
|