@open-mercato/shared 0.6.7 → 0.6.8-develop.6874.1.982d6097d8

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.
Files changed (195) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +57 -6
  3. package/build.mjs +2 -4
  4. package/dist/lib/ai/safety-identifier.js +32 -0
  5. package/dist/lib/ai/safety-identifier.js.map +7 -0
  6. package/dist/lib/auth/organizationScope.js +34 -0
  7. package/dist/lib/auth/organizationScope.js.map +7 -0
  8. package/dist/lib/auth/server.js +15 -2
  9. package/dist/lib/auth/server.js.map +2 -2
  10. package/dist/lib/bootstrap/clientOnlyModules.js +55 -0
  11. package/dist/lib/bootstrap/clientOnlyModules.js.map +7 -0
  12. package/dist/lib/bootstrap/dynamicLoader.js +117 -43
  13. package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
  14. package/dist/lib/commands/command-bus.js +5 -1
  15. package/dist/lib/commands/command-bus.js.map +2 -2
  16. package/dist/lib/commands/command-interceptor-runner.js +2 -2
  17. package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
  18. package/dist/lib/crud/enricher-runner.js +2 -11
  19. package/dist/lib/crud/enricher-runner.js.map +2 -2
  20. package/dist/lib/crud/factory.js +72 -9
  21. package/dist/lib/crud/factory.js.map +2 -2
  22. package/dist/lib/crud/interceptor-runner.js +2 -2
  23. package/dist/lib/crud/interceptor-runner.js.map +2 -2
  24. package/dist/lib/crud/mutation-guard-registry.js +2 -2
  25. package/dist/lib/crud/mutation-guard-registry.js.map +2 -2
  26. package/dist/lib/crud/types.js +4 -0
  27. package/dist/lib/crud/types.js.map +3 -3
  28. package/dist/lib/data/consistency.js +19 -0
  29. package/dist/lib/data/consistency.js.map +7 -0
  30. package/dist/lib/data/engine.js +37 -11
  31. package/dist/lib/data/engine.js.map +2 -2
  32. package/dist/lib/db/entityMetadata.js +26 -0
  33. package/dist/lib/db/entityMetadata.js.map +7 -0
  34. package/dist/lib/db/pg-errors.js +42 -0
  35. package/dist/lib/db/pg-errors.js.map +2 -2
  36. package/dist/lib/di/container.js +48 -6
  37. package/dist/lib/di/container.js.map +2 -2
  38. package/dist/lib/email/send.js +56 -0
  39. package/dist/lib/email/send.js.map +2 -2
  40. package/dist/lib/encryption/subscriber.js +2 -10
  41. package/dist/lib/encryption/subscriber.js.map +2 -2
  42. package/dist/lib/encryption/tenantDataEncryptionService.js +52 -17
  43. package/dist/lib/encryption/tenantDataEncryptionService.js.map +2 -2
  44. package/dist/lib/i18n/config.js +1 -1
  45. package/dist/lib/i18n/config.js.map +2 -2
  46. package/dist/lib/i18n/server.js +4 -4
  47. package/dist/lib/i18n/server.js.map +3 -3
  48. package/dist/lib/logger/extension.js +29 -0
  49. package/dist/lib/logger/extension.js.map +7 -0
  50. package/dist/lib/logger/index.js +47 -3
  51. package/dist/lib/logger/index.js.map +3 -3
  52. package/dist/lib/modules/registry.js +5 -1
  53. package/dist/lib/modules/registry.js.map +2 -2
  54. package/dist/lib/modules/surfaceFingerprint.js +47 -0
  55. package/dist/lib/modules/surfaceFingerprint.js.map +7 -0
  56. package/dist/lib/query/ciphertext-search-warning.js +45 -0
  57. package/dist/lib/query/ciphertext-search-warning.js.map +7 -0
  58. package/dist/lib/query/engine.js +54 -40
  59. package/dist/lib/query/engine.js.map +2 -2
  60. package/dist/lib/query/types.js.map +1 -1
  61. package/dist/lib/redis/connection.js +7 -2
  62. package/dist/lib/redis/connection.js.map +2 -2
  63. package/dist/lib/search/auto-indexing.js +14 -0
  64. package/dist/lib/search/auto-indexing.js.map +7 -0
  65. package/dist/lib/search/availability.js +107 -0
  66. package/dist/lib/search/availability.js.map +7 -0
  67. package/dist/lib/search/config.js +61 -2
  68. package/dist/lib/search/config.js.map +2 -2
  69. package/dist/lib/search/tokenLookup.js +46 -0
  70. package/dist/lib/search/tokenLookup.js.map +7 -0
  71. package/dist/lib/search/tokenize.js +23 -11
  72. package/dist/lib/search/tokenize.js.map +2 -2
  73. package/dist/lib/telemetry/runtime.js +35 -0
  74. package/dist/lib/telemetry/runtime.js.map +7 -0
  75. package/dist/lib/version.js +1 -1
  76. package/dist/lib/version.js.map +1 -1
  77. package/dist/lib/webhooks/body.js +57 -0
  78. package/dist/lib/webhooks/body.js.map +7 -0
  79. package/dist/lib/webhooks/inbound-types.js +1 -0
  80. package/dist/lib/webhooks/inbound-types.js.map +7 -0
  81. package/dist/lib/webhooks/index.js +13 -1
  82. package/dist/lib/webhooks/index.js.map +2 -2
  83. package/dist/lib/webhooks/verify.js +12 -7
  84. package/dist/lib/webhooks/verify.js.map +2 -2
  85. package/dist/modules/analytics.js.map +2 -2
  86. package/dist/modules/overrides.js +50 -1
  87. package/dist/modules/overrides.js.map +2 -2
  88. package/dist/modules/payment_gateways/types.js +8 -1
  89. package/dist/modules/payment_gateways/types.js.map +2 -2
  90. package/dist/modules/registry.js +1 -0
  91. package/dist/modules/registry.js.map +2 -2
  92. package/dist/modules/widgets/extension-points.js +96 -0
  93. package/dist/modules/widgets/extension-points.js.map +7 -0
  94. package/dist/security/enabledModulesRegistry.js +56 -11
  95. package/dist/security/enabledModulesRegistry.js.map +2 -2
  96. package/dist/security/featurePolicy.js +62 -0
  97. package/dist/security/featurePolicy.js.map +7 -0
  98. package/jest.config.cjs +2 -1
  99. package/package.json +19 -9
  100. package/scripts/versionSource.cjs +65 -0
  101. package/src/lib/__tests__/versionSource.test.ts +46 -0
  102. package/src/lib/ai/__tests__/llm-provider-contract.test.ts +59 -0
  103. package/src/lib/ai/__tests__/safety-identifier.test.ts +71 -0
  104. package/src/lib/ai/llm-provider.ts +33 -0
  105. package/src/lib/ai/safety-identifier.ts +73 -0
  106. package/src/lib/auth/__tests__/organizationScope.test.ts +107 -0
  107. package/src/lib/auth/__tests__/server.test.ts +23 -0
  108. package/src/lib/auth/organizationScope.ts +65 -0
  109. package/src/lib/auth/server.ts +23 -2
  110. package/src/lib/bootstrap/__tests__/clientOnlyModules.test.ts +189 -0
  111. package/src/lib/bootstrap/__tests__/dynamicLoader.aliasResolver.test.ts +98 -0
  112. package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +211 -0
  113. package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +63 -8
  114. package/src/lib/bootstrap/__tests__/dynamicLoader.generatedCacheRecovery.test.ts +256 -0
  115. package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +21 -1
  116. package/src/lib/bootstrap/clientOnlyModules.ts +85 -0
  117. package/src/lib/bootstrap/dynamicLoader.ts +213 -65
  118. package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +28 -0
  119. package/src/lib/commands/command-bus.ts +5 -1
  120. package/src/lib/commands/command-interceptor-runner.ts +2 -2
  121. package/src/lib/crud/__tests__/crud-factory.cache-user-scope.test.ts +171 -0
  122. package/src/lib/crud/__tests__/crud-factory.test.ts +351 -0
  123. package/src/lib/crud/__tests__/mutation-guard-registry.test.ts +26 -0
  124. package/src/lib/crud/enricher-runner.ts +2 -11
  125. package/src/lib/crud/factory.ts +82 -6
  126. package/src/lib/crud/interceptor-runner.ts +2 -2
  127. package/src/lib/crud/mutation-guard-registry.ts +2 -2
  128. package/src/lib/crud/types.ts +3 -0
  129. package/src/lib/data/__tests__/consistency.test.ts +38 -0
  130. package/src/lib/data/__tests__/engine.bulk-suppress.test.ts +18 -3
  131. package/src/lib/data/consistency.ts +17 -0
  132. package/src/lib/data/engine.ts +50 -16
  133. package/src/lib/db/__tests__/entityMetadata.test.ts +38 -0
  134. package/src/lib/db/__tests__/pg-errors.test.ts +48 -0
  135. package/src/lib/db/entityMetadata.ts +43 -0
  136. package/src/lib/db/pg-errors.ts +58 -0
  137. package/src/lib/di/__tests__/container-app-di-absent.test.ts +155 -0
  138. package/src/lib/di/__tests__/container-app-di.test.ts +173 -0
  139. package/src/lib/di/__tests__/registrar-error-log.test.ts +154 -0
  140. package/src/lib/di/container.ts +72 -8
  141. package/src/lib/email/__tests__/send.test.ts +37 -1
  142. package/src/lib/email/send.ts +79 -0
  143. package/src/lib/encryption/__tests__/subscriber.test.ts +54 -2
  144. package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +58 -0
  145. package/src/lib/encryption/subscriber.ts +2 -14
  146. package/src/lib/encryption/tenantDataEncryptionService.ts +64 -18
  147. package/src/lib/i18n/__tests__/config.test.ts +8 -0
  148. package/src/lib/i18n/__tests__/server-dictionary-cache.test.ts +30 -0
  149. package/src/lib/i18n/__tests__/server-unbootstrapped.test.ts +29 -0
  150. package/src/lib/i18n/config.ts +2 -2
  151. package/src/lib/i18n/server.ts +6 -2
  152. package/src/lib/logger/__tests__/logger.test.ts +77 -0
  153. package/src/lib/logger/extension.ts +59 -0
  154. package/src/lib/logger/index.ts +59 -1
  155. package/src/lib/modules/__tests__/surfaceFingerprint.test.ts +122 -0
  156. package/src/lib/modules/registry.ts +10 -0
  157. package/src/lib/modules/surfaceFingerprint.ts +87 -0
  158. package/src/lib/query/__tests__/ciphertext-search-warning.test.ts +178 -0
  159. package/src/lib/query/__tests__/engine.test.ts +65 -13
  160. package/src/lib/query/ciphertext-search-warning.ts +95 -0
  161. package/src/lib/query/engine.ts +75 -58
  162. package/src/lib/query/types.ts +4 -5
  163. package/src/lib/redis/__tests__/connection.test.ts +16 -0
  164. package/src/lib/redis/connection.ts +10 -7
  165. package/src/lib/search/__tests__/availability.test.ts +211 -0
  166. package/src/lib/search/__tests__/config.test.ts +177 -0
  167. package/src/lib/search/__tests__/tokenLookup.test.ts +206 -0
  168. package/src/lib/search/__tests__/tokenize.test.ts +49 -0
  169. package/src/lib/search/auto-indexing.ts +22 -0
  170. package/src/lib/search/availability.ts +232 -0
  171. package/src/lib/search/config.ts +106 -8
  172. package/src/lib/search/tokenLookup.ts +133 -0
  173. package/src/lib/search/tokenize.ts +33 -11
  174. package/src/lib/telemetry/runtime.ts +72 -0
  175. package/src/lib/webhooks/__tests__/body.test.ts +95 -0
  176. package/src/lib/webhooks/__tests__/verify.test.ts +20 -1
  177. package/src/lib/webhooks/body.ts +70 -0
  178. package/src/lib/webhooks/inbound-types.ts +125 -0
  179. package/src/lib/webhooks/index.ts +19 -1
  180. package/src/lib/webhooks/verify.ts +17 -9
  181. package/src/modules/__tests__/nav-group-order-override.test.ts +183 -0
  182. package/src/modules/__tests__/registry.test.ts +10 -2
  183. package/src/modules/analytics.ts +7 -0
  184. package/src/modules/customer-auth.ts +1 -0
  185. package/src/modules/encryption.ts +3 -0
  186. package/src/modules/navigation/backendChrome.ts +15 -0
  187. package/src/modules/overrides.ts +103 -0
  188. package/src/modules/payment_gateways/__tests__/types.test.ts +37 -0
  189. package/src/modules/payment_gateways/types.ts +41 -0
  190. package/src/modules/registry.ts +3 -0
  191. package/src/modules/widgets/__tests__/extension-points.test.ts +101 -0
  192. package/src/modules/widgets/extension-points.ts +524 -0
  193. package/src/security/__tests__/featurePolicy.test.ts +166 -0
  194. package/src/security/enabledModulesRegistry.ts +64 -12
  195. package/src/security/featurePolicy.ts +89 -0
@@ -1,13 +1,16 @@
1
1
  import { timingSafeEqual, createHmac } from "node:crypto";
2
2
  import { parseWebhookSecret } from "./secrets.js";
3
- const TOLERANCE_SECONDS = 5 * 60;
4
- function verifyWebhookSignature(msgId, timestamp, body, signatureHeader, secrets, toleranceSeconds = TOLERANCE_SECONDS) {
5
- const timestampNum = parseInt(timestamp, 10);
6
- if (isNaN(timestampNum)) {
7
- return { valid: false };
3
+ const WEBHOOK_SIGNATURE_TOLERANCE_SECONDS = 5 * 60;
4
+ function isWebhookTimestampWithinTolerance(timestamp, toleranceSeconds = WEBHOOK_SIGNATURE_TOLERANCE_SECONDS, nowSeconds = Math.floor(Date.now() / 1e3)) {
5
+ const normalizedTimestamp = timestamp.trim();
6
+ if (!/^\d+$/.test(normalizedTimestamp)) {
7
+ return false;
8
8
  }
9
- const now = Math.floor(Date.now() / 1e3);
10
- if (Math.abs(now - timestampNum) > toleranceSeconds) {
9
+ const timestampNum = Number.parseInt(normalizedTimestamp, 10);
10
+ return Math.abs(nowSeconds - timestampNum) <= toleranceSeconds;
11
+ }
12
+ function verifyWebhookSignature(msgId, timestamp, body, signatureHeader, secrets, toleranceSeconds = WEBHOOK_SIGNATURE_TOLERANCE_SECONDS) {
13
+ if (!isWebhookTimestampWithinTolerance(timestamp, toleranceSeconds)) {
11
14
  return { valid: false };
12
15
  }
13
16
  const signedContent = `${msgId}.${timestamp}.${body}`;
@@ -29,6 +32,8 @@ function verifyWebhookSignature(msgId, timestamp, body, signatureHeader, secrets
29
32
  return { valid: false };
30
33
  }
31
34
  export {
35
+ WEBHOOK_SIGNATURE_TOLERANCE_SECONDS,
36
+ isWebhookTimestampWithinTolerance,
32
37
  verifyWebhookSignature
33
38
  };
34
39
  //# sourceMappingURL=verify.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/webhooks/verify.ts"],
4
- "sourcesContent": ["import { timingSafeEqual, createHmac } from 'node:crypto'\nimport { parseWebhookSecret } from './secrets'\nimport type { WebhookVerificationResult } from './types'\n\nconst TOLERANCE_SECONDS = 5 * 60 // 5 minutes\n\n/**\n * Verify a Standard Webhooks signature against one or more secrets.\n * Returns verification result with which key matched.\n */\nexport function verifyWebhookSignature(\n msgId: string,\n timestamp: string,\n body: string,\n signatureHeader: string,\n secrets: string[],\n toleranceSeconds: number = TOLERANCE_SECONDS,\n): WebhookVerificationResult {\n const timestampNum = parseInt(timestamp, 10)\n if (isNaN(timestampNum)) {\n return { valid: false }\n }\n\n const now = Math.floor(Date.now() / 1000)\n if (Math.abs(now - timestampNum) > toleranceSeconds) {\n return { valid: false }\n }\n\n const signedContent = `${msgId}.${timestamp}.${body}`\n const providedSignatures = signatureHeader.split(' ')\n\n for (let keyIndex = 0; keyIndex < secrets.length; keyIndex++) {\n const key = parseWebhookSecret(secrets[keyIndex])\n const expectedSig = createHmac('sha256', key)\n .update(signedContent)\n .digest('base64')\n\n for (const sig of providedSignatures) {\n const parts = sig.split(',')\n if (parts.length !== 2 || parts[0] !== 'v1') continue\n\n const sigBytes = Buffer.from(parts[1], 'base64')\n const expectedBytes = Buffer.from(expectedSig, 'base64')\n\n if (sigBytes.length !== expectedBytes.length) continue\n\n if (timingSafeEqual(sigBytes, expectedBytes)) {\n return { valid: true, matchedKeyIndex: keyIndex }\n }\n }\n }\n\n return { valid: false }\n}\n"],
5
- "mappings": "AAAA,SAAS,iBAAiB,kBAAkB;AAC5C,SAAS,0BAA0B;AAGnC,MAAM,oBAAoB,IAAI;AAMvB,SAAS,uBACd,OACA,WACA,MACA,iBACA,SACA,mBAA2B,mBACA;AAC3B,QAAM,eAAe,SAAS,WAAW,EAAE;AAC3C,MAAI,MAAM,YAAY,GAAG;AACvB,WAAO,EAAE,OAAO,MAAM;AAAA,EACxB;AAEA,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,MAAI,KAAK,IAAI,MAAM,YAAY,IAAI,kBAAkB;AACnD,WAAO,EAAE,OAAO,MAAM;AAAA,EACxB;AAEA,QAAM,gBAAgB,GAAG,KAAK,IAAI,SAAS,IAAI,IAAI;AACnD,QAAM,qBAAqB,gBAAgB,MAAM,GAAG;AAEpD,WAAS,WAAW,GAAG,WAAW,QAAQ,QAAQ,YAAY;AAC5D,UAAM,MAAM,mBAAmB,QAAQ,QAAQ,CAAC;AAChD,UAAM,cAAc,WAAW,UAAU,GAAG,EACzC,OAAO,aAAa,EACpB,OAAO,QAAQ;AAElB,eAAW,OAAO,oBAAoB;AACpC,YAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,UAAI,MAAM,WAAW,KAAK,MAAM,CAAC,MAAM,KAAM;AAE7C,YAAM,WAAW,OAAO,KAAK,MAAM,CAAC,GAAG,QAAQ;AAC/C,YAAM,gBAAgB,OAAO,KAAK,aAAa,QAAQ;AAEvD,UAAI,SAAS,WAAW,cAAc,OAAQ;AAE9C,UAAI,gBAAgB,UAAU,aAAa,GAAG;AAC5C,eAAO,EAAE,OAAO,MAAM,iBAAiB,SAAS;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,MAAM;AACxB;",
4
+ "sourcesContent": ["import { timingSafeEqual, createHmac } from 'node:crypto'\nimport { parseWebhookSecret } from './secrets'\nimport type { WebhookVerificationResult } from './types'\n\nexport const WEBHOOK_SIGNATURE_TOLERANCE_SECONDS = 5 * 60 // 5 minutes\n\nexport function isWebhookTimestampWithinTolerance(\n timestamp: string,\n toleranceSeconds: number = WEBHOOK_SIGNATURE_TOLERANCE_SECONDS,\n nowSeconds: number = Math.floor(Date.now() / 1000),\n): boolean {\n const normalizedTimestamp = timestamp.trim()\n if (!/^\\d+$/.test(normalizedTimestamp)) {\n return false\n }\n\n const timestampNum = Number.parseInt(normalizedTimestamp, 10)\n return Math.abs(nowSeconds - timestampNum) <= toleranceSeconds\n}\n\n/**\n * Verify a Standard Webhooks signature against one or more secrets.\n * Returns verification result with which key matched.\n */\nexport function verifyWebhookSignature(\n msgId: string,\n timestamp: string,\n body: string,\n signatureHeader: string,\n secrets: string[],\n toleranceSeconds: number = WEBHOOK_SIGNATURE_TOLERANCE_SECONDS,\n): WebhookVerificationResult {\n if (!isWebhookTimestampWithinTolerance(timestamp, toleranceSeconds)) {\n return { valid: false }\n }\n\n const signedContent = `${msgId}.${timestamp}.${body}`\n const providedSignatures = signatureHeader.split(' ')\n\n for (let keyIndex = 0; keyIndex < secrets.length; keyIndex++) {\n const key = parseWebhookSecret(secrets[keyIndex])\n const expectedSig = createHmac('sha256', key)\n .update(signedContent)\n .digest('base64')\n\n for (const sig of providedSignatures) {\n const parts = sig.split(',')\n if (parts.length !== 2 || parts[0] !== 'v1') continue\n\n const sigBytes = Buffer.from(parts[1], 'base64')\n const expectedBytes = Buffer.from(expectedSig, 'base64')\n\n if (sigBytes.length !== expectedBytes.length) continue\n\n if (timingSafeEqual(sigBytes, expectedBytes)) {\n return { valid: true, matchedKeyIndex: keyIndex }\n }\n }\n }\n\n return { valid: false }\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB,kBAAkB;AAC5C,SAAS,0BAA0B;AAG5B,MAAM,sCAAsC,IAAI;AAEhD,SAAS,kCACd,WACA,mBAA2B,qCAC3B,aAAqB,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,GACxC;AACT,QAAM,sBAAsB,UAAU,KAAK;AAC3C,MAAI,CAAC,QAAQ,KAAK,mBAAmB,GAAG;AACtC,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,OAAO,SAAS,qBAAqB,EAAE;AAC5D,SAAO,KAAK,IAAI,aAAa,YAAY,KAAK;AAChD;AAMO,SAAS,uBACd,OACA,WACA,MACA,iBACA,SACA,mBAA2B,qCACA;AAC3B,MAAI,CAAC,kCAAkC,WAAW,gBAAgB,GAAG;AACnE,WAAO,EAAE,OAAO,MAAM;AAAA,EACxB;AAEA,QAAM,gBAAgB,GAAG,KAAK,IAAI,SAAS,IAAI,IAAI;AACnD,QAAM,qBAAqB,gBAAgB,MAAM,GAAG;AAEpD,WAAS,WAAW,GAAG,WAAW,QAAQ,QAAQ,YAAY;AAC5D,UAAM,MAAM,mBAAmB,QAAQ,QAAQ,CAAC;AAChD,UAAM,cAAc,WAAW,UAAU,GAAG,EACzC,OAAO,aAAa,EACpB,OAAO,QAAQ;AAElB,eAAW,OAAO,oBAAoB;AACpC,YAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,UAAI,MAAM,WAAW,KAAK,MAAM,CAAC,MAAM,KAAM;AAE7C,YAAM,WAAW,OAAO,KAAK,MAAM,CAAC,GAAG,QAAQ;AAC/C,YAAM,gBAAgB,OAAO,KAAK,aAAa,QAAQ;AAEvD,UAAI,SAAS,WAAW,cAAc,OAAQ;AAE9C,UAAI,gBAAgB,UAAU,aAAa,GAAG;AAC5C,eAAO,EAAE,OAAO,MAAM,iBAAiB,SAAS;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,MAAM;AACxB;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/modules/analytics.ts"],
4
- "sourcesContent": ["import { createLogger } from '../lib/logger'\n\nconst logger = createLogger('shared').child({ component: 'analytics-registry' })\n\nexport type AggregateFunction = 'count' | 'sum' | 'avg' | 'min' | 'max'\n\nexport type DateGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year'\n\nexport type AnalyticsFieldType = 'numeric' | 'text' | 'uuid' | 'timestamp' | 'jsonb'\n\nexport type AnalyticsEntityTypeConfig = {\n tableName: string\n schema?: string\n dateField: string\n defaultScopeFields: string[]\n}\n\nexport type AnalyticsFieldMapping = {\n dbColumn: string\n type: AnalyticsFieldType\n}\n\nexport type AnalyticsLabelResolverConfig = {\n table: string\n idColumn: string\n labelColumn: string\n}\n\nexport type AnalyticsEntityConfig = {\n entityId: string\n requiredFeatures: string[]\n entityConfig: AnalyticsEntityTypeConfig\n fieldMappings: Record<string, AnalyticsFieldMapping>\n labelResolvers?: Record<string, AnalyticsLabelResolverConfig>\n}\n\nexport type AnalyticsModuleConfig = {\n entities: AnalyticsEntityConfig[]\n}\n\nlet _analyticsModuleConfigs: AnalyticsModuleConfig[] | null = null\n\nexport function registerAnalyticsModuleConfigs(configs: AnalyticsModuleConfig[]): void {\n if (_analyticsModuleConfigs !== null && process.env.NODE_ENV === 'development') {\n logger.debug('Analytics module configs re-registered (this may occur during HMR)')\n }\n _analyticsModuleConfigs = configs\n}\n\nexport function getAnalyticsModuleConfigs(): AnalyticsModuleConfig[] {\n return _analyticsModuleConfigs ?? []\n}\n\nexport function getAllAnalyticsEntityConfigs(): AnalyticsEntityConfig[] {\n const configs = getAnalyticsModuleConfigs()\n return configs.flatMap((moduleConfig) => moduleConfig.entities)\n}\n\nexport function getAnalyticsEntityConfig(entityId: string): AnalyticsEntityConfig | null {\n const entities = getAllAnalyticsEntityConfigs()\n return entities.find((e) => e.entityId === entityId) ?? null\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,QAAQ,EAAE,MAAM,EAAE,WAAW,qBAAqB,CAAC;AAsC/E,IAAI,0BAA0D;AAEvD,SAAS,+BAA+B,SAAwC;AACrF,MAAI,4BAA4B,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAC9E,WAAO,MAAM,oEAAoE;AAAA,EACnF;AACA,4BAA0B;AAC5B;AAEO,SAAS,4BAAqD;AACnE,SAAO,2BAA2B,CAAC;AACrC;AAEO,SAAS,+BAAwD;AACtE,QAAM,UAAU,0BAA0B;AAC1C,SAAO,QAAQ,QAAQ,CAAC,iBAAiB,aAAa,QAAQ;AAChE;AAEO,SAAS,yBAAyB,UAAgD;AACvF,QAAM,WAAW,6BAA6B;AAC9C,SAAO,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ,KAAK;AAC1D;",
4
+ "sourcesContent": ["import { createLogger } from '../lib/logger'\n\nconst logger = createLogger('shared').child({ component: 'analytics-registry' })\n\nexport type AggregateFunction = 'count' | 'sum' | 'avg' | 'min' | 'max'\n\nexport type DateGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year'\n\nexport type AnalyticsFieldType = 'numeric' | 'text' | 'uuid' | 'timestamp' | 'jsonb'\n\nexport type AnalyticsEntityTypeConfig = {\n tableName: string\n schema?: string\n dateField: string\n defaultScopeFields: string[]\n /**\n * Field name (a key of `fieldMappings`) holding the per-row currency of monetary\n * columns. Declaring it lets analytics consumers verify that the rows they aggregate\n * all carry one currency before labelling the result with it (#4676). Omit it for\n * entities whose amounts are not per-row denominated.\n */\n currencyField?: string\n}\n\nexport type AnalyticsFieldMapping = {\n dbColumn: string\n type: AnalyticsFieldType\n}\n\nexport type AnalyticsLabelResolverConfig = {\n table: string\n idColumn: string\n labelColumn: string\n}\n\nexport type AnalyticsEntityConfig = {\n entityId: string\n requiredFeatures: string[]\n entityConfig: AnalyticsEntityTypeConfig\n fieldMappings: Record<string, AnalyticsFieldMapping>\n labelResolvers?: Record<string, AnalyticsLabelResolverConfig>\n}\n\nexport type AnalyticsModuleConfig = {\n entities: AnalyticsEntityConfig[]\n}\n\nlet _analyticsModuleConfigs: AnalyticsModuleConfig[] | null = null\n\nexport function registerAnalyticsModuleConfigs(configs: AnalyticsModuleConfig[]): void {\n if (_analyticsModuleConfigs !== null && process.env.NODE_ENV === 'development') {\n logger.debug('Analytics module configs re-registered (this may occur during HMR)')\n }\n _analyticsModuleConfigs = configs\n}\n\nexport function getAnalyticsModuleConfigs(): AnalyticsModuleConfig[] {\n return _analyticsModuleConfigs ?? []\n}\n\nexport function getAllAnalyticsEntityConfigs(): AnalyticsEntityConfig[] {\n const configs = getAnalyticsModuleConfigs()\n return configs.flatMap((moduleConfig) => moduleConfig.entities)\n}\n\nexport function getAnalyticsEntityConfig(entityId: string): AnalyticsEntityConfig | null {\n const entities = getAllAnalyticsEntityConfigs()\n return entities.find((e) => e.entityId === entityId) ?? null\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,QAAQ,EAAE,MAAM,EAAE,WAAW,qBAAqB,CAAC;AA6C/E,IAAI,0BAA0D;AAEvD,SAAS,+BAA+B,SAAwC;AACrF,MAAI,4BAA4B,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAC9E,WAAO,MAAM,oEAAoE;AAAA,EACnF;AACA,4BAA0B;AAC5B;AAEO,SAAS,4BAAqD;AACnE,SAAO,2BAA2B,CAAC;AACrC;AAEO,SAAS,+BAAwD;AACtE,QAAM,UAAU,0BAA0B;AAC1C,SAAO,QAAQ,QAAQ,CAAC,iBAAiB,aAAa,QAAQ;AAChE;AAEO,SAAS,yBAAyB,UAAgD;AACvF,QAAM,WAAW,6BAA6B;AAC9C,SAAO,SAAS,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ,KAAK;AAC1D;",
6
6
  "names": []
7
7
  }
@@ -23,7 +23,8 @@ const DOMAIN_KEYS = [
23
23
  "setup",
24
24
  "acl",
25
25
  "di",
26
- "encryption"
26
+ "encryption",
27
+ "nav"
27
28
  ];
28
29
  const TRACKING_ISSUE_HINT = "See `.ai/specs/implemented/2026-05-04-modules-ts-unified-overrides.md` and tracking issue https://github.com/open-mercato/open-mercato/issues/1787.";
29
30
  function applyModuleOverridesFromEnabledModules(modules) {
@@ -74,6 +75,33 @@ const aclFeatureOverrideStore = { modules: {}, programmatic: {} };
74
75
  const encryptionMapOverrideStore = { modules: {}, programmatic: {} };
75
76
  const diOverrideStore = { modules: {}, programmatic: {} };
76
77
  const setupOverridesByModule = {};
78
+ const GLOBAL_NAV_OVERRIDE_STATE_KEY = "__openMercatoNavOverrideState__";
79
+ function getNavOverrideState() {
80
+ const existing = globalThis[GLOBAL_NAV_OVERRIDE_STATE_KEY];
81
+ if (existing && typeof existing === "object") {
82
+ const typed = existing;
83
+ if ("modules" in typed && "programmatic" in typed) return typed;
84
+ }
85
+ const initial = { modules: null, programmatic: null };
86
+ globalThis[GLOBAL_NAV_OVERRIDE_STATE_KEY] = initial;
87
+ return initial;
88
+ }
89
+ function normalizeNavGroupOrder(value) {
90
+ if (!Array.isArray(value)) return null;
91
+ const ids = Array.from(
92
+ new Set(
93
+ value.filter((id) => typeof id === "string" && id.trim().length > 0).map((id) => id.trim())
94
+ )
95
+ );
96
+ return ids.length > 0 ? ids : null;
97
+ }
98
+ function applyNavGroupOrderOverrides(groupOrder) {
99
+ getNavOverrideState().programmatic = groupOrder === null ? null : normalizeNavGroupOrder(groupOrder);
100
+ }
101
+ function getNavGroupOrderOverride() {
102
+ const state = getNavOverrideState();
103
+ return state.programmatic ?? state.modules?.groupOrder ?? null;
104
+ }
77
105
  function normalizeIdOverrideKey(key, label) {
78
106
  if (typeof key !== "string") return null;
79
107
  const trimmed = key.trim();
@@ -261,6 +289,9 @@ function resetModuleContractOverridesForTests() {
261
289
  clearStore(encryptionMapOverrideStore);
262
290
  clearStore(diOverrideStore);
263
291
  for (const key of Object.keys(setupOverridesByModule)) delete setupOverridesByModule[key];
292
+ const navState = getNavOverrideState();
293
+ navState.modules = null;
294
+ navState.programmatic = null;
264
295
  }
265
296
  function composeApiRouteOverrides() {
266
297
  const modulesKeys = Object.keys(modulesConfigApiRouteOverrides);
@@ -827,7 +858,23 @@ function encryptionOverridesApplier(entries) {
827
858
  applyStoreOverrides(encryptionMapOverrideStore, "modules", entry.overrides?.maps, { label: "encryption.maps" });
828
859
  }
829
860
  }
861
+ function navOverridesApplier(entries) {
862
+ const state = getNavOverrideState();
863
+ for (const entry of entries) {
864
+ const groupOrder = normalizeNavGroupOrder(entry.overrides?.groupOrder);
865
+ if (!groupOrder) continue;
866
+ if (state.modules && state.modules.moduleId !== entry.moduleId) {
867
+ logger.warn("nav.groupOrder declared by more than one module \u2014 the later one wins", {
868
+ previousModuleId: state.modules.moduleId,
869
+ moduleId: entry.moduleId,
870
+ hint: "Sidebar group ordering is a single app-wide decision; declare it on one module entry."
871
+ });
872
+ }
873
+ state.modules = { moduleId: entry.moduleId, groupOrder };
874
+ }
875
+ }
830
876
  function registerBuiltInModuleOverrideAppliers() {
877
+ registerModuleOverrideApplier("nav", navOverridesApplier);
831
878
  registerModuleOverrideApplier("routes", routesOverridesApplier);
832
879
  registerModuleOverrideApplier("events", eventsOverridesApplier);
833
880
  registerModuleOverrideApplier("workers", workersOverridesApplier);
@@ -865,6 +912,7 @@ export {
865
912
  applyInjectionWidgetOverridesToTables,
866
913
  applyModuleOverridesFromEnabledModules,
867
914
  applyModuleOverridesToModules,
915
+ applyNavGroupOrderOverrides,
868
916
  applyNotificationHandlerOverrides,
869
917
  applyNotificationHandlerOverridesToEntries,
870
918
  applyNotificationTypeOverrides,
@@ -895,6 +943,7 @@ export {
895
943
  composeResponseEnricherOverrides,
896
944
  composeSubscriberOverrides,
897
945
  composeWorkerOverrides,
946
+ getNavGroupOrderOverride,
898
947
  registerModuleOverrideApplier,
899
948
  resetApiRouteOverridesForTests,
900
949
  resetModuleContractOverridesForTests,