@open-mercato/shared 0.4.2-canary-c02407ff85

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 (324) hide show
  1. package/build.mjs +101 -0
  2. package/dist/index.js +1 -0
  3. package/dist/index.js.map +7 -0
  4. package/dist/lib/api/crud.js +47 -0
  5. package/dist/lib/api/crud.js.map +7 -0
  6. package/dist/lib/api/scoped.js +140 -0
  7. package/dist/lib/api/scoped.js.map +7 -0
  8. package/dist/lib/auth/jwt.js +34 -0
  9. package/dist/lib/auth/jwt.js.map +7 -0
  10. package/dist/lib/auth/server.js +157 -0
  11. package/dist/lib/auth/server.js.map +7 -0
  12. package/dist/lib/boolean.js +22 -0
  13. package/dist/lib/boolean.js.map +7 -0
  14. package/dist/lib/bootstrap/appResolver.js +43 -0
  15. package/dist/lib/bootstrap/appResolver.js.map +7 -0
  16. package/dist/lib/bootstrap/dynamicLoader.js +108 -0
  17. package/dist/lib/bootstrap/dynamicLoader.js.map +7 -0
  18. package/dist/lib/bootstrap/factory.js +59 -0
  19. package/dist/lib/bootstrap/factory.js.map +7 -0
  20. package/dist/lib/bootstrap/index.js +11 -0
  21. package/dist/lib/bootstrap/index.js.map +7 -0
  22. package/dist/lib/bootstrap/types.js +1 -0
  23. package/dist/lib/bootstrap/types.js.map +7 -0
  24. package/dist/lib/cache/segments.js +36 -0
  25. package/dist/lib/cache/segments.js.map +7 -0
  26. package/dist/lib/cli/progress.js +46 -0
  27. package/dist/lib/cli/progress.js.map +7 -0
  28. package/dist/lib/commands/command-bus.js +285 -0
  29. package/dist/lib/commands/command-bus.js.map +7 -0
  30. package/dist/lib/commands/customFieldSnapshots.js +66 -0
  31. package/dist/lib/commands/customFieldSnapshots.js.map +7 -0
  32. package/dist/lib/commands/helpers.js +98 -0
  33. package/dist/lib/commands/helpers.js.map +7 -0
  34. package/dist/lib/commands/index.js +8 -0
  35. package/dist/lib/commands/index.js.map +7 -0
  36. package/dist/lib/commands/operationMetadata.js +32 -0
  37. package/dist/lib/commands/operationMetadata.js.map +7 -0
  38. package/dist/lib/commands/registry.js +43 -0
  39. package/dist/lib/commands/registry.js.map +7 -0
  40. package/dist/lib/commands/scope.js +44 -0
  41. package/dist/lib/commands/scope.js.map +7 -0
  42. package/dist/lib/commands/types.js +8 -0
  43. package/dist/lib/commands/types.js.map +7 -0
  44. package/dist/lib/crud/cache-stats.js +98 -0
  45. package/dist/lib/crud/cache-stats.js.map +7 -0
  46. package/dist/lib/crud/cache.js +175 -0
  47. package/dist/lib/crud/cache.js.map +7 -0
  48. package/dist/lib/crud/custom-fields-client.js +52 -0
  49. package/dist/lib/crud/custom-fields-client.js.map +7 -0
  50. package/dist/lib/crud/custom-fields.js +467 -0
  51. package/dist/lib/crud/custom-fields.js.map +7 -0
  52. package/dist/lib/crud/errors.js +24 -0
  53. package/dist/lib/crud/errors.js.map +7 -0
  54. package/dist/lib/crud/exporters.js +154 -0
  55. package/dist/lib/crud/exporters.js.map +7 -0
  56. package/dist/lib/crud/factory.js +1311 -0
  57. package/dist/lib/crud/factory.js.map +7 -0
  58. package/dist/lib/crud/types.js +1 -0
  59. package/dist/lib/crud/types.js.map +7 -0
  60. package/dist/lib/custom-fields/normalize.js +36 -0
  61. package/dist/lib/custom-fields/normalize.js.map +7 -0
  62. package/dist/lib/data/engine.js +396 -0
  63. package/dist/lib/data/engine.js.map +7 -0
  64. package/dist/lib/db/escapeLikePattern.js +5 -0
  65. package/dist/lib/db/escapeLikePattern.js.map +7 -0
  66. package/dist/lib/db/mikro.js +82 -0
  67. package/dist/lib/db/mikro.js.map +7 -0
  68. package/dist/lib/di/container.js +94 -0
  69. package/dist/lib/di/container.js.map +7 -0
  70. package/dist/lib/email/send.js +12 -0
  71. package/dist/lib/email/send.js.map +7 -0
  72. package/dist/lib/encryption/aes.js +58 -0
  73. package/dist/lib/encryption/aes.js.map +7 -0
  74. package/dist/lib/encryption/customFieldValues.js +49 -0
  75. package/dist/lib/encryption/customFieldValues.js.map +7 -0
  76. package/dist/lib/encryption/entityFields.js +26 -0
  77. package/dist/lib/encryption/entityFields.js.map +7 -0
  78. package/dist/lib/encryption/entityIds.js +80 -0
  79. package/dist/lib/encryption/entityIds.js.map +7 -0
  80. package/dist/lib/encryption/find.js +45 -0
  81. package/dist/lib/encryption/find.js.map +7 -0
  82. package/dist/lib/encryption/indexDoc.js +69 -0
  83. package/dist/lib/encryption/indexDoc.js.map +7 -0
  84. package/dist/lib/encryption/kms.js +282 -0
  85. package/dist/lib/encryption/kms.js.map +7 -0
  86. package/dist/lib/encryption/subscriber.js +330 -0
  87. package/dist/lib/encryption/subscriber.js.map +7 -0
  88. package/dist/lib/encryption/tenantDataEncryptionService.js +252 -0
  89. package/dist/lib/encryption/tenantDataEncryptionService.js.map +7 -0
  90. package/dist/lib/encryption/toggles.js +18 -0
  91. package/dist/lib/encryption/toggles.js.map +7 -0
  92. package/dist/lib/entities/naming.js +9 -0
  93. package/dist/lib/entities/naming.js.map +7 -0
  94. package/dist/lib/entities/system-entities.js +43 -0
  95. package/dist/lib/entities/system-entities.js.map +7 -0
  96. package/dist/lib/frontend/organizationEvents.js +41 -0
  97. package/dist/lib/frontend/organizationEvents.js.map +7 -0
  98. package/dist/lib/frontend/useOrganizationScope.js +32 -0
  99. package/dist/lib/frontend/useOrganizationScope.js.map +7 -0
  100. package/dist/lib/hotkeys/index.js +128 -0
  101. package/dist/lib/hotkeys/index.js.map +7 -0
  102. package/dist/lib/i18n/app-dictionaries.js +17 -0
  103. package/dist/lib/i18n/app-dictionaries.js.map +7 -0
  104. package/dist/lib/i18n/config.js +7 -0
  105. package/dist/lib/i18n/config.js.map +7 -0
  106. package/dist/lib/i18n/context.js +50 -0
  107. package/dist/lib/i18n/context.js.map +7 -0
  108. package/dist/lib/i18n/server.js +68 -0
  109. package/dist/lib/i18n/server.js.map +7 -0
  110. package/dist/lib/i18n/translate.js +45 -0
  111. package/dist/lib/i18n/translate.js.map +7 -0
  112. package/dist/lib/indexers/error-log.js +82 -0
  113. package/dist/lib/indexers/error-log.js.map +7 -0
  114. package/dist/lib/indexers/status-log.js +80 -0
  115. package/dist/lib/indexers/status-log.js.map +7 -0
  116. package/dist/lib/lib/auth/jwt.js +34 -0
  117. package/dist/lib/lib/auth/jwt.js.map +7 -0
  118. package/dist/lib/lib/auth/server.js +77 -0
  119. package/dist/lib/lib/auth/server.js.map +7 -0
  120. package/dist/lib/lib/email/send.js +12 -0
  121. package/dist/lib/lib/email/send.js.map +7 -0
  122. package/dist/lib/lib/i18n/config.js +7 -0
  123. package/dist/lib/lib/i18n/config.js.map +7 -0
  124. package/dist/lib/lib/i18n/context.js +31 -0
  125. package/dist/lib/lib/i18n/context.js.map +7 -0
  126. package/dist/lib/lib/utils.js +9 -0
  127. package/dist/lib/lib/utils.js.map +7 -0
  128. package/dist/lib/location/countries.js +68 -0
  129. package/dist/lib/location/countries.js.map +7 -0
  130. package/dist/lib/modules/index.js +6 -0
  131. package/dist/lib/modules/index.js.map +7 -0
  132. package/dist/lib/modules/registry.js +18 -0
  133. package/dist/lib/modules/registry.js.map +7 -0
  134. package/dist/lib/openapi/crud.js +137 -0
  135. package/dist/lib/openapi/crud.js.map +7 -0
  136. package/dist/lib/openapi/generator.js +1131 -0
  137. package/dist/lib/openapi/generator.js.map +7 -0
  138. package/dist/lib/openapi/index.js +10 -0
  139. package/dist/lib/openapi/index.js.map +7 -0
  140. package/dist/lib/openapi/sanitize.js +110 -0
  141. package/dist/lib/openapi/sanitize.js.map +7 -0
  142. package/dist/lib/openapi/types.js +1 -0
  143. package/dist/lib/openapi/types.js.map +7 -0
  144. package/dist/lib/profiler/index.js +258 -0
  145. package/dist/lib/profiler/index.js.map +7 -0
  146. package/dist/lib/query/engine.js +729 -0
  147. package/dist/lib/query/engine.js.map +7 -0
  148. package/dist/lib/query/join-utils.js +195 -0
  149. package/dist/lib/query/join-utils.js.map +7 -0
  150. package/dist/lib/query/types.js +9 -0
  151. package/dist/lib/query/types.js.map +7 -0
  152. package/dist/lib/search/config.js +32 -0
  153. package/dist/lib/search/config.js.map +7 -0
  154. package/dist/lib/search/tokenize.js +34 -0
  155. package/dist/lib/search/tokenize.js.map +7 -0
  156. package/dist/lib/slugify.js +24 -0
  157. package/dist/lib/slugify.js.map +7 -0
  158. package/dist/lib/testing/bootstrap.js +51 -0
  159. package/dist/lib/testing/bootstrap.js.map +7 -0
  160. package/dist/lib/testing/index.js +17 -0
  161. package/dist/lib/testing/index.js.map +7 -0
  162. package/dist/lib/testing/renderWithProviders.js +15 -0
  163. package/dist/lib/testing/renderWithProviders.js.map +7 -0
  164. package/dist/lib/url.js +12 -0
  165. package/dist/lib/url.js.map +7 -0
  166. package/dist/lib/utils.js +13 -0
  167. package/dist/lib/utils.js.map +7 -0
  168. package/dist/lib/version.js +7 -0
  169. package/dist/lib/version.js.map +7 -0
  170. package/dist/modules/dashboard/widgets.js +1 -0
  171. package/dist/modules/dashboard/widgets.js.map +7 -0
  172. package/dist/modules/dsl.js +30 -0
  173. package/dist/modules/dsl.js.map +7 -0
  174. package/dist/modules/entities/kinds.js +22 -0
  175. package/dist/modules/entities/kinds.js.map +7 -0
  176. package/dist/modules/entities/options.js +26 -0
  177. package/dist/modules/entities/options.js.map +7 -0
  178. package/dist/modules/entities/validation.js +102 -0
  179. package/dist/modules/entities/validation.js.map +7 -0
  180. package/dist/modules/entities/validators.js +88 -0
  181. package/dist/modules/entities/validators.js.map +7 -0
  182. package/dist/modules/entities.js +1 -0
  183. package/dist/modules/entities.js.map +7 -0
  184. package/dist/modules/navigation/sidebarPreferences.js +50 -0
  185. package/dist/modules/navigation/sidebarPreferences.js.map +7 -0
  186. package/dist/modules/perspectives/types.js +1 -0
  187. package/dist/modules/perspectives/types.js.map +7 -0
  188. package/dist/modules/registry.js +96 -0
  189. package/dist/modules/registry.js.map +7 -0
  190. package/dist/modules/search.js +15 -0
  191. package/dist/modules/search.js.map +7 -0
  192. package/dist/modules/vector.js +1 -0
  193. package/dist/modules/vector.js.map +7 -0
  194. package/dist/modules/widgets/injection-loader.js +180 -0
  195. package/dist/modules/widgets/injection-loader.js.map +7 -0
  196. package/dist/modules/widgets/injection.js +1 -0
  197. package/dist/modules/widgets/injection.js.map +7 -0
  198. package/dist/security/features.js +23 -0
  199. package/dist/security/features.js.map +7 -0
  200. package/dist/types/pg.d.js +1 -0
  201. package/dist/types/pg.d.js.map +7 -0
  202. package/dist/types/react-email.d.js +1 -0
  203. package/dist/types/react-email.d.js.map +7 -0
  204. package/dist/types/resend.d.js +1 -0
  205. package/dist/types/resend.d.js.map +7 -0
  206. package/jest.config.cjs +22 -0
  207. package/package.json +88 -0
  208. package/src/index.ts +0 -0
  209. package/src/lib/api/__tests__/scoped.test.ts +38 -0
  210. package/src/lib/api/crud.ts +59 -0
  211. package/src/lib/api/scoped.ts +239 -0
  212. package/src/lib/auth/jwt.ts +39 -0
  213. package/src/lib/auth/server.ts +199 -0
  214. package/src/lib/boolean.ts +17 -0
  215. package/src/lib/bootstrap/appResolver.ts +85 -0
  216. package/src/lib/bootstrap/dynamicLoader.ts +177 -0
  217. package/src/lib/bootstrap/factory.ts +108 -0
  218. package/src/lib/bootstrap/index.ts +23 -0
  219. package/src/lib/bootstrap/types.ts +31 -0
  220. package/src/lib/cache/segments.ts +56 -0
  221. package/src/lib/cli/progress.ts +55 -0
  222. package/src/lib/commands/__tests__/command-bus.test.ts +84 -0
  223. package/src/lib/commands/__tests__/helpers.test.ts +42 -0
  224. package/src/lib/commands/command-bus.ts +349 -0
  225. package/src/lib/commands/customFieldSnapshots.ts +86 -0
  226. package/src/lib/commands/helpers.ts +143 -0
  227. package/src/lib/commands/index.ts +4 -0
  228. package/src/lib/commands/operationMetadata.ts +40 -0
  229. package/src/lib/commands/registry.ts +46 -0
  230. package/src/lib/commands/scope.ts +59 -0
  231. package/src/lib/commands/types.ts +63 -0
  232. package/src/lib/crud/__tests__/crud-factory.test.ts +333 -0
  233. package/src/lib/crud/__tests__/custom-fields.test.ts +150 -0
  234. package/src/lib/crud/cache-stats.ts +127 -0
  235. package/src/lib/crud/cache.ts +205 -0
  236. package/src/lib/crud/custom-fields-client.ts +54 -0
  237. package/src/lib/crud/custom-fields.ts +607 -0
  238. package/src/lib/crud/errors.ts +23 -0
  239. package/src/lib/crud/exporters.ts +188 -0
  240. package/src/lib/crud/factory.ts +1622 -0
  241. package/src/lib/crud/types.ts +29 -0
  242. package/src/lib/custom-fields/normalize.ts +45 -0
  243. package/src/lib/data/engine.ts +562 -0
  244. package/src/lib/db/escapeLikePattern.ts +2 -0
  245. package/src/lib/db/mikro.ts +100 -0
  246. package/src/lib/di/container.ts +105 -0
  247. package/src/lib/email/send.ts +18 -0
  248. package/src/lib/encryption/__tests__/customFieldValues.test.ts +63 -0
  249. package/src/lib/encryption/__tests__/indexDoc.test.ts +115 -0
  250. package/src/lib/encryption/aes.ts +64 -0
  251. package/src/lib/encryption/customFieldValues.ts +67 -0
  252. package/src/lib/encryption/entityFields.ts +39 -0
  253. package/src/lib/encryption/entityIds.ts +107 -0
  254. package/src/lib/encryption/find.ts +81 -0
  255. package/src/lib/encryption/indexDoc.ts +104 -0
  256. package/src/lib/encryption/kms.ts +337 -0
  257. package/src/lib/encryption/subscriber.ts +416 -0
  258. package/src/lib/encryption/tenantDataEncryptionService.ts +313 -0
  259. package/src/lib/encryption/toggles.ts +15 -0
  260. package/src/lib/entities/naming.ts +6 -0
  261. package/src/lib/entities/system-entities.ts +43 -0
  262. package/src/lib/frontend/organizationEvents.ts +55 -0
  263. package/src/lib/frontend/useOrganizationScope.ts +30 -0
  264. package/src/lib/hotkeys/index.ts +168 -0
  265. package/src/lib/i18n/app-dictionaries.ts +18 -0
  266. package/src/lib/i18n/config.ts +4 -0
  267. package/src/lib/i18n/context.tsx +66 -0
  268. package/src/lib/i18n/server.ts +74 -0
  269. package/src/lib/i18n/translate.ts +54 -0
  270. package/src/lib/indexers/error-log.ts +106 -0
  271. package/src/lib/indexers/status-log.ts +119 -0
  272. package/src/lib/lib/auth/jwt.ts +39 -0
  273. package/src/lib/lib/auth/server.ts +94 -0
  274. package/src/lib/lib/email/send.ts +18 -0
  275. package/src/lib/lib/i18n/config.ts +4 -0
  276. package/src/lib/lib/i18n/context.tsx +38 -0
  277. package/src/lib/lib/utils.ts +6 -0
  278. package/src/lib/location/countries.ts +97 -0
  279. package/src/lib/modules/index.ts +1 -0
  280. package/src/lib/modules/registry.ts +18 -0
  281. package/src/lib/openapi/crud.ts +218 -0
  282. package/src/lib/openapi/generator.ts +1311 -0
  283. package/src/lib/openapi/index.ts +4 -0
  284. package/src/lib/openapi/sanitize.ts +137 -0
  285. package/src/lib/openapi/types.ts +79 -0
  286. package/src/lib/profiler/index.ts +371 -0
  287. package/src/lib/query/__tests__/engine.test.ts +274 -0
  288. package/src/lib/query/engine.ts +837 -0
  289. package/src/lib/query/join-utils.ts +238 -0
  290. package/src/lib/query/types.ts +121 -0
  291. package/src/lib/search/config.ts +49 -0
  292. package/src/lib/search/tokenize.ts +45 -0
  293. package/src/lib/slugify.ts +28 -0
  294. package/src/lib/testing/bootstrap.ts +124 -0
  295. package/src/lib/testing/index.ts +15 -0
  296. package/src/lib/testing/renderWithProviders.tsx +31 -0
  297. package/src/lib/url.ts +12 -0
  298. package/src/lib/utils.ts +17 -0
  299. package/src/lib/version.ts +5 -0
  300. package/src/modules/__tests__/dsl.test.ts +35 -0
  301. package/src/modules/__tests__/registry.test.ts +300 -0
  302. package/src/modules/dashboard/widgets.ts +57 -0
  303. package/src/modules/dsl.ts +32 -0
  304. package/src/modules/entities/__tests__/validation.test.ts +52 -0
  305. package/src/modules/entities/kinds.ts +20 -0
  306. package/src/modules/entities/options.ts +36 -0
  307. package/src/modules/entities/validation.ts +118 -0
  308. package/src/modules/entities/validators.ts +93 -0
  309. package/src/modules/entities.ts +102 -0
  310. package/src/modules/navigation/sidebarPreferences.ts +62 -0
  311. package/src/modules/perspectives/types.ts +40 -0
  312. package/src/modules/registry.ts +249 -0
  313. package/src/modules/search.ts +325 -0
  314. package/src/modules/vector.ts +122 -0
  315. package/src/modules/widgets/__tests__/injection.test.ts +48 -0
  316. package/src/modules/widgets/injection-loader.ts +235 -0
  317. package/src/modules/widgets/injection.ts +120 -0
  318. package/src/security/features.ts +22 -0
  319. package/src/types/pg.d.ts +2 -0
  320. package/src/types/react-email.d.ts +2 -0
  321. package/src/types/resend.d.ts +2 -0
  322. package/tsconfig.build.json +11 -0
  323. package/tsconfig.json +9 -0
  324. package/watch.mjs +6 -0
@@ -0,0 +1,66 @@
1
+ "use client"
2
+ import React, { createContext, useContext, useMemo } from 'react'
3
+ import type { Locale } from './config'
4
+
5
+ export type Dict = Record<string, string>
6
+
7
+ export type TranslateParams = Record<string, string | number>
8
+
9
+ export type TranslateFn = (
10
+ key: string,
11
+ fallbackOrParams?: string | TranslateParams,
12
+ params?: TranslateParams
13
+ ) => string
14
+
15
+ export type I18nContextValue = {
16
+ locale: Locale
17
+ t: TranslateFn
18
+ }
19
+
20
+ const I18nContext = createContext<I18nContextValue | null>(null)
21
+
22
+ function format(template: string, params?: TranslateParams) {
23
+ if (!params) return template
24
+ return template.replace(/\{\{(\w+)\}\}|\{(\w+)\}/g, (_, doubleKey, singleKey) => {
25
+ const key = doubleKey ?? singleKey
26
+ if (!key) return _
27
+ const value = params[key]
28
+ if (value === undefined) {
29
+ return doubleKey ? `{{${key}}}` : `{${key}}`
30
+ }
31
+ return String(value)
32
+ })
33
+ }
34
+
35
+ export function I18nProvider({ children, locale, dict }: { children: React.ReactNode; locale: Locale; dict: Dict }) {
36
+ const value = useMemo<I18nContextValue>(() => ({
37
+ locale,
38
+ t: (key, fallbackOrParams, params) => {
39
+ let fallback: string | undefined
40
+ let resolvedParams: TranslateParams | undefined
41
+
42
+ if (typeof fallbackOrParams === 'string') {
43
+ fallback = fallbackOrParams
44
+ resolvedParams = params
45
+ } else {
46
+ resolvedParams = fallbackOrParams ?? params
47
+ }
48
+
49
+ const template = dict[key] ?? fallback ?? key
50
+ return format(template, resolvedParams)
51
+ },
52
+ }), [locale, dict])
53
+ return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
54
+ }
55
+
56
+ export function useT() {
57
+ const ctx = useContext(I18nContext)
58
+ if (!ctx) throw new Error('useT must be used within I18nProvider')
59
+ return ctx.t
60
+ }
61
+
62
+ export function useLocale() {
63
+ const ctx = useContext(I18nContext)
64
+ if (!ctx) throw new Error('useLocale must be used within I18nProvider')
65
+ return ctx.locale
66
+ }
@@ -0,0 +1,74 @@
1
+ import { defaultLocale, locales, type Locale } from './config'
2
+ import type { Dict } from './context'
3
+ import { createFallbackTranslator, createTranslator } from './translate'
4
+ import { getModules } from '../modules/registry'
5
+ import { loadAppDictionary } from './app-dictionaries'
6
+
7
+ // Re-export for backwards compatibility
8
+ export { registerModules, getModules } from '../modules/registry'
9
+ export { registerAppDictionaryLoader } from './app-dictionaries'
10
+
11
+ function flattenDictionary(source: unknown, prefix = ''): Dict {
12
+ if (!source || typeof source !== 'object' || Array.isArray(source)) return {}
13
+ const result: Dict = {}
14
+ for (const [key, value] of Object.entries(source as Record<string, unknown>)) {
15
+ if (!key) continue
16
+ const nextKey = prefix ? `${prefix}.${key}` : key
17
+ if (typeof value === 'string') {
18
+ result[nextKey] = value
19
+ } else if (value && typeof value === 'object' && !Array.isArray(value)) {
20
+ Object.assign(result, flattenDictionary(value, nextKey))
21
+ }
22
+ }
23
+ return result
24
+ }
25
+
26
+ export async function detectLocale(): Promise<Locale> {
27
+ // Dynamic import to avoid requiring Next.js in non-Next.js contexts (CLI, tests)
28
+ try {
29
+ const { cookies, headers } = await import('next/headers')
30
+ try {
31
+ const c = (await cookies()).get('locale')?.value
32
+ if (c && locales.includes(c as Locale)) return c as Locale
33
+ } catch {
34
+ // cookies() may not be available outside request context (e.g., in tests)
35
+ }
36
+ try {
37
+ const accept = (await headers()).get('accept-language') || ''
38
+ const match = locales.find(l => new RegExp(`(^|,)\\s*${l}(-|;|,|$)`, 'i').test(accept))
39
+ if (match) return match
40
+ } catch {
41
+ // headers() may not be available outside request context (e.g., in tests)
42
+ }
43
+ } catch {
44
+ // next/headers not available (CLI context)
45
+ }
46
+ return defaultLocale
47
+ }
48
+
49
+ export async function loadDictionary(locale: Locale): Promise<Dict> {
50
+ // Load from registry instead of @/ import (works in standalone packages)
51
+ const baseRaw = await loadAppDictionary(locale)
52
+ const merged: Dict = { ...flattenDictionary(baseRaw) }
53
+ const modules = getModules()
54
+ for (const m of modules) {
55
+ const dict = m.translations?.[locale]
56
+ if (dict) Object.assign(merged, flattenDictionary(dict))
57
+ }
58
+ return merged
59
+ }
60
+
61
+ export async function resolveTranslations() {
62
+ const locale = await detectLocale()
63
+ const dict = await loadDictionary(locale)
64
+ const t = createTranslator(dict)
65
+ const translate = createFallbackTranslator(dict)
66
+ return { locale, dict, t, translate }
67
+ }
68
+ // Hint Next.js to keep this server-only; ignore if unavailable when running scripts outside Next.
69
+ try {
70
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
71
+ require('server-only')
72
+ } catch {
73
+ // noop: allows running generator scripts without Next's server-only package
74
+ }
@@ -0,0 +1,54 @@
1
+ import type { Dict, TranslateFn, TranslateParams } from './context'
2
+
3
+ export type TranslateWithFallbackFn = (key: string, fallback?: string, params?: TranslateParams) => string
4
+
5
+ function format(template: string, params?: TranslateParams) {
6
+ if (!params) return template
7
+ return template.replace(/\{\{(\w+)\}\}|\{(\w+)\}/g, (match, doubleKey, singleKey) => {
8
+ const key = doubleKey ?? singleKey
9
+ if (!key) return match
10
+ const value = params[key]
11
+ if (value === undefined) return match
12
+ return String(value)
13
+ })
14
+ }
15
+
16
+ export function createTranslator(dict: Dict): TranslateFn {
17
+ const translator = ((key: string, fallbackOrParams?: string | TranslateParams, params?: TranslateParams) => {
18
+ let fallback: string | undefined
19
+ let resolvedParams: TranslateParams | undefined
20
+
21
+ if (typeof fallbackOrParams === 'string') {
22
+ fallback = fallbackOrParams
23
+ resolvedParams = params
24
+ } else {
25
+ resolvedParams = fallbackOrParams
26
+ }
27
+
28
+ const template = dict[key] ?? fallback ?? key
29
+ return format(template, resolvedParams)
30
+ }) as TranslateFn
31
+
32
+ return translator
33
+ }
34
+
35
+ export function translateWithFallback(
36
+ t: TranslateFn,
37
+ key: string,
38
+ fallback?: string,
39
+ params?: TranslateParams,
40
+ ): string {
41
+ const value = params ? t(key, params) : t(key)
42
+ if (value !== key) return value
43
+ if (fallback === undefined) return key
44
+ return format(fallback, params)
45
+ }
46
+
47
+ export function createTranslatorWithFallback(translate: TranslateFn): TranslateWithFallbackFn {
48
+ return (key, fallback, params) => translateWithFallback(translate, key, fallback, params)
49
+ }
50
+
51
+ export function createFallbackTranslator(dict: Dict): TranslateWithFallbackFn {
52
+ const t = createTranslator(dict)
53
+ return (key, fallback, params) => translateWithFallback(t, key, fallback, params)
54
+ }
@@ -0,0 +1,106 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import type { Knex } from 'knex'
3
+
4
+ export type IndexerErrorSource = 'query_index' | 'vector' | 'fulltext'
5
+
6
+ export type RecordIndexerErrorInput = {
7
+ source: IndexerErrorSource
8
+ handler: string
9
+ error: unknown
10
+ entityType?: string | null
11
+ recordId?: string | null
12
+ tenantId?: string | null
13
+ organizationId?: string | null
14
+ payload?: unknown
15
+ }
16
+
17
+ type RecordIndexerErrorDeps = {
18
+ em?: EntityManager
19
+ knex?: Knex
20
+ }
21
+
22
+ const MAX_MESSAGE_LENGTH = 8_192
23
+ const MAX_STACK_LENGTH = 32_768
24
+
25
+ function truncate(input: string | null | undefined, limit: number): string | null {
26
+ if (!input) return null
27
+ return input.length > limit ? `${input.slice(0, limit - 3)}...` : input
28
+ }
29
+
30
+ function normalizeError(error: unknown): { message: string; stack: string | null } {
31
+ if (error instanceof Error) {
32
+ return {
33
+ message: error.message || error.name || 'Unknown error',
34
+ stack: typeof error.stack === 'string' ? error.stack : null,
35
+ }
36
+ }
37
+ if (typeof error === 'string') {
38
+ return { message: error, stack: null }
39
+ }
40
+ try {
41
+ const json = JSON.stringify(error)
42
+ return { message: json, stack: null }
43
+ } catch {
44
+ return { message: String(error ?? 'Unknown error'), stack: null }
45
+ }
46
+ }
47
+
48
+ function safeJson(value: unknown): unknown {
49
+ if (value === undefined) return null
50
+ try {
51
+ return JSON.parse(JSON.stringify(value))
52
+ } catch {
53
+ if (value == null) return null
54
+ if (typeof value === 'object') {
55
+ return { note: 'unserializable', asString: String(value) }
56
+ }
57
+ return value
58
+ }
59
+ }
60
+
61
+ function pickKnex(deps: RecordIndexerErrorDeps): Knex | null {
62
+ if (deps.knex) return deps.knex
63
+ if (deps.em) {
64
+ try {
65
+ const connection = deps.em.getConnection()
66
+ if (connection && typeof connection.getKnex === 'function') {
67
+ return connection.getKnex()
68
+ }
69
+ } catch {
70
+ return null
71
+ }
72
+ }
73
+ return null
74
+ }
75
+
76
+ export async function recordIndexerError(deps: RecordIndexerErrorDeps, input: RecordIndexerErrorInput): Promise<void> {
77
+ const knex = pickKnex(deps)
78
+ if (!knex) {
79
+ console.error('[indexers] Unable to record indexer error (missing knex connection)', {
80
+ source: input.source,
81
+ handler: input.handler,
82
+ })
83
+ return
84
+ }
85
+
86
+ const { message, stack } = normalizeError(input.error)
87
+ const payload = safeJson(input.payload)
88
+ const now = new Date()
89
+
90
+ try {
91
+ await knex('indexer_error_logs').insert({
92
+ source: input.source,
93
+ handler: input.handler,
94
+ entity_type: input.entityType ?? null,
95
+ record_id: input.recordId ?? null,
96
+ tenant_id: input.tenantId ?? null,
97
+ organization_id: input.organizationId ?? null,
98
+ payload,
99
+ message: truncate(message, MAX_MESSAGE_LENGTH),
100
+ stack: truncate(stack, MAX_STACK_LENGTH),
101
+ occurred_at: now,
102
+ })
103
+ } catch (loggingError) {
104
+ console.error('[indexers] Failed to persist indexer error', loggingError)
105
+ }
106
+ }
@@ -0,0 +1,119 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import type { Knex } from 'knex'
3
+ import type { IndexerErrorSource } from './error-log'
4
+
5
+ export type IndexerLogLevel = 'info' | 'warn'
6
+
7
+ export type RecordIndexerLogInput = {
8
+ source: IndexerErrorSource
9
+ handler: string
10
+ message: string
11
+ level?: IndexerLogLevel
12
+ entityType?: string | null
13
+ recordId?: string | null
14
+ tenantId?: string | null
15
+ organizationId?: string | null
16
+ details?: unknown
17
+ }
18
+
19
+ type RecordIndexerLogDeps = {
20
+ em?: EntityManager
21
+ knex?: Knex
22
+ }
23
+
24
+ const MAX_MESSAGE_LENGTH = 4_096
25
+ const MAX_DELETE_BATCH = 5_000
26
+ const MAX_LOGS_PER_SOURCE = 10_000
27
+
28
+ function truncate(input: string | null | undefined, limit: number): string | null {
29
+ if (!input) return null
30
+ return input.length > limit ? `${input.slice(0, limit - 3)}...` : input
31
+ }
32
+
33
+ function safeJson(value: unknown): unknown {
34
+ if (value === undefined) return null
35
+ try {
36
+ return JSON.parse(JSON.stringify(value))
37
+ } catch {
38
+ if (value == null) return null
39
+ if (typeof value === 'object') {
40
+ return { note: 'unserializable', asString: String(value) }
41
+ }
42
+ return value
43
+ }
44
+ }
45
+
46
+ function pickKnex(deps: RecordIndexerLogDeps): Knex | null {
47
+ if (deps.knex) return deps.knex
48
+ if (deps.em) {
49
+ try {
50
+ const connection = deps.em.getConnection()
51
+ if (connection && typeof connection.getKnex === 'function') {
52
+ return connection.getKnex()
53
+ }
54
+ } catch {
55
+ return null
56
+ }
57
+ }
58
+ return null
59
+ }
60
+
61
+ async function pruneExcessLogs(knex: Knex, source: IndexerErrorSource): Promise<void> {
62
+ const rows = await knex('indexer_status_logs')
63
+ .select('id')
64
+ .where('source', source)
65
+ .orderBy('occurred_at', 'desc')
66
+ .orderBy('id', 'desc')
67
+ .offset(MAX_LOGS_PER_SOURCE)
68
+ .limit(MAX_DELETE_BATCH)
69
+
70
+ if (!rows.length) return
71
+ const ids = rows.map((row: any) => row.id).filter(Boolean)
72
+ if (!ids.length) return
73
+ await knex('indexer_status_logs')
74
+ .whereIn('id', ids)
75
+ .del()
76
+ }
77
+
78
+ export async function recordIndexerLog(
79
+ deps: RecordIndexerLogDeps,
80
+ input: RecordIndexerLogInput,
81
+ ): Promise<void> {
82
+ const knex = pickKnex(deps)
83
+ if (!knex) {
84
+ console.warn('[indexers] Unable to record indexer log (missing knex connection)', {
85
+ source: input.source,
86
+ handler: input.handler,
87
+ })
88
+ return
89
+ }
90
+
91
+ const level: IndexerLogLevel = input.level === 'warn' ? 'warn' : 'info'
92
+ const message = truncate(input.message, MAX_MESSAGE_LENGTH) ?? '—'
93
+ const details = safeJson(input.details)
94
+ const occurredAt = new Date()
95
+
96
+ try {
97
+ await knex('indexer_status_logs').insert({
98
+ source: input.source,
99
+ handler: input.handler,
100
+ level,
101
+ entity_type: input.entityType ?? null,
102
+ record_id: input.recordId ?? null,
103
+ tenant_id: input.tenantId ?? null,
104
+ organization_id: input.organizationId ?? null,
105
+ message,
106
+ details,
107
+ occurred_at: occurredAt,
108
+ })
109
+ } catch (error) {
110
+ console.error('[indexers] Failed to persist indexer log', error)
111
+ return
112
+ }
113
+
114
+ try {
115
+ await pruneExcessLogs(knex, input.source)
116
+ } catch (pruneError) {
117
+ console.warn('[indexers] Failed to prune indexer logs', pruneError)
118
+ }
119
+ }
@@ -0,0 +1,39 @@
1
+ import crypto from 'node:crypto'
2
+
3
+ function base64url(input: Buffer | string) {
4
+ return (typeof input === 'string' ? Buffer.from(input) : input)
5
+ .toString('base64')
6
+ .replace(/=/g, '')
7
+ .replace(/\+/g, '-')
8
+ .replace(/\//g, '_')
9
+ }
10
+
11
+ export type JwtPayload = Record<string, any>
12
+
13
+ export function signJwt(payload: JwtPayload, secret = process.env.JWT_SECRET!, expiresInSec = 60 * 60 * 8) {
14
+ if (!secret) throw new Error('JWT_SECRET is not set')
15
+ const header = { alg: 'HS256', typ: 'JWT' }
16
+ const now = Math.floor(Date.now() / 1000)
17
+ const body = { iat: now, exp: now + expiresInSec, ...payload }
18
+ const encHeader = base64url(JSON.stringify(header))
19
+ const encBody = base64url(JSON.stringify(body))
20
+ const data = `${encHeader}.${encBody}`
21
+ const sig = crypto.createHmac('sha256', secret).update(data).digest()
22
+ const encSig = base64url(sig)
23
+ return `${data}.${encSig}`
24
+ }
25
+
26
+ export function verifyJwt(token: string, secret = process.env.JWT_SECRET!) {
27
+ if (!secret) throw new Error('JWT_SECRET is not set')
28
+ const parts = token.split('.')
29
+ if (parts.length !== 3) return null
30
+ const [h, p, s] = parts
31
+ const data = `${h}.${p}`
32
+ const expected = base64url(crypto.createHmac('sha256', secret).update(data).digest())
33
+ if (!crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected))) return null
34
+ const payload = JSON.parse(Buffer.from(p, 'base64').toString('utf8'))
35
+ const now = Math.floor(Date.now() / 1000)
36
+ if (payload.exp && now > payload.exp) return null
37
+ return payload
38
+ }
39
+
@@ -0,0 +1,94 @@
1
+ import { cookies } from 'next/headers'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import { verifyJwt } from './jwt'
4
+
5
+ export type AuthContext = {
6
+ sub: string
7
+ tenantId: string | null
8
+ orgId: string | null
9
+ email?: string
10
+ roles?: string[]
11
+ isApiKey?: boolean
12
+ keyId?: string
13
+ keyName?: string
14
+ [k: string]: any
15
+ } | null
16
+
17
+ async function resolveApiKeyAuth(secret: string): Promise<AuthContext> {
18
+ if (!secret) return null
19
+ try {
20
+ const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')
21
+ const container = await createRequestContainer()
22
+ const em = (container.resolve('em') as EntityManager)
23
+ const { findApiKeyBySecret } = await import('@open-mercato/core/modules/api_keys/services/apiKeyService')
24
+ const { Role } = await import('@open-mercato/core/modules/auth/data/entities')
25
+
26
+ const record = await findApiKeyBySecret(em, secret)
27
+ if (!record) return null
28
+
29
+ const roleIds = Array.isArray(record.rolesJson) ? record.rolesJson : []
30
+ const roles = roleIds.length
31
+ ? await em.find(Role, { id: { $in: roleIds as any } } as any)
32
+ : []
33
+ const roleNames = roles.map((role) => role.name).filter((name): name is string => typeof name === 'string' && name.length > 0)
34
+
35
+ try {
36
+ record.lastUsedAt = new Date()
37
+ await em.persistAndFlush(record)
38
+ } catch {}
39
+
40
+ return {
41
+ sub: `api_key:${record.id}`,
42
+ tenantId: record.tenantId ?? null,
43
+ orgId: record.organizationId ?? null,
44
+ roles: roleNames,
45
+ isApiKey: true,
46
+ keyId: record.id,
47
+ keyName: record.name,
48
+ }
49
+ } catch {
50
+ return null
51
+ }
52
+ }
53
+
54
+ function extractApiKey(req: Request): string | null {
55
+ const header = (req.headers.get('x-api-key') || '').trim()
56
+ if (header) return header
57
+ const authHeader = (req.headers.get('authorization') || '').trim()
58
+ if (authHeader.toLowerCase().startsWith('apikey ')) {
59
+ return authHeader.slice(7).trim()
60
+ }
61
+ return null
62
+ }
63
+
64
+ export async function getAuthFromCookies(): Promise<AuthContext> {
65
+ const token = (await cookies()).get('auth_token')?.value
66
+ if (!token) return null
67
+ try {
68
+ const payload = verifyJwt(token)
69
+ return payload
70
+ } catch {
71
+ return null
72
+ }
73
+ }
74
+
75
+ export async function getAuthFromRequest(req: Request): Promise<AuthContext> {
76
+ const authHeader = (req.headers.get('authorization') || '').trim()
77
+ let token: string | undefined
78
+ if (authHeader.toLowerCase().startsWith('bearer ')) token = authHeader.slice(7).trim()
79
+ if (!token) {
80
+ const cookie = req.headers.get('cookie') || ''
81
+ const match = cookie.match(/(?:^|;\s*)auth_token=([^;]+)/)
82
+ if (match) token = decodeURIComponent(match[1])
83
+ }
84
+ if (token) {
85
+ try {
86
+ const payload = verifyJwt(token)
87
+ if (payload) return payload
88
+ } catch {}
89
+ }
90
+
91
+ const apiKey = extractApiKey(req)
92
+ if (!apiKey) return null
93
+ return resolveApiKeyAuth(apiKey)
94
+ }
@@ -0,0 +1,18 @@
1
+ import { Resend } from 'resend'
2
+ import React from 'react'
3
+
4
+ export type SendEmailOptions = {
5
+ to: string
6
+ subject: string
7
+ react: React.ReactElement
8
+ from?: string
9
+ }
10
+
11
+ export async function sendEmail({ to, subject, react, from }: SendEmailOptions) {
12
+ const apiKey = process.env.RESEND_API_KEY
13
+ if (!apiKey) throw new Error('RESEND_API_KEY is not set')
14
+ const resend = new Resend(apiKey)
15
+ const fromAddr = from || process.env.EMAIL_FROM || 'no-reply@localhost'
16
+ await resend.emails.send({ to, subject, from: fromAddr, react })
17
+ }
18
+
@@ -0,0 +1,4 @@
1
+ export type Locale = 'en' | 'pl' | 'es' | 'de'
2
+
3
+ export const locales: Locale[] = ['en', 'pl', 'es', 'de']
4
+ export const defaultLocale: Locale = 'en'
@@ -0,0 +1,38 @@
1
+ "use client"
2
+ import React, { createContext, useContext, useMemo } from 'react'
3
+ import type { Locale } from './config'
4
+
5
+ export type Dict = Record<string, string>
6
+
7
+ export type I18nContextValue = {
8
+ locale: Locale
9
+ t: (key: string, params?: Record<string, string | number>) => string
10
+ }
11
+
12
+ const I18nContext = createContext<I18nContextValue | null>(null)
13
+
14
+ function format(template: string, params?: Record<string, string | number>) {
15
+ if (!params) return template
16
+ return template.replace(/\{(\w+)\}/g, (_, k) => String(params[k] ?? `{${k}}`))
17
+ }
18
+
19
+ export function I18nProvider({ children, locale, dict }: { children: React.ReactNode; locale: Locale; dict: Dict }) {
20
+ const value = useMemo<I18nContextValue>(() => ({
21
+ locale,
22
+ t: (key, params) => format(dict[key] ?? key, params),
23
+ }), [locale, dict])
24
+ return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
25
+ }
26
+
27
+ export function useT() {
28
+ const ctx = useContext(I18nContext)
29
+ if (!ctx) throw new Error('useT must be used within I18nProvider')
30
+ return ctx.t
31
+ }
32
+
33
+ export function useLocale() {
34
+ const ctx = useContext(I18nContext)
35
+ if (!ctx) throw new Error('useLocale must be used within I18nProvider')
36
+ return ctx.locale
37
+ }
38
+
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }