@happyvertical/smrt-core 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (631) hide show
  1. package/AGENTS.md +124 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +265 -0
  5. package/bin/smrt-prebuild.js +26 -0
  6. package/dist/__tests__/fixtures/advisor-test-classes.d.ts +28 -0
  7. package/dist/__tests__/fixtures/advisor-test-classes.d.ts.map +1 -0
  8. package/dist/__tests__/fixtures/collection-coverage-fixtures.d.ts +12 -0
  9. package/dist/__tests__/fixtures/collection-coverage-fixtures.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/inheritance-resolver-fixtures.d.ts +28 -0
  11. package/dist/__tests__/fixtures/inheritance-resolver-fixtures.d.ts.map +1 -0
  12. package/dist/__tests__/fixtures/inheritance-test-classes.d.ts +43 -0
  13. package/dist/__tests__/fixtures/inheritance-test-classes.d.ts.map +1 -0
  14. package/dist/__tests__/fixtures/mcp-integration-test-classes.d.ts +18 -0
  15. package/dist/__tests__/fixtures/mcp-integration-test-classes.d.ts.map +1 -0
  16. package/dist/__tests__/fixtures/object-ai-memory-fixtures.d.ts +15 -0
  17. package/dist/__tests__/fixtures/object-ai-memory-fixtures.d.ts.map +1 -0
  18. package/dist/__tests__/fixtures/object-spec-test-classes.d.ts +13 -0
  19. package/dist/__tests__/fixtures/object-spec-test-classes.d.ts.map +1 -0
  20. package/dist/__tests__/fixtures/registry-test-classes.d.ts +23 -0
  21. package/dist/__tests__/fixtures/registry-test-classes.d.ts.map +1 -0
  22. package/dist/adapters/ai-usage.d.ts +23 -0
  23. package/dist/adapters/ai-usage.d.ts.map +1 -0
  24. package/dist/adapters/ai-usage.js +105 -0
  25. package/dist/adapters/ai-usage.js.map +1 -0
  26. package/dist/adapters/cost-rates.d.ts +20 -0
  27. package/dist/adapters/cost-rates.d.ts.map +1 -0
  28. package/dist/adapters/cost-rates.js +40 -0
  29. package/dist/adapters/cost-rates.js.map +1 -0
  30. package/dist/adapters/index.d.ts +19 -0
  31. package/dist/adapters/index.d.ts.map +1 -0
  32. package/dist/adapters/metrics.d.ts +111 -0
  33. package/dist/adapters/metrics.d.ts.map +1 -0
  34. package/dist/adapters/metrics.js +169 -0
  35. package/dist/adapters/metrics.js.map +1 -0
  36. package/dist/adapters/pubsub.d.ts +124 -0
  37. package/dist/adapters/pubsub.d.ts.map +1 -0
  38. package/dist/adapters/pubsub.js +121 -0
  39. package/dist/adapters/pubsub.js.map +1 -0
  40. package/dist/browser.d.ts +32 -0
  41. package/dist/browser.d.ts.map +1 -0
  42. package/dist/browser.js +68 -0
  43. package/dist/browser.js.map +1 -0
  44. package/dist/child-accessors.d.ts +27 -0
  45. package/dist/child-accessors.d.ts.map +1 -0
  46. package/dist/child-accessors.js +35 -0
  47. package/dist/child-accessors.js.map +1 -0
  48. package/dist/class.d.ts +313 -0
  49. package/dist/class.d.ts.map +1 -0
  50. package/dist/class.js +896 -0
  51. package/dist/class.js.map +1 -0
  52. package/dist/collection-cache.d.ts +110 -0
  53. package/dist/collection-cache.d.ts.map +1 -0
  54. package/dist/collection-cache.js +187 -0
  55. package/dist/collection-cache.js.map +1 -0
  56. package/dist/collection.d.ts +894 -0
  57. package/dist/collection.d.ts.map +1 -0
  58. package/dist/collection.js +1987 -0
  59. package/dist/collection.js.map +1 -0
  60. package/dist/config/global-config.d.ts +3 -0
  61. package/dist/config/global-config.d.ts.map +1 -0
  62. package/dist/config/global-config.js +19 -0
  63. package/dist/config/global-config.js.map +1 -0
  64. package/dist/config.d.ts +145 -0
  65. package/dist/config.d.ts.map +1 -0
  66. package/dist/config.js +57 -0
  67. package/dist/config.js.map +1 -0
  68. package/dist/consumer-plugin/index.d.ts +22 -0
  69. package/dist/consumer-plugin/index.d.ts.map +1 -0
  70. package/dist/consumer-plugin/index.js +452 -0
  71. package/dist/consumer-plugin/index.js.map +1 -0
  72. package/dist/consumer-plugin.d.ts +8 -0
  73. package/dist/consumer-plugin.d.ts.map +1 -0
  74. package/dist/consumer-plugin.js +5 -0
  75. package/dist/consumer-plugin.js.map +1 -0
  76. package/dist/database.d.ts +95 -0
  77. package/dist/database.d.ts.map +1 -0
  78. package/dist/database.js +32 -0
  79. package/dist/database.js.map +1 -0
  80. package/dist/decorators/compatibility.d.ts +14 -0
  81. package/dist/decorators/compatibility.d.ts.map +1 -0
  82. package/dist/decorators/compatibility.js +111 -0
  83. package/dist/decorators/compatibility.js.map +1 -0
  84. package/dist/decorators/index.d.ts +381 -0
  85. package/dist/decorators/index.d.ts.map +1 -0
  86. package/dist/decorators/index.js +104 -0
  87. package/dist/decorators/index.js.map +1 -0
  88. package/dist/dispatch/bus.d.ts +306 -0
  89. package/dist/dispatch/bus.d.ts.map +1 -0
  90. package/dist/dispatch/bus.js +583 -0
  91. package/dist/dispatch/bus.js.map +1 -0
  92. package/dist/dispatch/collections/DispatchSubscriptions.d.ts +79 -0
  93. package/dist/dispatch/collections/DispatchSubscriptions.d.ts.map +1 -0
  94. package/dist/dispatch/collections/DispatchSubscriptions.js +243 -0
  95. package/dist/dispatch/collections/DispatchSubscriptions.js.map +1 -0
  96. package/dist/dispatch/collections/Dispatches.d.ts +98 -0
  97. package/dist/dispatch/collections/Dispatches.d.ts.map +1 -0
  98. package/dist/dispatch/collections/Dispatches.js +358 -0
  99. package/dist/dispatch/collections/Dispatches.js.map +1 -0
  100. package/dist/dispatch/index.d.ts +47 -0
  101. package/dist/dispatch/index.d.ts.map +1 -0
  102. package/dist/dispatch/models/Dispatch.d.ts +101 -0
  103. package/dist/dispatch/models/Dispatch.d.ts.map +1 -0
  104. package/dist/dispatch/models/Dispatch.js +162 -0
  105. package/dist/dispatch/models/Dispatch.js.map +1 -0
  106. package/dist/dispatch/models/DispatchSubscription.d.ts +83 -0
  107. package/dist/dispatch/models/DispatchSubscription.d.ts.map +1 -0
  108. package/dist/dispatch/models/DispatchSubscription.js +112 -0
  109. package/dist/dispatch/models/DispatchSubscription.js.map +1 -0
  110. package/dist/dispatch/tenant-resolver.d.ts +98 -0
  111. package/dist/dispatch/tenant-resolver.d.ts.map +1 -0
  112. package/dist/dispatch/tenant-resolver.js +32 -0
  113. package/dist/dispatch/tenant-resolver.js.map +1 -0
  114. package/dist/dispatch/types.d.ts +149 -0
  115. package/dist/dispatch/types.d.ts.map +1 -0
  116. package/dist/embeddings/hash.d.ts +33 -0
  117. package/dist/embeddings/hash.d.ts.map +1 -0
  118. package/dist/embeddings/hash.js +37 -0
  119. package/dist/embeddings/hash.js.map +1 -0
  120. package/dist/embeddings/index.d.ts +36 -0
  121. package/dist/embeddings/index.d.ts.map +1 -0
  122. package/dist/embeddings/provider.d.ts +75 -0
  123. package/dist/embeddings/provider.d.ts.map +1 -0
  124. package/dist/embeddings/provider.js +170 -0
  125. package/dist/embeddings/provider.js.map +1 -0
  126. package/dist/embeddings/similarity.d.ts +47 -0
  127. package/dist/embeddings/similarity.d.ts.map +1 -0
  128. package/dist/embeddings/similarity.js +64 -0
  129. package/dist/embeddings/similarity.js.map +1 -0
  130. package/dist/embeddings/storage.d.ts +125 -0
  131. package/dist/embeddings/storage.d.ts.map +1 -0
  132. package/dist/embeddings/storage.js +283 -0
  133. package/dist/embeddings/storage.js.map +1 -0
  134. package/dist/embeddings/types.d.ts +250 -0
  135. package/dist/embeddings/types.d.ts.map +1 -0
  136. package/dist/errors.d.ts +363 -0
  137. package/dist/errors.d.ts.map +1 -0
  138. package/dist/errors.js +669 -0
  139. package/dist/errors.js.map +1 -0
  140. package/dist/generators/cli.d.ts +162 -0
  141. package/dist/generators/cli.d.ts.map +1 -0
  142. package/dist/generators/cli.js +462 -0
  143. package/dist/generators/cli.js.map +1 -0
  144. package/dist/generators/index.d.ts +13 -0
  145. package/dist/generators/index.d.ts.map +1 -0
  146. package/dist/generators/mcp-runtime-template.d.ts +60 -0
  147. package/dist/generators/mcp-runtime-template.d.ts.map +1 -0
  148. package/dist/generators/mcp-runtime-template.js +509 -0
  149. package/dist/generators/mcp-runtime-template.js.map +1 -0
  150. package/dist/generators/mcp.d.ts +231 -0
  151. package/dist/generators/mcp.d.ts.map +1 -0
  152. package/dist/generators/mcp.js +1220 -0
  153. package/dist/generators/mcp.js.map +1 -0
  154. package/dist/generators/rest.d.ts +171 -0
  155. package/dist/generators/rest.d.ts.map +1 -0
  156. package/dist/generators/rest.js +591 -0
  157. package/dist/generators/rest.js.map +1 -0
  158. package/dist/generators/swagger.d.ts +21 -0
  159. package/dist/generators/swagger.d.ts.map +1 -0
  160. package/dist/generators/swagger.js +307 -0
  161. package/dist/generators/swagger.js.map +1 -0
  162. package/dist/generators/tenant-gate.d.ts +74 -0
  163. package/dist/generators/tenant-gate.d.ts.map +1 -0
  164. package/dist/generators/tenant-gate.js +15 -0
  165. package/dist/generators/tenant-gate.js.map +1 -0
  166. package/dist/generators.d.ts +8 -0
  167. package/dist/generators.d.ts.map +1 -0
  168. package/dist/generators.js +19 -0
  169. package/dist/generators.js.map +1 -0
  170. package/dist/hierarchical.d.ts +103 -0
  171. package/dist/hierarchical.d.ts.map +1 -0
  172. package/dist/hierarchical.js +184 -0
  173. package/dist/hierarchical.js.map +1 -0
  174. package/dist/index.d.ts +57 -0
  175. package/dist/index.d.ts.map +1 -0
  176. package/dist/index.js +202 -0
  177. package/dist/index.js.map +1 -0
  178. package/dist/interceptors.d.ts +251 -0
  179. package/dist/interceptors.d.ts.map +1 -0
  180. package/dist/interceptors.js +259 -0
  181. package/dist/interceptors.js.map +1 -0
  182. package/dist/junction.d.ts +99 -0
  183. package/dist/junction.d.ts.map +1 -0
  184. package/dist/junction.js +136 -0
  185. package/dist/junction.js.map +1 -0
  186. package/dist/knowledge.d.ts +11 -0
  187. package/dist/knowledge.d.ts.map +1 -0
  188. package/dist/knowledge.js +310 -0
  189. package/dist/knowledge.js.map +1 -0
  190. package/dist/lazy-config.d.ts +160 -0
  191. package/dist/lazy-config.d.ts.map +1 -0
  192. package/dist/lazy-config.js +146 -0
  193. package/dist/lazy-config.js.map +1 -0
  194. package/dist/manifest/discover-base-classes.d.ts +78 -0
  195. package/dist/manifest/discover-base-classes.d.ts.map +1 -0
  196. package/dist/manifest/discover-base-classes.js +85 -0
  197. package/dist/manifest/discover-base-classes.js.map +1 -0
  198. package/dist/manifest/discover-smrt-packages.d.ts +48 -0
  199. package/dist/manifest/discover-smrt-packages.d.ts.map +1 -0
  200. package/dist/manifest/discover-smrt-packages.js +361 -0
  201. package/dist/manifest/discover-smrt-packages.js.map +1 -0
  202. package/dist/manifest/generator.d.ts +93 -0
  203. package/dist/manifest/generator.d.ts.map +1 -0
  204. package/dist/manifest/generator.js +380 -0
  205. package/dist/manifest/generator.js.map +1 -0
  206. package/dist/manifest/index.d.ts +16 -0
  207. package/dist/manifest/index.d.ts.map +1 -0
  208. package/dist/manifest/index.js +51 -0
  209. package/dist/manifest/index.js.map +1 -0
  210. package/dist/manifest/manager.d.ts +51 -0
  211. package/dist/manifest/manager.d.ts.map +1 -0
  212. package/dist/manifest/manager.js +89 -0
  213. package/dist/manifest/manager.js.map +1 -0
  214. package/dist/manifest/manifest-loader.d.ts +187 -0
  215. package/dist/manifest/manifest-loader.d.ts.map +1 -0
  216. package/dist/manifest/manifest-loader.js +847 -0
  217. package/dist/manifest/manifest-loader.js.map +1 -0
  218. package/dist/manifest/sources/composite.d.ts +22 -0
  219. package/dist/manifest/sources/composite.d.ts.map +1 -0
  220. package/dist/manifest/sources/composite.js +60 -0
  221. package/dist/manifest/sources/composite.js.map +1 -0
  222. package/dist/manifest/sources/embedded.d.ts +7 -0
  223. package/dist/manifest/sources/embedded.d.ts.map +1 -0
  224. package/dist/manifest/sources/embedded.js +30 -0
  225. package/dist/manifest/sources/embedded.js.map +1 -0
  226. package/dist/manifest/sources/explicit-paths.d.ts +17 -0
  227. package/dist/manifest/sources/explicit-paths.d.ts.map +1 -0
  228. package/dist/manifest/sources/explicit-paths.js +35 -0
  229. package/dist/manifest/sources/explicit-paths.js.map +1 -0
  230. package/dist/manifest/sources/fallback.d.ts +25 -0
  231. package/dist/manifest/sources/fallback.d.ts.map +1 -0
  232. package/dist/manifest/sources/fallback.js +63 -0
  233. package/dist/manifest/sources/fallback.js.map +1 -0
  234. package/dist/manifest/sources/index.d.ts +17 -0
  235. package/dist/manifest/sources/index.d.ts.map +1 -0
  236. package/dist/manifest/sources/local-test.d.ts +7 -0
  237. package/dist/manifest/sources/local-test.d.ts.map +1 -0
  238. package/dist/manifest/sources/local-test.js +21 -0
  239. package/dist/manifest/sources/local-test.js.map +1 -0
  240. package/dist/manifest/sources/static.d.ts +7 -0
  241. package/dist/manifest/sources/static.d.ts.map +1 -0
  242. package/dist/manifest/sources/static.js +19 -0
  243. package/dist/manifest/sources/static.js.map +1 -0
  244. package/dist/manifest/sources/test.d.ts +7 -0
  245. package/dist/manifest/sources/test.d.ts.map +1 -0
  246. package/dist/manifest/sources/test.js +21 -0
  247. package/dist/manifest/sources/test.js.map +1 -0
  248. package/dist/manifest/sources/types.d.ts +79 -0
  249. package/dist/manifest/sources/types.d.ts.map +1 -0
  250. package/dist/manifest/sources/types.js +61 -0
  251. package/dist/manifest/sources/types.js.map +1 -0
  252. package/dist/manifest/static-manifest.d.ts +4 -0
  253. package/dist/manifest/static-manifest.d.ts.map +1 -0
  254. package/dist/manifest/static-manifest.js +1535 -0
  255. package/dist/manifest/static-manifest.js.map +1 -0
  256. package/dist/manifest/store.d.ts +111 -0
  257. package/dist/manifest/store.d.ts.map +1 -0
  258. package/dist/manifest/store.js +431 -0
  259. package/dist/manifest/store.js.map +1 -0
  260. package/dist/manifest/test-manifest-loader.d.ts +3 -0
  261. package/dist/manifest/test-manifest-loader.d.ts.map +1 -0
  262. package/dist/manifest/test-manifest-stub.d.ts +4 -0
  263. package/dist/manifest/test-manifest-stub.d.ts.map +1 -0
  264. package/dist/manifest/test-manifest-stub.js +80013 -0
  265. package/dist/manifest/test-manifest-stub.js.map +1 -0
  266. package/dist/manifest.d.ts +8 -0
  267. package/dist/manifest.d.ts.map +1 -0
  268. package/dist/manifest.js +20 -0
  269. package/dist/manifest.js.map +1 -0
  270. package/dist/manifest.json +1489 -0
  271. package/dist/mcp-advisor/index.d.ts +499 -0
  272. package/dist/mcp-advisor/index.d.ts.map +1 -0
  273. package/dist/mcp-advisor/tools/add-ai-methods.d.ts +6 -0
  274. package/dist/mcp-advisor/tools/add-ai-methods.d.ts.map +1 -0
  275. package/dist/mcp-advisor/tools/configure-decorators.d.ts +6 -0
  276. package/dist/mcp-advisor/tools/configure-decorators.d.ts.map +1 -0
  277. package/dist/mcp-advisor/tools/generate-collection.d.ts +6 -0
  278. package/dist/mcp-advisor/tools/generate-collection.d.ts.map +1 -0
  279. package/dist/mcp-advisor/tools/generate-field-definitions.d.ts +6 -0
  280. package/dist/mcp-advisor/tools/generate-field-definitions.d.ts.map +1 -0
  281. package/dist/mcp-advisor/tools/generate-smrt-class.d.ts +6 -0
  282. package/dist/mcp-advisor/tools/generate-smrt-class.d.ts.map +1 -0
  283. package/dist/mcp-advisor/tools/get-object-config.d.ts +6 -0
  284. package/dist/mcp-advisor/tools/get-object-config.d.ts.map +1 -0
  285. package/dist/mcp-advisor/tools/get-object-schema.d.ts +10 -0
  286. package/dist/mcp-advisor/tools/get-object-schema.d.ts.map +1 -0
  287. package/dist/mcp-advisor/tools/list-registered-objects.d.ts +9 -0
  288. package/dist/mcp-advisor/tools/list-registered-objects.d.ts.map +1 -0
  289. package/dist/mcp-advisor/tools/preview-api-endpoints.d.ts +9 -0
  290. package/dist/mcp-advisor/tools/preview-api-endpoints.d.ts.map +1 -0
  291. package/dist/mcp-advisor/tools/preview-mcp-tools.d.ts +9 -0
  292. package/dist/mcp-advisor/tools/preview-mcp-tools.d.ts.map +1 -0
  293. package/dist/mcp-advisor/tools/validate-smrt-object.d.ts +6 -0
  294. package/dist/mcp-advisor/tools/validate-smrt-object.d.ts.map +1 -0
  295. package/dist/mcp-advisor/types.d.ts +209 -0
  296. package/dist/mcp-advisor/types.d.ts.map +1 -0
  297. package/dist/migrations/backfill-tracker.d.ts +84 -0
  298. package/dist/migrations/backfill-tracker.d.ts.map +1 -0
  299. package/dist/migrations/backfill-tracker.js +118 -0
  300. package/dist/migrations/backfill-tracker.js.map +1 -0
  301. package/dist/migrations/checksum.d.ts +43 -0
  302. package/dist/migrations/checksum.d.ts.map +1 -0
  303. package/dist/migrations/checksum.js +32 -0
  304. package/dist/migrations/checksum.js.map +1 -0
  305. package/dist/migrations/differ.d.ts +186 -0
  306. package/dist/migrations/differ.d.ts.map +1 -0
  307. package/dist/migrations/differ.js +601 -0
  308. package/dist/migrations/differ.js.map +1 -0
  309. package/dist/migrations/generator.d.ts +133 -0
  310. package/dist/migrations/generator.d.ts.map +1 -0
  311. package/dist/migrations/generator.js +328 -0
  312. package/dist/migrations/generator.js.map +1 -0
  313. package/dist/migrations/index.d.ts +20 -0
  314. package/dist/migrations/index.d.ts.map +1 -0
  315. package/dist/migrations/orchestrate.d.ts +148 -0
  316. package/dist/migrations/orchestrate.d.ts.map +1 -0
  317. package/dist/migrations/orchestrate.js +118 -0
  318. package/dist/migrations/orchestrate.js.map +1 -0
  319. package/dist/migrations/tracker.d.ts +134 -0
  320. package/dist/migrations/tracker.d.ts.map +1 -0
  321. package/dist/migrations/tracker.js +624 -0
  322. package/dist/migrations/tracker.js.map +1 -0
  323. package/dist/migrations/types.d.ts +221 -0
  324. package/dist/migrations/types.d.ts.map +1 -0
  325. package/dist/migrations.d.ts +7 -0
  326. package/dist/migrations.d.ts.map +1 -0
  327. package/dist/migrations.js +26 -0
  328. package/dist/migrations.js.map +1 -0
  329. package/dist/node_modules/.pnpm/balanced-match@4.0.4/node_modules/balanced-match/dist/esm/index.js +56 -0
  330. package/dist/node_modules/.pnpm/balanced-match@4.0.4/node_modules/balanced-match/dist/esm/index.js.map +1 -0
  331. package/dist/node_modules/.pnpm/brace-expansion@5.0.5/node_modules/brace-expansion/dist/esm/index.js +163 -0
  332. package/dist/node_modules/.pnpm/brace-expansion@5.0.5/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
  333. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/assert-valid-pattern.js +13 -0
  334. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -0
  335. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/ast.js +654 -0
  336. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/ast.js.map +1 -0
  337. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/brace-expressions.js +111 -0
  338. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -0
  339. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/escape.js +10 -0
  340. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/escape.js.map +1 -0
  341. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/index.js +824 -0
  342. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/index.js.map +1 -0
  343. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/unescape.js +10 -0
  344. package/dist/node_modules/.pnpm/minimatch@10.2.3/node_modules/minimatch/dist/esm/unescape.js.map +1 -0
  345. package/dist/object.d.ts +1202 -0
  346. package/dist/object.d.ts.map +1 -0
  347. package/dist/object.js +2731 -0
  348. package/dist/object.js.map +1 -0
  349. package/dist/polymorphic-association.d.ts +69 -0
  350. package/dist/polymorphic-association.d.ts.map +1 -0
  351. package/dist/polymorphic-association.js +96 -0
  352. package/dist/polymorphic-association.js.map +1 -0
  353. package/dist/prebuild/cli.d.ts +7 -0
  354. package/dist/prebuild/cli.d.ts.map +1 -0
  355. package/dist/prebuild/cli.js +29 -0
  356. package/dist/prebuild/cli.js.map +1 -0
  357. package/dist/prebuild/index.d.ts +22 -0
  358. package/dist/prebuild/index.d.ts.map +1 -0
  359. package/dist/prebuild/index.js +239 -0
  360. package/dist/prebuild/index.js.map +1 -0
  361. package/dist/prebuild.d.ts +8 -0
  362. package/dist/prebuild.d.ts.map +1 -0
  363. package/dist/prebuild.js +6 -0
  364. package/dist/prebuild.js.map +1 -0
  365. package/dist/registry/cache-config.d.ts +13 -0
  366. package/dist/registry/cache-config.d.ts.map +1 -0
  367. package/dist/registry/cache-config.js +17 -0
  368. package/dist/registry/cache-config.js.map +1 -0
  369. package/dist/registry/class-registration.d.ts +31 -0
  370. package/dist/registry/class-registration.d.ts.map +1 -0
  371. package/dist/registry/class-registration.js +1074 -0
  372. package/dist/registry/class-registration.js.map +1 -0
  373. package/dist/registry/collection-resolution.d.ts +45 -0
  374. package/dist/registry/collection-resolution.d.ts.map +1 -0
  375. package/dist/registry/collection-resolution.js +121 -0
  376. package/dist/registry/collection-resolution.js.map +1 -0
  377. package/dist/registry/collision-policy.d.ts +179 -0
  378. package/dist/registry/collision-policy.d.ts.map +1 -0
  379. package/dist/registry/collision-policy.js +153 -0
  380. package/dist/registry/collision-policy.js.map +1 -0
  381. package/dist/registry/diagnostics.d.ts +58 -0
  382. package/dist/registry/diagnostics.d.ts.map +1 -0
  383. package/dist/registry/diagnostics.js +54 -0
  384. package/dist/registry/diagnostics.js.map +1 -0
  385. package/dist/registry/embedding-manager.d.ts +23 -0
  386. package/dist/registry/embedding-manager.d.ts.map +1 -0
  387. package/dist/registry/embedding-manager.js +62 -0
  388. package/dist/registry/embedding-manager.js.map +1 -0
  389. package/dist/registry/index.d.ts +13 -0
  390. package/dist/registry/index.d.ts.map +1 -0
  391. package/dist/registry/inheritance-resolver.d.ts +13 -0
  392. package/dist/registry/inheritance-resolver.d.ts.map +1 -0
  393. package/dist/registry/inheritance-resolver.js +244 -0
  394. package/dist/registry/inheritance-resolver.js.map +1 -0
  395. package/dist/registry/manifest-field-merge.d.ts +4 -0
  396. package/dist/registry/manifest-field-merge.d.ts.map +1 -0
  397. package/dist/registry/manifest-field-merge.js +82 -0
  398. package/dist/registry/manifest-field-merge.js.map +1 -0
  399. package/dist/registry/name-resolver.d.ts +102 -0
  400. package/dist/registry/name-resolver.d.ts.map +1 -0
  401. package/dist/registry/name-resolver.js +241 -0
  402. package/dist/registry/name-resolver.js.map +1 -0
  403. package/dist/registry/relationship-graph.d.ts +16 -0
  404. package/dist/registry/relationship-graph.d.ts.map +1 -0
  405. package/dist/registry/relationship-graph.js +79 -0
  406. package/dist/registry/relationship-graph.js.map +1 -0
  407. package/dist/registry/schema-builder.d.ts +113 -0
  408. package/dist/registry/schema-builder.d.ts.map +1 -0
  409. package/dist/registry/schema-builder.js +474 -0
  410. package/dist/registry/schema-builder.js.map +1 -0
  411. package/dist/registry/shared-state.d.ts +62 -0
  412. package/dist/registry/shared-state.d.ts.map +1 -0
  413. package/dist/registry/shared-state.js +135 -0
  414. package/dist/registry/shared-state.js.map +1 -0
  415. package/dist/registry/types.d.ts +667 -0
  416. package/dist/registry/types.d.ts.map +1 -0
  417. package/dist/registry/validator.d.ts +13 -0
  418. package/dist/registry/validator.d.ts.map +1 -0
  419. package/dist/registry/validator.js +138 -0
  420. package/dist/registry/validator.js.map +1 -0
  421. package/dist/registry.d.ts +1358 -0
  422. package/dist/registry.d.ts.map +1 -0
  423. package/dist/registry.js +2301 -0
  424. package/dist/registry.js.map +1 -0
  425. package/dist/runtime/client.d.ts +34 -0
  426. package/dist/runtime/client.d.ts.map +1 -0
  427. package/dist/runtime/client.js +104 -0
  428. package/dist/runtime/client.js.map +1 -0
  429. package/dist/runtime/index.d.ts +10 -0
  430. package/dist/runtime/index.d.ts.map +1 -0
  431. package/dist/runtime/mcp.d.ts +47 -0
  432. package/dist/runtime/mcp.d.ts.map +1 -0
  433. package/dist/runtime/mcp.js +72 -0
  434. package/dist/runtime/mcp.js.map +1 -0
  435. package/dist/runtime/server.d.ts +92 -0
  436. package/dist/runtime/server.d.ts.map +1 -0
  437. package/dist/runtime/server.js +390 -0
  438. package/dist/runtime/server.js.map +1 -0
  439. package/dist/runtime/types.d.ts +58 -0
  440. package/dist/runtime/types.d.ts.map +1 -0
  441. package/dist/runtime.d.ts +8 -0
  442. package/dist/runtime.d.ts.map +1 -0
  443. package/dist/runtime.js +10 -0
  444. package/dist/runtime.js.map +1 -0
  445. package/dist/scanner/import-scanner.d.ts +7 -0
  446. package/dist/scanner/import-scanner.d.ts.map +1 -0
  447. package/dist/scanner/index.d.ts +12 -0
  448. package/dist/scanner/index.d.ts.map +1 -0
  449. package/dist/scanner/manifest-generator.d.ts +304 -0
  450. package/dist/scanner/manifest-generator.d.ts.map +1 -0
  451. package/dist/scanner/manifest-generator.js +1707 -0
  452. package/dist/scanner/manifest-generator.js.map +1 -0
  453. package/dist/scanner/test-file-patterns.d.ts +18 -0
  454. package/dist/scanner/test-file-patterns.d.ts.map +1 -0
  455. package/dist/scanner/test-file-patterns.js +16 -0
  456. package/dist/scanner/test-file-patterns.js.map +1 -0
  457. package/dist/scanner/types.d.ts +313 -0
  458. package/dist/scanner/types.d.ts.map +1 -0
  459. package/dist/scanner/types.js +2 -0
  460. package/dist/scanner/types.js.map +1 -0
  461. package/dist/scanner.d.ts +6 -0
  462. package/dist/scanner.d.ts.map +1 -0
  463. package/dist/scanner.js +6 -0
  464. package/dist/scanner.js.map +1 -0
  465. package/dist/schema/code-generator.d.ts +53 -0
  466. package/dist/schema/code-generator.d.ts.map +1 -0
  467. package/dist/schema/ddl/base-strategy.d.ts +80 -0
  468. package/dist/schema/ddl/base-strategy.d.ts.map +1 -0
  469. package/dist/schema/ddl/base-strategy.js +240 -0
  470. package/dist/schema/ddl/base-strategy.js.map +1 -0
  471. package/dist/schema/ddl/duckdb-strategy.d.ts +33 -0
  472. package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -0
  473. package/dist/schema/ddl/duckdb-strategy.js +74 -0
  474. package/dist/schema/ddl/duckdb-strategy.js.map +1 -0
  475. package/dist/schema/ddl/index.d.ts +53 -0
  476. package/dist/schema/ddl/index.d.ts.map +1 -0
  477. package/dist/schema/ddl/index.js +80 -0
  478. package/dist/schema/ddl/index.js.map +1 -0
  479. package/dist/schema/ddl/json-duckdb-strategy.d.ts +8 -0
  480. package/dist/schema/ddl/json-duckdb-strategy.d.ts.map +1 -0
  481. package/dist/schema/ddl/json-duckdb-strategy.js +14 -0
  482. package/dist/schema/ddl/json-duckdb-strategy.js.map +1 -0
  483. package/dist/schema/ddl/postgres-strategy.d.ts +29 -0
  484. package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -0
  485. package/dist/schema/ddl/postgres-strategy.js +102 -0
  486. package/dist/schema/ddl/postgres-strategy.js.map +1 -0
  487. package/dist/schema/ddl/sqlite-strategy.d.ts +38 -0
  488. package/dist/schema/ddl/sqlite-strategy.d.ts.map +1 -0
  489. package/dist/schema/ddl/sqlite-strategy.js +74 -0
  490. package/dist/schema/ddl/sqlite-strategy.js.map +1 -0
  491. package/dist/schema/ddl/types.d.ts +114 -0
  492. package/dist/schema/ddl/types.d.ts.map +1 -0
  493. package/dist/schema/generator.d.ts +176 -0
  494. package/dist/schema/generator.d.ts.map +1 -0
  495. package/dist/schema/generator.js +1076 -0
  496. package/dist/schema/generator.js.map +1 -0
  497. package/dist/schema/index-utils.d.ts +19 -0
  498. package/dist/schema/index-utils.d.ts.map +1 -0
  499. package/dist/schema/index-utils.js +32 -0
  500. package/dist/schema/index-utils.js.map +1 -0
  501. package/dist/schema/index.d.ts +13 -0
  502. package/dist/schema/index.d.ts.map +1 -0
  503. package/dist/schema/override-system.d.ts +43 -0
  504. package/dist/schema/override-system.d.ts.map +1 -0
  505. package/dist/schema/schema-aggregator.d.ts +112 -0
  506. package/dist/schema/schema-aggregator.d.ts.map +1 -0
  507. package/dist/schema/schema-manager.d.ts +95 -0
  508. package/dist/schema/schema-manager.d.ts.map +1 -0
  509. package/dist/schema/schema-manager.js +283 -0
  510. package/dist/schema/schema-manager.js.map +1 -0
  511. package/dist/schema/sql-identifiers.d.ts +107 -0
  512. package/dist/schema/sql-identifiers.d.ts.map +1 -0
  513. package/dist/schema/sql-identifiers.js +190 -0
  514. package/dist/schema/sql-identifiers.js.map +1 -0
  515. package/dist/schema/table-verifier.d.ts +10 -0
  516. package/dist/schema/table-verifier.d.ts.map +1 -0
  517. package/dist/schema/table-verifier.js +37 -0
  518. package/dist/schema/table-verifier.js.map +1 -0
  519. package/dist/schema/types.d.ts +241 -0
  520. package/dist/schema/types.d.ts.map +1 -0
  521. package/dist/schema/utils.d.ts +32 -0
  522. package/dist/schema/utils.d.ts.map +1 -0
  523. package/dist/schema/utils.js +134 -0
  524. package/dist/schema/utils.js.map +1 -0
  525. package/dist/scripts/create-wrappers.js +89 -0
  526. package/dist/scripts/generate-manifest.js +155 -0
  527. package/dist/scripts/generate-test-manifest.js +77 -0
  528. package/dist/scripts/migrate-datetime-to-timestamp.ts +310 -0
  529. package/dist/scripts/prepack.js +49 -0
  530. package/dist/signals/bus.d.ts +64 -0
  531. package/dist/signals/bus.d.ts.map +1 -0
  532. package/dist/signals/bus.js +102 -0
  533. package/dist/signals/bus.js.map +1 -0
  534. package/dist/signals/index.d.ts +11 -0
  535. package/dist/signals/index.d.ts.map +1 -0
  536. package/dist/signals/sanitizer.d.ts +54 -0
  537. package/dist/signals/sanitizer.d.ts.map +1 -0
  538. package/dist/signals/sanitizer.js +111 -0
  539. package/dist/signals/sanitizer.js.map +1 -0
  540. package/dist/smrt-knowledge.json +335 -0
  541. package/dist/system/compatibility.d.ts +8 -0
  542. package/dist/system/compatibility.d.ts.map +1 -0
  543. package/dist/system/compatibility.js +409 -0
  544. package/dist/system/compatibility.js.map +1 -0
  545. package/dist/system/index.d.ts +9 -0
  546. package/dist/system/index.d.ts.map +1 -0
  547. package/dist/system/schema.d.ts +69 -0
  548. package/dist/system/schema.d.ts.map +1 -0
  549. package/dist/system/schema.js +271 -0
  550. package/dist/system/schema.js.map +1 -0
  551. package/dist/system/types.d.ts +135 -0
  552. package/dist/system/types.d.ts.map +1 -0
  553. package/dist/system-fields.d.ts +44 -0
  554. package/dist/system-fields.d.ts.map +1 -0
  555. package/dist/system-fields.js +55 -0
  556. package/dist/system-fields.js.map +1 -0
  557. package/dist/table-cache.d.ts +28 -0
  558. package/dist/table-cache.d.ts.map +1 -0
  559. package/dist/table-cache.js +21 -0
  560. package/dist/table-cache.js.map +1 -0
  561. package/dist/test-utils.d.ts +140 -0
  562. package/dist/test-utils.d.ts.map +1 -0
  563. package/dist/testing/database.d.ts +73 -0
  564. package/dist/testing/database.d.ts.map +1 -0
  565. package/dist/testing/database.js +204 -0
  566. package/dist/testing/database.js.map +1 -0
  567. package/dist/testing/index.d.ts +21 -0
  568. package/dist/testing/index.d.ts.map +1 -0
  569. package/dist/testing.d.ts +6 -0
  570. package/dist/testing.d.ts.map +1 -0
  571. package/dist/testing.js +5 -0
  572. package/dist/testing.js.map +1 -0
  573. package/dist/tools/index.d.ts +8 -0
  574. package/dist/tools/index.d.ts.map +1 -0
  575. package/dist/tools/tool-executor.d.ts +101 -0
  576. package/dist/tools/tool-executor.d.ts.map +1 -0
  577. package/dist/tools/tool-executor.js +142 -0
  578. package/dist/tools/tool-executor.js.map +1 -0
  579. package/dist/tools/tool-generator.d.ts +54 -0
  580. package/dist/tools/tool-generator.d.ts.map +1 -0
  581. package/dist/tools/tool-generator.js +121 -0
  582. package/dist/tools/tool-generator.js.map +1 -0
  583. package/dist/utils/chunk.d.ts +32 -0
  584. package/dist/utils/chunk.d.ts.map +1 -0
  585. package/dist/utils/chunk.js +14 -0
  586. package/dist/utils/chunk.js.map +1 -0
  587. package/dist/utils/import-workspace-module.d.ts +8 -0
  588. package/dist/utils/import-workspace-module.d.ts.map +1 -0
  589. package/dist/utils/import-workspace-module.js +81 -0
  590. package/dist/utils/import-workspace-module.js.map +1 -0
  591. package/dist/utils/json.d.ts +102 -0
  592. package/dist/utils/json.d.ts.map +1 -0
  593. package/dist/utils/json.js +43 -0
  594. package/dist/utils/json.js.map +1 -0
  595. package/dist/utils/lru-cache.d.ts +69 -0
  596. package/dist/utils/lru-cache.d.ts.map +1 -0
  597. package/dist/utils/lru-cache.js +100 -0
  598. package/dist/utils/lru-cache.js.map +1 -0
  599. package/dist/utils/naming.d.ts +16 -0
  600. package/dist/utils/naming.d.ts.map +1 -0
  601. package/dist/utils/naming.js +23 -0
  602. package/dist/utils/naming.js.map +1 -0
  603. package/dist/utils/qualified-names.d.ts +122 -0
  604. package/dist/utils/qualified-names.d.ts.map +1 -0
  605. package/dist/utils/qualified-names.js +82 -0
  606. package/dist/utils/qualified-names.js.map +1 -0
  607. package/dist/utils/scanner-module.d.ts +37 -0
  608. package/dist/utils/scanner-module.d.ts.map +1 -0
  609. package/dist/utils.d.ts +177 -0
  610. package/dist/utils.d.ts.map +1 -0
  611. package/dist/utils.js +185 -0
  612. package/dist/utils.js.map +1 -0
  613. package/dist/vite-plugin/import-build-aware.d.ts +68 -0
  614. package/dist/vite-plugin/import-build-aware.d.ts.map +1 -0
  615. package/dist/vite-plugin/import-build-aware.js +72 -0
  616. package/dist/vite-plugin/import-build-aware.js.map +1 -0
  617. package/dist/vite-plugin/index.d.ts +59 -0
  618. package/dist/vite-plugin/index.d.ts.map +1 -0
  619. package/dist/vite-plugin/index.js +1400 -0
  620. package/dist/vite-plugin/index.js.map +1 -0
  621. package/dist/vite-plugin/sveltekit-generator.d.ts +66 -0
  622. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -0
  623. package/dist/vite-plugin/sveltekit-generator.js +1375 -0
  624. package/dist/vite-plugin/sveltekit-generator.js.map +1 -0
  625. package/dist/vite-plugin/templates/default-ui.ts +432 -0
  626. package/dist/vite-plugin/templates/default.html +206 -0
  627. package/dist/vite-plugin.d.ts +8 -0
  628. package/dist/vite-plugin.d.ts.map +1 -0
  629. package/dist/vite-plugin.js +11 -0
  630. package/dist/vite-plugin.js.map +1 -0
  631. package/package.json +208 -0
@@ -0,0 +1,1987 @@
1
+ import { createLogger } from "@happyvertical/logger";
2
+ import { buildWhere } from "@happyvertical/sql";
3
+ import { SmrtClass } from "./class.js";
4
+ import { resolveDbCacheKey, buildQueryCacheKey, ensureCacheInvalidationListener, registerCrossProcessCacheInterest, getCachedRows, getCacheGeneration, setCachedRows, invalidateCollectionCache } from "./collection-cache.js";
5
+ import { EmbeddingProvider } from "./embeddings/provider.js";
6
+ import { EmbeddingStorage } from "./embeddings/storage.js";
7
+ import { createInterceptorContext, GlobalInterceptors } from "./interceptors.js";
8
+ import { ObjectRegistry } from "./registry.js";
9
+ import { verifyPersistenceTable } from "./schema/table-verifier.js";
10
+ import { fieldsFromClass, toCamelCase, formatDataJs } from "./utils.js";
11
+ import { chunkArray, IN_LIST_CHUNK_SIZE } from "./utils/chunk.js";
12
+ import { toSnakeCase, classnameToTablename } from "./utils/naming.js";
13
+ const logger = createLogger({ level: "info" });
14
+ function resolveMetaTypeInWhere(where) {
15
+ if (!where?._meta_type || typeof where._meta_type !== "string") {
16
+ return where;
17
+ }
18
+ const metaTypeValue = where._meta_type;
19
+ if (metaTypeValue.includes(":")) {
20
+ return where;
21
+ }
22
+ const registeredClass = ObjectRegistry.getClass(metaTypeValue);
23
+ if (registeredClass?.qualifiedName) {
24
+ return {
25
+ ...where,
26
+ _meta_type: registeredClass.qualifiedName
27
+ };
28
+ }
29
+ return where;
30
+ }
31
+ class SmrtCollection extends SmrtClass {
32
+ /**
33
+ * Cached fields for sync access during queries.
34
+ * Populated during create() to avoid async getFields() calls on every query.
35
+ * @private
36
+ */
37
+ _cachedFields = null;
38
+ getRegisteredItemClass() {
39
+ return ObjectRegistry.getClassByConstructor(this._itemClass) || ObjectRegistry.getClass(this._itemClass.name);
40
+ }
41
+ getResolvedItemClassName() {
42
+ return this.getRegisteredItemClass()?.name || this._itemClass.name;
43
+ }
44
+ getResolvedItemQualifiedName() {
45
+ const registered = this.getRegisteredItemClass();
46
+ return registered?.qualifiedName || registered?.name || this._itemClass.name;
47
+ }
48
+ /**
49
+ * Convert WHERE clause field names from camelCase to snake_case while preserving operators.
50
+ * Validates operators and field names to prevent SQL injection and invalid queries.
51
+ *
52
+ * Uses cached fields for sync access (issue #663) to avoid async overhead on every query.
53
+ *
54
+ * @param where - WHERE clause object with camelCase field names
55
+ * @returns WHERE clause object with snake_case field names
56
+ * @private
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // Input: { 'typeId': 'foo', 'categoryId >': 100 }
61
+ * // Output: { 'type_id': 'foo', 'category_id >': 100 }
62
+ * ```
63
+ */
64
+ convertWhereKeys(where) {
65
+ const VALID_OPERATORS = [
66
+ "=",
67
+ ">",
68
+ "<",
69
+ ">=",
70
+ "<=",
71
+ "!=",
72
+ "in",
73
+ "not in",
74
+ "like",
75
+ "contains"
76
+ ];
77
+ const fields = this.getFieldsSync();
78
+ const validFieldNames = new Set(
79
+ Object.keys(fields).map((f) => toSnakeCase(f))
80
+ );
81
+ const sensitiveFieldNames = /* @__PURE__ */ new Set();
82
+ const collectSensitive = (fieldMap) => {
83
+ const entries = fieldMap instanceof Map ? fieldMap.entries() : Object.entries(fieldMap);
84
+ for (const [fieldName, def] of entries) {
85
+ if (def && (def.sensitive === true || def._meta?.sensitive === true)) {
86
+ sensitiveFieldNames.add(toSnakeCase(fieldName));
87
+ sensitiveFieldNames.add(fieldName);
88
+ }
89
+ }
90
+ };
91
+ collectSensitive(fields);
92
+ validFieldNames.add("id");
93
+ validFieldNames.add("slug");
94
+ validFieldNames.add("context");
95
+ validFieldNames.add("created_at");
96
+ validFieldNames.add("updated_at");
97
+ const itemClassName = this.getResolvedItemClassName();
98
+ const itemQualifiedName = this.getResolvedItemQualifiedName();
99
+ const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);
100
+ if (tableStrategy === "sti") {
101
+ validFieldNames.add("_meta_type");
102
+ validFieldNames.add("meta_type");
103
+ validFieldNames.add("_meta_data");
104
+ validFieldNames.add("meta_data");
105
+ const inheritanceChain = ObjectRegistry.getInheritanceChain(itemQualifiedName);
106
+ for (const ancestorName of inheritanceChain) {
107
+ if (ancestorName === "SmrtObject" || ancestorName === "SmrtClass" || ancestorName === itemQualifiedName || ancestorName === itemClassName) {
108
+ continue;
109
+ }
110
+ const ancestorFields = ObjectRegistry.getFields(ancestorName);
111
+ for (const fieldName of ancestorFields.keys()) {
112
+ validFieldNames.add(toSnakeCase(fieldName));
113
+ }
114
+ collectSensitive(ancestorFields);
115
+ }
116
+ const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);
117
+ if (stiBase) {
118
+ for (const descendant of ObjectRegistry.getDescendants(stiBase)) {
119
+ collectSensitive(ObjectRegistry.getFields(descendant));
120
+ }
121
+ }
122
+ }
123
+ const hasRegisteredFields = Object.keys(fields).length > 0;
124
+ const skipFieldValidation = !hasRegisteredFields;
125
+ const converted = {};
126
+ if (Object.hasOwn(where, "__proto__") || Object.hasOwn(where, "constructor") || Object.hasOwn(where, "prototype")) {
127
+ throw new Error(
128
+ `Invalid WHERE clause: Prototype pollution attempts are not allowed. Detected dangerous properties in WHERE clause.`
129
+ );
130
+ }
131
+ for (const [key, value] of Object.entries(where)) {
132
+ const parts = key.trim().split(/\s+/);
133
+ const fieldName = parts[0];
134
+ const operator = parts.slice(1).join(" ") || "=";
135
+ if (fieldName === "__proto__" || fieldName === "constructor" || fieldName === "prototype" || fieldName.includes("__proto__") || fieldName.includes("constructor") || fieldName.includes("prototype")) {
136
+ throw new Error(
137
+ `Invalid WHERE clause field: '${fieldName}'. Prototype pollution attempts are not allowed.`
138
+ );
139
+ }
140
+ const dotIndex = fieldName.indexOf(".");
141
+ const baseFieldName = dotIndex >= 0 ? fieldName.substring(0, dotIndex) : fieldName;
142
+ const jsonPath = dotIndex >= 0 ? fieldName.substring(dotIndex) : null;
143
+ const snakeBaseFieldName = baseFieldName.startsWith("_") ? `_${toSnakeCase(baseFieldName.slice(1))}` : toSnakeCase(baseFieldName);
144
+ const snakeFieldName = jsonPath ? `${snakeBaseFieldName}${jsonPath}` : snakeBaseFieldName;
145
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)*$/.test(snakeFieldName)) {
146
+ throw new Error(
147
+ `Invalid WHERE clause field: '${fieldName}'. Field names must be identifiers (letters, digits, underscore) with optional dot-separated JSON-path segments.`
148
+ );
149
+ }
150
+ const baseIsMetaData = snakeBaseFieldName === "_meta_data" || snakeBaseFieldName === "meta_data";
151
+ const metaPathTargetsSensitive = baseIsMetaData && !!jsonPath && jsonPath.split(".").filter(Boolean).some(
152
+ (segment) => sensitiveFieldNames.has(segment) || sensitiveFieldNames.has(toSnakeCase(segment))
153
+ );
154
+ if (sensitiveFieldNames.has(snakeBaseFieldName) || metaPathTargetsSensitive) {
155
+ throw new Error(
156
+ `Invalid WHERE clause field: '${fieldName}'. Filtering on sensitive fields is not allowed.`
157
+ );
158
+ }
159
+ const effectiveOperator = operator === "=" && Array.isArray(value) ? "in" : operator;
160
+ if (!VALID_OPERATORS.includes(effectiveOperator)) {
161
+ throw new Error(
162
+ `Invalid WHERE clause operator: '${operator}'. Valid operators: ${VALID_OPERATORS.join(", ")}`
163
+ );
164
+ }
165
+ if (!skipFieldValidation && !validFieldNames.has(snakeBaseFieldName)) {
166
+ throw new Error(
167
+ `Invalid WHERE clause field: '${fieldName}'. Field does not exist on ${itemClassName}. Valid fields: ${Array.from(validFieldNames).sort().join(", ")}`
168
+ );
169
+ }
170
+ if ((effectiveOperator === "in" || effectiveOperator === "not in") && !Array.isArray(value)) {
171
+ throw new Error(
172
+ `WHERE clause operator '${effectiveOperator}' requires an array value for field '${fieldName}', got ${typeof value}`
173
+ );
174
+ }
175
+ if ((effectiveOperator === "in" || effectiveOperator === "not in") && Array.isArray(value) && value.length === 0) {
176
+ throw new Error(
177
+ `WHERE clause operator '${effectiveOperator}' requires a non-empty array for field '${fieldName}'. Use listByIds([]) for graceful empty array handling.`
178
+ );
179
+ }
180
+ if (effectiveOperator === "like" && typeof value !== "string") {
181
+ throw new Error(
182
+ `WHERE clause operator 'like' requires a string value for field '${fieldName}', got ${typeof value}`
183
+ );
184
+ }
185
+ const newKey = effectiveOperator === "=" ? snakeFieldName : `${snakeFieldName} ${effectiveOperator}`;
186
+ converted[newKey] = value;
187
+ }
188
+ return converted;
189
+ }
190
+ /**
191
+ * Gets the class constructor for items in this collection
192
+ */
193
+ get _itemClass() {
194
+ const ctor = this.constructor;
195
+ if (!ctor._itemClass) {
196
+ const className = this.constructor.name;
197
+ const errorMessage = [
198
+ `Collection "${className}" must define a static _itemClass property.`,
199
+ "",
200
+ "Example:",
201
+ ` class ${className} extends SmrtCollection<YourItemClass> {`,
202
+ " static readonly _itemClass = YourItemClass;",
203
+ " }",
204
+ "",
205
+ "Make sure your item class is imported and defined before the collection class."
206
+ ].join("\n");
207
+ throw new Error(errorMessage);
208
+ }
209
+ return ctor._itemClass;
210
+ }
211
+ /**
212
+ * Static reference to the item class constructor
213
+ */
214
+ static _itemClass;
215
+ /**
216
+ * Validates that the collection is properly configured
217
+ * Call this during development to catch configuration issues early
218
+ */
219
+ static validate() {
220
+ if (!SmrtCollection._itemClass) {
221
+ const className = SmrtCollection.name;
222
+ const errorMessage = [
223
+ `Collection "${className}" is missing required static _itemClass property.`,
224
+ "",
225
+ "Fix by adding:",
226
+ ` class ${className} extends SmrtCollection<YourItemClass> {`,
227
+ " static readonly _itemClass = YourItemClass;",
228
+ " }"
229
+ ].join("\n");
230
+ throw new Error(errorMessage);
231
+ }
232
+ if (typeof SmrtCollection._itemClass !== "function") {
233
+ throw new Error(
234
+ `Collection "${SmrtCollection.name}"._itemClass must be a constructor function`
235
+ );
236
+ }
237
+ const hasCreateMethod = typeof SmrtCollection._itemClass.create === "function" || typeof SmrtCollection._itemClass.prototype?.create === "function";
238
+ if (!hasCreateMethod) {
239
+ logger.warn(
240
+ `Collection "${SmrtCollection.name}"._itemClass should have a create() method for optimal functionality`
241
+ );
242
+ }
243
+ }
244
+ /**
245
+ * Database table name for this collection
246
+ */
247
+ _tableName;
248
+ /**
249
+ * Creates a new SmrtCollection instance
250
+ *
251
+ * @deprecated Use the static create() factory method instead
252
+ * @param options - Configuration options
253
+ */
254
+ constructor(options = {}) {
255
+ super(options);
256
+ if (this.constructor !== SmrtCollection && this.constructor._itemClass) {
257
+ const itemClass = this.constructor._itemClass;
258
+ const itemClassName = ObjectRegistry.getClassByConstructor(itemClass)?.name || itemClass.name;
259
+ ObjectRegistry.registerCollection(itemClassName, this.constructor);
260
+ }
261
+ }
262
+ /**
263
+ * Factory method — the recommended way to instantiate a collection.
264
+ *
265
+ * Creates the collection instance, calls `initialize()`, and pre-populates
266
+ * the field cache used by synchronous query helpers.
267
+ *
268
+ * Pass the same `options` object you received in a `SmrtObject` constructor
269
+ * or a `SvelteKit` load function to share the database connection.
270
+ *
271
+ * @param options - Database, AI, filesystem, and other configuration options.
272
+ * At minimum, provide `db` (or `persistence`) to connect to a database.
273
+ * @returns A fully initialized, ready-to-query collection instance
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * // Share a DB connection from a SvelteKit load function
278
+ * const products = await Products.create(event.locals.smrtOptions);
279
+ *
280
+ * // Explicit configuration
281
+ * const products = await Products.create({
282
+ * db: myDb,
283
+ * ai: { provider: 'openai', apiKey: process.env.OPENAI_API_KEY },
284
+ * });
285
+ * ```
286
+ */
287
+ static async create(options = {}) {
288
+ const {
289
+ _className,
290
+ db,
291
+ persistence,
292
+ // Also extract persistence alias
293
+ ai,
294
+ fs,
295
+ logging,
296
+ metrics,
297
+ pubsub,
298
+ sanitization,
299
+ signals
300
+ } = options;
301
+ const collectionOptions = {
302
+ _className,
303
+ db,
304
+ persistence,
305
+ // Pass persistence through so initialize() can map it to db
306
+ ai,
307
+ fs,
308
+ logging,
309
+ metrics,
310
+ pubsub,
311
+ sanitization,
312
+ signals
313
+ };
314
+ if (this._isJunctionBase === true) {
315
+ const itemCtor = this._itemClass;
316
+ const itemRegistered = itemCtor && ObjectRegistry.getClassByConstructor(itemCtor);
317
+ if (!itemRegistered) {
318
+ throw new Error(
319
+ `SmrtJunction subclass "${this.name}" has no registered item class. The scanner likely didn't pick up its model — usually because the consuming package's manifest doesn't include "${itemCtor?.name ?? "<unknown>"}". Check that the scanner's FRAMEWORK_BASE_CLASSES (packages/scanner/src/inheritance-resolver.ts) recognizes every framework abstract base in your inheritance chain, that the package has been built (manifest.json present in dist/), and that runtime manifest loading (__smrt-register__.ts) runs before any junction is instantiated.`
320
+ );
321
+ }
322
+ }
323
+ const instance = new this(collectionOptions);
324
+ await instance.initialize();
325
+ const fields = await instance.getFields();
326
+ instance._cachedFields = fields;
327
+ return instance;
328
+ }
329
+ /**
330
+ * Async initialization hook for the collection.
331
+ *
332
+ * Called automatically by the static `create()` factory. In most cases you
333
+ * do not need to call this directly — use `create()` instead.
334
+ *
335
+ * Runtime schema checks are deferred until a query actually touches the
336
+ * backing table. This keeps collection construction safe during SSR and
337
+ * import-time module evaluation without mutating application schema.
338
+ *
339
+ * @returns This instance (enables chaining)
340
+ */
341
+ async initialize() {
342
+ await super.initialize();
343
+ return this;
344
+ }
345
+ /**
346
+ * Verify that the collection's backing table exists before running a query.
347
+ *
348
+ * This is a fail-fast check only. It does not create or alter schema.
349
+ */
350
+ async ensureStorageReady() {
351
+ await verifyPersistenceTable(
352
+ this.db,
353
+ this.tableName,
354
+ this.getResolvedItemClassName()
355
+ );
356
+ }
357
+ /**
358
+ * Find a single record by criteria (convenience method - delegates to get())
359
+ *
360
+ * @param options - Query options with where clause
361
+ * @returns Promise resolving to the object or null if not found
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const councils = await Councils.create({ persistence: { type: 'sql', url: 'db.sqlite' } });
366
+ * const council = await councils.findOne({ where: { name: 'Example Council' } });
367
+ * ```
368
+ */
369
+ async findOne(options) {
370
+ return await this.get(options.where);
371
+ }
372
+ /**
373
+ * Find a record by ID (convenience method - delegates to get())
374
+ *
375
+ * @param id - Record ID to find (string or Field instance)
376
+ * @returns Promise resolving to the object or null if not found
377
+ *
378
+ * @example
379
+ * ```typescript
380
+ * const councils = await Councils.create({ persistence: { type: 'sql', url: 'db.sqlite' } });
381
+ * const council = await councils.findById('uuid-123');
382
+ *
383
+ * // Also works with Field instances (e.g., from foreignKey fields)
384
+ * const meeting = new Meeting({ councilId: 'uuid-123' });
385
+ * const council = await councils.findById(meeting.councilId);
386
+ * ```
387
+ */
388
+ async findById(id) {
389
+ return await this.get(id);
390
+ }
391
+ /**
392
+ * Find all records matching criteria (convenience method - delegates to list())
393
+ *
394
+ * @param options - Query options (where, orderBy, limit, etc.)
395
+ * @returns Promise resolving to array of objects
396
+ *
397
+ * @example
398
+ * ```typescript
399
+ * const councils = await Councils.create({ persistence: { type: 'sql', url: 'db.sqlite' } });
400
+ * const active = await councils.findAll({ where: { status: 'active' } });
401
+ * ```
402
+ */
403
+ async findAll(options = {}) {
404
+ return await this.list(options);
405
+ }
406
+ /**
407
+ * Find multiple objects by their IDs in a single query.
408
+ *
409
+ * This is a convenience method that avoids N+1 queries when you have
410
+ * a list of IDs and need to fetch the corresponding records.
411
+ *
412
+ * @param ids - Array of UUIDs to fetch
413
+ * @returns Promise resolving to array of objects (order not guaranteed)
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * const profiles = await profileCollection.listByIds(['id1', 'id2', 'id3']);
418
+ * ```
419
+ */
420
+ async listByIds(ids) {
421
+ if (ids.length === 0) return [];
422
+ return this.list({ where: { id: ids } });
423
+ }
424
+ /**
425
+ * Resolve the effective cache config for a read (issue #1498).
426
+ *
427
+ * Per-call options win: `false` forces a fresh read, `{ ttl }` enables
428
+ * caching for this call. Otherwise falls back to the model-level
429
+ * `@smrt({ cache })` config (inheritance-aware). Returns undefined when
430
+ * the read should go straight to the database — the default.
431
+ */
432
+ resolveReadCacheConfig(perCall) {
433
+ if (perCall === false) return void 0;
434
+ if (perCall) return perCall.ttl > 0 ? perCall : void 0;
435
+ return ObjectRegistry.resolveCollectionCacheConfig(
436
+ this.getResolvedItemQualifiedName()
437
+ );
438
+ }
439
+ /**
440
+ * Execute a SELECT, optionally through the collection read cache.
441
+ *
442
+ * The cache key is the final SQL + bound parameters — computed after
443
+ * interceptors (tenancy filters) and STI discriminators are applied, so
444
+ * differently-scoped queries can never share an entry. Cached values are
445
+ * raw rows; hydration and read interceptors still run on every call.
446
+ */
447
+ async queryRowsWithCache(sql, params, cacheConfig) {
448
+ if (!cacheConfig) {
449
+ const result2 = await this.db.query(sql, ...params);
450
+ return result2.rows;
451
+ }
452
+ const dbKey = resolveDbCacheKey(this.db);
453
+ const queryKey = buildQueryCacheKey(sql, params);
454
+ if (cacheConfig.crossProcess) {
455
+ ensureCacheInvalidationListener(this.db);
456
+ registerCrossProcessCacheInterest(dbKey, this.tableName);
457
+ }
458
+ const cached = getCachedRows(dbKey, this.tableName, queryKey);
459
+ if (cached) {
460
+ return cached;
461
+ }
462
+ const generation = getCacheGeneration(dbKey, this.tableName);
463
+ const result = await this.db.query(sql, ...params);
464
+ setCachedRows(
465
+ dbKey,
466
+ this.tableName,
467
+ queryKey,
468
+ result.rows,
469
+ cacheConfig.ttl,
470
+ generation
471
+ );
472
+ return result.rows;
473
+ }
474
+ /**
475
+ * Retrieves a single object from the collection by ID, slug, or a custom filter.
476
+ *
477
+ * Filter resolution:
478
+ * - UUID string → `WHERE id = ?`
479
+ * - Non-UUID string → `WHERE slug = ? AND context = ''`
480
+ * - Object → `WHERE <key> = <value> [AND ...]`
481
+ *
482
+ * For STI child collections, an `AND _meta_type = '<qualifiedName>'` clause is
483
+ * automatically appended so you only receive the correct subclass.
484
+ *
485
+ * Runs `beforeGet` / `afterGet` interceptors (used by multi-tenancy, etc.).
486
+ *
487
+ * @param filter - UUID string, slug string, or a WHERE conditions object
488
+ * @param options.cache - Opt-in read-through cache for this call
489
+ * (`{ ttl }` in milliseconds), or `false` to force a fresh read when the
490
+ * model opted in via `@smrt({ cache })`. Defaults to the model config.
491
+ * @returns The matching object instance, or `null` if not found
492
+ *
493
+ * @example
494
+ * ```typescript
495
+ * // By UUID
496
+ * const product = await products.get('550e8400-e29b-41d4-a716-446655440000');
497
+ *
498
+ * // By slug
499
+ * const product = await products.get('my-widget');
500
+ *
501
+ * // By custom filter
502
+ * const product = await products.get({ sku: 'WID-001' });
503
+ *
504
+ * // Cached read (memoized for 60s, invalidated on writes)
505
+ * const product = await products.get('my-widget', { cache: { ttl: 60_000 } });
506
+ * ```
507
+ *
508
+ * @see {@link list} for multiple results
509
+ * @see {@link findById} / {@link findOne} for convenience aliases
510
+ */
511
+ async get(filter, options = {}) {
512
+ await this.ensureStorageReady();
513
+ const itemClassName = this.getResolvedItemClassName();
514
+ const itemQualifiedName = this.getResolvedItemQualifiedName();
515
+ const interceptorContext = createInterceptorContext(
516
+ itemClassName,
517
+ "get",
518
+ this.constructor.name
519
+ );
520
+ const interceptedFilter = await GlobalInterceptors.executeBeforeGet(
521
+ itemClassName,
522
+ filter,
523
+ interceptorContext
524
+ );
525
+ let where = typeof interceptedFilter === "string" ? /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
526
+ interceptedFilter
527
+ ) ? { id: interceptedFilter } : { slug: interceptedFilter, context: "" } : interceptedFilter;
528
+ const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);
529
+ const isSTI = tableStrategy === "sti";
530
+ if (isSTI) {
531
+ const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);
532
+ if (stiBase && stiBase !== itemQualifiedName) {
533
+ where = {
534
+ _meta_type: itemQualifiedName,
535
+ ...where
536
+ };
537
+ }
538
+ }
539
+ const convertedWhere = this.convertWhereKeys(where);
540
+ const { sql: whereSql, values: whereValues } = buildWhere(convertedWhere);
541
+ const fullSQL = `SELECT * FROM ${this.tableName} ${whereSql}`;
542
+ const rows = await this.queryRowsWithCache(
543
+ fullSQL,
544
+ whereValues,
545
+ this.resolveReadCacheConfig(options.cache)
546
+ );
547
+ if (!rows?.[0]) {
548
+ return await GlobalInterceptors.executeAfterGet(
549
+ itemClassName,
550
+ null,
551
+ interceptorContext
552
+ );
553
+ }
554
+ const fields = this.getFieldsSync();
555
+ const instance = await this.hydrateResultRow(rows[0], fields, isSTI);
556
+ return await GlobalInterceptors.executeAfterGet(
557
+ itemClassName,
558
+ instance,
559
+ interceptorContext
560
+ );
561
+ }
562
+ /**
563
+ * Lists records from the collection with flexible filtering options
564
+ *
565
+ * @param options - Query options object
566
+ * @param options.where - Record of conditions to filter results. Each key can include an operator
567
+ * separated by a space (e.g., 'price >', 'name like'). Default operator is '='.
568
+ * @param options.offset - Number of records to skip
569
+ * @param options.limit - Maximum number of records to return
570
+ * @param options.orderBy - Field(s) to order results by, with optional direction
571
+ *
572
+ * @example
573
+ * ```typescript
574
+ * // Find active products priced between $100-$200
575
+ * await collection.list({
576
+ * where: {
577
+ * 'price >': 100,
578
+ * 'price <=': 200,
579
+ * 'status': 'active', // equals operator is default
580
+ * 'category in': ['A', 'B', 'C'], // IN operator for arrays
581
+ * 'name like': '%shirt%', // LIKE for pattern matching
582
+ * 'deleted_at !=': null // exclude deleted items
583
+ * },
584
+ * limit: 10,
585
+ * offset: 0
586
+ * });
587
+ *
588
+ * // Find users matching pattern but not in specific roles
589
+ * await users.list({
590
+ * where: {
591
+ * 'email like': '%@company.com',
592
+ * 'active': true,
593
+ * 'role in': ['guest', 'blocked'],
594
+ * 'last_login <': lastMonth
595
+ * }
596
+ * });
597
+ * ```
598
+ *
599
+ * @returns Promise resolving to an array of model instances
600
+ */
601
+ async list(options = {}) {
602
+ await this.ensureStorageReady();
603
+ const itemClassName = this.getResolvedItemClassName();
604
+ const itemQualifiedName = this.getResolvedItemQualifiedName();
605
+ const interceptorContext = createInterceptorContext(
606
+ itemClassName,
607
+ "list",
608
+ this.constructor.name
609
+ );
610
+ const interceptedOptions = await GlobalInterceptors.executeBeforeList(
611
+ itemClassName,
612
+ options,
613
+ interceptorContext
614
+ ) ?? options ?? {};
615
+ let { where, offset, limit, orderBy } = interceptedOptions;
616
+ const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);
617
+ const isSTI = tableStrategy === "sti";
618
+ if (isSTI) {
619
+ const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);
620
+ if (stiBase && stiBase !== itemQualifiedName) {
621
+ where = {
622
+ _meta_type: itemQualifiedName,
623
+ ...where || {}
624
+ };
625
+ }
626
+ }
627
+ where = resolveMetaTypeInWhere(where);
628
+ const convertedWhere = this.convertWhereKeys(where || {});
629
+ const { sql: whereSql, values: whereValues } = buildWhere(convertedWhere);
630
+ let orderBySql = "";
631
+ if (orderBy) {
632
+ orderBySql = " ORDER BY ";
633
+ const orderByItems = Array.isArray(orderBy) ? orderBy : [orderBy];
634
+ orderBySql += orderByItems.map((item) => {
635
+ const [field, direction = "ASC"] = item.split(" ");
636
+ if (!/^[a-zA-Z0-9_]+$/.test(field)) {
637
+ throw new Error(`Invalid field name for ordering: ${field}`);
638
+ }
639
+ const normalizedDirection = direction.toUpperCase();
640
+ if (normalizedDirection !== "ASC" && normalizedDirection !== "DESC") {
641
+ throw new Error(
642
+ `Invalid sort direction: ${direction}. Must be ASC or DESC.`
643
+ );
644
+ }
645
+ const snakeField = toSnakeCase(field);
646
+ return `${snakeField} ${normalizedDirection}`;
647
+ }).join(", ");
648
+ }
649
+ let limitOffsetSql = "";
650
+ const limitOffsetValues = [];
651
+ let paramIndex = whereValues.length + 1;
652
+ if (limit !== void 0) {
653
+ limitOffsetSql += ` LIMIT $${paramIndex++}`;
654
+ limitOffsetValues.push(limit);
655
+ }
656
+ if (offset !== void 0) {
657
+ limitOffsetSql += ` OFFSET $${paramIndex++}`;
658
+ limitOffsetValues.push(offset);
659
+ }
660
+ const sql = `SELECT * FROM ${this.tableName} ${whereSql} ${orderBySql} ${limitOffsetSql}`;
661
+ const params = [...whereValues, ...limitOffsetValues];
662
+ const rows = await this.queryRowsWithCache(
663
+ sql,
664
+ params,
665
+ this.resolveReadCacheConfig(
666
+ interceptedOptions.cache
667
+ )
668
+ );
669
+ const fields = this.getFieldsSync();
670
+ const instances = await Promise.all(
671
+ rows.map((item) => this.hydrateResultRow(item, fields, isSTI))
672
+ );
673
+ if (interceptedOptions.include && interceptedOptions.include.length > 0) {
674
+ await this.eagerLoadRelationships(
675
+ instances,
676
+ interceptedOptions.include
677
+ );
678
+ }
679
+ const finalInstances = await GlobalInterceptors.executeAfterList(
680
+ itemClassName,
681
+ instances,
682
+ interceptorContext
683
+ );
684
+ return finalInstances;
685
+ }
686
+ /**
687
+ * Eagerly load relationships for a collection of instances
688
+ *
689
+ * Optimizes loading by batching queries for foreignKey relationships to avoid N+1 queries.
690
+ *
691
+ * @param instances - Array of object instances to load relationships for
692
+ * @param relationships - Array of relationship field names to load
693
+ * @private
694
+ */
695
+ async eagerLoadRelationships(instances, relationships) {
696
+ if (instances.length === 0) return;
697
+ for (const fieldName of relationships) {
698
+ const relationshipMeta = ObjectRegistry.getRelationships(
699
+ this._itemClass.name
700
+ );
701
+ const relationship = relationshipMeta.find(
702
+ (r) => r.fieldName === fieldName
703
+ );
704
+ if (!relationship) {
705
+ logger.warn(
706
+ `Relationship ${fieldName} not found on ${this._itemClass.name}, skipping eager load`
707
+ );
708
+ continue;
709
+ }
710
+ if (relationship.type === "foreignKey" || relationship.type === "crossPackageRef") {
711
+ if (relationship.type === "crossPackageRef") {
712
+ await ObjectRegistry.ensureManifestLoaded(relationship.targetClass);
713
+ }
714
+ await this.batchLoadForeignKeys(instances, fieldName, relationship);
715
+ } else if (relationship.type === "oneToMany") {
716
+ await this.batchLoadOneToMany(instances, fieldName, relationship);
717
+ } else if (relationship.type === "manyToMany") {
718
+ await this.batchLoadManyToMany(instances, fieldName, relationship);
719
+ }
720
+ }
721
+ }
722
+ /**
723
+ * Batch load foreignKey relationships to avoid N+1 queries
724
+ *
725
+ * @param instances - Instances to load relationships for
726
+ * @param fieldName - Name of the foreignKey field
727
+ * @param relationship - Relationship metadata
728
+ * @private
729
+ */
730
+ async batchLoadForeignKeys(instances, fieldName, relationship) {
731
+ const foreignKeyValues = /* @__PURE__ */ new Set();
732
+ for (const instance of instances) {
733
+ const value = instance[fieldName];
734
+ if (value && typeof value === "string") {
735
+ foreignKeyValues.add(value);
736
+ }
737
+ }
738
+ if (foreignKeyValues.size === 0) return;
739
+ let targetCollection;
740
+ try {
741
+ targetCollection = await ObjectRegistry.getCollection(
742
+ relationship.targetClass,
743
+ this.options
744
+ );
745
+ } catch (error) {
746
+ logger.warn(`Could not get collection for ${relationship.targetClass}`, {
747
+ error
748
+ });
749
+ return;
750
+ }
751
+ const fkValueList = Array.from(foreignKeyValues);
752
+ const relatedObjects = [];
753
+ for (const idChunk of chunkArray(fkValueList, IN_LIST_CHUNK_SIZE)) {
754
+ const batch = await targetCollection.list({
755
+ where: { "id in": idChunk }
756
+ });
757
+ relatedObjects.push(...batch);
758
+ }
759
+ const relatedMap = /* @__PURE__ */ new Map();
760
+ for (const obj of relatedObjects) {
761
+ relatedMap.set(obj.id, obj);
762
+ }
763
+ for (const instance of instances) {
764
+ const foreignKeyValue = instance[fieldName];
765
+ if (foreignKeyValue && typeof foreignKeyValue === "string") {
766
+ const relatedObject = relatedMap.get(foreignKeyValue);
767
+ if (relatedObject) {
768
+ instance._loadedRelationships.set(fieldName, relatedObject);
769
+ }
770
+ }
771
+ }
772
+ }
773
+ /**
774
+ * Batch load oneToMany relationships
775
+ *
776
+ * @param instances - Instances to load relationships for
777
+ * @param fieldName - Name of the oneToMany field
778
+ * @param relationship - Relationship metadata
779
+ * @private
780
+ */
781
+ async batchLoadOneToMany(instances, fieldName, relationship) {
782
+ const inverseRelationships = ObjectRegistry.getInverseRelationshipsForSelf(
783
+ this._itemClass.name
784
+ );
785
+ const inverseCandidates = inverseRelationships.filter(
786
+ (r) => r.sourceClass === relationship.targetClass && r.type === "foreignKey"
787
+ );
788
+ const explicitForeignKey = relationship.options?.foreignKey;
789
+ const matchedForeignKey = explicitForeignKey ? inverseCandidates.find((r) => r.fieldName === explicitForeignKey) : void 0;
790
+ if (explicitForeignKey && !matchedForeignKey) {
791
+ throw new Error(
792
+ `oneToMany ${fieldName} specifies foreignKey '${explicitForeignKey}', but ${relationship.targetClass} has no matching inverse foreignKey. Candidates: ${inverseCandidates.map((r) => r.fieldName).join(", ") || "(none)"}`
793
+ );
794
+ }
795
+ const inverseForeignKey = matchedForeignKey ?? inverseCandidates.find((r) => r.targetClass === this._itemClass.name) ?? inverseCandidates[0];
796
+ if (!inverseForeignKey) {
797
+ logger.warn(
798
+ `Could not find inverse foreignKey for oneToMany ${fieldName}`
799
+ );
800
+ return;
801
+ }
802
+ const instanceIds = instances.map((i) => i.id).filter((id) => !!id);
803
+ if (instanceIds.length === 0) return;
804
+ let targetCollection;
805
+ try {
806
+ targetCollection = await ObjectRegistry.getCollection(
807
+ relationship.targetClass,
808
+ this.options
809
+ );
810
+ } catch (error) {
811
+ logger.warn(`Could not get collection for ${relationship.targetClass}`, {
812
+ error
813
+ });
814
+ return;
815
+ }
816
+ const relatedObjects = [];
817
+ for (const idChunk of chunkArray(instanceIds, IN_LIST_CHUNK_SIZE)) {
818
+ const batch = await targetCollection.list({
819
+ where: { [`${inverseForeignKey.fieldName} in`]: idChunk }
820
+ });
821
+ relatedObjects.push(...batch);
822
+ }
823
+ const relatedMap = /* @__PURE__ */ new Map();
824
+ for (const obj of relatedObjects) {
825
+ const foreignKeyValue = obj[inverseForeignKey.fieldName];
826
+ if (!relatedMap.has(foreignKeyValue)) {
827
+ relatedMap.set(foreignKeyValue, []);
828
+ }
829
+ relatedMap.get(foreignKeyValue)?.push(obj);
830
+ }
831
+ for (const instance of instances) {
832
+ const relatedArray = relatedMap.get(instance.id) || [];
833
+ instance._loadedRelationships.set(fieldName, relatedArray);
834
+ }
835
+ }
836
+ /**
837
+ * Batch-load manyToMany relationships through a junction table.
838
+ *
839
+ * Issues two queries instead of N: one against the junction table to map
840
+ * source IDs to target IDs, and one against the target table to hydrate
841
+ * the related rows. Results are grouped by source instance.
842
+ *
843
+ * @param instances - Instances whose manyToMany field should be populated
844
+ * @param fieldName - Name of the @manyToMany decorated field
845
+ * @param relationship - Relationship metadata from the registry
846
+ * @private
847
+ */
848
+ async batchLoadManyToMany(instances, fieldName, relationship) {
849
+ const instanceIds = instances.map((i) => i.id).filter((id) => !!id);
850
+ if (instanceIds.length === 0) return;
851
+ let through;
852
+ let sourceColumn;
853
+ let targetColumn;
854
+ let targetClassName;
855
+ try {
856
+ const sample = instances[0];
857
+ const join = await sample.resolveManyToManyJoin(fieldName, relationship);
858
+ through = join.through;
859
+ sourceColumn = join.sourceColumn;
860
+ targetColumn = join.targetColumn;
861
+ targetClassName = join.targetClassName;
862
+ } catch (error) {
863
+ logger.warn(
864
+ `Could not resolve manyToMany join for ${fieldName} on ${this._itemClass.name}`,
865
+ { error }
866
+ );
867
+ return;
868
+ }
869
+ for (const instance of instances) {
870
+ instance._loadedRelationships.set(fieldName, []);
871
+ }
872
+ const junctionRowsAll = [];
873
+ for (const idChunk of chunkArray(instanceIds, IN_LIST_CHUNK_SIZE)) {
874
+ const placeholders = idChunk.map(() => "?").join(", ");
875
+ const result = await this.db.query(
876
+ `SELECT "${sourceColumn}", "${targetColumn}" FROM "${through}" WHERE "${sourceColumn}" IN (${placeholders})`,
877
+ idChunk
878
+ );
879
+ junctionRowsAll.push(...result.rows);
880
+ }
881
+ if (junctionRowsAll.length === 0) return;
882
+ const sourceToTargets = /* @__PURE__ */ new Map();
883
+ const allTargetIds = /* @__PURE__ */ new Set();
884
+ for (const row of junctionRowsAll) {
885
+ const sId = row[sourceColumn];
886
+ const tId = row[targetColumn];
887
+ if (typeof sId !== "string" || typeof tId !== "string") continue;
888
+ allTargetIds.add(tId);
889
+ const list = sourceToTargets.get(sId) ?? [];
890
+ list.push(tId);
891
+ sourceToTargets.set(sId, list);
892
+ }
893
+ if (allTargetIds.size === 0) return;
894
+ let targetCollection;
895
+ try {
896
+ targetCollection = await ObjectRegistry.getCollection(
897
+ targetClassName,
898
+ this.options
899
+ );
900
+ } catch (error) {
901
+ logger.warn(
902
+ `Could not get collection for manyToMany target ${targetClassName}`,
903
+ { error }
904
+ );
905
+ return;
906
+ }
907
+ const targetIdList = Array.from(allTargetIds);
908
+ const targetObjects = [];
909
+ for (const idChunk of chunkArray(targetIdList, IN_LIST_CHUNK_SIZE)) {
910
+ const batch = await targetCollection.list({
911
+ where: { "id in": idChunk }
912
+ });
913
+ targetObjects.push(...batch);
914
+ }
915
+ const targetById = /* @__PURE__ */ new Map();
916
+ for (const obj of targetObjects) {
917
+ if (obj.id) targetById.set(obj.id, obj);
918
+ }
919
+ for (const instance of instances) {
920
+ const targetIds = sourceToTargets.get(instance.id) ?? [];
921
+ const objects = targetIds.map((id) => targetById.get(id)).filter((o) => o !== void 0);
922
+ instance._loadedRelationships.set(fieldName, objects);
923
+ }
924
+ }
925
+ /**
926
+ * Creates and persists a new instance of the collection's item class.
927
+ *
928
+ * Instantiates the model with the given field values, calls `initialize()`,
929
+ * assigns a UUID if none is provided, then calls `save()` to write the row
930
+ * to the database.
931
+ *
932
+ * For STI collections, pass `_meta_type` to create a specific subclass.
933
+ * The correct constructor is resolved via `ObjectRegistry` (polymorphic creation).
934
+ *
935
+ * @param options - Field values for the new object. Accepts any public field on
936
+ * the model class plus the STI `_meta_type` discriminator.
937
+ * @returns The newly created and saved model instance
938
+ * @throws {ValidationError} If a `required` field is missing or a unique constraint is violated
939
+ * @throws {DatabaseError} If the write fails
940
+ *
941
+ * @example
942
+ * ```typescript
943
+ * // Regular creation
944
+ * const product = await products.create({ name: 'Widget', price: 9.99 });
945
+ * console.log(product.id); // UUID assigned during save
946
+ *
947
+ * // STI polymorphic creation
948
+ * const article = await contents.create({
949
+ * _meta_type: '@happyvertical/smrt-content:Article',
950
+ * title: 'Hello World',
951
+ * });
952
+ * ```
953
+ *
954
+ * @see {@link getOrUpsert} to avoid duplicates by finding-or-creating
955
+ */
956
+ async create(options) {
957
+ let itemClassName = this.getResolvedItemClassName();
958
+ await ObjectRegistry.ensureManifestLoaded(itemClassName);
959
+ itemClassName = this.getResolvedItemClassName();
960
+ const itemQualifiedName = this.getResolvedItemQualifiedName();
961
+ const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);
962
+ if (tableStrategy === "sti" && options._meta_type) {
963
+ const instance2 = await this.createPolymorphic(
964
+ options._meta_type,
965
+ options
966
+ );
967
+ if (!instance2.id) {
968
+ instance2._id = crypto.randomUUID();
969
+ }
970
+ await instance2.save();
971
+ return instance2;
972
+ }
973
+ const params = {
974
+ ai: this.options.ai,
975
+ // Pass the actual database instance, not options
976
+ // This ensures objects share the same connection as the collection
977
+ // Critical for in-memory databases like DuckDB :memory: where each
978
+ // connection gets a separate database
979
+ db: this.db,
980
+ _skipLoad: true,
981
+ // Don't try to load from DB - this is a new object
982
+ ...options
983
+ };
984
+ const instance = new this._itemClass(params);
985
+ await instance.initialize();
986
+ if (tableStrategy === "sti") {
987
+ instance._meta_type = itemQualifiedName;
988
+ }
989
+ if (!instance.id) {
990
+ instance._id = crypto.randomUUID();
991
+ }
992
+ await instance.save();
993
+ return instance;
994
+ }
995
+ /**
996
+ * Creates an instance of the correct subclass for STI polymorphic queries
997
+ *
998
+ * @param className - Name of the class to instantiate (from _meta_type)
999
+ * @param options - Data to initialize the instance with
1000
+ * @returns Promise resolving to the instance of the correct subclass
1001
+ * @private
1002
+ */
1003
+ async createPolymorphic(className, options, hydrationOptions = {}) {
1004
+ if (!className || className === null || className === void 0) {
1005
+ const { DatabaseError } = await import("./errors.js");
1006
+ throw DatabaseError.missingDiscriminator(
1007
+ this._itemClass.name,
1008
+ options?.id
1009
+ );
1010
+ }
1011
+ let registeredClass = ObjectRegistry.getClassByQualifiedName(className);
1012
+ if (!registeredClass) {
1013
+ registeredClass = ObjectRegistry.getClass(className);
1014
+ }
1015
+ if (!registeredClass) {
1016
+ await ObjectRegistry.ensureManifestLoaded(className);
1017
+ registeredClass = ObjectRegistry.getClassByQualifiedName(className);
1018
+ if (!registeredClass) {
1019
+ registeredClass = ObjectRegistry.getClass(className);
1020
+ }
1021
+ }
1022
+ if (!registeredClass) {
1023
+ throw new Error(
1024
+ `STI polymorphic query failed: Class '${className}' not found in ObjectRegistry. Ensure the class is registered with @smrt() decorator or available via an installed SMRT manifest.`
1025
+ );
1026
+ }
1027
+ const params = {
1028
+ ai: this.options.ai,
1029
+ db: this.db,
1030
+ _skipLoad: true,
1031
+ ...hydrationOptions.hydrateOnly ? {
1032
+ _reuseInitializedDb: true,
1033
+ _deferRuntimeInitialization: true
1034
+ } : {},
1035
+ ...options
1036
+ };
1037
+ const instance = new registeredClass.constructor(params);
1038
+ await instance.initialize();
1039
+ instance._meta_type = registeredClass?.qualifiedName || className;
1040
+ return instance;
1041
+ }
1042
+ /**
1043
+ * Finds an existing record matching `data` or creates it if not found.
1044
+ *
1045
+ * Look-up priority:
1046
+ * 1. `data.id` — query by primary key
1047
+ * 2. `data.slug` — query by slug + context
1048
+ * 3. Fallback — query by the full `data` object as a WHERE clause
1049
+ *
1050
+ * If a matching record is found, it is updated with any changed fields from
1051
+ * `data` (diffed against the existing record) and saved. If no match is
1052
+ * found, `defaults` are merged with `data` and a new record is created.
1053
+ *
1054
+ * @param data - The field values to find or upsert
1055
+ * @param defaults - Extra default values applied only when creating a new record
1056
+ * @returns The existing (possibly updated) or newly created object instance
1057
+ *
1058
+ * @example
1059
+ * ```typescript
1060
+ * // Find-or-create a tag by slug
1061
+ * const tag = await tags.getOrUpsert({ slug: 'javascript', name: 'JavaScript' });
1062
+ *
1063
+ * // With defaults applied only on creation
1064
+ * const user = await users.getOrUpsert(
1065
+ * { email: 'alice@example.com' },
1066
+ * { role: 'member', active: true },
1067
+ * );
1068
+ * ```
1069
+ *
1070
+ * @see {@link create} for always-insert semantics
1071
+ * @see {@link get} for read-only lookup
1072
+ */
1073
+ async getOrUpsert(data, defaults = {}) {
1074
+ const logicalData = this.normalizeLogicalData(data);
1075
+ const logicalDefaults = this.normalizeLogicalData(defaults);
1076
+ let where = {};
1077
+ const diffData = { ...logicalData };
1078
+ if (logicalData.id) {
1079
+ where = { id: logicalData.id };
1080
+ delete diffData.id;
1081
+ delete diffData.slug;
1082
+ delete diffData.context;
1083
+ } else if (logicalData.slug) {
1084
+ where = { slug: logicalData.slug, context: logicalData.context || "" };
1085
+ delete diffData.slug;
1086
+ delete diffData.context;
1087
+ } else {
1088
+ where = logicalData;
1089
+ }
1090
+ const existing = await this.get(where, { cache: false });
1091
+ if (existing) {
1092
+ const diff = this.getDiffSync(existing, diffData);
1093
+ if (diff) {
1094
+ Object.assign(existing, diff);
1095
+ await existing.save();
1096
+ }
1097
+ return existing;
1098
+ }
1099
+ const createData = {
1100
+ ...logicalDefaults,
1101
+ ...logicalData
1102
+ };
1103
+ return await this.create(createData);
1104
+ }
1105
+ /**
1106
+ * Gets differences between an existing object and new data
1107
+ *
1108
+ * @param existing - Existing object
1109
+ * @param data - New data
1110
+ * @returns Object containing only the changed fields
1111
+ */
1112
+ async getDiff(existing, data) {
1113
+ return this.getDiffSync(existing, data);
1114
+ }
1115
+ getDiffSync(existing, data) {
1116
+ const fields = this.getFieldsSync();
1117
+ const validKeys = /* @__PURE__ */ new Set([
1118
+ ...Object.keys(fields),
1119
+ "id",
1120
+ "slug",
1121
+ "context"
1122
+ ]);
1123
+ const diff = Object.keys(data).reduce(
1124
+ (acc, key) => {
1125
+ if (validKeys.has(key) && !this.areEquivalentValues(existing[key], data[key])) {
1126
+ acc[key] = data[key];
1127
+ }
1128
+ return acc;
1129
+ },
1130
+ {}
1131
+ );
1132
+ return Object.keys(diff).length > 0 ? diff : null;
1133
+ }
1134
+ /**
1135
+ * Gets field definitions for the collection's item class
1136
+ *
1137
+ * @returns Object containing field definitions
1138
+ */
1139
+ async getFields() {
1140
+ return await fieldsFromClass(this._itemClass);
1141
+ }
1142
+ /**
1143
+ * Normalize user input into the model's logical field names before diffing or creation.
1144
+ *
1145
+ * Accepts both camelCase and snake_case keys while preserving framework meta fields.
1146
+ */
1147
+ normalizeLogicalData(data) {
1148
+ const fields = this.getFieldsSync();
1149
+ const normalized = {};
1150
+ for (const [key, value] of Object.entries(data)) {
1151
+ if (key.startsWith("_")) {
1152
+ normalized[key] = value;
1153
+ continue;
1154
+ }
1155
+ const camelKey = key.includes("_") ? toCamelCase(key) : key;
1156
+ const snakeKey = key.includes("_") ? key : toSnakeCase(key);
1157
+ const outputKey = key in fields ? key : camelKey in fields ? camelKey : snakeKey in fields ? snakeKey : key;
1158
+ normalized[outputKey] = value;
1159
+ }
1160
+ return normalized;
1161
+ }
1162
+ /**
1163
+ * Preserve persisted core timestamp fields during lightweight hydration.
1164
+ */
1165
+ withHydratedCoreFields(data) {
1166
+ const hydratedData = { ...data };
1167
+ if (hydratedData.createdAt !== void 0 && hydratedData.created_at === void 0) {
1168
+ hydratedData.created_at = hydratedData.createdAt;
1169
+ }
1170
+ if (hydratedData.updatedAt !== void 0 && hydratedData.updated_at === void 0) {
1171
+ hydratedData.updated_at = hydratedData.updatedAt;
1172
+ }
1173
+ return hydratedData;
1174
+ }
1175
+ /**
1176
+ * Treat date-equivalent values as unchanged even if their runtime types differ.
1177
+ */
1178
+ areEquivalentValues(existingValue, nextValue) {
1179
+ if (existingValue instanceof Date || nextValue instanceof Date) {
1180
+ const existingTime = this.toComparableTime(existingValue);
1181
+ const nextTime = this.toComparableTime(nextValue);
1182
+ if (existingTime !== null && nextTime !== null) {
1183
+ return existingTime === nextTime;
1184
+ }
1185
+ }
1186
+ return existingValue === nextValue;
1187
+ }
1188
+ /**
1189
+ * Convert supported date inputs into comparable timestamps.
1190
+ */
1191
+ toComparableTime(value) {
1192
+ if (value instanceof Date) {
1193
+ const timestamp = value.getTime();
1194
+ return Number.isNaN(timestamp) ? null : timestamp;
1195
+ }
1196
+ if (typeof value === "string" && value.trim()) {
1197
+ const parsedDate = new Date(value);
1198
+ const timestamp = parsedDate.getTime();
1199
+ return Number.isNaN(timestamp) ? null : timestamp;
1200
+ }
1201
+ return null;
1202
+ }
1203
+ /**
1204
+ * Gets field definitions synchronously from cache.
1205
+ *
1206
+ * This method provides sync access to fields for use in query methods,
1207
+ * avoiding the async overhead of getFields() on every query.
1208
+ * Fields are cached during create() initialization.
1209
+ *
1210
+ * @returns Map containing field definitions
1211
+ * @private
1212
+ */
1213
+ getFieldsSync() {
1214
+ if (this._cachedFields) {
1215
+ return this._cachedFields;
1216
+ }
1217
+ const className = this.getResolvedItemClassName();
1218
+ const fields = ObjectRegistry.getFields(className);
1219
+ return Object.fromEntries(fields);
1220
+ }
1221
+ /**
1222
+ * Generates database schema for the collection's item class
1223
+ *
1224
+ * Leverages ObjectRegistry's cached schema for instant retrieval.
1225
+ *
1226
+ * @returns Schema object for database setup
1227
+ */
1228
+ async generateSchema() {
1229
+ const { generateSchema } = await import("./schema/utils.js");
1230
+ return await generateSchema(this._itemClass);
1231
+ }
1232
+ /**
1233
+ * Gets the database table name for this collection
1234
+ */
1235
+ get tableName() {
1236
+ if (!this._tableName) {
1237
+ const className = this.getResolvedItemClassName();
1238
+ const qualifiedName = this.getResolvedItemQualifiedName();
1239
+ const tableStrategy = ObjectRegistry.getTableStrategy(qualifiedName);
1240
+ const fallbackTableName = this._itemClass.SMRT_TABLE_NAME || classnameToTablename(className);
1241
+ if (tableStrategy === "sti") {
1242
+ const stiBase = ObjectRegistry.getSTIBase(qualifiedName);
1243
+ if (stiBase) {
1244
+ const baseSchema = ObjectRegistry.getSchema(stiBase);
1245
+ if (baseSchema?.tableName) {
1246
+ this._tableName = baseSchema.tableName;
1247
+ } else {
1248
+ const ownSchema = ObjectRegistry.getSchema(className);
1249
+ this._tableName = ownSchema?.tableName || fallbackTableName;
1250
+ }
1251
+ } else {
1252
+ const ownSchema = ObjectRegistry.getSchema(className);
1253
+ this._tableName = ownSchema?.tableName || fallbackTableName;
1254
+ }
1255
+ } else {
1256
+ const ownSchema = ObjectRegistry.getSchema(className);
1257
+ this._tableName = ownSchema?.tableName || fallbackTableName;
1258
+ }
1259
+ }
1260
+ return this._tableName;
1261
+ }
1262
+ /**
1263
+ * Generates a table name from the collection class name
1264
+ *
1265
+ * @returns Generated table name
1266
+ */
1267
+ generateTableName() {
1268
+ const tableName = this._className.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/([^s])$/, "$1s").replace(/y$/, "ies");
1269
+ return tableName;
1270
+ }
1271
+ /**
1272
+ * Deletes a record from the collection by ID
1273
+ *
1274
+ * Loads the object and calls its delete() method, ensuring all interceptors
1275
+ * and lifecycle hooks (beforeDelete/afterDelete) are executed correctly.
1276
+ *
1277
+ * @param id - The ID of the record to delete
1278
+ * @returns Promise resolving to true if deleted, false if not found
1279
+ *
1280
+ * @example
1281
+ * ```typescript
1282
+ * const success = await collection.delete('some-uuid');
1283
+ * if (success) {
1284
+ * console.log('Record deleted');
1285
+ * }
1286
+ * ```
1287
+ */
1288
+ async delete(id) {
1289
+ await ObjectRegistry.ensureManifestLoaded(this.getResolvedItemClassName());
1290
+ const instance = await this.get(id, { cache: false });
1291
+ if (!instance) {
1292
+ return false;
1293
+ }
1294
+ await instance.delete();
1295
+ return true;
1296
+ }
1297
+ /**
1298
+ * Returns the number of records matching the given filter conditions.
1299
+ *
1300
+ * Executes a `SELECT COUNT(*)` query with the same WHERE conversion as
1301
+ * `list()` (camelCase field names, operator suffixes, STI auto-filtering).
1302
+ * `limit`, `offset`, and `orderBy` are not applicable and are ignored.
1303
+ *
1304
+ * Note: `count()` is never served from the read cache (issue #1498) — only
1305
+ * `list()`/`get()` are. On a page that caches `list()`, a `count()` issued
1306
+ * in the same request reflects the live database and may briefly diverge
1307
+ * from the cached rows within the TTL window.
1308
+ *
1309
+ * @param options.where - Filter conditions (same syntax as `list()`)
1310
+ * @returns Total count of matching records as an integer
1311
+ *
1312
+ * @example
1313
+ * ```typescript
1314
+ * const total = await products.count();
1315
+ * const activeCount = await products.count({ where: { status: 'active' } });
1316
+ * const expensiveCount = await products.count({ where: { 'price >': 100 } });
1317
+ * ```
1318
+ *
1319
+ * @see {@link list} for retrieving the actual records
1320
+ */
1321
+ async count(options = {}) {
1322
+ await this.ensureStorageReady();
1323
+ const itemQualifiedName = this.getResolvedItemQualifiedName();
1324
+ const itemClassName = this.getResolvedItemClassName();
1325
+ const interceptorContext = createInterceptorContext(
1326
+ itemClassName,
1327
+ "list",
1328
+ this.constructor.name
1329
+ );
1330
+ const interceptedOptions = await GlobalInterceptors.executeBeforeList(
1331
+ itemClassName,
1332
+ options,
1333
+ interceptorContext
1334
+ ) ?? options ?? {};
1335
+ let { where } = interceptedOptions;
1336
+ const tableStrategy = ObjectRegistry.getTableStrategy(itemQualifiedName);
1337
+ const isSTI = tableStrategy === "sti";
1338
+ if (isSTI) {
1339
+ const stiBase = ObjectRegistry.getSTIBase(itemQualifiedName);
1340
+ if (stiBase && stiBase !== itemQualifiedName) {
1341
+ where = {
1342
+ _meta_type: itemQualifiedName,
1343
+ ...where || {}
1344
+ };
1345
+ }
1346
+ }
1347
+ where = resolveMetaTypeInWhere(where);
1348
+ const { sql: whereSql, values: whereValues } = buildWhere(
1349
+ this.convertWhereKeys(where || {})
1350
+ );
1351
+ const result = await this.db.query(
1352
+ `SELECT COUNT(*) as count FROM ${this.tableName} ${whereSql}`,
1353
+ ...whereValues
1354
+ );
1355
+ return Number.parseInt(result.rows[0].count, 10);
1356
+ }
1357
+ /**
1358
+ * Execute a raw SQL query and hydrate results as collection item instances
1359
+ *
1360
+ * Provides full SQL power for complex queries (JOINs, CTEs, NOT EXISTS, etc.)
1361
+ * while still returning properly hydrated SMRT objects.
1362
+ *
1363
+ * @param sql - Raw SQL query string (should select from this.tableName)
1364
+ * @param params - Query parameters for prepared statement
1365
+ * @returns Promise resolving to array of hydrated model instances
1366
+ *
1367
+ * @example
1368
+ * ```typescript
1369
+ * // Find meetings without corresponding recaps (NOT EXISTS pattern)
1370
+ * const meetings = await meetingCollection.query(`
1371
+ * SELECT m.* FROM meetings m
1372
+ * WHERE m.start_date < datetime('now')
1373
+ * AND NOT EXISTS (
1374
+ * SELECT 1 FROM contents c
1375
+ * WHERE c.meeting_id = m.id
1376
+ * AND c._meta_type = 'MeetingRecap'
1377
+ * )
1378
+ * ORDER BY m.start_date DESC
1379
+ * LIMIT ?
1380
+ * `, [10]);
1381
+ *
1382
+ * // Complex JOIN query
1383
+ * const products = await productCollection.query(`
1384
+ * SELECT p.* FROM products p
1385
+ * INNER JOIN categories c ON p.category_id = c.id
1386
+ * WHERE c.name = ? AND p.price > ?
1387
+ * ORDER BY p.price ASC
1388
+ * `, ['Electronics', 100]);
1389
+ * ```
1390
+ */
1391
+ async query(sql, params = [], options = {}) {
1392
+ await this.ensureStorageReady();
1393
+ const interceptorContext = createInterceptorContext(
1394
+ this._itemClass.name,
1395
+ "query",
1396
+ this.constructor.name
1397
+ );
1398
+ const interceptedQuery = await GlobalInterceptors.executeBeforeQuery(
1399
+ this._itemClass.name,
1400
+ { sql, params, allowRawOnTenantScoped: options.allowRawOnTenantScoped },
1401
+ interceptorContext
1402
+ );
1403
+ const result = await this.db.query(
1404
+ interceptedQuery.sql,
1405
+ ...interceptedQuery.params
1406
+ );
1407
+ if (/\b(?:insert|update|delete|merge|truncate|replace)\b/i.test(
1408
+ interceptedQuery.sql
1409
+ )) {
1410
+ invalidateCollectionCache(resolveDbCacheKey(this.db), this.tableName);
1411
+ }
1412
+ const fields = this.getFieldsSync();
1413
+ const tableStrategy = ObjectRegistry.getTableStrategy(
1414
+ this.getResolvedItemQualifiedName()
1415
+ );
1416
+ const isSTI = tableStrategy === "sti";
1417
+ const instances = await Promise.all(
1418
+ result.rows.map((row) => this.hydrateResultRow(row, fields, isSTI))
1419
+ );
1420
+ return await GlobalInterceptors.executeAfterQuery(
1421
+ this._itemClass.name,
1422
+ instances,
1423
+ interceptorContext
1424
+ );
1425
+ }
1426
+ async hydrateResultRow(row, fields, isSTI) {
1427
+ const formattedData = this.withHydratedCoreFields(
1428
+ formatDataJs(row, fields)
1429
+ );
1430
+ if (isSTI && formattedData._meta_type) {
1431
+ const polymorphicInstance = await this.createPolymorphic(
1432
+ formattedData._meta_type,
1433
+ formattedData,
1434
+ { hydrateOnly: true }
1435
+ );
1436
+ polymorphicInstance.markAsPersisted();
1437
+ return polymorphicInstance;
1438
+ }
1439
+ const instanceParams = {
1440
+ ai: this.options.ai,
1441
+ db: this.db,
1442
+ _skipLoad: true,
1443
+ _reuseInitializedDb: true,
1444
+ _deferRuntimeInitialization: true,
1445
+ ...formattedData
1446
+ };
1447
+ const instance = new this._itemClass(instanceParams);
1448
+ await instance.initialize();
1449
+ if (isSTI) {
1450
+ const registeredClass = ObjectRegistry.getClass(this._itemClass.name);
1451
+ instance._meta_type = registeredClass?.qualifiedName || this._itemClass.name;
1452
+ }
1453
+ instance.markAsPersisted();
1454
+ return instance;
1455
+ }
1456
+ /**
1457
+ * Remember collection-level context
1458
+ *
1459
+ * Stores context applicable to all instances of this collection type.
1460
+ * Use for patterns that apply to the entire collection (e.g., default parsing strategies).
1461
+ *
1462
+ * @param options - Context options
1463
+ * @returns Promise that resolves when context is stored
1464
+ * @example
1465
+ * ```typescript
1466
+ * // Remember a default parsing strategy for all documents
1467
+ * await documentCollection.remember({
1468
+ * scope: 'parser/default',
1469
+ * key: 'selector',
1470
+ * value: { pattern: '.content article' },
1471
+ * confidence: 0.8
1472
+ * });
1473
+ *
1474
+ * // Update an existing context entry by specifying id
1475
+ * await documentCollection.remember({
1476
+ * id: 'existing-context-id',
1477
+ * scope: 'parser/default',
1478
+ * key: 'selector',
1479
+ * value: { pattern: '.content main article' },
1480
+ * confidence: 0.85
1481
+ * });
1482
+ * ```
1483
+ */
1484
+ async remember(options) {
1485
+ if (!this.systemDb) {
1486
+ throw new Error("Database not initialized. Call initialize() first.");
1487
+ }
1488
+ const id = options.id || crypto.randomUUID();
1489
+ const now = /* @__PURE__ */ new Date();
1490
+ await this.systemDb.upsert(
1491
+ "_smrt_contexts",
1492
+ // UNIQUE constraint: (owner_class, owner_id, scope, key, version)
1493
+ ["owner_class", "owner_id", "scope", "key", "version"],
1494
+ {
1495
+ id,
1496
+ owner_class: this._itemClass.name,
1497
+ owner_id: "__collection__",
1498
+ scope: options.scope,
1499
+ key: options.key,
1500
+ value: JSON.stringify(options.value),
1501
+ metadata: options.metadata ? JSON.stringify(options.metadata) : null,
1502
+ version: options.version ?? 1,
1503
+ confidence: options.confidence ?? 1,
1504
+ success_count: 0,
1505
+ failure_count: 0,
1506
+ created_at: now,
1507
+ updated_at: now,
1508
+ last_used_at: now,
1509
+ expires_at: options.expiresAt ?? null
1510
+ }
1511
+ );
1512
+ }
1513
+ /**
1514
+ * Recall collection-level context
1515
+ *
1516
+ * Retrieves context that applies to all instances of this collection.
1517
+ *
1518
+ * @param options - Recall options
1519
+ * @returns Promise resolving to the context value or null if not found
1520
+ * @example
1521
+ * ```typescript
1522
+ * // Recall default parsing strategy
1523
+ * const strategy = await documentCollection.recall({
1524
+ * scope: 'parser/default',
1525
+ * key: 'selector',
1526
+ * minConfidence: 0.5
1527
+ * });
1528
+ * ```
1529
+ */
1530
+ async recall(options) {
1531
+ if (!this.systemDb) {
1532
+ throw new Error("Database not initialized. Call initialize() first.");
1533
+ }
1534
+ let result;
1535
+ if (options.minConfidence !== void 0) {
1536
+ result = await this.systemDb.single`
1537
+ SELECT value, confidence
1538
+ FROM _smrt_contexts
1539
+ WHERE owner_class = ${this._itemClass.name}
1540
+ AND owner_id = ${"__collection__"}
1541
+ AND scope = ${options.scope}
1542
+ AND key = ${options.key}
1543
+ AND confidence >= ${options.minConfidence}
1544
+ ORDER BY confidence DESC, version DESC
1545
+ LIMIT 1
1546
+ `;
1547
+ } else {
1548
+ result = await this.systemDb.single`
1549
+ SELECT value, confidence
1550
+ FROM _smrt_contexts
1551
+ WHERE owner_class = ${this._itemClass.name}
1552
+ AND owner_id = ${"__collection__"}
1553
+ AND scope = ${options.scope}
1554
+ AND key = ${options.key}
1555
+ ORDER BY confidence DESC, version DESC
1556
+ LIMIT 1
1557
+ `;
1558
+ }
1559
+ if (result) {
1560
+ try {
1561
+ return JSON.parse(result.value);
1562
+ } catch (error) {
1563
+ logger.warn("Skipping corrupted _smrt_contexts value in recall()", {
1564
+ ownerClass: this._itemClass.name,
1565
+ scope: options.scope,
1566
+ key: options.key,
1567
+ error: error instanceof Error ? error.message : String(error)
1568
+ });
1569
+ }
1570
+ }
1571
+ if (options.includeAncestors) {
1572
+ const scopeParts = options.scope.split("/");
1573
+ while (scopeParts.length > 0) {
1574
+ scopeParts.pop();
1575
+ const parentScope = scopeParts.join("/") || "global";
1576
+ const parentResult = await this.recall({
1577
+ ...options,
1578
+ scope: parentScope,
1579
+ includeAncestors: false
1580
+ });
1581
+ if (parentResult) return parentResult;
1582
+ }
1583
+ }
1584
+ return null;
1585
+ }
1586
+ /**
1587
+ * Recall all collection-level context in a scope
1588
+ *
1589
+ * Returns a Map of key -> value for all collection contexts matching the criteria.
1590
+ *
1591
+ * @param options - Recall options
1592
+ * @returns Promise resolving to Map of key -> value pairs
1593
+ * @example
1594
+ * ```typescript
1595
+ * // Get all default strategies
1596
+ * const strategies = await documentCollection.recallAll({
1597
+ * scope: 'parser/default',
1598
+ * minConfidence: 0.5
1599
+ * });
1600
+ * ```
1601
+ */
1602
+ async recallAll(options = {}) {
1603
+ if (!this.systemDb) {
1604
+ throw new Error("Database not initialized. Call initialize() first.");
1605
+ }
1606
+ const results = /* @__PURE__ */ new Map();
1607
+ let query = `
1608
+ SELECT key, value, confidence
1609
+ FROM _smrt_contexts
1610
+ WHERE owner_class = ? AND owner_id = ?
1611
+ `;
1612
+ const params = [this._itemClass.name, "__collection__"];
1613
+ if (options.scope) {
1614
+ if (options.includeDescendants) {
1615
+ query += ` AND (scope = ? OR scope LIKE ?)`;
1616
+ params.push(options.scope, `${options.scope}/%`);
1617
+ } else {
1618
+ query += ` AND scope = ?`;
1619
+ params.push(options.scope);
1620
+ }
1621
+ }
1622
+ if (options.minConfidence !== void 0) {
1623
+ query += ` AND confidence >= ?`;
1624
+ params.push(options.minConfidence);
1625
+ }
1626
+ query += ` ORDER BY confidence DESC`;
1627
+ const { rows } = await this.systemDb.query(query, ...params);
1628
+ for (const row of rows) {
1629
+ try {
1630
+ results.set(row.key, JSON.parse(row.value));
1631
+ } catch (error) {
1632
+ logger.warn("Skipping corrupted _smrt_contexts value in recallAll()", {
1633
+ ownerClass: this._itemClass.name,
1634
+ scope: options.scope,
1635
+ key: row.key,
1636
+ error: error instanceof Error ? error.message : String(error)
1637
+ });
1638
+ }
1639
+ }
1640
+ return results;
1641
+ }
1642
+ /**
1643
+ * Forget collection-level context
1644
+ *
1645
+ * Deletes collection context by scope and key.
1646
+ *
1647
+ * @param options - Context identification
1648
+ * @returns Promise that resolves when context is deleted
1649
+ * @example
1650
+ * ```typescript
1651
+ * // Remove a default strategy
1652
+ * await documentCollection.forget({
1653
+ * scope: 'parser/default',
1654
+ * key: 'selector'
1655
+ * });
1656
+ * ```
1657
+ */
1658
+ async forget(options) {
1659
+ if (!this.systemDb) {
1660
+ throw new Error("Database not initialized. Call initialize() first.");
1661
+ }
1662
+ await this.systemDb.query(
1663
+ `DELETE FROM _smrt_contexts
1664
+ WHERE owner_class = ? AND owner_id = ? AND scope = ? AND key = ?`,
1665
+ this._itemClass.name,
1666
+ "__collection__",
1667
+ options.scope,
1668
+ options.key
1669
+ );
1670
+ }
1671
+ /**
1672
+ * Forget all collection-level context in a scope
1673
+ *
1674
+ * Deletes all collection contexts matching the scope pattern.
1675
+ *
1676
+ * @param options - Scope options
1677
+ * @returns Promise resolving to number of contexts deleted
1678
+ * @example
1679
+ * ```typescript
1680
+ * // Clear all default strategies
1681
+ * const count = await documentCollection.forgetScope({
1682
+ * scope: 'parser/default',
1683
+ * includeDescendants: true
1684
+ * });
1685
+ * ```
1686
+ */
1687
+ async forgetScope(options) {
1688
+ if (!this.systemDb) {
1689
+ throw new Error("Database not initialized. Call initialize() first.");
1690
+ }
1691
+ let query = `
1692
+ DELETE FROM _smrt_contexts
1693
+ WHERE owner_class = ? AND owner_id = ?
1694
+ `;
1695
+ const params = [this._itemClass.name, "__collection__"];
1696
+ if (options.includeDescendants) {
1697
+ query += ` AND (scope = ? OR scope LIKE ?)`;
1698
+ params.push(options.scope, `${options.scope}/%`);
1699
+ } else {
1700
+ query += ` AND scope = ?`;
1701
+ params.push(options.scope);
1702
+ }
1703
+ const { rowCount } = await this.systemDb.query(query, ...params);
1704
+ return rowCount || 0;
1705
+ }
1706
+ // ============================================================================
1707
+ // Semantic Search Methods
1708
+ // ============================================================================
1709
+ /**
1710
+ * Semantic search by text query
1711
+ *
1712
+ * Generates an embedding for the query text and finds similar objects
1713
+ * based on cosine similarity of stored embeddings.
1714
+ *
1715
+ * @param query - Text to search for
1716
+ * @param options - Search options
1717
+ * @param options.field - Specific field to search (defaults to first embedding field)
1718
+ * @param options.limit - Maximum results to return (default: 10)
1719
+ * @param options.minSimilarity - Minimum similarity threshold 0-1 (default: 0)
1720
+ * @param options.where - Additional WHERE filters to apply
1721
+ * @returns Promise resolving to array of objects with _similarity score
1722
+ *
1723
+ * @example
1724
+ * ```typescript
1725
+ * const results = await articles.semanticSearch('machine learning trends', {
1726
+ * limit: 10,
1727
+ * minSimilarity: 0.7
1728
+ * });
1729
+ *
1730
+ * for (const article of results) {
1731
+ * console.log(`${article.title} (similarity: ${article._similarity})`);
1732
+ * }
1733
+ * ```
1734
+ */
1735
+ async semanticSearch(query, options = {}) {
1736
+ const { field, limit = 10, minSimilarity = 0, where } = options;
1737
+ const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(
1738
+ this._itemClass.name
1739
+ );
1740
+ if (!embeddingConfig) {
1741
+ throw new Error(
1742
+ `No embedding configuration found for ${this._itemClass.name}. Add embeddings config to @smrt() decorator.`
1743
+ );
1744
+ }
1745
+ const searchField = field || embeddingConfig.fields[0];
1746
+ if (!embeddingConfig.fields.includes(searchField)) {
1747
+ throw new Error(
1748
+ `Field '${searchField}' is not configured for embeddings on ${this._itemClass.name}. Available fields: ${embeddingConfig.fields.join(", ")}`
1749
+ );
1750
+ }
1751
+ const provider = new EmbeddingProvider(
1752
+ {
1753
+ dimensions: embeddingConfig.dimensions,
1754
+ provider: embeddingConfig.provider,
1755
+ localModel: embeddingConfig.localModel,
1756
+ aiModel: embeddingConfig.aiModel,
1757
+ fallbackToAI: embeddingConfig.fallbackToAI
1758
+ },
1759
+ this.ai
1760
+ );
1761
+ const [queryEmbedding] = await provider.embed(query);
1762
+ return this.findSimilarToEmbedding(queryEmbedding, {
1763
+ field: searchField,
1764
+ limit,
1765
+ minSimilarity,
1766
+ where
1767
+ });
1768
+ }
1769
+ /**
1770
+ * Find objects similar to a given object
1771
+ *
1772
+ * Uses stored embeddings to find objects most similar to the provided object.
1773
+ *
1774
+ * @param object - Object to find similar items for (or object ID)
1775
+ * @param options - Search options
1776
+ * @param options.field - Specific field to compare (defaults to first embedding field)
1777
+ * @param options.limit - Maximum results to return (default: 5)
1778
+ * @param options.excludeSelf - Whether to exclude the source object (default: true)
1779
+ * @returns Promise resolving to array of similar objects with _similarity score
1780
+ *
1781
+ * @example
1782
+ * ```typescript
1783
+ * const article = await articles.get('some-article-id');
1784
+ * const similar = await articles.findSimilar(article, {
1785
+ * limit: 5,
1786
+ * excludeSelf: true
1787
+ * });
1788
+ * ```
1789
+ */
1790
+ async findSimilar(object, options = {}) {
1791
+ const { field, limit = 5, excludeSelf = true } = options;
1792
+ let sourceObject;
1793
+ if (typeof object === "string") {
1794
+ const found = await this.get(object);
1795
+ if (!found) {
1796
+ throw new Error(`Object not found: ${object}`);
1797
+ }
1798
+ sourceObject = found;
1799
+ } else {
1800
+ sourceObject = object;
1801
+ }
1802
+ const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(
1803
+ this._itemClass.name
1804
+ );
1805
+ if (!embeddingConfig) {
1806
+ throw new Error(
1807
+ `No embedding configuration found for ${this._itemClass.name}.`
1808
+ );
1809
+ }
1810
+ const searchField = field || embeddingConfig.fields[0];
1811
+ const provider = new EmbeddingProvider(
1812
+ {
1813
+ dimensions: embeddingConfig.dimensions,
1814
+ provider: embeddingConfig.provider,
1815
+ localModel: embeddingConfig.localModel,
1816
+ aiModel: embeddingConfig.aiModel,
1817
+ fallbackToAI: embeddingConfig.fallbackToAI
1818
+ },
1819
+ this.ai
1820
+ );
1821
+ const model = provider.getModelName();
1822
+ const storedEmbedding = await EmbeddingStorage.get(
1823
+ this.systemDb,
1824
+ this._itemClass.name,
1825
+ sourceObject.id,
1826
+ searchField,
1827
+ model
1828
+ );
1829
+ if (!storedEmbedding) {
1830
+ throw new Error(
1831
+ `No embedding found for object ${sourceObject.id} field '${searchField}'. Generate embeddings first with object.generateEmbeddings().`
1832
+ );
1833
+ }
1834
+ const where = excludeSelf ? { "id !=": sourceObject.id } : void 0;
1835
+ return this.findSimilarToEmbedding(storedEmbedding.embedding, {
1836
+ field: searchField,
1837
+ limit,
1838
+ minSimilarity: 0,
1839
+ where
1840
+ });
1841
+ }
1842
+ /**
1843
+ * Find objects similar to a raw embedding vector
1844
+ *
1845
+ * Low-level method for finding objects by embedding similarity.
1846
+ *
1847
+ * @param embedding - Embedding vector to compare against
1848
+ * @param options - Search options
1849
+ * @param options.field - Field name to search (defaults to first embedding field)
1850
+ * @param options.limit - Maximum results to return (default: 10)
1851
+ * @param options.minSimilarity - Minimum similarity threshold 0-1 (default: 0)
1852
+ * @param options.where - Additional WHERE filters to apply
1853
+ * @returns Promise resolving to array of objects with _similarity score
1854
+ *
1855
+ * @example
1856
+ * ```typescript
1857
+ * // Using a pre-computed embedding
1858
+ * const results = await articles.findSimilarToEmbedding(myEmbedding, {
1859
+ * limit: 10,
1860
+ * minSimilarity: 0.5
1861
+ * });
1862
+ * ```
1863
+ */
1864
+ async findSimilarToEmbedding(embedding, options = {}) {
1865
+ const { field, limit = 10, minSimilarity = 0, where } = options;
1866
+ const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(
1867
+ this._itemClass.name
1868
+ );
1869
+ if (!embeddingConfig) {
1870
+ throw new Error(
1871
+ `No embedding configuration found for ${this._itemClass.name}.`
1872
+ );
1873
+ }
1874
+ const searchField = field || embeddingConfig.fields[0];
1875
+ const provider = new EmbeddingProvider(
1876
+ {
1877
+ dimensions: embeddingConfig.dimensions,
1878
+ provider: embeddingConfig.provider,
1879
+ localModel: embeddingConfig.localModel,
1880
+ aiModel: embeddingConfig.aiModel,
1881
+ fallbackToAI: embeddingConfig.fallbackToAI
1882
+ },
1883
+ this.ai
1884
+ );
1885
+ const model = provider.getModelName();
1886
+ const projectConfig = ObjectRegistry.getProjectEmbeddingConfig();
1887
+ const storage = projectConfig?.storage || "json";
1888
+ const vector = storage === "native" ? this.systemDb.vector : void 0;
1889
+ const scored = await EmbeddingStorage.searchSimilar(
1890
+ this.systemDb,
1891
+ this._itemClass.name,
1892
+ embedding,
1893
+ {
1894
+ field: searchField,
1895
+ model,
1896
+ limit,
1897
+ minSimilarity
1898
+ },
1899
+ vector
1900
+ );
1901
+ if (scored.length === 0) {
1902
+ return [];
1903
+ }
1904
+ const objectIds = scored.map((s) => s.objectId);
1905
+ const similarityMap = new Map(
1906
+ scored.map((s) => [s.objectId, s.similarity])
1907
+ );
1908
+ const whereClause = where ? { "id in": objectIds, ...where } : { "id in": objectIds };
1909
+ const objects = await this.list({
1910
+ where: whereClause
1911
+ });
1912
+ const results = objects.map((obj) => {
1913
+ obj._similarity = similarityMap.get(obj.id) || 0;
1914
+ return obj;
1915
+ });
1916
+ results.sort((a, b) => b._similarity - a._similarity);
1917
+ return results;
1918
+ }
1919
+ /**
1920
+ * Generate missing embeddings for all objects in the collection
1921
+ *
1922
+ * Batch generates embeddings for objects that don't have them yet
1923
+ * or have stale embeddings.
1924
+ *
1925
+ * @param options - Generation options
1926
+ * @param options.batchSize - Number of objects to process at once (default: 50)
1927
+ * @param options.onProgress - Progress callback
1928
+ * @returns Promise resolving to generation statistics
1929
+ *
1930
+ * @example
1931
+ * ```typescript
1932
+ * const stats = await articles.generateMissingEmbeddings({
1933
+ * batchSize: 100,
1934
+ * onProgress: ({ completed, total }) => {
1935
+ * console.log(`Progress: ${completed}/${total}`);
1936
+ * }
1937
+ * });
1938
+ *
1939
+ * console.log(`Generated: ${stats.generated}, Skipped: ${stats.skipped}`);
1940
+ * ```
1941
+ */
1942
+ async generateMissingEmbeddings(options = {}) {
1943
+ const { batchSize = 50, onProgress } = options;
1944
+ const embeddingConfig = ObjectRegistry.resolveEmbeddingConfig(
1945
+ this._itemClass.name
1946
+ );
1947
+ if (!embeddingConfig) {
1948
+ throw new Error(
1949
+ `No embedding configuration found for ${this._itemClass.name}.`
1950
+ );
1951
+ }
1952
+ const total = await this.count({});
1953
+ let completed = 0;
1954
+ let generated = 0;
1955
+ let skipped = 0;
1956
+ let offset = 0;
1957
+ while (offset < total) {
1958
+ const batch = await this.list({ limit: batchSize, offset });
1959
+ for (const obj of batch) {
1960
+ try {
1961
+ const hasStale = await obj.hasStaleEmbeddings();
1962
+ if (hasStale) {
1963
+ await obj.generateEmbeddings();
1964
+ generated++;
1965
+ } else {
1966
+ skipped++;
1967
+ }
1968
+ } catch (error) {
1969
+ logger.warn(`Failed to generate embeddings for ${obj.id}`, {
1970
+ error: error instanceof Error ? error.message : error
1971
+ });
1972
+ skipped++;
1973
+ }
1974
+ completed++;
1975
+ if (onProgress) {
1976
+ onProgress({ completed, total });
1977
+ }
1978
+ }
1979
+ offset += batchSize;
1980
+ }
1981
+ return { generated, skipped };
1982
+ }
1983
+ }
1984
+ export {
1985
+ SmrtCollection
1986
+ };
1987
+ //# sourceMappingURL=collection.js.map