@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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # @veloxts/router
2
2
 
3
+ ## 0.6.88
4
+
5
+ ### Patch Changes
6
+
7
+ - add ecosystem presets for environment-aware configuration
8
+ - Updated dependencies
9
+ - @veloxts/core@0.6.88
10
+ - @veloxts/validation@0.6.88
11
+
3
12
  ## 0.6.87
4
13
 
5
14
  ### Patch Changes
@@ -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
  *
@@ -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 = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
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 declare const PROCEDURE_METHOD_MAP: Record<string, HttpMethod>;
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 const PROCEDURE_METHOD_MAP = {
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.87",
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.87",
44
- "@veloxts/core": "0.6.87"
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",