@soulbatical/tetra-core 0.1.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.

Potentially problematic release.


This version of @soulbatical/tetra-core might be problematic. Click here for more details.

Files changed (181) hide show
  1. package/dist/core/SupabaseUserClient.d.ts +14 -0
  2. package/dist/core/SupabaseUserClient.d.ts.map +1 -0
  3. package/dist/core/SupabaseUserClient.js +49 -0
  4. package/dist/core/SupabaseUserClient.js.map +1 -0
  5. package/dist/core/adminDb.d.ts +21 -0
  6. package/dist/core/adminDb.d.ts.map +1 -0
  7. package/dist/core/adminDb.js +43 -0
  8. package/dist/core/adminDb.js.map +1 -0
  9. package/dist/core/publicDb.d.ts +19 -0
  10. package/dist/core/publicDb.d.ts.map +1 -0
  11. package/dist/core/publicDb.js +29 -0
  12. package/dist/core/publicDb.js.map +1 -0
  13. package/dist/core/superadminDb.d.ts +29 -0
  14. package/dist/core/superadminDb.d.ts.map +1 -0
  15. package/dist/core/superadminDb.js +53 -0
  16. package/dist/core/superadminDb.js.map +1 -0
  17. package/dist/core/systemDb.d.ts +34 -0
  18. package/dist/core/systemDb.d.ts.map +1 -0
  19. package/dist/core/systemDb.js +64 -0
  20. package/dist/core/systemDb.js.map +1 -0
  21. package/dist/core/userDb.d.ts +22 -0
  22. package/dist/core/userDb.d.ts.map +1 -0
  23. package/dist/core/userDb.js +36 -0
  24. package/dist/core/userDb.js.map +1 -0
  25. package/dist/core/webhookDb.d.ts +16 -0
  26. package/dist/core/webhookDb.d.ts.map +1 -0
  27. package/dist/core/webhookDb.js +26 -0
  28. package/dist/core/webhookDb.js.map +1 -0
  29. package/dist/frontend/components/FeatureFilters.d.ts +81 -0
  30. package/dist/frontend/components/FeatureFilters.d.ts.map +1 -0
  31. package/dist/frontend/components/FeatureFilters.js +257 -0
  32. package/dist/frontend/components/FeatureFilters.js.map +1 -0
  33. package/dist/frontend/components/FeatureTable.d.ts +96 -0
  34. package/dist/frontend/components/FeatureTable.d.ts.map +1 -0
  35. package/dist/frontend/components/FeatureTable.js +279 -0
  36. package/dist/frontend/components/FeatureTable.js.map +1 -0
  37. package/dist/frontend/hooks/useFeature.d.ts +108 -0
  38. package/dist/frontend/hooks/useFeature.d.ts.map +1 -0
  39. package/dist/frontend/hooks/useFeature.js +354 -0
  40. package/dist/frontend/hooks/useFeature.js.map +1 -0
  41. package/dist/frontend/index.d.ts +17 -0
  42. package/dist/frontend/index.d.ts.map +1 -0
  43. package/dist/frontend/index.js +14 -0
  44. package/dist/frontend/index.js.map +1 -0
  45. package/dist/index.d.ts +78 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +71 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/middleware/authMiddleware.d.ts +137 -0
  50. package/dist/middleware/authMiddleware.d.ts.map +1 -0
  51. package/dist/middleware/authMiddleware.js +292 -0
  52. package/dist/middleware/authMiddleware.js.map +1 -0
  53. package/dist/middleware/autoRegisterValidators.d.ts +31 -0
  54. package/dist/middleware/autoRegisterValidators.d.ts.map +1 -0
  55. package/dist/middleware/autoRegisterValidators.js +125 -0
  56. package/dist/middleware/autoRegisterValidators.js.map +1 -0
  57. package/dist/middleware/validateQueryParams.d.ts +62 -0
  58. package/dist/middleware/validateQueryParams.d.ts.map +1 -0
  59. package/dist/middleware/validateQueryParams.js +321 -0
  60. package/dist/middleware/validateQueryParams.js.map +1 -0
  61. package/dist/shared/controllers/BaseMutationController.d.ts +79 -0
  62. package/dist/shared/controllers/BaseMutationController.d.ts.map +1 -0
  63. package/dist/shared/controllers/BaseMutationController.js +241 -0
  64. package/dist/shared/controllers/BaseMutationController.js.map +1 -0
  65. package/dist/shared/controllers/BaseQueryController.d.ts +72 -0
  66. package/dist/shared/controllers/BaseQueryController.d.ts.map +1 -0
  67. package/dist/shared/controllers/BaseQueryController.js +375 -0
  68. package/dist/shared/controllers/BaseQueryController.js.map +1 -0
  69. package/dist/shared/controllers/FilterConfigController.d.ts +37 -0
  70. package/dist/shared/controllers/FilterConfigController.d.ts.map +1 -0
  71. package/dist/shared/controllers/FilterConfigController.js +94 -0
  72. package/dist/shared/controllers/FilterConfigController.js.map +1 -0
  73. package/dist/shared/controllers/index.d.ts +5 -0
  74. package/dist/shared/controllers/index.d.ts.map +1 -0
  75. package/dist/shared/controllers/index.js +4 -0
  76. package/dist/shared/controllers/index.js.map +1 -0
  77. package/dist/shared/controllers/types.d.ts +57 -0
  78. package/dist/shared/controllers/types.d.ts.map +1 -0
  79. package/dist/shared/controllers/types.js +2 -0
  80. package/dist/shared/controllers/types.js.map +1 -0
  81. package/dist/shared/factories/BatchRouteFactory.d.ts +33 -0
  82. package/dist/shared/factories/BatchRouteFactory.d.ts.map +1 -0
  83. package/dist/shared/factories/BatchRouteFactory.js +54 -0
  84. package/dist/shared/factories/BatchRouteFactory.js.map +1 -0
  85. package/dist/shared/factories/MutationRouteFactory.d.ts +27 -0
  86. package/dist/shared/factories/MutationRouteFactory.d.ts.map +1 -0
  87. package/dist/shared/factories/MutationRouteFactory.js +39 -0
  88. package/dist/shared/factories/MutationRouteFactory.js.map +1 -0
  89. package/dist/shared/factories/PhaseRouteFactory.d.ts +33 -0
  90. package/dist/shared/factories/PhaseRouteFactory.d.ts.map +1 -0
  91. package/dist/shared/factories/PhaseRouteFactory.js +67 -0
  92. package/dist/shared/factories/PhaseRouteFactory.js.map +1 -0
  93. package/dist/shared/factories/QueryRouteFactory.d.ts +37 -0
  94. package/dist/shared/factories/QueryRouteFactory.d.ts.map +1 -0
  95. package/dist/shared/factories/QueryRouteFactory.js +244 -0
  96. package/dist/shared/factories/QueryRouteFactory.js.map +1 -0
  97. package/dist/shared/factories/QueryServiceFactory.d.ts +282 -0
  98. package/dist/shared/factories/QueryServiceFactory.d.ts.map +1 -0
  99. package/dist/shared/factories/QueryServiceFactory.js +277 -0
  100. package/dist/shared/factories/QueryServiceFactory.js.map +1 -0
  101. package/dist/shared/factories/index.d.ts +8 -0
  102. package/dist/shared/factories/index.d.ts.map +1 -0
  103. package/dist/shared/factories/index.js +6 -0
  104. package/dist/shared/factories/index.js.map +1 -0
  105. package/dist/shared/factories/types.d.ts +98 -0
  106. package/dist/shared/factories/types.d.ts.map +1 -0
  107. package/dist/shared/factories/types.js +8 -0
  108. package/dist/shared/factories/types.js.map +1 -0
  109. package/dist/shared/ownership/types.d.ts +84 -0
  110. package/dist/shared/ownership/types.d.ts.map +1 -0
  111. package/dist/shared/ownership/types.js +8 -0
  112. package/dist/shared/ownership/types.js.map +1 -0
  113. package/dist/shared/rfc7807ErrorResponse.d.ts +54 -0
  114. package/dist/shared/rfc7807ErrorResponse.d.ts.map +1 -0
  115. package/dist/shared/rfc7807ErrorResponse.js +98 -0
  116. package/dist/shared/rfc7807ErrorResponse.js.map +1 -0
  117. package/dist/shared/services/BaseCronService.d.ts +171 -0
  118. package/dist/shared/services/BaseCronService.d.ts.map +1 -0
  119. package/dist/shared/services/BaseCronService.js +444 -0
  120. package/dist/shared/services/BaseCronService.js.map +1 -0
  121. package/dist/shared/services/BasePhaseService.d.ts +170 -0
  122. package/dist/shared/services/BasePhaseService.d.ts.map +1 -0
  123. package/dist/shared/services/BasePhaseService.js +409 -0
  124. package/dist/shared/services/BasePhaseService.js.map +1 -0
  125. package/dist/shared/services/CronRegistry.d.ts +67 -0
  126. package/dist/shared/services/CronRegistry.d.ts.map +1 -0
  127. package/dist/shared/services/CronRegistry.js +68 -0
  128. package/dist/shared/services/CronRegistry.js.map +1 -0
  129. package/dist/shared/services/SimpleRPCQueryService.d.ts +111 -0
  130. package/dist/shared/services/SimpleRPCQueryService.d.ts.map +1 -0
  131. package/dist/shared/services/SimpleRPCQueryService.js +265 -0
  132. package/dist/shared/services/SimpleRPCQueryService.js.map +1 -0
  133. package/dist/shared/types/feature-config.d.ts +611 -0
  134. package/dist/shared/types/feature-config.d.ts.map +1 -0
  135. package/dist/shared/types/feature-config.js +85 -0
  136. package/dist/shared/types/feature-config.js.map +1 -0
  137. package/dist/shared/types/query-config.d.ts +41 -0
  138. package/dist/shared/types/query-config.d.ts.map +1 -0
  139. package/dist/shared/types/query-config.js +6 -0
  140. package/dist/shared/types/query-config.js.map +1 -0
  141. package/dist/shared/utils/config-driven-filters.d.ts +215 -0
  142. package/dist/shared/utils/config-driven-filters.d.ts.map +1 -0
  143. package/dist/shared/utils/config-driven-filters.js +451 -0
  144. package/dist/shared/utils/config-driven-filters.js.map +1 -0
  145. package/dist/shared/utils/controllerErrorHandler.d.ts +44 -0
  146. package/dist/shared/utils/controllerErrorHandler.d.ts.map +1 -0
  147. package/dist/shared/utils/controllerErrorHandler.js +126 -0
  148. package/dist/shared/utils/controllerErrorHandler.js.map +1 -0
  149. package/dist/shared/utils/parseInclude.d.ts +14 -0
  150. package/dist/shared/utils/parseInclude.d.ts.map +1 -0
  151. package/dist/shared/utils/parseInclude.js +28 -0
  152. package/dist/shared/utils/parseInclude.js.map +1 -0
  153. package/dist/shared/utils/queryHelpers.d.ts +62 -0
  154. package/dist/shared/utils/queryHelpers.d.ts.map +1 -0
  155. package/dist/shared/utils/queryHelpers.js +61 -0
  156. package/dist/shared/utils/queryHelpers.js.map +1 -0
  157. package/dist/shared/utils/response-mapper.d.ts +42 -0
  158. package/dist/shared/utils/response-mapper.d.ts.map +1 -0
  159. package/dist/shared/utils/response-mapper.js +176 -0
  160. package/dist/shared/utils/response-mapper.js.map +1 -0
  161. package/dist/shared/utils/responseBuilder.d.ts +103 -0
  162. package/dist/shared/utils/responseBuilder.d.ts.map +1 -0
  163. package/dist/shared/utils/responseBuilder.js +131 -0
  164. package/dist/shared/utils/responseBuilder.js.map +1 -0
  165. package/dist/shared/validators/featureConfigValidator.d.ts +23 -0
  166. package/dist/shared/validators/featureConfigValidator.d.ts.map +1 -0
  167. package/dist/shared/validators/featureConfigValidator.js +143 -0
  168. package/dist/shared/validators/featureConfigValidator.js.map +1 -0
  169. package/dist/shared/validators/organizationValidator.d.ts +53 -0
  170. package/dist/shared/validators/organizationValidator.d.ts.map +1 -0
  171. package/dist/shared/validators/organizationValidator.js +69 -0
  172. package/dist/shared/validators/organizationValidator.js.map +1 -0
  173. package/dist/shared/validators/uuidValidator.d.ts +57 -0
  174. package/dist/shared/validators/uuidValidator.d.ts.map +1 -0
  175. package/dist/shared/validators/uuidValidator.js +77 -0
  176. package/dist/shared/validators/uuidValidator.js.map +1 -0
  177. package/dist/utils/logger.d.ts +40 -0
  178. package/dist/utils/logger.d.ts.map +1 -0
  179. package/dist/utils/logger.js +47 -0
  180. package/dist/utils/logger.js.map +1 -0
  181. package/package.json +80 -0
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Parse ?include= query parameter
3
+ *
4
+ * Supports: ?include=booktemplate,humandesignchart,productionitem
5
+ * Returns array of include strings
6
+ *
7
+ * @example
8
+ * parseIncludeParam('users,orders') // ['users', 'orders']
9
+ * parseIncludeParam(['users', 'orders']) // ['users', 'orders']
10
+ * parseIncludeParam(undefined) // []
11
+ */
12
+ export function parseIncludeParam(include) {
13
+ if (!include)
14
+ return [];
15
+ // Handle array (already parsed by Express)
16
+ if (Array.isArray(include)) {
17
+ return include.filter((item) => typeof item === 'string');
18
+ }
19
+ // Handle comma-separated string
20
+ if (typeof include === 'string') {
21
+ return include
22
+ .split(',')
23
+ .map(s => s.trim())
24
+ .filter(s => s.length > 0);
25
+ }
26
+ return [];
27
+ }
28
+ //# sourceMappingURL=parseInclude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseInclude.js","sourceRoot":"","sources":["../../../src/shared/utils/parseInclude.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAyE;IACzG,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAExB,2CAA2C;IAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAa,CAAC;IACxF,CAAC;IAED,gCAAgC;IAChC,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,OAAO;aACX,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aAClB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,62 @@
1
+ interface RequestWithValidatedQuery {
2
+ validatedQuery?: any;
3
+ query: any;
4
+ }
5
+ type AuthenticatedRequest = RequestWithValidatedQuery;
6
+ /**
7
+ * Query Parameter Extraction Utilities
8
+ *
9
+ * Provides consistent fallback between validatedQuery and query
10
+ * Prevents inconsistent query parameter handling across controllers
11
+ */
12
+ /**
13
+ * Get query parameters with fallback to validated query
14
+ *
15
+ * Use this at the start of controller methods for consistent behavior
16
+ *
17
+ * @param req - Authenticated request
18
+ * @returns Query parameters (validated if available, otherwise raw)
19
+ *
20
+ * @example
21
+ * const querySource = getValidatedQuery(req);
22
+ * const { limit = 50, offset = 0, search } = querySource;
23
+ */
24
+ export declare function getValidatedQuery(req: AuthenticatedRequest): any;
25
+ /**
26
+ * Parse query parameters with defaults
27
+ *
28
+ * Merges default values with query parameters
29
+ *
30
+ * @param query - Query object (from req.query or req.validatedQuery)
31
+ * @param defaults - Default values for parameters
32
+ * @returns Merged parameters with defaults
33
+ *
34
+ * @example
35
+ * const params = parseQueryParams(req.query, {
36
+ * limit: 50,
37
+ * offset: 0,
38
+ * sortBy: 'created_at'
39
+ * });
40
+ * // Returns: { limit: 50, offset: 0, sortBy: 'created_at', ...req.query }
41
+ */
42
+ export declare function parseQueryParams<T extends Record<string, any>>(query: any, defaults: T): T;
43
+ /**
44
+ * Parse pagination parameters from query
45
+ *
46
+ * Extracts and validates limit/offset with defaults
47
+ *
48
+ * @param query - Query object
49
+ * @param defaultLimit - Default limit (default: 50)
50
+ * @param maxLimit - Maximum allowed limit (default: 100)
51
+ * @returns Pagination parameters
52
+ *
53
+ * @example
54
+ * const { limit, offset } = parsePaginationParams(req.query);
55
+ * // Returns: { limit: 50, offset: 0 } (or values from query)
56
+ */
57
+ export declare function parsePaginationParams(query: any, defaultLimit?: number, maxLimit?: number): {
58
+ limit: number;
59
+ offset: number;
60
+ };
61
+ export {};
62
+ //# sourceMappingURL=queryHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryHelpers.d.ts","sourceRoot":"","sources":["../../../src/shared/utils/queryHelpers.ts"],"names":[],"mappings":"AAAA,UAAU,yBAAyB;IACjC,cAAc,CAAC,EAAE,GAAG,CAAC;IACrB,KAAK,EAAE,GAAG,CAAC;CACZ;AAED,KAAK,oBAAoB,GAAG,yBAAyB,CAAC;AAEtD;;;;;GAKG;AAEH;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,oBAAoB,GAAG,GAAG,CAEhE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5D,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,CAAC,GACV,CAAC,CAEH;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,GAAG,EACV,YAAY,GAAE,MAAW,EACzB,QAAQ,GAAE,MAAY,GACrB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAQnC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Query Parameter Extraction Utilities
3
+ *
4
+ * Provides consistent fallback between validatedQuery and query
5
+ * Prevents inconsistent query parameter handling across controllers
6
+ */
7
+ /**
8
+ * Get query parameters with fallback to validated query
9
+ *
10
+ * Use this at the start of controller methods for consistent behavior
11
+ *
12
+ * @param req - Authenticated request
13
+ * @returns Query parameters (validated if available, otherwise raw)
14
+ *
15
+ * @example
16
+ * const querySource = getValidatedQuery(req);
17
+ * const { limit = 50, offset = 0, search } = querySource;
18
+ */
19
+ export function getValidatedQuery(req) {
20
+ return req.validatedQuery || req.query;
21
+ }
22
+ /**
23
+ * Parse query parameters with defaults
24
+ *
25
+ * Merges default values with query parameters
26
+ *
27
+ * @param query - Query object (from req.query or req.validatedQuery)
28
+ * @param defaults - Default values for parameters
29
+ * @returns Merged parameters with defaults
30
+ *
31
+ * @example
32
+ * const params = parseQueryParams(req.query, {
33
+ * limit: 50,
34
+ * offset: 0,
35
+ * sortBy: 'created_at'
36
+ * });
37
+ * // Returns: { limit: 50, offset: 0, sortBy: 'created_at', ...req.query }
38
+ */
39
+ export function parseQueryParams(query, defaults) {
40
+ return { ...defaults, ...query };
41
+ }
42
+ /**
43
+ * Parse pagination parameters from query
44
+ *
45
+ * Extracts and validates limit/offset with defaults
46
+ *
47
+ * @param query - Query object
48
+ * @param defaultLimit - Default limit (default: 50)
49
+ * @param maxLimit - Maximum allowed limit (default: 100)
50
+ * @returns Pagination parameters
51
+ *
52
+ * @example
53
+ * const { limit, offset } = parsePaginationParams(req.query);
54
+ * // Returns: { limit: 50, offset: 0 } (or values from query)
55
+ */
56
+ export function parsePaginationParams(query, defaultLimit = 50, maxLimit = 100) {
57
+ const limit = Math.min(parseInt(String(query.limit)) || defaultLimit, maxLimit);
58
+ const offset = parseInt(String(query.offset)) || 0;
59
+ return { limit, offset };
60
+ }
61
+ //# sourceMappingURL=queryHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queryHelpers.js","sourceRoot":"","sources":["../../../src/shared/utils/queryHelpers.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAyB;IACzD,OAAO,GAAG,CAAC,cAAc,IAAI,GAAG,CAAC,KAAK,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAU,EACV,QAAW;IAEX,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAU,EACV,eAAuB,EAAE,EACzB,WAAmB,GAAG;IAEtB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,YAAY,EAC7C,QAAQ,CACT,CAAC;IACF,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;IAEnD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * response-mapper.ts
3
+ *
4
+ * Generic response mapper for config-driven counts responses.
5
+ * Keeps RPC JSON structure nested - NO FLATTENING!
6
+ *
7
+ * ✅ Works for ALL features (orders, vouchers, products, etc.)
8
+ * ✅ 100% config-driven - no hardcoding
9
+ * ✅ Single source of truth
10
+ * ✅ Consistent nested format: byXxx: { option1: count1, option2: count2 }
11
+ */
12
+ import { FeatureConfig } from '../types/feature-config.js';
13
+ /**
14
+ * Map RPC counts response to API response format
15
+ *
16
+ * ✨ ENFORCES POSTGRESQL STANDARD: snake_case keys in RPC (SQL industry standard)
17
+ *
18
+ * RPC MUST return snake_case keys, mapper transforms to option.value for frontend:
19
+ * - Enum filters: byStatus: { 'paid': 10, 'pending': 0 } → API: { paid: 10, pending: 0 }
20
+ * - Related filters: byVoucher: { 'with': 10, 'without': 0 } → API: { with: 10, without: 0 }
21
+ * - Time filters: byTimePeriod: { 'this_week': 10, 'this_month': 20 } → API: { 'this-week': 10, 'this-month': 20 }
22
+ *
23
+ * ✅ STRICT VALIDATION - Fails fast with clear error if RPC keys don't match expected format
24
+ *
25
+ * @param rpcData - Raw data from RPC function (snake_case keys)
26
+ * @param featureConfig - Feature configuration with filter definitions
27
+ * @returns Nested response object (option.value keys for frontend)
28
+ * @throws Error if RPC missing required counts or using wrong key format
29
+ */
30
+ export declare function mapCountsFromRPC(rpcData: any, featureConfig: FeatureConfig): Record<string, any>;
31
+ /**
32
+ * Example usage:
33
+ *
34
+ * // In OrderQueryService:
35
+ * const rpcData = await this.supabase.rpc('get_order_counts_simple', rpcParams);
36
+ * return mapCountsFromRPC(rpcData, ordersFeatureConfig);
37
+ *
38
+ * // Works for ANY feature!
39
+ * const rpcData = await this.supabase.rpc('get_voucher_counts_simple', rpcParams);
40
+ * return mapCountsFromRPC(rpcData, vouchersFeatureConfig);
41
+ */
42
+ //# sourceMappingURL=response-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-mapper.d.ts","sourceRoot":"","sources":["../../../src/shared/utils/response-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAgB,MAAM,4BAA4B,CAAC;AA0BzE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,GAAG,EACZ,aAAa,EAAE,aAAa,GAC3B,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAyIrB;AAED;;;;;;;;;;GAUG"}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * response-mapper.ts
3
+ *
4
+ * Generic response mapper for config-driven counts responses.
5
+ * Keeps RPC JSON structure nested - NO FLATTENING!
6
+ *
7
+ * ✅ Works for ALL features (orders, vouchers, products, etc.)
8
+ * ✅ 100% config-driven - no hardcoding
9
+ * ✅ Single source of truth
10
+ * ✅ Consistent nested format: byXxx: { option1: count1, option2: count2 }
11
+ */
12
+ import { createLogger } from '../../utils/logger.js';
13
+ const logger = createLogger('utils:response-mapper');
14
+ /**
15
+ * Capitalize first letter of string
16
+ */
17
+ // Removed unused capitalize function
18
+ /**
19
+ * Convert option.value (kebab-case) to RPC key (snake_case)
20
+ *
21
+ * PostgreSQL Standard: Database functions use snake_case (industry standard for SQL)
22
+ * Config uses kebab-case for UI ('this-week'), RPC uses snake_case ('this_week')
23
+ *
24
+ * Examples:
25
+ * - 'this-week' → 'this_week'
26
+ * - 'with' → 'with' (no change)
27
+ * - 'paid' → 'paid' (no change)
28
+ */
29
+ function optionValueToRpcKey(optionValue) {
30
+ return optionValue.replace(/-/g, '_');
31
+ }
32
+ /**
33
+ * Map RPC counts response to API response format
34
+ *
35
+ * ✨ ENFORCES POSTGRESQL STANDARD: snake_case keys in RPC (SQL industry standard)
36
+ *
37
+ * RPC MUST return snake_case keys, mapper transforms to option.value for frontend:
38
+ * - Enum filters: byStatus: { 'paid': 10, 'pending': 0 } → API: { paid: 10, pending: 0 }
39
+ * - Related filters: byVoucher: { 'with': 10, 'without': 0 } → API: { with: 10, without: 0 }
40
+ * - Time filters: byTimePeriod: { 'this_week': 10, 'this_month': 20 } → API: { 'this-week': 10, 'this-month': 20 }
41
+ *
42
+ * ✅ STRICT VALIDATION - Fails fast with clear error if RPC keys don't match expected format
43
+ *
44
+ * @param rpcData - Raw data from RPC function (snake_case keys)
45
+ * @param featureConfig - Feature configuration with filter definitions
46
+ * @returns Nested response object (option.value keys for frontend)
47
+ * @throws Error if RPC missing required counts or using wrong key format
48
+ */
49
+ export function mapCountsFromRPC(rpcData, featureConfig) {
50
+ if (!rpcData) {
51
+ logger.warn('No RPC data provided to mapper');
52
+ return { total: 0 };
53
+ }
54
+ const result = {
55
+ total: rpcData.total || 0
56
+ };
57
+ logger.debug({
58
+ filterCount: featureConfig.filters.length,
59
+ rpcKeys: Object.keys(rpcData)
60
+ }, '🗺️ Mapping RPC response to API format');
61
+ // Process each filter in config
62
+ featureConfig.filters.forEach((filter) => {
63
+ const countsKey = filter.countsKey;
64
+ if (!countsKey) {
65
+ // Filter has no counts (e.g., search filter)
66
+ return;
67
+ }
68
+ const countsData = rpcData[countsKey];
69
+ // ✨ UNIVERSAL PATTERN - Works for ALL filter types!
70
+ // Initialize all options from config with 0, then merge RPC data
71
+ // RPC: { byXxx: { option1: 10 } }
72
+ // API: { byXxx: { option1: 10, option2: 0, option3: 0 } }
73
+ if (!filter.ui?.options) {
74
+ // No options defined - just copy data as-is
75
+ result[countsKey] = countsData || {};
76
+ logger.debug({ filter: filter.name, countsKey }, '✅ Mapped filter (no options)');
77
+ return;
78
+ }
79
+ // Initialize with 0 for all defined options
80
+ const initializedCounts = {};
81
+ filter.ui.options.forEach(option => {
82
+ // Skip 'all' option - it's not a real count
83
+ if (option.value === 'all')
84
+ return;
85
+ // ✅ KEEP EXACT MATCH - No transformation!
86
+ // AutoFilter does direct lookup: countsData[option.value]
87
+ // So keys MUST match option values exactly:
88
+ // 'this-week' → 'this-week', 'with' → 'with', 'paid' → 'paid'
89
+ const optionKey = option.value;
90
+ initializedCounts[optionKey] = 0;
91
+ });
92
+ // ✅ STRICT VALIDATION - Enforce industry standard (camelCase keys in JSON)
93
+ const transformedData = {};
94
+ if (countsData && typeof countsData === 'object') {
95
+ filter.ui.options.forEach(option => {
96
+ if (option.value === 'all')
97
+ return;
98
+ const optionKey = option.value; // e.g., 'this-week', 'paid', 'with', 'EN-GB'
99
+ const rpcKey = optionValueToRpcKey(optionKey); // e.g., 'this_week', 'paid', 'with', 'EN_GB'
100
+ // ✅ RPC can return snake_case OR original keys (for special values like ISO language codes)
101
+ // Try snake_case first (PostgreSQL standard), then fallback to original key
102
+ const actualRpcKey = countsData[rpcKey] !== undefined ? rpcKey : optionKey;
103
+ if (countsData[actualRpcKey] === undefined) {
104
+ // ❌ FAIL FAST - Missing count in RPC response!
105
+ throw new Error(`❌ RPC response missing count for filter option!\n` +
106
+ `Filter: "${filter.name}" (${filter.type})\n` +
107
+ `Expected counts key: "${countsKey}"\n` +
108
+ `Missing RPC key: "${rpcKey}" (for option "${optionKey}")\n` +
109
+ `RPC returned keys in ${countsKey}: ${Object.keys(countsData).join(', ')}\n` +
110
+ `\n` +
111
+ `💡 FIX: Update your RPC function to use snake_case keys (PostgreSQL standard):\n` +
112
+ ` ${countsKey}: jsonb_build_object(\n` +
113
+ ` ${filter.ui.options.filter(o => o.value !== 'all').map(o => `'${optionValueToRpcKey(o.value)}', COUNT(...)`).join(',\n ')}\n` +
114
+ ` )\n` +
115
+ `\n` +
116
+ ` Example for your filter:\n` +
117
+ ` ${countsKey}: jsonb_build_object('${rpcKey}', COUNT(...))`);
118
+ }
119
+ // Store with UI key (option.value) for frontend consistency
120
+ transformedData[optionKey] = countsData[actualRpcKey];
121
+ });
122
+ }
123
+ else if (filter.type !== 'search') {
124
+ // ❌ FAIL FAST - RPC missing entire counts object!
125
+ throw new Error(`❌ RPC response missing counts object!\n` +
126
+ `Filter: "${filter.name}" (${filter.type})\n` +
127
+ `Expected counts key: "${countsKey}"\n` +
128
+ `RPC returned keys: ${Object.keys(rpcData).join(', ')}\n` +
129
+ `\n` +
130
+ `💡 FIX: Update your RPC function to return (camelCase keys - industry standard):\n` +
131
+ ` ${countsKey}: jsonb_build_object(\n` +
132
+ ` ${filter.ui.options.filter(o => o.value !== 'all').map(o => `'${optionValueToRpcKey(o.value)}', COUNT(...)`).join(',\n ')}\n` +
133
+ ` )`);
134
+ }
135
+ // Merge transformed RPC data with initialized counts
136
+ result[countsKey] = { ...initializedCounts, ...transformedData };
137
+ logger.debug({
138
+ filter: filter.name,
139
+ type: filter.type,
140
+ countsKey,
141
+ optionsCount: Object.keys(initializedCounts).length
142
+ }, '✅ Mapped filter (nested, zero-initialized)');
143
+ });
144
+ // ✨ ALSO MAP STANDALONE COUNTS (not tied to filters)
145
+ // These are passive counts that are always returned (e.g., time-based counts)
146
+ if (featureConfig.counts) {
147
+ Object.keys(featureConfig.counts).forEach(countKey => {
148
+ // Skip if already mapped from filter or is 'total'
149
+ if (countKey === 'total' || result[countKey] !== undefined) {
150
+ return;
151
+ }
152
+ // Copy standalone count directly from RPC response
153
+ if (rpcData[countKey] !== undefined) {
154
+ result[countKey] = rpcData[countKey];
155
+ logger.debug({ countKey, value: rpcData[countKey] }, '✅ Mapped standalone count');
156
+ }
157
+ });
158
+ }
159
+ logger.debug({
160
+ resultKeys: Object.keys(result),
161
+ total: result.total
162
+ }, '✅ Response mapping complete');
163
+ return result;
164
+ }
165
+ /**
166
+ * Example usage:
167
+ *
168
+ * // In OrderQueryService:
169
+ * const rpcData = await this.supabase.rpc('get_order_counts_simple', rpcParams);
170
+ * return mapCountsFromRPC(rpcData, ordersFeatureConfig);
171
+ *
172
+ * // Works for ANY feature!
173
+ * const rpcData = await this.supabase.rpc('get_voucher_counts_simple', rpcParams);
174
+ * return mapCountsFromRPC(rpcData, vouchersFeatureConfig);
175
+ */
176
+ //# sourceMappingURL=response-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-mapper.js","sourceRoot":"","sources":["../../../src/shared/utils/response-mapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * API Response Builder Utilities
3
+ *
4
+ * Provides consistent response format across all endpoints
5
+ * Prevents:
6
+ * - hasNextPage vs hasMore inconsistency
7
+ * - data: items vs data: { items } wrapping confusion
8
+ * - Missing/inconsistent pagination fields
9
+ * - Different message field usage
10
+ */
11
+ export interface PaginationMeta {
12
+ total: number;
13
+ limit: number;
14
+ offset: number;
15
+ }
16
+ export interface ResponseOptions {
17
+ /**
18
+ * Wrap data in object with key
19
+ * @example { dataKey: 'books' } → data: { books: [...] }
20
+ * @example undefined → data: [...]
21
+ */
22
+ dataKey?: string;
23
+ /**
24
+ * Success message to include
25
+ * @example "Books retrieved successfully"
26
+ */
27
+ message?: string;
28
+ /**
29
+ * Include hasPreviousPage (default: true)
30
+ */
31
+ includePreviousPage?: boolean;
32
+ }
33
+ /**
34
+ * Build paginated API response with consistent format
35
+ *
36
+ * Standard format:
37
+ * {
38
+ * success: true,
39
+ * data: [...],
40
+ * meta: {
41
+ * total: 100,
42
+ * limit: 20,
43
+ * offset: 0,
44
+ * hasMore: true,
45
+ * hasNextPage: true,
46
+ * hasPreviousPage: false
47
+ * }
48
+ * }
49
+ *
50
+ * @param data - Array of items to return
51
+ * @param meta - Pagination metadata
52
+ * @param options - Optional customization
53
+ * @returns Standardized API response
54
+ *
55
+ * @example
56
+ * // Simple response
57
+ * buildPaginatedResponse(orders, { total: 100, limit: 20, offset: 0 })
58
+ *
59
+ * // With wrapped data
60
+ * buildPaginatedResponse(
61
+ * books,
62
+ * { total: 50, limit: 10, offset: 0 },
63
+ * { dataKey: 'books', message: 'Books retrieved successfully' }
64
+ * )
65
+ */
66
+ export declare function buildPaginatedResponse<T>(data: T[], meta: PaginationMeta, options?: ResponseOptions): any;
67
+ /**
68
+ * Build single item response
69
+ *
70
+ * Standard format:
71
+ * {
72
+ * success: true,
73
+ * data: { ... }
74
+ * }
75
+ *
76
+ * @param data - Single item to return
77
+ * @param message - Optional success message
78
+ * @returns Standardized API response
79
+ *
80
+ * @example
81
+ * buildSingleResponse(order)
82
+ * buildSingleResponse(book, 'Book retrieved successfully')
83
+ */
84
+ export declare function buildSingleResponse<T>(data: T, message?: string): any;
85
+ /**
86
+ * Build error response (for RFC 7807 compliance, use RFC7807ErrorResponse instead)
87
+ *
88
+ * Simple error format:
89
+ * {
90
+ * success: false,
91
+ * message: "Error message"
92
+ * }
93
+ *
94
+ * @param message - Error message
95
+ * @param details - Optional error details
96
+ * @returns Standardized error response
97
+ *
98
+ * @example
99
+ * buildErrorResponse('Resource not found')
100
+ * buildErrorResponse('Validation failed', { field: 'email' })
101
+ */
102
+ export declare function buildErrorResponse(message: string, details?: any): any;
103
+ //# sourceMappingURL=responseBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responseBuilder.d.ts","sourceRoot":"","sources":["../../../src/shared/utils/responseBuilder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,IAAI,EAAE,CAAC,EAAE,EACT,IAAI,EAAE,cAAc,EACpB,OAAO,CAAC,EAAE,eAAe,OAsC1B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,MAAM,OAYjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,GAAG,OAYd"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * API Response Builder Utilities
3
+ *
4
+ * Provides consistent response format across all endpoints
5
+ * Prevents:
6
+ * - hasNextPage vs hasMore inconsistency
7
+ * - data: items vs data: { items } wrapping confusion
8
+ * - Missing/inconsistent pagination fields
9
+ * - Different message field usage
10
+ */
11
+ /**
12
+ * Build paginated API response with consistent format
13
+ *
14
+ * Standard format:
15
+ * {
16
+ * success: true,
17
+ * data: [...],
18
+ * meta: {
19
+ * total: 100,
20
+ * limit: 20,
21
+ * offset: 0,
22
+ * hasMore: true,
23
+ * hasNextPage: true,
24
+ * hasPreviousPage: false
25
+ * }
26
+ * }
27
+ *
28
+ * @param data - Array of items to return
29
+ * @param meta - Pagination metadata
30
+ * @param options - Optional customization
31
+ * @returns Standardized API response
32
+ *
33
+ * @example
34
+ * // Simple response
35
+ * buildPaginatedResponse(orders, { total: 100, limit: 20, offset: 0 })
36
+ *
37
+ * // With wrapped data
38
+ * buildPaginatedResponse(
39
+ * books,
40
+ * { total: 50, limit: 10, offset: 0 },
41
+ * { dataKey: 'books', message: 'Books retrieved successfully' }
42
+ * )
43
+ */
44
+ export function buildPaginatedResponse(data, meta, options) {
45
+ const hasMore = meta.offset + meta.limit < meta.total;
46
+ const hasPreviousPage = meta.offset > 0;
47
+ const response = {
48
+ success: true
49
+ };
50
+ // Add optional message
51
+ if (options?.message) {
52
+ response.message = options.message;
53
+ }
54
+ // Wrap data if dataKey provided
55
+ if (options?.dataKey) {
56
+ response.data = {
57
+ [options.dataKey]: data
58
+ };
59
+ }
60
+ else {
61
+ response.data = data;
62
+ }
63
+ // Add pagination metadata
64
+ response.meta = {
65
+ total: meta.total,
66
+ limit: meta.limit,
67
+ offset: meta.offset,
68
+ hasMore,
69
+ hasNextPage: hasMore, // Include both for compatibility
70
+ };
71
+ // Add hasPreviousPage if requested (default: true)
72
+ if (options?.includePreviousPage !== false) {
73
+ response.meta.hasPreviousPage = hasPreviousPage;
74
+ }
75
+ return response;
76
+ }
77
+ /**
78
+ * Build single item response
79
+ *
80
+ * Standard format:
81
+ * {
82
+ * success: true,
83
+ * data: { ... }
84
+ * }
85
+ *
86
+ * @param data - Single item to return
87
+ * @param message - Optional success message
88
+ * @returns Standardized API response
89
+ *
90
+ * @example
91
+ * buildSingleResponse(order)
92
+ * buildSingleResponse(book, 'Book retrieved successfully')
93
+ */
94
+ export function buildSingleResponse(data, message) {
95
+ const response = {
96
+ success: true,
97
+ data
98
+ };
99
+ if (message) {
100
+ response.message = message;
101
+ }
102
+ return response;
103
+ }
104
+ /**
105
+ * Build error response (for RFC 7807 compliance, use RFC7807ErrorResponse instead)
106
+ *
107
+ * Simple error format:
108
+ * {
109
+ * success: false,
110
+ * message: "Error message"
111
+ * }
112
+ *
113
+ * @param message - Error message
114
+ * @param details - Optional error details
115
+ * @returns Standardized error response
116
+ *
117
+ * @example
118
+ * buildErrorResponse('Resource not found')
119
+ * buildErrorResponse('Validation failed', { field: 'email' })
120
+ */
121
+ export function buildErrorResponse(message, details) {
122
+ const response = {
123
+ success: false,
124
+ message
125
+ };
126
+ if (details) {
127
+ response.details = details;
128
+ }
129
+ return response;
130
+ }
131
+ //# sourceMappingURL=responseBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responseBuilder.js","sourceRoot":"","sources":["../../../src/shared/utils/responseBuilder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA4BH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAS,EACT,IAAoB,EACpB,OAAyB;IAEzB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACtD,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IAExC,MAAM,QAAQ,GAAQ;QACpB,OAAO,EAAE,IAAI;KACd,CAAC;IAEF,uBAAuB;IACvB,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC;QACrB,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACrC,CAAC;IAED,gCAAgC;IAChC,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC;QACrB,QAAQ,CAAC,IAAI,GAAG;YACd,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI;SACxB,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC;IACvB,CAAC;IAED,0BAA0B;IAC1B,QAAQ,CAAC,IAAI,GAAG;QACd,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO;QACP,WAAW,EAAE,OAAO,EAAE,iCAAiC;KACxD,CAAC;IAEF,mDAAmD;IACnD,IAAI,OAAO,EAAE,mBAAmB,KAAK,KAAK,EAAE,CAAC;QAC3C,QAAQ,CAAC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IAClD,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAO,EACP,OAAgB;IAEhB,MAAM,QAAQ,GAAQ;QACpB,OAAO,EAAE,IAAI;QACb,IAAI;KACL,CAAC;IAEF,IAAI,OAAO,EAAE,CAAC;QACZ,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC;IAC7B,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,OAAa;IAEb,MAAM,QAAQ,GAAQ;QACpB,OAAO,EAAE,KAAK;QACd,OAAO;KACR,CAAC;IAEF,IAAI,OAAO,EAAE,CAAC;QACZ,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC;IAC7B,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Feature Config Validator
3
+ *
4
+ * Validates feature configurations at startup to catch common mistakes early.
5
+ * Runs during RouteManager initialization to fail fast with clear error messages.
6
+ */
7
+ import { FeatureConfig } from '../types/feature-config.js';
8
+ /**
9
+ * Register known wrong table name mappings for validation.
10
+ * @example addWrongTableNames({ 'userspublic': 'users_public' });
11
+ */
12
+ export declare function addWrongTableNames(mappings: Record<string, string>): void;
13
+ /**
14
+ * Validate a feature configuration
15
+ * @throws Error if validation fails (fail-fast approach)
16
+ */
17
+ export declare function validateFeatureConfig(config: FeatureConfig, featureName: string): void;
18
+ /**
19
+ * Validate all feature configs
20
+ * Call this during application startup
21
+ */
22
+ export declare function validateAllFeatureConfigs(configs: Record<string, FeatureConfig>): void;
23
+ //# sourceMappingURL=featureConfigValidator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"featureConfigValidator.d.ts","sourceRoot":"","sources":["../../../src/shared/validators/featureConfigValidator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAU3D;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAEzE;AASD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAkHtF;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,IAAI,CAiBtF"}