@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,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/modules/registry.ts"],
4
+ "sourcesContent": ["import type { ReactNode } from 'react'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi/types'\nimport type { DashboardWidgetModule } from './dashboard/widgets'\nimport type { InjectionWidgetModule, ModuleInjectionTable } from './widgets/injection'\n\n// Context passed to dynamic metadata guards\nexport type RouteVisibilityContext = { path?: string; auth?: any }\n\n// Metadata you can export from page.meta.ts or directly from a server page\nexport type PageMetadata = {\n requireAuth?: boolean\n requireRoles?: readonly string[]\n // Optional fine-grained feature requirements\n requireFeatures?: readonly string[]\n // Titles and grouping (aliases supported)\n title?: string\n titleKey?: string\n pageTitle?: string\n pageTitleKey?: string\n group?: string\n groupKey?: string\n pageGroup?: string\n pageGroupKey?: string\n // Ordering and visuals\n order?: number\n pageOrder?: number\n icon?: ReactNode\n navHidden?: boolean\n // Dynamic flags\n visible?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n enabled?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n // Optional static breadcrumb trail for header\n breadcrumb?: Array<{ label: string; labelKey?: string; href?: string }>\n}\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'\n\nexport type ApiHandler = (req: Request, ctx?: any) => Promise<Response> | Response\n\nexport type ModuleRoute = {\n pattern?: string\n path?: string\n requireAuth?: boolean\n requireRoles?: string[]\n // Optional fine-grained feature requirements\n requireFeatures?: string[]\n title?: string\n titleKey?: string\n group?: string\n groupKey?: string\n icon?: ReactNode\n order?: number\n priority?: number\n navHidden?: boolean\n visible?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n enabled?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n breadcrumb?: Array<{ label: string; labelKey?: string; href?: string }>\n Component: (props: any) => ReactNode | Promise<ReactNode>\n}\n\nexport type ModuleApiLegacy = {\n method: HttpMethod\n path: string\n handler: ApiHandler\n docs?: OpenApiMethodDoc\n}\n\nexport type ModuleApiRouteFile = {\n path: string\n handlers: Partial<Record<HttpMethod, ApiHandler>>\n requireAuth?: boolean\n requireRoles?: string[]\n // Optional fine-grained feature requirements for the entire route file\n // Note: per-method feature requirements should be expressed inside metadata\n requireFeatures?: string[]\n docs?: OpenApiRouteDoc\n metadata?: Partial<Record<HttpMethod, unknown>>\n}\n\nexport type ModuleApi = ModuleApiLegacy | ModuleApiRouteFile\n\nexport type ModuleCli = {\n command: string\n run: (argv: string[]) => Promise<void> | void\n}\n\nexport type ModuleInfo = {\n name?: string\n title?: string\n version?: string\n description?: string\n author?: string\n license?: string\n homepage?: string\n copyright?: string\n // Optional hard dependencies: module ids that must be enabled\n requires?: string[]\n}\n\nexport type ModuleDashboardWidgetEntry = {\n moduleId: string\n key: string\n source: 'app' | 'package'\n loader: () => Promise<DashboardWidgetModule<any>>\n}\n\nexport type ModuleInjectionWidgetEntry = {\n moduleId: string\n key: string\n source: 'app' | 'package'\n loader: () => Promise<InjectionWidgetModule<any, any>>\n}\n\nexport type Module = {\n id: string\n info?: ModuleInfo\n backendRoutes?: ModuleRoute[]\n frontendRoutes?: ModuleRoute[]\n apis?: ModuleApi[]\n cli?: ModuleCli[]\n translations?: Record<string, Record<string, string>>\n // Optional: per-module feature declarations discovered from acl.ts (module root)\n features?: Array<{ id: string; title: string; module: string }>\n // Auto-discovered event subscribers\n subscribers?: Array<{\n id: string\n event: string\n persistent?: boolean\n // Imported function reference; will be registered into event bus\n handler: (payload: any, ctx: any) => Promise<void> | void\n }>\n // Auto-discovered queue workers\n workers?: Array<{\n id: string\n queue: string\n concurrency: number\n // Imported function reference; will be called by the queue worker\n handler: (job: unknown, ctx: unknown) => Promise<void> | void\n }>\n // Optional: per-module declared entity extensions and custom fields (static)\n // Extensions discovered from data/extensions.ts; Custom fields discovered from ce.ts (entities[].fields)\n entityExtensions?: import('./entities').EntityExtension[]\n customFieldSets?: import('./entities').CustomFieldSet[]\n // Optional: per-module declared custom entities (virtual/logical entities)\n // Discovered from ce.ts (module root). Each entry represents an entityId with optional label/description.\n customEntities?: Array<{ id: string; label?: string; description?: string }>\n dashboardWidgets?: ModuleDashboardWidgetEntry[]\n injectionWidgets?: ModuleInjectionWidgetEntry[]\n injectionTable?: ModuleInjectionTable\n}\n\nfunction normPath(s: string) {\n return (s.startsWith('/') ? s : '/' + s).replace(/\\/+$/, '') || '/'\n}\n\nfunction matchPattern(pattern: string, pathname: string): Record<string, string | string[]> | undefined {\n const p = normPath(pattern)\n const u = normPath(pathname)\n const pSegs = p.split('/').slice(1)\n const uSegs = u.split('/').slice(1)\n const params: Record<string, string | string[]> = {}\n let i = 0\n for (let j = 0; j < pSegs.length; j++, i++) {\n const seg = pSegs[j]\n const mCatchAll = seg.match(/^\\[\\.\\.\\.(.+)\\]$/)\n const mOptCatch = seg.match(/^\\[\\[\\.\\.\\.(.+)\\]\\]$/)\n const mDyn = seg.match(/^\\[(.+)\\]$/)\n if (mCatchAll) {\n const key = mCatchAll[1]\n if (i >= uSegs.length) return undefined\n params[key] = uSegs.slice(i)\n i = uSegs.length\n return i === uSegs.length ? params : undefined\n } else if (mOptCatch) {\n const key = mOptCatch[1]\n params[key] = i < uSegs.length ? uSegs.slice(i) : []\n i = uSegs.length\n return params\n } else if (mDyn) {\n if (i >= uSegs.length) return undefined\n params[mDyn[1]] = uSegs[i]\n } else {\n if (i >= uSegs.length || uSegs[i] !== seg) return undefined\n }\n }\n if (i !== uSegs.length) return undefined\n return params\n}\n\nfunction getPattern(r: ModuleRoute) {\n return r.pattern ?? r.path ?? '/'\n}\n\nexport function findFrontendMatch(modules: Module[], pathname: string): { route: ModuleRoute; params: Record<string, string | string[]> } | undefined {\n for (const m of modules) {\n const routes = m.frontendRoutes ?? []\n for (const r of routes) {\n const params = matchPattern(getPattern(r), pathname)\n if (params) return { route: r, params }\n }\n }\n}\n\nexport function findBackendMatch(modules: Module[], pathname: string): { route: ModuleRoute; params: Record<string, string | string[]> } | undefined {\n for (const m of modules) {\n const routes = m.backendRoutes ?? []\n for (const r of routes) {\n const params = matchPattern(getPattern(r), pathname)\n if (params) return { route: r, params }\n }\n }\n}\n\nexport function findApi(modules: Module[], method: HttpMethod, pathname: string): { handler: ApiHandler; params: Record<string, string | string[]>; requireAuth?: boolean; requireRoles?: string[]; metadata?: any } | undefined {\n for (const m of modules) {\n const apis = m.apis ?? []\n for (const a of apis) {\n if ('handlers' in a) {\n const params = matchPattern(a.path, pathname)\n const handler = (a.handlers as any)[method]\n if (params && handler) return { handler, params, requireAuth: a.requireAuth, requireRoles: (a as any).requireRoles, metadata: (a as any).metadata }\n } else {\n const al = a as ModuleApiLegacy\n if (al.method === method && al.path === pathname) {\n return { handler: al.handler, params: {} }\n }\n }\n }\n }\n}\n\n// CLI modules registry - shared between CLI and module workers\nlet _cliModules: Module[] | null = null\n\nexport function registerCliModules(modules: Module[]) {\n if (_cliModules !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] CLI modules re-registered (this may occur during HMR)')\n }\n _cliModules = modules\n}\n\nexport function getCliModules(): Module[] {\n // Return empty array if not registered - allows generate command to work without bootstrap\n return _cliModules ?? []\n}\n\nexport function hasCliModules(): boolean {\n return _cliModules !== null && _cliModules.length > 0\n}\n"],
5
+ "mappings": "AAuJA,SAAS,SAAS,GAAW;AAC3B,UAAQ,EAAE,WAAW,GAAG,IAAI,IAAI,MAAM,GAAG,QAAQ,QAAQ,EAAE,KAAK;AAClE;AAEA,SAAS,aAAa,SAAiB,UAAiE;AACtG,QAAM,IAAI,SAAS,OAAO;AAC1B,QAAM,IAAI,SAAS,QAAQ;AAC3B,QAAM,QAAQ,EAAE,MAAM,GAAG,EAAE,MAAM,CAAC;AAClC,QAAM,QAAQ,EAAE,MAAM,GAAG,EAAE,MAAM,CAAC;AAClC,QAAM,SAA4C,CAAC;AACnD,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,KAAK;AAC1C,UAAM,MAAM,MAAM,CAAC;AACnB,UAAM,YAAY,IAAI,MAAM,kBAAkB;AAC9C,UAAM,YAAY,IAAI,MAAM,sBAAsB;AAClD,UAAM,OAAO,IAAI,MAAM,YAAY;AACnC,QAAI,WAAW;AACb,YAAM,MAAM,UAAU,CAAC;AACvB,UAAI,KAAK,MAAM,OAAQ,QAAO;AAC9B,aAAO,GAAG,IAAI,MAAM,MAAM,CAAC;AAC3B,UAAI,MAAM;AACV,aAAO,MAAM,MAAM,SAAS,SAAS;AAAA,IACvC,WAAW,WAAW;AACpB,YAAM,MAAM,UAAU,CAAC;AACvB,aAAO,GAAG,IAAI,IAAI,MAAM,SAAS,MAAM,MAAM,CAAC,IAAI,CAAC;AACnD,UAAI,MAAM;AACV,aAAO;AAAA,IACT,WAAW,MAAM;AACf,UAAI,KAAK,MAAM,OAAQ,QAAO;AAC9B,aAAO,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC;AAAA,IAC3B,OAAO;AACL,UAAI,KAAK,MAAM,UAAU,MAAM,CAAC,MAAM,IAAK,QAAO;AAAA,IACpD;AAAA,EACF;AACA,MAAI,MAAM,MAAM,OAAQ,QAAO;AAC/B,SAAO;AACT;AAEA,SAAS,WAAW,GAAgB;AAClC,SAAO,EAAE,WAAW,EAAE,QAAQ;AAChC;AAEO,SAAS,kBAAkB,SAAmB,UAAiG;AACpJ,aAAW,KAAK,SAAS;AACvB,UAAM,SAAS,EAAE,kBAAkB,CAAC;AACpC,eAAW,KAAK,QAAQ;AACtB,YAAM,SAAS,aAAa,WAAW,CAAC,GAAG,QAAQ;AACnD,UAAI,OAAQ,QAAO,EAAE,OAAO,GAAG,OAAO;AAAA,IACxC;AAAA,EACF;AACF;AAEO,SAAS,iBAAiB,SAAmB,UAAiG;AACnJ,aAAW,KAAK,SAAS;AACvB,UAAM,SAAS,EAAE,iBAAiB,CAAC;AACnC,eAAW,KAAK,QAAQ;AACtB,YAAM,SAAS,aAAa,WAAW,CAAC,GAAG,QAAQ;AACnD,UAAI,OAAQ,QAAO,EAAE,OAAO,GAAG,OAAO;AAAA,IACxC;AAAA,EACF;AACF;AAEO,SAAS,QAAQ,SAAmB,QAAoB,UAAkK;AAC/N,aAAW,KAAK,SAAS;AACvB,UAAM,OAAO,EAAE,QAAQ,CAAC;AACxB,eAAW,KAAK,MAAM;AACpB,UAAI,cAAc,GAAG;AACnB,cAAM,SAAS,aAAa,EAAE,MAAM,QAAQ;AAC5C,cAAM,UAAW,EAAE,SAAiB,MAAM;AAC1C,YAAI,UAAU,QAAS,QAAO,EAAE,SAAS,QAAQ,aAAa,EAAE,aAAa,cAAe,EAAU,cAAc,UAAW,EAAU,SAAS;AAAA,MACpJ,OAAO;AACL,cAAM,KAAK;AACX,YAAI,GAAG,WAAW,UAAU,GAAG,SAAS,UAAU;AAChD,iBAAO,EAAE,SAAS,GAAG,SAAS,QAAQ,CAAC,EAAE;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAGA,IAAI,cAA+B;AAE5B,SAAS,mBAAmB,SAAmB;AACpD,MAAI,gBAAgB,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAClE,YAAQ,MAAM,mEAAmE;AAAA,EACnF;AACA,gBAAc;AAChB;AAEO,SAAS,gBAA0B;AAExC,SAAO,eAAe,CAAC;AACzB;AAEO,SAAS,gBAAyB;AACvC,SAAO,gBAAgB,QAAQ,YAAY,SAAS;AACtD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,15 @@
1
+ let _searchModuleConfigs = null;
2
+ function registerSearchModuleConfigs(configs) {
3
+ if (_searchModuleConfigs !== null && process.env.NODE_ENV === "development") {
4
+ console.debug("[Bootstrap] Search module configs re-registered (this may occur during HMR)");
5
+ }
6
+ _searchModuleConfigs = configs;
7
+ }
8
+ function getSearchModuleConfigs() {
9
+ return _searchModuleConfigs ?? [];
10
+ }
11
+ export {
12
+ getSearchModuleConfigs,
13
+ registerSearchModuleConfigs
14
+ };
15
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/modules/search.ts"],
4
+ "sourcesContent": ["import type { EntityId } from './entities'\n\n// =============================================================================\n// Strategy Identifiers\n// =============================================================================\n\n/**\n * Built-in strategy identifiers plus extensible string for third-party strategies.\n */\nexport type SearchStrategyId = 'tokens' | 'vector' | 'fulltext' | (string & {})\n\n// =============================================================================\n// Result Types\n// =============================================================================\n\n/**\n * Presenter metadata for displaying search results in UI (Cmd+K, global search).\n */\nexport type SearchResultPresenter = {\n title: string\n subtitle?: string\n icon?: string\n badge?: string\n}\n\n/**\n * Deep link rendered next to a search result.\n */\nexport type SearchResultLink = {\n href: string\n label: string\n kind?: 'primary' | 'secondary'\n}\n\n/**\n * A single search result returned by a strategy.\n */\nexport type SearchResult = {\n /** Entity type identifier, e.g., 'customers:customer_person_profile' */\n entityId: EntityId\n /** Record primary key */\n recordId: string\n /** Relevance score (normalized 0-1 range preferred, but RRF scores may exceed 1) */\n score: number\n /** Which strategy produced this result */\n source: SearchStrategyId\n /** Optional presenter for quick display */\n presenter?: SearchResultPresenter\n /** Primary URL when result is clicked */\n url?: string\n /** Additional action links */\n links?: SearchResultLink[]\n /** Extra metadata from the strategy */\n metadata?: Record<string, unknown>\n}\n\n// =============================================================================\n// Search Options\n// =============================================================================\n\n/**\n * Options passed to SearchService.search()\n */\nexport type SearchOptions = {\n /** Tenant isolation - required */\n tenantId: string\n /** Optional organization filter */\n organizationId?: string | null\n /** Filter to specific entity types */\n entityTypes?: EntityId[]\n /** Use only specific strategies (defaults to all available) */\n strategies?: SearchStrategyId[]\n /** Maximum results per strategy before merging */\n limit?: number\n /** Offset for pagination */\n offset?: number\n /** How to combine results: 'or' merges all, 'and' requires match in all strategies */\n combineMode?: 'or' | 'and'\n}\n\n// =============================================================================\n// Indexable Record\n// =============================================================================\n\n/**\n * A record prepared for indexing across all strategies.\n */\nexport type IndexableRecord = {\n /** Entity type identifier */\n entityId: EntityId\n /** Record primary key */\n recordId: string\n /** Tenant for isolation */\n tenantId: string\n /** Optional organization for additional filtering */\n organizationId?: string | null\n /** All fields from the record (strategies will filter based on their needs) */\n fields: Record<string, unknown>\n /** Optional presenter for result display */\n presenter?: SearchResultPresenter\n /** Primary URL for the record */\n url?: string\n /** Additional action links */\n links?: SearchResultLink[]\n /** Text content for embedding (from buildSource, used by vector strategy) */\n text?: string | string[]\n /** Source object for checksum calculation (change detection) */\n checksumSource?: unknown\n}\n\n// =============================================================================\n// Strategy Interface\n// =============================================================================\n\n/**\n * Interface that all search strategies must implement.\n * Following the cache module's strategy pattern.\n */\nexport interface SearchStrategy {\n /** Unique strategy identifier */\n readonly id: SearchStrategyId\n\n /** Human-readable name for debugging/logging */\n readonly name: string\n\n /** Priority for result merging (higher = more prominent in results) */\n readonly priority: number\n\n /** Check if strategy is available and configured */\n isAvailable(): Promise<boolean>\n\n /** Initialize strategy resources (lazy, called on first use) */\n ensureReady(): Promise<void>\n\n /** Execute a search query */\n search(query: string, options: SearchOptions): Promise<SearchResult[]>\n\n /** Index a record */\n index(record: IndexableRecord): Promise<void>\n\n /** Delete a record from the index */\n delete(entityId: EntityId, recordId: string, tenantId: string): Promise<void>\n\n /** Bulk index multiple records (optional optimization) */\n bulkIndex?(records: IndexableRecord[]): Promise<void>\n\n /** Purge all records for an entity type (optional) */\n purge?(entityId: EntityId, tenantId: string): Promise<void>\n}\n\n// =============================================================================\n// Service Configuration\n// =============================================================================\n\n/**\n * Configuration for result merging across strategies.\n */\nexport type ResultMergeConfig = {\n /** How to handle duplicate results: 'highest_score' | 'first' | 'merge_scores' */\n duplicateHandling: 'highest_score' | 'first' | 'merge_scores'\n /** Weight multipliers per strategy (e.g., { meilisearch: 1.2, tokens: 0.8 }) */\n strategyWeights?: Record<SearchStrategyId, number>\n /** Minimum score threshold to include in results */\n minScore?: number\n}\n\n/**\n * Callback function to enrich search results with presenter data.\n * Used to load presenter from database when not available from search strategy.\n */\nexport type PresenterEnricherFn = (\n results: SearchResult[],\n tenantId: string,\n organizationId?: string | null,\n) => Promise<SearchResult[]>\n\n/**\n * Options for creating a SearchService instance.\n */\nexport type SearchServiceOptions = {\n /** Array of strategy instances */\n strategies?: SearchStrategy[]\n /** Default strategies to use when not specified in search options */\n defaultStrategies?: SearchStrategyId[]\n /** Fallback strategy when others fail */\n fallbackStrategy?: SearchStrategyId\n /** Configuration for merging results from multiple strategies */\n mergeConfig?: ResultMergeConfig\n /** Callback to enrich results with presenter data from database */\n presenterEnricher?: PresenterEnricherFn\n}\n\n// =============================================================================\n// Module Configuration (for modules defining searchable entities)\n// =============================================================================\n\n/**\n * Context passed to buildSource, formatResult, resolveUrl, and resolveLinks.\n */\nexport type SearchBuildContext = {\n /** The record being indexed */\n record: Record<string, unknown>\n /** Custom fields for the record */\n customFields: Record<string, unknown>\n /** Organization ID if applicable */\n organizationId?: string | null\n /** Tenant ID */\n tenantId?: string | null\n /** DI container for resolving dependencies */\n container?: unknown\n /** Query engine for loading related records (optional, used by buildSource for entity hydration) */\n queryEngine?: unknown\n}\n\n/**\n * Source data for indexing a record.\n */\nexport type SearchIndexSource = {\n /** Text content for keyword/fuzzy search (single string or array of chunks) */\n text: string | string[]\n /** Optional structured fields for filtering */\n fields?: Record<string, unknown>\n /** Presenter for quick display in search results */\n presenter?: SearchResultPresenter\n /** Deep links for the result */\n links?: SearchResultLink[]\n /** Source object used for checksum calculation (change detection) */\n checksumSource?: unknown\n}\n\n/**\n * Policy defining how fields should be handled for search indexing.\n */\nexport type SearchFieldPolicy = {\n /** Fields safe to send to external providers (fuzzy searchable) */\n searchable?: string[]\n /** Fields for hash-based search only (encrypted/sensitive) */\n hashOnly?: string[]\n /** Fields to exclude from all search */\n excluded?: string[]\n}\n\n/**\n * Configuration for a single searchable entity within a module.\n */\nexport type SearchEntityConfig = {\n /** Entity identifier, e.g., 'customers:customer_person_profile' */\n entityId: EntityId\n /** Enable/disable search for this entity (default: true) */\n enabled?: boolean\n /** Override strategies for this specific entity */\n strategies?: SearchStrategyId[]\n /** Priority for result ordering (higher = more prominent) */\n priority?: number\n /** Build searchable content from record */\n buildSource?: (ctx: SearchBuildContext) => Promise<SearchIndexSource | null> | SearchIndexSource | null\n /** Format result for display in Cmd+K */\n formatResult?: (ctx: SearchBuildContext) => Promise<SearchResultPresenter | null> | SearchResultPresenter | null\n /** Resolve primary URL when result is clicked */\n resolveUrl?: (ctx: SearchBuildContext) => Promise<string | null> | string | null\n /** Resolve additional action links */\n resolveLinks?: (ctx: SearchBuildContext) => Promise<SearchResultLink[] | null> | SearchResultLink[] | null\n /** Define which fields are searchable vs hash-only */\n fieldPolicy?: SearchFieldPolicy\n}\n\n/**\n * Module-level search configuration (defined in search.ts files).\n */\nexport type SearchModuleConfig = {\n /** Default strategies for all entities in this module */\n defaultStrategies?: SearchStrategyId[]\n /** Entity configurations */\n entities: SearchEntityConfig[]\n}\n\n// =============================================================================\n// Event Payloads (for indexer events)\n// =============================================================================\n\n/**\n * Payload for search.index_record events.\n */\nexport type SearchIndexPayload = {\n entityId: EntityId\n recordId: string\n tenantId: string\n organizationId?: string | null\n record: Record<string, unknown>\n customFields?: Record<string, unknown>\n}\n\n/**\n * Payload for search.delete_record events.\n */\nexport type SearchDeletePayload = {\n entityId: EntityId\n recordId: string\n tenantId: string\n}\n\n// =============================================================================\n// Global Registry for Search Module Configs\n// =============================================================================\n\nlet _searchModuleConfigs: SearchModuleConfig[] | null = null\n\n/**\n * Register search module configurations globally.\n * Called during app bootstrap with configs from search.generated.ts.\n */\nexport function registerSearchModuleConfigs(configs: SearchModuleConfig[]): void {\n if (_searchModuleConfigs !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Search module configs re-registered (this may occur during HMR)')\n }\n _searchModuleConfigs = configs\n}\n\n/**\n * Get registered search module configurations.\n * Returns empty array if not registered (search module may not be enabled).\n */\nexport function getSearchModuleConfigs(): SearchModuleConfig[] {\n return _searchModuleConfigs ?? []\n}\n"],
5
+ "mappings": "AAiTA,IAAI,uBAAoD;AAMjD,SAAS,4BAA4B,SAAqC;AAC/E,MAAI,yBAAyB,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAC3E,YAAQ,MAAM,6EAA6E;AAAA,EAC7F;AACA,yBAAuB;AACzB;AAMO,SAAS,yBAA+C;AAC7D,SAAO,wBAAwB,CAAC;AAClC;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=vector.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,180 @@
1
+ let _coreInjectionWidgetEntries = null;
2
+ let _coreInjectionTables = null;
3
+ function registerCoreInjectionWidgets(entries) {
4
+ if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === "development") {
5
+ console.debug("[Bootstrap] Core injection widgets re-registered (this may occur during HMR)");
6
+ }
7
+ _coreInjectionWidgetEntries = entries;
8
+ }
9
+ function getCoreInjectionWidgets() {
10
+ if (!_coreInjectionWidgetEntries) {
11
+ if (typeof window !== "undefined") {
12
+ return [];
13
+ }
14
+ throw new Error("[Bootstrap] Core injection widgets not registered. Call registerCoreInjectionWidgets() at bootstrap.");
15
+ }
16
+ return _coreInjectionWidgetEntries;
17
+ }
18
+ function registerCoreInjectionTables(tables) {
19
+ if (_coreInjectionTables !== null && process.env.NODE_ENV === "development") {
20
+ console.debug("[Bootstrap] Core injection tables re-registered (this may occur during HMR)");
21
+ }
22
+ _coreInjectionTables = tables;
23
+ }
24
+ function getCoreInjectionTables() {
25
+ if (!_coreInjectionTables) {
26
+ if (typeof window !== "undefined") {
27
+ return [];
28
+ }
29
+ throw new Error("[Bootstrap] Core injection tables not registered. Call registerCoreInjectionTables() at bootstrap.");
30
+ }
31
+ return _coreInjectionTables;
32
+ }
33
+ let widgetEntriesPromise = null;
34
+ let injectionTablePromise = null;
35
+ function isInjectionSlotObject(value) {
36
+ return typeof value === "object" && value !== null && "widgetId" in value;
37
+ }
38
+ function invalidateInjectionWidgetCache() {
39
+ widgetEntriesPromise = null;
40
+ injectionTablePromise = null;
41
+ widgetCache.clear();
42
+ }
43
+ async function loadWidgetEntries() {
44
+ if (!widgetEntriesPromise) {
45
+ const promise = Promise.resolve().then(
46
+ () => getCoreInjectionWidgets().map((entry) => ({
47
+ ...entry,
48
+ moduleId: entry.moduleId || "unknown"
49
+ }))
50
+ );
51
+ widgetEntriesPromise = promise.catch((err) => {
52
+ if (widgetEntriesPromise === promise) {
53
+ widgetEntriesPromise = null;
54
+ }
55
+ throw err;
56
+ });
57
+ }
58
+ return widgetEntriesPromise;
59
+ }
60
+ async function loadInjectionTable() {
61
+ if (!injectionTablePromise) {
62
+ const promise = Promise.resolve().then(() => {
63
+ const list = getCoreInjectionTables();
64
+ const table = /* @__PURE__ */ new Map();
65
+ for (const entry of list) {
66
+ const injectionTable = entry.table ?? {};
67
+ for (const [spotId, widgetIds] of Object.entries(injectionTable)) {
68
+ const widgets = Array.isArray(widgetIds) ? widgetIds : [widgetIds];
69
+ const existing = table.get(spotId) ?? [];
70
+ for (const widgetEntry of widgets) {
71
+ if (typeof widgetEntry === "string") {
72
+ existing.push({ widgetId: widgetEntry, moduleId: entry.moduleId, priority: 0 });
73
+ continue;
74
+ }
75
+ if (isInjectionSlotObject(widgetEntry)) {
76
+ const { widgetId, priority = 0, ...placement } = widgetEntry;
77
+ existing.push({
78
+ widgetId,
79
+ moduleId: entry.moduleId,
80
+ priority: typeof priority === "number" ? priority : 0,
81
+ placement
82
+ });
83
+ continue;
84
+ }
85
+ }
86
+ table.set(spotId, existing);
87
+ }
88
+ }
89
+ for (const [spotId, widgets] of table.entries()) {
90
+ table.set(spotId, widgets.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)));
91
+ }
92
+ return table;
93
+ });
94
+ injectionTablePromise = promise.catch((err) => {
95
+ if (injectionTablePromise === promise) {
96
+ injectionTablePromise = null;
97
+ }
98
+ throw err;
99
+ });
100
+ }
101
+ return injectionTablePromise;
102
+ }
103
+ const widgetCache = /* @__PURE__ */ new Map();
104
+ function ensureValidWidgetModule(mod, key, moduleId) {
105
+ if (!mod || typeof mod !== "object") {
106
+ throw new Error(`Invalid injection widget module "${key}" from "${moduleId}" (expected object export)`);
107
+ }
108
+ const widget = mod.default ?? mod;
109
+ if (!widget || typeof widget !== "object") {
110
+ throw new Error(`Invalid injection widget export "${key}" from "${moduleId}" (missing default export)`);
111
+ }
112
+ if (!widget.metadata || typeof widget.metadata !== "object") {
113
+ throw new Error(`Injection widget "${key}" from "${moduleId}" is missing metadata`);
114
+ }
115
+ const { metadata } = widget;
116
+ if (typeof metadata.id !== "string" || metadata.id.length === 0) {
117
+ throw new Error(`Injection widget "${key}" from "${moduleId}" metadata.id must be a non-empty string`);
118
+ }
119
+ if (typeof metadata.title !== "string" || metadata.title.length === 0) {
120
+ throw new Error(`Injection widget "${metadata.id}" from "${moduleId}" must have a title`);
121
+ }
122
+ return {
123
+ ...widget,
124
+ metadata
125
+ };
126
+ }
127
+ async function loadEntry(entry) {
128
+ if (!widgetCache.has(entry.key)) {
129
+ const promise = entry.loader().then((mod) => ensureValidWidgetModule(mod, entry.key, entry.moduleId));
130
+ widgetCache.set(entry.key, promise);
131
+ }
132
+ return widgetCache.get(entry.key);
133
+ }
134
+ async function loadAllInjectionWidgets() {
135
+ const widgetEntries = await loadWidgetEntries();
136
+ const loaded = await Promise.all(widgetEntries.map(async (entry) => {
137
+ const widget = await loadEntry(entry);
138
+ return { ...widget, moduleId: entry.moduleId, key: entry.key };
139
+ }));
140
+ const byId = /* @__PURE__ */ new Map();
141
+ for (const widget of loaded) {
142
+ if (!byId.has(widget.metadata.id)) {
143
+ byId.set(widget.metadata.id, widget);
144
+ }
145
+ }
146
+ return Array.from(byId.values());
147
+ }
148
+ async function loadInjectionWidgetById(widgetId) {
149
+ const widgetEntries = await loadWidgetEntries();
150
+ for (const entry of widgetEntries) {
151
+ const widget = await loadEntry(entry);
152
+ if (widget.metadata.id === widgetId) {
153
+ return { ...widget, moduleId: entry.moduleId, key: entry.key };
154
+ }
155
+ }
156
+ return null;
157
+ }
158
+ async function loadInjectionWidgetsForSpot(spotId) {
159
+ const table = await loadInjectionTable();
160
+ const entries = table.get(spotId) ?? [];
161
+ const widgets = await Promise.all(
162
+ entries.map(async ({ widgetId, placement, priority }) => {
163
+ const widget = await loadInjectionWidgetById(widgetId);
164
+ const combinedPlacement = placement ? { ...placement, priority: typeof priority === "number" ? priority : 0 } : { priority: typeof priority === "number" ? priority : 0 };
165
+ return widget ? { ...widget, placement: combinedPlacement } : null;
166
+ })
167
+ );
168
+ return widgets.filter((w) => w !== null);
169
+ }
170
+ export {
171
+ getCoreInjectionTables,
172
+ getCoreInjectionWidgets,
173
+ invalidateInjectionWidgetCache,
174
+ loadAllInjectionWidgets,
175
+ loadInjectionWidgetById,
176
+ loadInjectionWidgetsForSpot,
177
+ registerCoreInjectionTables,
178
+ registerCoreInjectionWidgets
179
+ };
180
+ //# sourceMappingURL=injection-loader.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/widgets/injection-loader.ts"],
4
+ "sourcesContent": ["import type { ModuleInjectionWidgetEntry } from '../registry'\nimport type {\n InjectionWidgetMetadata,\n InjectionWidgetModule,\n InjectionSpotId,\n ModuleInjectionSlot,\n ModuleInjectionTable,\n InjectionWidgetPlacement,\n} from './injection'\n\ntype LoadedWidgetModule = InjectionWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\nexport type LoadedInjectionWidget = LoadedWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\ntype WidgetEntry = ModuleInjectionWidgetEntry & { moduleId: string }\n\n// Registration pattern for publishable packages\nlet _coreInjectionWidgetEntries: ModuleInjectionWidgetEntry[] | null = null\nlet _coreInjectionTables: Array<{ moduleId: string; table: ModuleInjectionTable }> | null = null\n\nexport function registerCoreInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Core injection widgets re-registered (this may occur during HMR)')\n }\n _coreInjectionWidgetEntries = entries\n}\n\nexport function getCoreInjectionWidgets(): ModuleInjectionWidgetEntry[] {\n if (!_coreInjectionWidgetEntries) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection widgets not registered. Call registerCoreInjectionWidgets() at bootstrap.')\n }\n return _coreInjectionWidgetEntries\n}\n\nexport function registerCoreInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n if (_coreInjectionTables !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] Core injection tables re-registered (this may occur during HMR)')\n }\n _coreInjectionTables = tables\n}\n\nexport function getCoreInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> {\n if (!_coreInjectionTables) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection tables not registered. Call registerCoreInjectionTables() at bootstrap.')\n }\n return _coreInjectionTables\n}\n\nlet widgetEntriesPromise: Promise<WidgetEntry[]> | null = null\ntype TableEntry = {\n widgetId: string\n moduleId: string\n priority: number\n placement?: ModuleInjectionSlot extends infer S\n ? S extends { widgetId: string }\n ? Omit<S, 'widgetId' | 'priority'>\n : never\n : never\n}\nlet injectionTablePromise: Promise<Map<InjectionSpotId, TableEntry[]>> | null = null\n\nfunction isInjectionSlotObject(value: ModuleInjectionSlot): value is InjectionWidgetPlacement & { widgetId: string; priority?: number } {\n return typeof value === 'object' && value !== null && 'widgetId' in value\n}\n\n/**\n * Invalidate the widget entries and widget module cache.\n * Call this when the generated registry is updated or modules are reloaded.\n */\nexport function invalidateInjectionWidgetCache() {\n widgetEntriesPromise = null\n injectionTablePromise = null\n widgetCache.clear()\n}\n\nasync function loadWidgetEntries(): Promise<WidgetEntry[]> {\n if (!widgetEntriesPromise) {\n const promise = Promise.resolve().then(() =>\n getCoreInjectionWidgets().map((entry) => ({\n ...entry,\n moduleId: entry.moduleId || 'unknown',\n }))\n )\n widgetEntriesPromise = promise.catch((err) => {\n // Clear cache on error so next call can retry after registration\n if (widgetEntriesPromise === promise) {\n widgetEntriesPromise = null\n }\n throw err\n })\n }\n return widgetEntriesPromise\n}\n\nasync function loadInjectionTable(): Promise<Map<InjectionSpotId, TableEntry[]>> {\n if (!injectionTablePromise) {\n const promise = Promise.resolve().then(() => {\n const list = getCoreInjectionTables()\n const table = new Map<InjectionSpotId, TableEntry[]>()\n\n for (const entry of list) {\n const injectionTable = entry.table ?? {}\n for (const [spotId, widgetIds] of Object.entries(injectionTable)) {\n const widgets = Array.isArray(widgetIds) ? widgetIds : [widgetIds]\n const existing = table.get(spotId) ?? []\n for (const widgetEntry of widgets) {\n if (typeof widgetEntry === 'string') {\n existing.push({ widgetId: widgetEntry, moduleId: entry.moduleId, priority: 0 })\n continue\n }\n if (isInjectionSlotObject(widgetEntry)) {\n const { widgetId, priority = 0, ...placement } = widgetEntry\n existing.push({\n widgetId,\n moduleId: entry.moduleId,\n priority: typeof priority === 'number' ? priority : 0,\n placement,\n })\n continue\n }\n }\n table.set(spotId, existing)\n }\n }\n\n for (const [spotId, widgets] of table.entries()) {\n table.set(spotId, widgets.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)))\n }\n\n return table\n })\n injectionTablePromise = promise.catch((err) => {\n // Clear cache on error so next call can retry after registration\n if (injectionTablePromise === promise) {\n injectionTablePromise = null\n }\n throw err\n })\n }\n return injectionTablePromise\n}\n\nconst widgetCache = new Map<string, Promise<LoadedWidgetModule>>()\n\nfunction ensureValidWidgetModule(mod: any, key: string, moduleId: string): LoadedWidgetModule {\n if (!mod || typeof mod !== 'object') {\n throw new Error(`Invalid injection widget module \"${key}\" from \"${moduleId}\" (expected object export)`)\n }\n const widget = (mod.default ?? mod) as InjectionWidgetModule<any, any>\n if (!widget || typeof widget !== 'object') {\n throw new Error(`Invalid injection widget export \"${key}\" from \"${moduleId}\" (missing default export)`)\n }\n if (!widget.metadata || typeof widget.metadata !== 'object') {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" is missing metadata`)\n }\n const { metadata } = widget\n if (typeof metadata.id !== 'string' || metadata.id.length === 0) {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" metadata.id must be a non-empty string`)\n }\n if (typeof metadata.title !== 'string' || metadata.title.length === 0) {\n throw new Error(`Injection widget \"${metadata.id}\" from \"${moduleId}\" must have a title`)\n }\n return {\n ...widget,\n metadata,\n }\n}\n\nasync function loadEntry(entry: WidgetEntry): Promise<LoadedWidgetModule> {\n if (!widgetCache.has(entry.key)) {\n const promise = entry.loader()\n .then((mod) => ensureValidWidgetModule(mod, entry.key, entry.moduleId))\n widgetCache.set(entry.key, promise)\n }\n return widgetCache.get(entry.key)!\n}\n\nexport async function loadAllInjectionWidgets(): Promise<LoadedInjectionWidget[]> {\n const widgetEntries = await loadWidgetEntries()\n const loaded = await Promise.all(widgetEntries.map(async (entry) => {\n const widget = await loadEntry(entry)\n return { ...widget, moduleId: entry.moduleId, key: entry.key }\n }))\n const byId = new Map<string, LoadedWidgetModule & { moduleId: string; key: string }>()\n for (const widget of loaded) {\n if (!byId.has(widget.metadata.id)) {\n byId.set(widget.metadata.id, widget)\n }\n }\n return Array.from(byId.values())\n}\n\nexport async function loadInjectionWidgetById(widgetId: string): Promise<LoadedInjectionWidget | null> {\n const widgetEntries = await loadWidgetEntries()\n for (const entry of widgetEntries) {\n const widget = await loadEntry(entry)\n if (widget.metadata.id === widgetId) {\n return { ...widget, moduleId: entry.moduleId, key: entry.key }\n }\n }\n return null\n}\n\nexport async function loadInjectionWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionWidget[]> {\n const table = await loadInjectionTable()\n const entries = table.get(spotId) ?? []\n const widgets = await Promise.all(\n entries.map(async ({ widgetId, placement, priority }) => {\n const widget = await loadInjectionWidgetById(widgetId)\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n return widget ? { ...widget, placement: combinedPlacement } : null\n })\n )\n return widgets.filter((w): w is NonNullable<typeof w> => w !== null)\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=injection.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,23 @@
1
+ function matchFeature(required, granted) {
2
+ if (granted === "*") return true;
3
+ if (granted.endsWith(".*")) {
4
+ const prefix = granted.slice(0, -2);
5
+ return required === prefix || required.startsWith(prefix + ".");
6
+ }
7
+ return granted === required;
8
+ }
9
+ function hasFeature(granted, required) {
10
+ if (!Array.isArray(granted) || !granted.length) return false;
11
+ return granted.some((feature) => matchFeature(required, feature));
12
+ }
13
+ function hasAllFeatures(granted, required) {
14
+ if (!required || required.length === 0) return true;
15
+ if (!Array.isArray(granted) || !granted.length) return false;
16
+ return required.every((feature) => hasFeature(granted, feature));
17
+ }
18
+ export {
19
+ hasAllFeatures,
20
+ hasFeature,
21
+ matchFeature
22
+ };
23
+ //# sourceMappingURL=features.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/security/features.ts"],
4
+ "sourcesContent": ["export function matchFeature(required: string, granted: string): boolean {\n if (granted === '*') return true\n if (granted.endsWith('.*')) {\n const prefix = granted.slice(0, -2)\n return required === prefix || required.startsWith(prefix + '.')\n }\n return granted === required\n}\n\nexport function hasFeature(granted: readonly string[] | undefined, required: string): boolean {\n if (!Array.isArray(granted) || !granted.length) return false\n return granted.some((feature) => matchFeature(required, feature))\n}\n\nexport function hasAllFeatures(\n granted: readonly string[] | undefined,\n required: readonly string[] | undefined\n): boolean {\n if (!required || required.length === 0) return true\n if (!Array.isArray(granted) || !granted.length) return false\n return required.every((feature) => hasFeature(granted, feature))\n}\n"],
5
+ "mappings": "AAAO,SAAS,aAAa,UAAkB,SAA0B;AACvE,MAAI,YAAY,IAAK,QAAO;AAC5B,MAAI,QAAQ,SAAS,IAAI,GAAG;AAC1B,UAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,WAAO,aAAa,UAAU,SAAS,WAAW,SAAS,GAAG;AAAA,EAChE;AACA,SAAO,YAAY;AACrB;AAEO,SAAS,WAAW,SAAwC,UAA2B;AAC5F,MAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,CAAC,QAAQ,OAAQ,QAAO;AACvD,SAAO,QAAQ,KAAK,CAAC,YAAY,aAAa,UAAU,OAAO,CAAC;AAClE;AAEO,SAAS,eACd,SACA,UACS;AACT,MAAI,CAAC,YAAY,SAAS,WAAW,EAAG,QAAO;AAC/C,MAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,CAAC,QAAQ,OAAQ,QAAO;AACvD,SAAO,SAAS,MAAM,CAAC,YAAY,WAAW,SAAS,OAAO,CAAC;AACjE;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=pg.d.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=react-email.d.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=resend.d.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,22 @@
1
+ /** @type {import('jest').Config} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: 'node',
5
+ rootDir: '.',
6
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
7
+ moduleNameMapper: {
8
+ '^@open-mercato/shared/(.*)$': '<rootDir>/src/$1',
9
+ },
10
+ transform: {
11
+ '^.+\\.(t|j)sx?$': [
12
+ 'ts-jest',
13
+ {
14
+ tsconfig: {
15
+ jsx: 'react-jsx',
16
+ },
17
+ },
18
+ ],
19
+ },
20
+ testMatch: ['<rootDir>/src/**/__tests__/**/*.test.(ts|tsx)'],
21
+ passWithNoTests: true,
22
+ }
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@open-mercato/shared",
3
+ "version": "0.4.2-canary-c02407ff85",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "scripts": {
7
+ "build": "node build.mjs",
8
+ "watch": "node watch.mjs",
9
+ "test": "jest --config jest.config.cjs",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "exports": {
13
+ ".": "./dist/index.js",
14
+ "./lib/openapi": {
15
+ "types": "./src/lib/openapi/index.ts",
16
+ "default": "./dist/lib/openapi/index.js"
17
+ },
18
+ "./lib/commands": {
19
+ "types": "./src/lib/commands/index.ts",
20
+ "default": "./dist/lib/commands/index.js"
21
+ },
22
+ "./lib/hotkeys": {
23
+ "types": "./src/lib/hotkeys/index.ts",
24
+ "default": "./dist/lib/hotkeys/index.js"
25
+ },
26
+ "./lib/profiler": {
27
+ "types": "./src/lib/profiler/index.ts",
28
+ "default": "./dist/lib/profiler/index.js"
29
+ },
30
+ "./lib/testing": {
31
+ "types": "./src/lib/testing/index.ts",
32
+ "default": "./dist/lib/testing/index.js"
33
+ },
34
+ "./lib/bootstrap": {
35
+ "types": "./src/lib/bootstrap/index.ts",
36
+ "default": "./dist/lib/bootstrap/index.js"
37
+ },
38
+ "./lib/bootstrap/dynamicLoader": {
39
+ "types": "./src/lib/bootstrap/dynamicLoader.ts",
40
+ "default": "./dist/lib/bootstrap/dynamicLoader.js"
41
+ },
42
+ "./*.ts": {
43
+ "types": "./src/*.ts",
44
+ "default": "./dist/*.js"
45
+ },
46
+ "./*.tsx": {
47
+ "types": "./src/*.tsx",
48
+ "default": "./dist/*.js"
49
+ },
50
+ "./*": {
51
+ "types": [
52
+ "./src/*.ts",
53
+ "./src/*.tsx"
54
+ ],
55
+ "default": "./dist/*.js"
56
+ },
57
+ "./*/*": {
58
+ "types": [
59
+ "./src/*/*.ts",
60
+ "./src/*/*.tsx"
61
+ ],
62
+ "default": "./dist/*/*.js"
63
+ },
64
+ "./*/*/*": {
65
+ "types": [
66
+ "./src/*/*/*.ts",
67
+ "./src/*/*/*.tsx"
68
+ ],
69
+ "default": "./dist/*/*/*.js"
70
+ },
71
+ "./*/*/*/*": {
72
+ "types": [
73
+ "./src/*/*/*/*.ts",
74
+ "./src/*/*/*/*.tsx"
75
+ ],
76
+ "default": "./dist/*/*/*/*.js"
77
+ }
78
+ },
79
+ "devDependencies": {
80
+ "@types/jest": "^30.0.0",
81
+ "jest": "^30.2.0",
82
+ "ts-jest": "^29.4.6"
83
+ },
84
+ "publishConfig": {
85
+ "access": "public"
86
+ },
87
+ "stableVersion": "0.4.1"
88
+ }
package/src/index.ts ADDED
File without changes
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod'
2
+ import { parseScopedCommandInput } from '../scoped'
3
+
4
+ const translate = (_key: string, fallback?: string) => fallback ?? _key
5
+
6
+ describe('parseScopedCommandInput', () => {
7
+ it('preserves custom fields when parsing scoped payloads', () => {
8
+ const schema = z.object({
9
+ tenantId: z.string(),
10
+ organizationId: z.string().optional(),
11
+ name: z.string(),
12
+ })
13
+ const ctx = {
14
+ auth: { tenantId: 'tenant-1', orgId: 'org-1' },
15
+ selectedOrganizationId: 'org-1',
16
+ } as any
17
+
18
+ const result = parseScopedCommandInput(
19
+ schema,
20
+ {
21
+ tenantId: 'tenant-1',
22
+ organizationId: 'org-1',
23
+ name: 'Test Product',
24
+ customFields: { foo: 'bar' },
25
+ cf_extra: '123',
26
+ },
27
+ ctx,
28
+ translate
29
+ )
30
+
31
+ expect(result).toMatchObject({
32
+ tenantId: 'tenant-1',
33
+ organizationId: 'org-1',
34
+ name: 'Test Product',
35
+ customFields: { foo: 'bar', extra: '123' },
36
+ })
37
+ })
38
+ })
@@ -0,0 +1,59 @@
1
+ import type { EntityManager } from '@mikro-orm/core'
2
+
3
+ type Scope = { organizationId?: string | null; organizationIds?: string[] | null; tenantId?: string | null }
4
+
5
+ export function buildScopedWhere(
6
+ base: Record<string, any>,
7
+ scope: Scope & { orgField?: string | null; tenantField?: string | null; softDeleteField?: string | null }
8
+ ): Record<string, any> {
9
+ const where: any = { ...base }
10
+ const orgField = scope.orgField === null ? null : (scope.orgField as string) || 'organizationId'
11
+ const tenantField = scope.tenantField === null ? null : (scope.tenantField as string) || 'tenantId'
12
+ const softField = scope.softDeleteField === null ? null : (scope.softDeleteField as string) || 'deletedAt'
13
+
14
+ if (orgField) {
15
+ if (scope.organizationIds !== undefined) {
16
+ const ids = (scope.organizationIds ?? []).filter((id): id is string => typeof id === 'string' && id.length > 0)
17
+ if (ids.length === 0) {
18
+ where[orgField] = { $in: [] }
19
+ } else if (ids.length === 1) {
20
+ where[orgField] = ids[0]
21
+ } else {
22
+ where[orgField] = { $in: ids }
23
+ }
24
+ } else if (scope.organizationId !== undefined) {
25
+ where[orgField] = scope.organizationId
26
+ }
27
+ }
28
+
29
+ if (tenantField && scope.tenantId !== undefined) where[tenantField] = scope.tenantId
30
+ if (softField) where[softField] = null
31
+ return where
32
+ }
33
+
34
+ export function extractScopeFromAuth(auth: { orgId?: string | null; tenantId?: string | null } | null | undefined): { organizationId?: string | null; tenantId?: string | null } {
35
+ if (!auth) return {}
36
+ return { organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }
37
+ }
38
+
39
+ export async function findOneScoped<T extends { id: string }>(
40
+ em: EntityManager,
41
+ entity: { new (): T },
42
+ id: string,
43
+ scope: Scope & { orgField?: keyof T; tenantField?: keyof T }
44
+ ): Promise<T | null> {
45
+ const orgField = (scope.orgField as string) || 'organizationId'
46
+ const tenantField = (scope.tenantField as string) || 'tenantId'
47
+ const where: any = { id }
48
+ if (scope.organizationId != null) where[orgField] = scope.organizationId
49
+ if (scope.tenantId != null) where[tenantField] = scope.tenantId
50
+ return em.getRepository(entity).findOne(where as any)
51
+ }
52
+
53
+ export async function softDelete<T extends { deletedAt?: Date | null }>(
54
+ em: EntityManager,
55
+ entity: T
56
+ ): Promise<void> {
57
+ ;(entity as any).deletedAt = new Date()
58
+ await em.persistAndFlush(entity)
59
+ }