@veloxts/auth 0.6.68 → 0.6.69

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,52 @@
1
1
  # @veloxts/auth
2
2
 
3
+ ## 0.6.69
4
+
5
+ ### Patch Changes
6
+
7
+ - implement user feedback improvements across packages
8
+
9
+ ## Summary
10
+
11
+ Addresses 9 user feedback items to improve DX, reduce boilerplate, and eliminate template duplications.
12
+
13
+ ### Phase 1: Validation Helpers (`@veloxts/validation`)
14
+
15
+ - Add `prismaDecimal()`, `prismaDecimalNullable()`, `prismaDecimalOptional()` for Prisma Decimal → number conversion
16
+ - Add `dateToIso`, `dateToIsoNullable`, `dateToIsoOptional` aliases for consistency
17
+
18
+ ### Phase 2: Template Deduplication (`@veloxts/auth`)
19
+
20
+ - Export `createEnhancedTokenStore()` with token revocation and refresh token reuse detection
21
+ - Export `parseUserRoles()` and `DEFAULT_ALLOWED_ROLES`
22
+ - Fix memory leak: track pending timeouts for proper cleanup on `destroy()`
23
+ - Update templates to import from `@veloxts/auth` instead of duplicating code
24
+ - Fix jwtManager singleton pattern in templates
25
+
26
+ ### Phase 3: Router Helpers (`@veloxts/router`)
27
+
28
+ - Add `createRouter()` returning `{ collections, router }` for DRY setup
29
+ - Add `toRouter()` for router-only use cases
30
+ - Update all router templates to use `createRouter()`
31
+
32
+ ### Phase 4: Guard Type Narrowing - Experimental (`@veloxts/auth`, `@veloxts/router`)
33
+
34
+ - Add `NarrowingGuard` interface with phantom `_narrows` type
35
+ - Add `authenticatedNarrow` and `hasRoleNarrow()` guards
36
+ - Add `guardNarrow()` method to `ProcedureBuilder` for context narrowing
37
+ - Enables `ctx.user` to be non-null after guard passes
38
+
39
+ ### Phase 5: Documentation (`@veloxts/router`)
40
+
41
+ - Document `.rest()` override patterns
42
+ - Document `createRouter()` helper usage
43
+ - Document `guardNarrow()` experimental API
44
+ - Add schema browser-safety patterns for RSC apps
45
+
46
+ - Updated dependencies
47
+ - @veloxts/core@0.6.69
48
+ - @veloxts/router@0.6.69
49
+
3
50
  ## 0.6.68
4
51
 
5
52
  ### Patch Changes
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Narrowing Guards (Experimental)
3
+ *
4
+ * These guards provide TypeScript type narrowing after they pass.
5
+ * When using `guardNarrow(authenticatedNarrow)`, the context type
6
+ * is narrowed to guarantee `ctx.user` is non-null.
7
+ *
8
+ * EXPERIMENTAL: This API may change. The current recommended approach
9
+ * is to use middleware for context type extension.
10
+ *
11
+ * @module auth/guards-narrowing
12
+ */
13
+ import type { AuthContext, GuardFunction, User } from './types.js';
14
+ /**
15
+ * A guard that narrows the context type after passing.
16
+ *
17
+ * The `_narrows` phantom type indicates what the guard guarantees
18
+ * about the context after it passes.
19
+ *
20
+ * @template TRequired - Context properties required to run the guard
21
+ * @template TGuaranteed - Context properties guaranteed after guard passes
22
+ */
23
+ export interface NarrowingGuard<TRequired, TGuaranteed> {
24
+ /** Guard name for error messages */
25
+ name: string;
26
+ /** Guard check function (matches GuardFunction signature) */
27
+ check: GuardFunction<TRequired>;
28
+ /** Custom error message */
29
+ message?: string;
30
+ /** HTTP status code for guard failures */
31
+ statusCode?: number;
32
+ /**
33
+ * Phantom type declaring what the guard guarantees.
34
+ * Used by ProcedureBuilder.guardNarrow() for type narrowing.
35
+ * @internal
36
+ */
37
+ readonly _narrows: TGuaranteed;
38
+ }
39
+ /**
40
+ * Context type with a guaranteed authenticated user.
41
+ *
42
+ * After `authenticatedNarrow` passes, the context is narrowed to this type.
43
+ */
44
+ export interface AuthenticatedContext {
45
+ auth: AuthContext & {
46
+ isAuthenticated: true;
47
+ };
48
+ user: User;
49
+ }
50
+ /**
51
+ * Context type with a guaranteed user having specific roles.
52
+ */
53
+ export interface RoleNarrowedContext {
54
+ user: User & {
55
+ roles: string[];
56
+ };
57
+ }
58
+ /**
59
+ * Authenticated guard with type narrowing.
60
+ *
61
+ * When used with `guardNarrow()`, narrows `ctx.user` from `User | undefined`
62
+ * to `User`, eliminating the need for null checks in the handler.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * import { authenticatedNarrow } from '@veloxts/auth';
67
+ *
68
+ * // With guardNarrow (experimental):
69
+ * procedure()
70
+ * .guardNarrow(authenticatedNarrow)
71
+ * .query(({ ctx }) => {
72
+ * // ctx.user is typed as User (non-null)
73
+ * return { email: ctx.user.email };
74
+ * });
75
+ *
76
+ * // Current recommended alternative using middleware:
77
+ * procedure()
78
+ * .guard(authenticated)
79
+ * .use(async ({ ctx, next }) => {
80
+ * if (!ctx.user) throw new Error('Unreachable');
81
+ * return next({ ctx: { user: ctx.user } });
82
+ * })
83
+ * .query(({ ctx }) => {
84
+ * // ctx.user is non-null via middleware
85
+ * });
86
+ * ```
87
+ */
88
+ export declare const authenticatedNarrow: NarrowingGuard<{
89
+ auth?: AuthContext;
90
+ }, AuthenticatedContext>;
91
+ /**
92
+ * Creates a role-checking guard with type narrowing.
93
+ *
94
+ * Narrows `ctx.user` to guarantee non-null with roles array.
95
+ *
96
+ * @param roles - Required role(s)
97
+ * @returns NarrowingGuard that guarantees user with roles
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * import { hasRoleNarrow } from '@veloxts/auth';
102
+ *
103
+ * procedure()
104
+ * .guardNarrow(hasRoleNarrow('admin'))
105
+ * .mutation(({ ctx }) => {
106
+ * // ctx.user is typed as User (non-null)
107
+ * // ctx.user.roles is string[]
108
+ * });
109
+ * ```
110
+ */
111
+ export declare function hasRoleNarrow(roles: string | string[]): NarrowingGuard<{
112
+ user?: User;
113
+ }, RoleNarrowedContext>;
114
+ /**
115
+ * Extracts the narrowed context type from a NarrowingGuard.
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * type Ctx = InferNarrowedContext<typeof authenticatedNarrow>;
120
+ * // Ctx = AuthenticatedContext = { auth: AuthContext & { isAuthenticated: true }; user: User }
121
+ * ```
122
+ */
123
+ export type InferNarrowedContext<T> = T extends NarrowingGuard<unknown, infer U> ? U : never;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Narrowing Guards (Experimental)
3
+ *
4
+ * These guards provide TypeScript type narrowing after they pass.
5
+ * When using `guardNarrow(authenticatedNarrow)`, the context type
6
+ * is narrowed to guarantee `ctx.user` is non-null.
7
+ *
8
+ * EXPERIMENTAL: This API may change. The current recommended approach
9
+ * is to use middleware for context type extension.
10
+ *
11
+ * @module auth/guards-narrowing
12
+ */
13
+ import { authenticated, hasRole as hasRoleBase } from './guards.js';
14
+ // ============================================================================
15
+ // Narrowing Guards
16
+ // ============================================================================
17
+ /**
18
+ * Authenticated guard with type narrowing.
19
+ *
20
+ * When used with `guardNarrow()`, narrows `ctx.user` from `User | undefined`
21
+ * to `User`, eliminating the need for null checks in the handler.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { authenticatedNarrow } from '@veloxts/auth';
26
+ *
27
+ * // With guardNarrow (experimental):
28
+ * procedure()
29
+ * .guardNarrow(authenticatedNarrow)
30
+ * .query(({ ctx }) => {
31
+ * // ctx.user is typed as User (non-null)
32
+ * return { email: ctx.user.email };
33
+ * });
34
+ *
35
+ * // Current recommended alternative using middleware:
36
+ * procedure()
37
+ * .guard(authenticated)
38
+ * .use(async ({ ctx, next }) => {
39
+ * if (!ctx.user) throw new Error('Unreachable');
40
+ * return next({ ctx: { user: ctx.user } });
41
+ * })
42
+ * .query(({ ctx }) => {
43
+ * // ctx.user is non-null via middleware
44
+ * });
45
+ * ```
46
+ */
47
+ export const authenticatedNarrow = {
48
+ ...authenticated,
49
+ // Phantom type: value is never used at runtime, only carries type info.
50
+ // The `undefined as unknown as T` pattern is standard for phantom types.
51
+ _narrows: undefined,
52
+ };
53
+ /**
54
+ * Creates a role-checking guard with type narrowing.
55
+ *
56
+ * Narrows `ctx.user` to guarantee non-null with roles array.
57
+ *
58
+ * @param roles - Required role(s)
59
+ * @returns NarrowingGuard that guarantees user with roles
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import { hasRoleNarrow } from '@veloxts/auth';
64
+ *
65
+ * procedure()
66
+ * .guardNarrow(hasRoleNarrow('admin'))
67
+ * .mutation(({ ctx }) => {
68
+ * // ctx.user is typed as User (non-null)
69
+ * // ctx.user.roles is string[]
70
+ * });
71
+ * ```
72
+ */
73
+ export function hasRoleNarrow(roles) {
74
+ const baseGuard = hasRoleBase(roles);
75
+ return {
76
+ ...baseGuard,
77
+ // Phantom type: carries type info for guardNarrow() context narrowing
78
+ _narrows: undefined,
79
+ };
80
+ }
package/dist/index.d.ts CHANGED
@@ -16,6 +16,10 @@ LegacySessionConfig, PolicyAction, PolicyDefinition, RateLimitConfig, TokenPair,
16
16
  export { AuthError } from './types.js';
17
17
  export type { TokenStore } from './jwt.js';
18
18
  export { createInMemoryTokenStore, generateTokenId, isValidTimespan, JwtManager, jwtManager, parseTimeToSeconds, validateTokenExpiration, } from './jwt.js';
19
+ export type { EnhancedTokenStore, EnhancedTokenStoreOptions } from './token-store.js';
20
+ export { createEnhancedTokenStore, DEFAULT_ALLOWED_ROLES, parseUserRoles, } from './token-store.js';
21
+ export type { AuthenticatedContext, InferNarrowedContext, NarrowingGuard, RoleNarrowedContext, } from './guards-narrowing.js';
22
+ export { authenticatedNarrow, hasRoleNarrow } from './guards-narrowing.js';
19
23
  export { hashPassword, PasswordHasher, passwordHasher, verifyPassword, } from './hash.js';
20
24
  export { allOf, anyOf, authenticated, defineGuard, emailVerified, executeGuard, executeGuards, guard, hasAnyPermission, hasPermission, hasRole, not, userCan, } from './guards.js';
21
25
  export { authorize, can, cannot, clearPolicies, createAdminOnlyPolicy, createOwnerOrAdminPolicy, createPolicyBuilder, createReadOnlyPolicy, definePolicy, getPolicy, registerPolicy, } from './policies.js';
package/dist/index.js CHANGED
@@ -13,6 +13,8 @@
13
13
  export { AUTH_VERSION } from './plugin.js';
14
14
  export { AuthError } from './types.js';
15
15
  export { createInMemoryTokenStore, generateTokenId, isValidTimespan, JwtManager, jwtManager, parseTimeToSeconds, validateTokenExpiration, } from './jwt.js';
16
+ export { createEnhancedTokenStore, DEFAULT_ALLOWED_ROLES, parseUserRoles, } from './token-store.js';
17
+ export { authenticatedNarrow, hasRoleNarrow } from './guards-narrowing.js';
16
18
  // ============================================================================
17
19
  // Password Hashing
18
20
  // ============================================================================
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Enhanced Token Store for JWT Revocation
3
+ *
4
+ * Provides an enhanced in-memory token store with:
5
+ * - Token revocation with expiry
6
+ * - Refresh token reuse detection
7
+ * - Automatic cleanup of expired entries
8
+ *
9
+ * @module auth/token-store
10
+ */
11
+ /**
12
+ * Enhanced token store interface
13
+ *
14
+ * Extends basic token revocation with refresh token reuse detection
15
+ * and automatic cleanup capabilities.
16
+ */
17
+ export interface EnhancedTokenStore {
18
+ /** Revoke a token with optional expiry time */
19
+ revoke(jti: string, expiresInMs?: number): void;
20
+ /** Check if token is revoked */
21
+ isRevoked(jti: string): boolean;
22
+ /** Mark refresh token as used (for reuse detection) */
23
+ markRefreshTokenUsed(jti: string, userId: string): void;
24
+ /** Check if refresh token was already used, returns userId if reused */
25
+ isRefreshTokenUsed(jti: string): string | undefined;
26
+ /** Revoke all tokens for a user (placeholder for production implementation) */
27
+ revokeAllUserTokens(userId: string): void;
28
+ /** Clear all entries (useful for testing) */
29
+ clear(): void;
30
+ /** Stop cleanup interval and release resources */
31
+ destroy(): void;
32
+ }
33
+ /**
34
+ * Options for creating an enhanced token store
35
+ */
36
+ export interface EnhancedTokenStoreOptions {
37
+ /** Interval for cleanup of expired entries (default: 5 minutes) */
38
+ cleanupIntervalMs?: number;
39
+ /** Default expiry for revoked tokens (default: 7 days) */
40
+ defaultExpiryMs?: number;
41
+ }
42
+ /**
43
+ * Creates an enhanced in-memory token store
44
+ *
45
+ * Provides token revocation with expiry tracking and refresh token
46
+ * reuse detection for security.
47
+ *
48
+ * **WARNING: NOT suitable for production!**
49
+ * Use Redis or database-backed store for:
50
+ * - Persistence across server restarts
51
+ * - Horizontal scaling (multiple server instances)
52
+ * - Proper token revocation across deployments
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * import { createEnhancedTokenStore } from '@veloxts/auth';
57
+ *
58
+ * // Create store with defaults
59
+ * const tokenStore = createEnhancedTokenStore();
60
+ *
61
+ * // Revoke token on logout
62
+ * tokenStore.revoke(accessTokenJti);
63
+ *
64
+ * // Detect refresh token reuse (security measure)
65
+ * const previousUser = tokenStore.isRefreshTokenUsed(refreshJti);
66
+ * if (previousUser) {
67
+ * // Potential token theft - revoke all user tokens
68
+ * tokenStore.revokeAllUserTokens(previousUser);
69
+ * throw new SecurityError('Token reuse detected');
70
+ * }
71
+ * tokenStore.markRefreshTokenUsed(refreshJti, userId);
72
+ *
73
+ * // Clean up on shutdown
74
+ * process.on('SIGTERM', () => tokenStore.destroy());
75
+ * ```
76
+ */
77
+ export declare function createEnhancedTokenStore(options?: EnhancedTokenStoreOptions): EnhancedTokenStore;
78
+ /**
79
+ * Default allowed roles for role parsing
80
+ */
81
+ export declare const DEFAULT_ALLOWED_ROLES: readonly ["user", "admin", "moderator", "editor"];
82
+ /**
83
+ * Parses JSON-encoded roles string to array
84
+ *
85
+ * Safely parses a JSON array of role strings, filtering to only allowed roles.
86
+ * Returns default ['user'] role if parsing fails or no valid roles found.
87
+ *
88
+ * @param rolesJson - JSON string of roles (e.g., '["admin", "user"]')
89
+ * @param allowedRoles - Optional list of valid roles (defaults to DEFAULT_ALLOWED_ROLES)
90
+ * @returns Array of valid roles, defaults to ['user'] if parsing fails
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * import { parseUserRoles } from '@veloxts/auth';
95
+ *
96
+ * const roles = parseUserRoles(user.roles);
97
+ * // Input: '["admin", "user"]' -> Output: ['admin', 'user']
98
+ * // Input: null -> Output: ['user']
99
+ * // Input: 'invalid' -> Output: ['user']
100
+ *
101
+ * // With custom allowed roles
102
+ * const roles = parseUserRoles(user.roles, ['admin', 'superadmin']);
103
+ * ```
104
+ */
105
+ export declare function parseUserRoles(rolesJson: string | null, allowedRoles?: readonly string[]): string[];
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Enhanced Token Store for JWT Revocation
3
+ *
4
+ * Provides an enhanced in-memory token store with:
5
+ * - Token revocation with expiry
6
+ * - Refresh token reuse detection
7
+ * - Automatic cleanup of expired entries
8
+ *
9
+ * @module auth/token-store
10
+ */
11
+ // ============================================================================
12
+ // Implementation
13
+ // ============================================================================
14
+ /**
15
+ * Creates an enhanced in-memory token store
16
+ *
17
+ * Provides token revocation with expiry tracking and refresh token
18
+ * reuse detection for security.
19
+ *
20
+ * **WARNING: NOT suitable for production!**
21
+ * Use Redis or database-backed store for:
22
+ * - Persistence across server restarts
23
+ * - Horizontal scaling (multiple server instances)
24
+ * - Proper token revocation across deployments
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { createEnhancedTokenStore } from '@veloxts/auth';
29
+ *
30
+ * // Create store with defaults
31
+ * const tokenStore = createEnhancedTokenStore();
32
+ *
33
+ * // Revoke token on logout
34
+ * tokenStore.revoke(accessTokenJti);
35
+ *
36
+ * // Detect refresh token reuse (security measure)
37
+ * const previousUser = tokenStore.isRefreshTokenUsed(refreshJti);
38
+ * if (previousUser) {
39
+ * // Potential token theft - revoke all user tokens
40
+ * tokenStore.revokeAllUserTokens(previousUser);
41
+ * throw new SecurityError('Token reuse detected');
42
+ * }
43
+ * tokenStore.markRefreshTokenUsed(refreshJti, userId);
44
+ *
45
+ * // Clean up on shutdown
46
+ * process.on('SIGTERM', () => tokenStore.destroy());
47
+ * ```
48
+ */
49
+ export function createEnhancedTokenStore(options) {
50
+ const { cleanupIntervalMs = 5 * 60 * 1000, defaultExpiryMs = 7 * 24 * 60 * 60 * 1000 } = options ?? {};
51
+ const revokedTokens = new Map();
52
+ const usedRefreshTokens = new Map();
53
+ // Track pending timeouts to prevent memory leaks on destroy()
54
+ const pendingTimeouts = new Set();
55
+ const cleanup = () => {
56
+ const now = Date.now();
57
+ for (const [jti, expiry] of revokedTokens.entries()) {
58
+ if (now > expiry) {
59
+ revokedTokens.delete(jti);
60
+ }
61
+ }
62
+ };
63
+ const cleanupInterval = setInterval(cleanup, cleanupIntervalMs);
64
+ return {
65
+ revoke(jti, expiresInMs = defaultExpiryMs) {
66
+ revokedTokens.set(jti, Date.now() + expiresInMs);
67
+ },
68
+ isRevoked(jti) {
69
+ const expiry = revokedTokens.get(jti);
70
+ if (!expiry)
71
+ return false;
72
+ if (Date.now() > expiry) {
73
+ revokedTokens.delete(jti);
74
+ return false;
75
+ }
76
+ return true;
77
+ },
78
+ markRefreshTokenUsed(jti, userId) {
79
+ usedRefreshTokens.set(jti, userId);
80
+ // Auto-expire after default expiry, track timeout for cleanup
81
+ const timeout = setTimeout(() => {
82
+ usedRefreshTokens.delete(jti);
83
+ pendingTimeouts.delete(timeout);
84
+ }, defaultExpiryMs);
85
+ pendingTimeouts.add(timeout);
86
+ },
87
+ isRefreshTokenUsed(jti) {
88
+ return usedRefreshTokens.get(jti);
89
+ },
90
+ revokeAllUserTokens(userId) {
91
+ // Placeholder - in production, implement proper user->token mapping
92
+ console.warn(`[Security] Token reuse detected for user ${userId}. ` +
93
+ 'All tokens should be revoked. Implement proper user->token mapping for production.');
94
+ },
95
+ clear() {
96
+ // Clear pending timeouts since we're clearing the tokens they reference
97
+ for (const timeout of pendingTimeouts) {
98
+ clearTimeout(timeout);
99
+ }
100
+ pendingTimeouts.clear();
101
+ revokedTokens.clear();
102
+ usedRefreshTokens.clear();
103
+ },
104
+ destroy() {
105
+ clearInterval(cleanupInterval);
106
+ // Clear all pending timeouts to prevent memory leaks
107
+ for (const timeout of pendingTimeouts) {
108
+ clearTimeout(timeout);
109
+ }
110
+ pendingTimeouts.clear();
111
+ revokedTokens.clear();
112
+ usedRefreshTokens.clear();
113
+ },
114
+ };
115
+ }
116
+ /**
117
+ * Default allowed roles for role parsing
118
+ */
119
+ export const DEFAULT_ALLOWED_ROLES = ['user', 'admin', 'moderator', 'editor'];
120
+ /**
121
+ * Parses JSON-encoded roles string to array
122
+ *
123
+ * Safely parses a JSON array of role strings, filtering to only allowed roles.
124
+ * Returns default ['user'] role if parsing fails or no valid roles found.
125
+ *
126
+ * @param rolesJson - JSON string of roles (e.g., '["admin", "user"]')
127
+ * @param allowedRoles - Optional list of valid roles (defaults to DEFAULT_ALLOWED_ROLES)
128
+ * @returns Array of valid roles, defaults to ['user'] if parsing fails
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * import { parseUserRoles } from '@veloxts/auth';
133
+ *
134
+ * const roles = parseUserRoles(user.roles);
135
+ * // Input: '["admin", "user"]' -> Output: ['admin', 'user']
136
+ * // Input: null -> Output: ['user']
137
+ * // Input: 'invalid' -> Output: ['user']
138
+ *
139
+ * // With custom allowed roles
140
+ * const roles = parseUserRoles(user.roles, ['admin', 'superadmin']);
141
+ * ```
142
+ */
143
+ export function parseUserRoles(rolesJson, allowedRoles = DEFAULT_ALLOWED_ROLES) {
144
+ if (!rolesJson)
145
+ return ['user'];
146
+ try {
147
+ const parsed = JSON.parse(rolesJson);
148
+ if (!Array.isArray(parsed)) {
149
+ return ['user'];
150
+ }
151
+ const validRoles = parsed
152
+ .filter((role) => typeof role === 'string')
153
+ .filter((role) => allowedRoles.includes(role));
154
+ return validRoles.length > 0 ? validRoles : ['user'];
155
+ }
156
+ catch {
157
+ return ['user'];
158
+ }
159
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/auth",
3
- "version": "0.6.68",
3
+ "version": "0.6.69",
4
4
  "description": "Authentication and authorization system for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -57,8 +57,8 @@
57
57
  "dependencies": {
58
58
  "@fastify/cookie": "11.0.2",
59
59
  "fastify": "5.6.2",
60
- "@veloxts/router": "0.6.68",
61
- "@veloxts/core": "0.6.68"
60
+ "@veloxts/core": "0.6.69",
61
+ "@veloxts/router": "0.6.69"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "argon2": ">=0.30.0",
@@ -82,8 +82,8 @@
82
82
  "fastify-plugin": "5.1.0",
83
83
  "typescript": "5.9.3",
84
84
  "vitest": "4.0.16",
85
- "@veloxts/testing": "0.6.68",
86
- "@veloxts/validation": "0.6.68"
85
+ "@veloxts/testing": "0.6.69",
86
+ "@veloxts/validation": "0.6.69"
87
87
  },
88
88
  "keywords": [
89
89
  "velox",