@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,4 @@
1
+ export * from './types'
2
+ export { buildOpenApiDocument, generateMarkdownFromOpenApi } from './generator'
3
+ export * from './crud'
4
+ export { sanitizeOpenApiDocument } from './sanitize'
@@ -0,0 +1,137 @@
1
+ type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }
2
+ type MutableOpenApiDoc = Record<string, any> & {
3
+ components?: {
4
+ schemas?: Record<string, JsonValue>
5
+ }
6
+ }
7
+
8
+ function isPlainObject(value: unknown): value is Record<string, any> {
9
+ return typeof value === 'object' && value !== null && Object.getPrototypeOf(value) === Object.prototype
10
+ }
11
+
12
+ function toComponentName(hint: string, fallbackIndex: number): string {
13
+ const cleaned = hint
14
+ .split(/[^a-zA-Z0-9]+/)
15
+ .filter(Boolean)
16
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
17
+ .join('')
18
+ return cleaned.length ? cleaned : `Schema${fallbackIndex}`
19
+ }
20
+
21
+ const SCHEMA_KEYS = new Set([
22
+ 'type',
23
+ 'properties',
24
+ 'items',
25
+ 'anyOf',
26
+ 'oneOf',
27
+ 'allOf',
28
+ '$ref',
29
+ 'enum',
30
+ 'format',
31
+ 'not',
32
+ 'additionalProperties',
33
+ 'required',
34
+ 'pattern',
35
+ 'minimum',
36
+ 'maximum',
37
+ ])
38
+
39
+ function looksLikeSchema(value: Record<string, unknown>): boolean {
40
+ if (typeof value.$ref === 'string') return true
41
+ for (const key of Object.keys(value)) {
42
+ if (SCHEMA_KEYS.has(key)) return true
43
+ }
44
+ return false
45
+ }
46
+
47
+ export function sanitizeOpenApiDocument<T extends Record<string, any>>(doc: T): T {
48
+ const target = doc as T & MutableOpenApiDoc
49
+ const schemaNameMap = new Map<object, string>()
50
+ const schemas: Record<string, JsonValue> = target.components?.schemas ?? {}
51
+ if (!target.components) target.components = {}
52
+ target.components.schemas = schemas
53
+ let counter = Object.keys(schemas).length
54
+
55
+ const cloneContainer = (
56
+ source: Record<string, any>,
57
+ hint: string,
58
+ mode?: 'properties' | 'patternProperties'
59
+ ): JsonValue => {
60
+ const clone: Record<string, JsonValue> = {}
61
+ for (const [key, value] of Object.entries(source)) {
62
+ const forceSchema = mode === 'properties' || mode === 'patternProperties'
63
+ clone[key] = cloneSchemaValue(value, `${hint}_${key}`, key, forceSchema)
64
+ }
65
+ return clone
66
+ }
67
+
68
+ const serializeSchema = (schema: Record<string, any>, hint: string): JsonValue => {
69
+ if (schemaNameMap.has(schema)) {
70
+ const name = schemaNameMap.get(schema)!
71
+ return { $ref: `#/components/schemas/${name}` }
72
+ }
73
+ counter += 1
74
+ const tentativeName = toComponentName(hint, counter)
75
+ let name = tentativeName
76
+ let suffix = 1
77
+ while (schemas[name]) {
78
+ name = `${tentativeName}${suffix++}`
79
+ }
80
+ schemaNameMap.set(schema, name)
81
+ schemas[name] = cloneContainer(schema, hint)
82
+ return { $ref: `#/components/schemas/${name}` }
83
+ }
84
+
85
+ const cloneSchemaValue = (
86
+ value: any,
87
+ hint: string,
88
+ parentKey?: string,
89
+ forceSchema = false
90
+ ): JsonValue => {
91
+ if (Array.isArray(value)) {
92
+ return value.map((entry, idx) => cloneSchemaValue(entry, `${hint}_${idx}`, parentKey, forceSchema)) as JsonValue
93
+ }
94
+ if (isPlainObject(value)) {
95
+ if (typeof value.$ref === 'string') return value
96
+ if (parentKey === 'properties' || parentKey === 'patternProperties') {
97
+ return cloneContainer(value, hint, parentKey)
98
+ }
99
+ if (parentKey === 'additionalProperties' && Object.keys(value).length) {
100
+ return serializeSchema(value, hint)
101
+ }
102
+ if (forceSchema || looksLikeSchema(value)) {
103
+ return serializeSchema(value, hint)
104
+ }
105
+ return cloneContainer(value, hint)
106
+ }
107
+ // Convert BigInt to string for JSON serialization
108
+ if (typeof value === 'bigint') {
109
+ return value.toString()
110
+ }
111
+ return value as JsonValue
112
+ }
113
+
114
+ const traverse = (value: any, hint: string): any => {
115
+ if (Array.isArray(value)) {
116
+ return value.map((entry, idx) => traverse(entry, `${hint}_${idx}`))
117
+ }
118
+ if (!isPlainObject(value)) {
119
+ // Convert BigInt to string for JSON serialization
120
+ if (typeof value === 'bigint') {
121
+ return value.toString()
122
+ }
123
+ return value
124
+ }
125
+ const result: Record<string, unknown> = { ...value }
126
+ for (const [key, child] of Object.entries(result)) {
127
+ if (key === 'schema' && child && typeof child === 'object') {
128
+ result[key] = cloneSchemaValue(child, `${hint}_${key}`, key)
129
+ } else {
130
+ result[key] = traverse(child, `${hint}_${key}`)
131
+ }
132
+ }
133
+ return result
134
+ }
135
+
136
+ return traverse(target, 'doc') as T
137
+ }
@@ -0,0 +1,79 @@
1
+ import type { ZodTypeAny } from 'zod'
2
+
3
+ export type OpenApiHttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
4
+
5
+ export type OpenApiSecurityScheme = 'bearerAuth'
6
+
7
+ export type OpenApiCodeSample = {
8
+ lang: string
9
+ label?: string
10
+ source: string
11
+ }
12
+
13
+ export type OpenApiResponseDoc = {
14
+ status: number
15
+ description?: string
16
+ schema?: ZodTypeAny
17
+ example?: unknown
18
+ mediaType?: string
19
+ xAutoGenerated?: boolean
20
+ }
21
+
22
+ export type OpenApiRequestBodyDoc = {
23
+ contentType?: string
24
+ schema: ZodTypeAny
25
+ example?: unknown
26
+ description?: string
27
+ }
28
+
29
+ export type OpenApiMethodDoc = {
30
+ operationId?: string
31
+ summary?: string
32
+ description?: string
33
+ tags?: string[]
34
+ query?: ZodTypeAny
35
+ headers?: ZodTypeAny
36
+ pathParams?: ZodTypeAny
37
+ requestBody?: OpenApiRequestBodyDoc
38
+ responses?: OpenApiResponseDoc[]
39
+ errors?: OpenApiResponseDoc[]
40
+ deprecated?: boolean
41
+ security?: OpenApiSecurityScheme[]
42
+ codeSamples?: OpenApiCodeSample[]
43
+ externalDocs?: { url: string; description?: string }
44
+ extensions?: Record<string, unknown>
45
+ }
46
+
47
+ export type OpenApiRouteDoc = {
48
+ tag?: string
49
+ summary?: string
50
+ description?: string
51
+ pathParams?: ZodTypeAny
52
+ methods: Partial<Record<OpenApiHttpMethod, OpenApiMethodDoc>>
53
+ extensions?: Record<string, unknown>
54
+ }
55
+
56
+ export type OpenApiDocumentOptions = {
57
+ title?: string
58
+ version?: string
59
+ description?: string
60
+ servers?: Array<{ url: string; description?: string }>
61
+ baseUrlForExamples?: string
62
+ defaultSecurity?: OpenApiSecurityScheme[]
63
+ }
64
+
65
+ export type OpenApiDocument = {
66
+ openapi: '3.1.0'
67
+ info: {
68
+ title: string
69
+ version: string
70
+ description?: string
71
+ }
72
+ servers?: Array<{ url: string; description?: string }>
73
+ tags?: Array<{ name: string; description?: string }>
74
+ paths: Record<string, Record<string, any>>
75
+ components?: {
76
+ schemas?: Record<string, any>
77
+ securitySchemes?: Record<string, any>
78
+ }
79
+ }
@@ -0,0 +1,371 @@
1
+ import { parseBooleanToken } from '../boolean'
2
+
3
+ const ROUND_PRECISION = 1000
4
+ const NS_PER_MS = BigInt(1_000_000)
5
+ const BIGINT_ZERO = BigInt(0)
6
+
7
+ export type ProfilerExtra = Record<string, unknown>
8
+
9
+ export type ProfilerSnapshotNode = {
10
+ label: string
11
+ durationMs: number
12
+ extra?: ProfilerExtra
13
+ children?: ProfilerSnapshotNode[]
14
+ type?: 'mark' | 'span' | 'record'
15
+ }
16
+
17
+ export type ProfilerSnapshot = {
18
+ scope: string
19
+ target?: string
20
+ totalMs: number
21
+ tree: ProfilerSnapshotNode[]
22
+ meta?: ProfilerExtra
23
+ }
24
+
25
+ export type ProfilerSection = {
26
+ end: (extra?: ProfilerExtra) => void
27
+ }
28
+
29
+ export type Profiler = {
30
+ readonly enabled: boolean
31
+ mark: (label: string, extra?: ProfilerExtra) => void
32
+ section: (label: string, extra?: ProfilerExtra) => ProfilerSection
33
+ measure: <T>(
34
+ label: string,
35
+ fn: () => Promise<T> | T,
36
+ extra?: ((result: T) => ProfilerExtra | undefined) | ProfilerExtra
37
+ ) => Promise<T>
38
+ record: (label: string, durationMs: number, extra?: ProfilerExtra) => void
39
+ child: (label: string, extra?: ProfilerExtra) => Profiler
40
+ end: (extra?: ProfilerExtra) => void
41
+ }
42
+
43
+ type ProfilerNodeCommon = {
44
+ label: string
45
+ start: bigint
46
+ durationNs: bigint
47
+ extra?: ProfilerExtra
48
+ type: 'mark' | 'span' | 'record'
49
+ }
50
+
51
+ type ProfilerSpanNode = ProfilerNodeCommon & {
52
+ type: 'span'
53
+ children: ProfilerInternalNode[]
54
+ lastTimestamp: bigint
55
+ closed: boolean
56
+ }
57
+
58
+ type ProfilerMarkNode = ProfilerNodeCommon & { type: 'mark' }
59
+ type ProfilerRecordNode = ProfilerNodeCommon & { type: 'record' }
60
+
61
+ type ProfilerInternalNode = ProfilerSpanNode | ProfilerMarkNode | ProfilerRecordNode
62
+
63
+ type ProfilerRootState = {
64
+ scope: string
65
+ target?: string
66
+ label: string
67
+ loggerLabel: string
68
+ enabled: boolean
69
+ startedAt: bigint
70
+ lastTimestamp: bigint
71
+ nodes: ProfilerInternalNode[]
72
+ closed: boolean
73
+ meta?: ProfilerExtra
74
+ }
75
+
76
+ type ProfilerImplOptions = {
77
+ root: ProfilerRootState
78
+ span?: ProfilerSpanNode
79
+ parent?: ProfilerImpl
80
+ }
81
+
82
+ function now(): bigint {
83
+ return process.hrtime.bigint()
84
+ }
85
+
86
+ function roundDuration(ns: bigint): number {
87
+ if (ns <= BIGINT_ZERO) return 0
88
+ const ms = Number(ns) / Number(NS_PER_MS)
89
+ return Math.round(ms * ROUND_PRECISION) / ROUND_PRECISION
90
+ }
91
+
92
+ function mergeExtra(base: ProfilerExtra | undefined, extra: ProfilerExtra | undefined): ProfilerExtra | undefined {
93
+ if (!base && !extra) return undefined
94
+ if (!base) return extra ? { ...extra } : undefined
95
+ if (!extra) return base ? { ...base } : undefined
96
+ return { ...base, ...extra }
97
+ }
98
+
99
+ function serializeNode(node: ProfilerInternalNode): ProfilerSnapshotNode {
100
+ const base: ProfilerSnapshotNode = {
101
+ label: node.label,
102
+ durationMs: roundDuration(node.durationNs),
103
+ }
104
+ if (node.extra && Object.keys(node.extra).length > 0) {
105
+ base.extra = node.extra
106
+ }
107
+ base.type = node.type
108
+ if (node.type === 'span' && node.children.length > 0) {
109
+ base.children = node.children.map(serializeNode)
110
+ }
111
+ return base
112
+ }
113
+
114
+ class ProfilerImpl implements Profiler {
115
+ private span?: ProfilerSpanNode
116
+ private parent?: ProfilerImpl
117
+ private root: ProfilerRootState
118
+ private lastTimestamp: bigint
119
+ private closed = false
120
+
121
+ constructor(options: ProfilerImplOptions) {
122
+ this.root = options.root
123
+ this.span = options.span
124
+ this.parent = options.parent
125
+ this.lastTimestamp = options.span ? options.span.lastTimestamp : options.root.lastTimestamp
126
+ }
127
+
128
+ get enabled(): boolean {
129
+ return this.root.enabled
130
+ }
131
+
132
+ mark(label: string, extra?: ProfilerExtra) {
133
+ if (!this.enabled || this.closed) return
134
+ const timestamp = now()
135
+ const duration = timestamp - this.lastTimestamp
136
+ const node: ProfilerMarkNode = {
137
+ type: 'mark',
138
+ label,
139
+ start: timestamp,
140
+ durationNs: duration >= BIGINT_ZERO ? duration : BIGINT_ZERO,
141
+ extra: extra ? { ...extra } : undefined,
142
+ }
143
+ this.appendNode(node)
144
+ this.updateTimestamps(timestamp)
145
+ }
146
+
147
+ section(label: string, extra?: ProfilerExtra): ProfilerSection {
148
+ if (!this.enabled || this.closed) {
149
+ return disabledSection
150
+ }
151
+ const child = this.child(label, extra)
152
+ return {
153
+ end: (childExtra?: ProfilerExtra) => child.end(childExtra),
154
+ }
155
+ }
156
+
157
+ async measure<T>(
158
+ label: string,
159
+ fn: () => Promise<T> | T,
160
+ extra?: ((result: T) => ProfilerExtra | undefined) | ProfilerExtra
161
+ ): Promise<T> {
162
+ if (!this.enabled || this.closed) {
163
+ return Promise.resolve(fn())
164
+ }
165
+ const child = this.child(label)
166
+ try {
167
+ const result = await Promise.resolve(fn())
168
+ const payload = typeof extra === 'function' ? extra(result) : extra
169
+ child.end(payload)
170
+ return result
171
+ } catch (err) {
172
+ child.end({ error: err instanceof Error ? err.message : String(err) })
173
+ throw err
174
+ }
175
+ }
176
+
177
+ record(label: string, durationMs: number, extra?: ProfilerExtra) {
178
+ if (!this.enabled || this.closed) return
179
+ const safeDurationMs = Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 0
180
+ const durationNs = BigInt(Math.round(safeDurationMs * Number(NS_PER_MS)))
181
+ const node: ProfilerRecordNode = {
182
+ type: 'record',
183
+ label,
184
+ start: this.lastTimestamp,
185
+ durationNs: durationNs >= BIGINT_ZERO ? durationNs : BIGINT_ZERO,
186
+ extra: extra ? { ...extra } : undefined,
187
+ }
188
+ this.appendNode(node)
189
+ this.updateTimestamps(this.lastTimestamp + node.durationNs)
190
+ }
191
+
192
+ child(label: string, extra?: ProfilerExtra): Profiler {
193
+ if (!this.enabled || this.closed) return disabledProfiler
194
+ const timestamp = now()
195
+ const span: ProfilerSpanNode = {
196
+ type: 'span',
197
+ label,
198
+ start: timestamp,
199
+ durationNs: BIGINT_ZERO,
200
+ extra: extra ? { ...extra } : undefined,
201
+ children: [],
202
+ lastTimestamp: timestamp,
203
+ closed: false,
204
+ }
205
+ this.appendNode(span)
206
+ this.updateTimestamps(timestamp)
207
+ return new ProfilerImpl({ root: this.root, span, parent: this })
208
+ }
209
+
210
+ end(extra?: ProfilerExtra) {
211
+ if (!this.enabled || this.closed) return
212
+ const current = now()
213
+ if (this.span) {
214
+ this.closeSpan(current, extra)
215
+ } else {
216
+ this.closeRoot(current, extra)
217
+ }
218
+ }
219
+
220
+ private appendNode(node: ProfilerInternalNode) {
221
+ if (this.span) {
222
+ this.span.children.push(node)
223
+ this.span.lastTimestamp = node.start
224
+ } else {
225
+ this.root.nodes.push(node)
226
+ }
227
+ }
228
+
229
+ private updateTimestamps(newTimestamp: bigint) {
230
+ if (this.span) {
231
+ this.span.lastTimestamp = newTimestamp
232
+ } else {
233
+ this.root.lastTimestamp = newTimestamp
234
+ }
235
+ this.lastTimestamp = newTimestamp
236
+ }
237
+
238
+ private closeSpan(closedAt: bigint, extra?: ProfilerExtra) {
239
+ if (!this.span || this.span.closed) return
240
+ const effectiveEnd = closedAt >= this.span.start ? closedAt : this.span.start
241
+ this.span.durationNs = effectiveEnd - this.span.start
242
+ this.span.extra = mergeExtra(this.span.extra, extra)
243
+ this.span.closed = true
244
+ this.closed = true
245
+ this.updateTimestamps(effectiveEnd)
246
+ if (this.parent) {
247
+ this.parent.updateTimestamps(effectiveEnd)
248
+ } else {
249
+ this.root.lastTimestamp = effectiveEnd
250
+ }
251
+ }
252
+
253
+ private closeRoot(closedAt: bigint, extra?: ProfilerExtra) {
254
+ if (this.root.closed) return
255
+ const endTime = closedAt >= this.root.startedAt ? closedAt : this.root.startedAt
256
+ this.root.lastTimestamp = endTime
257
+ this.root.meta = mergeExtra(this.root.meta, extra)
258
+ this.root.closed = true
259
+ this.closed = true
260
+ const totalNs = endTime - this.root.startedAt
261
+ const snapshot: ProfilerSnapshot = {
262
+ scope: this.root.scope,
263
+ target: this.root.target,
264
+ totalMs: roundDuration(totalNs),
265
+ tree: this.root.nodes.map(serializeNode),
266
+ }
267
+ if (this.root.meta && Object.keys(this.root.meta).length > 0) {
268
+ snapshot.meta = this.root.meta
269
+ }
270
+ const serialized = JSON.stringify(snapshot, null, 2)
271
+ try {
272
+ console.info(this.root.loggerLabel, serialized)
273
+ } catch {
274
+ // ignore logging failures
275
+ }
276
+ }
277
+ }
278
+
279
+ const disabledSection: ProfilerSection = { end: () => {} }
280
+
281
+ const disabledProfiler: Profiler = {
282
+ get enabled() {
283
+ return false
284
+ },
285
+ mark: () => {},
286
+ section: () => disabledSection,
287
+ measure: async (_label, fn) => Promise.resolve(fn()),
288
+ record: () => {},
289
+ child: () => disabledProfiler,
290
+ end: () => {},
291
+ }
292
+
293
+ export type CreateProfilerOptions = {
294
+ scope: string
295
+ target?: string
296
+ label?: string
297
+ enabled?: boolean
298
+ loggerLabel?: string
299
+ meta?: ProfilerExtra
300
+ }
301
+
302
+ export function createProfiler(options: CreateProfilerOptions): Profiler {
303
+ if (options.enabled === false) return disabledProfiler
304
+ const startedAt = now()
305
+ const enabledFlag = options.enabled !== undefined ? options.enabled : true
306
+ const root: ProfilerRootState = {
307
+ scope: options.scope,
308
+ target: options.target,
309
+ label: options.label ?? options.scope,
310
+ loggerLabel: options.loggerLabel ?? '[profile]',
311
+ enabled: enabledFlag,
312
+ startedAt,
313
+ lastTimestamp: startedAt,
314
+ nodes: [],
315
+ closed: false,
316
+ meta: options.meta ? { ...options.meta } : undefined,
317
+ }
318
+ return new ProfilerImpl({ root })
319
+ }
320
+
321
+ export function normalizeProfilerTokens(input: string | null | undefined): string[] {
322
+ if (!input) return []
323
+ return input
324
+ .split(',')
325
+ .map((token) => token.trim().toLowerCase())
326
+ .filter((token) => token.length > 0)
327
+ }
328
+
329
+ export function profilerMatches(target: string, tokens: string[]): boolean {
330
+ if (!tokens.length) return false
331
+ const lower = target.toLowerCase()
332
+ return tokens.some((token) => {
333
+ if (token === '*' || token === 'all' || parseBooleanToken(token) === true) return true
334
+ if (token.endsWith('*')) {
335
+ const prefix = token.slice(0, -1)
336
+ return prefix.length === 0 ? true : lower.startsWith(prefix)
337
+ }
338
+ return token === lower
339
+ })
340
+ }
341
+
342
+ const DEFAULT_ENV_KEYS = [
343
+ 'OM_PROFILE',
344
+ 'NEXT_PUBLIC_OM_PROFILE',
345
+ 'OM_CRUD_PROFILE',
346
+ 'NEXT_PUBLIC_OM_CRUD_PROFILE',
347
+ 'OM_QE_PROFILE',
348
+ 'NEXT_PUBLIC_OM_QE_PROFILE',
349
+ ] as const
350
+
351
+ export function resolveProfilerTokens(envKeys: readonly string[] = DEFAULT_ENV_KEYS): string[] {
352
+ const tokens: string[] = []
353
+ envKeys.forEach((key) => {
354
+ const raw = process.env[key]
355
+ if (!raw) return
356
+ const parsed = normalizeProfilerTokens(raw)
357
+ if (parsed.length) tokens.push(...parsed)
358
+ })
359
+ return Array.from(new Set(tokens))
360
+ }
361
+
362
+ export function shouldEnableProfiler(
363
+ target: string,
364
+ options?: { tokens?: string[]; envKeys?: readonly string[] }
365
+ ): boolean {
366
+ const tokens = options?.tokens ?? resolveProfilerTokens(options?.envKeys)
367
+ if (!tokens.length) return false
368
+ return profilerMatches(target, tokens)
369
+ }
370
+
371
+ export const disabledProfilerInstance = disabledProfiler