@proseql/core 0.1.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 (342) hide show
  1. package/LICENSE +21 -0
  2. package/dist/errors/crud-errors.d.ts +98 -0
  3. package/dist/errors/crud-errors.d.ts.map +1 -0
  4. package/dist/errors/crud-errors.js +23 -0
  5. package/dist/errors/crud-errors.js.map +1 -0
  6. package/dist/errors/index.d.ts +16 -0
  7. package/dist/errors/index.d.ts.map +1 -0
  8. package/dist/errors/index.js +12 -0
  9. package/dist/errors/index.js.map +1 -0
  10. package/dist/errors/migration-errors.d.ts +22 -0
  11. package/dist/errors/migration-errors.d.ts.map +1 -0
  12. package/dist/errors/migration-errors.js +14 -0
  13. package/dist/errors/migration-errors.js.map +1 -0
  14. package/dist/errors/plugin-errors.d.ts +15 -0
  15. package/dist/errors/plugin-errors.d.ts.map +1 -0
  16. package/dist/errors/plugin-errors.js +11 -0
  17. package/dist/errors/plugin-errors.js.map +1 -0
  18. package/dist/errors/query-errors.d.ts +31 -0
  19. package/dist/errors/query-errors.d.ts.map +1 -0
  20. package/dist/errors/query-errors.js +11 -0
  21. package/dist/errors/query-errors.js.map +1 -0
  22. package/dist/errors/storage-errors.d.ts +30 -0
  23. package/dist/errors/storage-errors.d.ts.map +1 -0
  24. package/dist/errors/storage-errors.js +11 -0
  25. package/dist/errors/storage-errors.js.map +1 -0
  26. package/dist/factories/crud-factory-with-relationships.d.ts +28 -0
  27. package/dist/factories/crud-factory-with-relationships.d.ts.map +1 -0
  28. package/dist/factories/crud-factory-with-relationships.js +8 -0
  29. package/dist/factories/crud-factory-with-relationships.js.map +1 -0
  30. package/dist/factories/crud-factory.d.ts +25 -0
  31. package/dist/factories/crud-factory.d.ts.map +1 -0
  32. package/dist/factories/crud-factory.js +8 -0
  33. package/dist/factories/crud-factory.js.map +1 -0
  34. package/dist/factories/database-effect.d.ts +241 -0
  35. package/dist/factories/database-effect.d.ts.map +1 -0
  36. package/dist/factories/database-effect.js +859 -0
  37. package/dist/factories/database-effect.js.map +1 -0
  38. package/dist/hooks/hook-runner.d.ts +60 -0
  39. package/dist/hooks/hook-runner.d.ts.map +1 -0
  40. package/dist/hooks/hook-runner.js +107 -0
  41. package/dist/hooks/hook-runner.js.map +1 -0
  42. package/dist/index.d.ts +84 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +110 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/indexes/index-lookup.d.ts +33 -0
  47. package/dist/indexes/index-lookup.d.ts.map +1 -0
  48. package/dist/indexes/index-lookup.js +180 -0
  49. package/dist/indexes/index-lookup.js.map +1 -0
  50. package/dist/indexes/index-manager.d.ts +118 -0
  51. package/dist/indexes/index-manager.d.ts.map +1 -0
  52. package/dist/indexes/index-manager.js +345 -0
  53. package/dist/indexes/index-manager.js.map +1 -0
  54. package/dist/indexes/search-index.d.ts +179 -0
  55. package/dist/indexes/search-index.d.ts.map +1 -0
  56. package/dist/indexes/search-index.js +405 -0
  57. package/dist/indexes/search-index.js.map +1 -0
  58. package/dist/migrations/migration-runner.d.ts +70 -0
  59. package/dist/migrations/migration-runner.d.ts.map +1 -0
  60. package/dist/migrations/migration-runner.js +271 -0
  61. package/dist/migrations/migration-runner.js.map +1 -0
  62. package/dist/migrations/migration-types.d.ts +63 -0
  63. package/dist/migrations/migration-types.d.ts.map +1 -0
  64. package/dist/migrations/migration-types.js +5 -0
  65. package/dist/migrations/migration-types.js.map +1 -0
  66. package/dist/operations/crud/create-with-relationships.d.ts +44 -0
  67. package/dist/operations/crud/create-with-relationships.d.ts.map +1 -0
  68. package/dist/operations/crud/create-with-relationships.js +483 -0
  69. package/dist/operations/crud/create-with-relationships.js.map +1 -0
  70. package/dist/operations/crud/create.d.ts +48 -0
  71. package/dist/operations/crud/create.d.ts.map +1 -0
  72. package/dist/operations/crud/create.js +333 -0
  73. package/dist/operations/crud/create.js.map +1 -0
  74. package/dist/operations/crud/delete-with-relationships.d.ts +63 -0
  75. package/dist/operations/crud/delete-with-relationships.d.ts.map +1 -0
  76. package/dist/operations/crud/delete-with-relationships.js +395 -0
  77. package/dist/operations/crud/delete-with-relationships.js.map +1 -0
  78. package/dist/operations/crud/delete.d.ts +58 -0
  79. package/dist/operations/crud/delete.d.ts.map +1 -0
  80. package/dist/operations/crud/delete.js +267 -0
  81. package/dist/operations/crud/delete.js.map +1 -0
  82. package/dist/operations/crud/unique-check.d.ts +114 -0
  83. package/dist/operations/crud/unique-check.d.ts.map +1 -0
  84. package/dist/operations/crud/unique-check.js +383 -0
  85. package/dist/operations/crud/unique-check.js.map +1 -0
  86. package/dist/operations/crud/update-with-relationships.d.ts +45 -0
  87. package/dist/operations/crud/update-with-relationships.d.ts.map +1 -0
  88. package/dist/operations/crud/update-with-relationships.js +516 -0
  89. package/dist/operations/crud/update-with-relationships.js.map +1 -0
  90. package/dist/operations/crud/update.d.ts +91 -0
  91. package/dist/operations/crud/update.d.ts.map +1 -0
  92. package/dist/operations/crud/update.js +505 -0
  93. package/dist/operations/crud/update.js.map +1 -0
  94. package/dist/operations/crud/upsert.d.ts +52 -0
  95. package/dist/operations/crud/upsert.d.ts.map +1 -0
  96. package/dist/operations/crud/upsert.js +386 -0
  97. package/dist/operations/crud/upsert.js.map +1 -0
  98. package/dist/operations/query/aggregate.d.ts +30 -0
  99. package/dist/operations/query/aggregate.d.ts.map +1 -0
  100. package/dist/operations/query/aggregate.js +227 -0
  101. package/dist/operations/query/aggregate.js.map +1 -0
  102. package/dist/operations/query/cursor-stream.d.ts +18 -0
  103. package/dist/operations/query/cursor-stream.d.ts.map +1 -0
  104. package/dist/operations/query/cursor-stream.js +199 -0
  105. package/dist/operations/query/cursor-stream.js.map +1 -0
  106. package/dist/operations/query/filter-stream.d.ts +12 -0
  107. package/dist/operations/query/filter-stream.d.ts.map +1 -0
  108. package/dist/operations/query/filter-stream.js +167 -0
  109. package/dist/operations/query/filter-stream.js.map +1 -0
  110. package/dist/operations/query/filter.d.ts +13 -0
  111. package/dist/operations/query/filter.d.ts.map +1 -0
  112. package/dist/operations/query/filter.js +267 -0
  113. package/dist/operations/query/filter.js.map +1 -0
  114. package/dist/operations/query/paginate-stream.d.ts +11 -0
  115. package/dist/operations/query/paginate-stream.d.ts.map +1 -0
  116. package/dist/operations/query/paginate-stream.js +22 -0
  117. package/dist/operations/query/paginate-stream.js.map +1 -0
  118. package/dist/operations/query/query-helpers.d.ts +14 -0
  119. package/dist/operations/query/query-helpers.d.ts.map +1 -0
  120. package/dist/operations/query/query-helpers.js +22 -0
  121. package/dist/operations/query/query-helpers.js.map +1 -0
  122. package/dist/operations/query/resolve-computed.d.ts +142 -0
  123. package/dist/operations/query/resolve-computed.d.ts.map +1 -0
  124. package/dist/operations/query/resolve-computed.js +197 -0
  125. package/dist/operations/query/resolve-computed.js.map +1 -0
  126. package/dist/operations/query/search.d.ts +110 -0
  127. package/dist/operations/query/search.d.ts.map +1 -0
  128. package/dist/operations/query/search.js +188 -0
  129. package/dist/operations/query/search.js.map +1 -0
  130. package/dist/operations/query/select-stream.d.ts +27 -0
  131. package/dist/operations/query/select-stream.d.ts.map +1 -0
  132. package/dist/operations/query/select-stream.js +88 -0
  133. package/dist/operations/query/select-stream.js.map +1 -0
  134. package/dist/operations/query/select.d.ts +54 -0
  135. package/dist/operations/query/select.d.ts.map +1 -0
  136. package/dist/operations/query/select.js +159 -0
  137. package/dist/operations/query/select.js.map +1 -0
  138. package/dist/operations/query/sort-stream.d.ts +46 -0
  139. package/dist/operations/query/sort-stream.d.ts.map +1 -0
  140. package/dist/operations/query/sort-stream.js +158 -0
  141. package/dist/operations/query/sort-stream.js.map +1 -0
  142. package/dist/operations/query/sort.d.ts +9 -0
  143. package/dist/operations/query/sort.d.ts.map +1 -0
  144. package/dist/operations/query/sort.js +58 -0
  145. package/dist/operations/query/sort.js.map +1 -0
  146. package/dist/operations/relationships/populate-stream.d.ts +29 -0
  147. package/dist/operations/relationships/populate-stream.d.ts.map +1 -0
  148. package/dist/operations/relationships/populate-stream.js +159 -0
  149. package/dist/operations/relationships/populate-stream.js.map +1 -0
  150. package/dist/operations/relationships/populate.d.ts +15 -0
  151. package/dist/operations/relationships/populate.d.ts.map +1 -0
  152. package/dist/operations/relationships/populate.js +228 -0
  153. package/dist/operations/relationships/populate.js.map +1 -0
  154. package/dist/plugins/plugin-hooks.d.ts +25 -0
  155. package/dist/plugins/plugin-hooks.d.ts.map +1 -0
  156. package/dist/plugins/plugin-hooks.js +64 -0
  157. package/dist/plugins/plugin-hooks.js.map +1 -0
  158. package/dist/plugins/plugin-registry.d.ts +26 -0
  159. package/dist/plugins/plugin-registry.d.ts.map +1 -0
  160. package/dist/plugins/plugin-registry.js +150 -0
  161. package/dist/plugins/plugin-registry.js.map +1 -0
  162. package/dist/plugins/plugin-types.d.ts +95 -0
  163. package/dist/plugins/plugin-types.d.ts.map +1 -0
  164. package/dist/plugins/plugin-types.js +6 -0
  165. package/dist/plugins/plugin-types.js.map +1 -0
  166. package/dist/plugins/plugin-validation.d.ts +49 -0
  167. package/dist/plugins/plugin-validation.d.ts.map +1 -0
  168. package/dist/plugins/plugin-validation.js +295 -0
  169. package/dist/plugins/plugin-validation.js.map +1 -0
  170. package/dist/reactive/change-event.d.ts +44 -0
  171. package/dist/reactive/change-event.d.ts.map +1 -0
  172. package/dist/reactive/change-event.js +49 -0
  173. package/dist/reactive/change-event.js.map +1 -0
  174. package/dist/reactive/change-pubsub.d.ts +32 -0
  175. package/dist/reactive/change-pubsub.d.ts.map +1 -0
  176. package/dist/reactive/change-pubsub.js +31 -0
  177. package/dist/reactive/change-pubsub.js.map +1 -0
  178. package/dist/reactive/evaluate-query.d.ts +62 -0
  179. package/dist/reactive/evaluate-query.d.ts.map +1 -0
  180. package/dist/reactive/evaluate-query.js +57 -0
  181. package/dist/reactive/evaluate-query.js.map +1 -0
  182. package/dist/reactive/watch-by-id.d.ts +53 -0
  183. package/dist/reactive/watch-by-id.d.ts.map +1 -0
  184. package/dist/reactive/watch-by-id.js +55 -0
  185. package/dist/reactive/watch-by-id.js.map +1 -0
  186. package/dist/reactive/watch.d.ts +78 -0
  187. package/dist/reactive/watch.d.ts.map +1 -0
  188. package/dist/reactive/watch.js +133 -0
  189. package/dist/reactive/watch.js.map +1 -0
  190. package/dist/serializers/codecs/hjson.d.ts +33 -0
  191. package/dist/serializers/codecs/hjson.d.ts.map +1 -0
  192. package/dist/serializers/codecs/hjson.js +40 -0
  193. package/dist/serializers/codecs/hjson.js.map +1 -0
  194. package/dist/serializers/codecs/json.d.ts +22 -0
  195. package/dist/serializers/codecs/json.d.ts.map +1 -0
  196. package/dist/serializers/codecs/json.js +28 -0
  197. package/dist/serializers/codecs/json.js.map +1 -0
  198. package/dist/serializers/codecs/json5.d.ts +26 -0
  199. package/dist/serializers/codecs/json5.d.ts.map +1 -0
  200. package/dist/serializers/codecs/json5.js +33 -0
  201. package/dist/serializers/codecs/json5.js.map +1 -0
  202. package/dist/serializers/codecs/jsonc.d.ts +29 -0
  203. package/dist/serializers/codecs/jsonc.d.ts.map +1 -0
  204. package/dist/serializers/codecs/jsonc.js +38 -0
  205. package/dist/serializers/codecs/jsonc.js.map +1 -0
  206. package/dist/serializers/codecs/jsonl.d.ts +17 -0
  207. package/dist/serializers/codecs/jsonl.d.ts.map +1 -0
  208. package/dist/serializers/codecs/jsonl.js +31 -0
  209. package/dist/serializers/codecs/jsonl.js.map +1 -0
  210. package/dist/serializers/codecs/prose.d.ts +419 -0
  211. package/dist/serializers/codecs/prose.d.ts.map +1 -0
  212. package/dist/serializers/codecs/prose.js +1060 -0
  213. package/dist/serializers/codecs/prose.js.map +1 -0
  214. package/dist/serializers/codecs/toml.d.ts +23 -0
  215. package/dist/serializers/codecs/toml.d.ts.map +1 -0
  216. package/dist/serializers/codecs/toml.js +66 -0
  217. package/dist/serializers/codecs/toml.js.map +1 -0
  218. package/dist/serializers/codecs/toon.d.ts +20 -0
  219. package/dist/serializers/codecs/toon.d.ts.map +1 -0
  220. package/dist/serializers/codecs/toon.js +33 -0
  221. package/dist/serializers/codecs/toon.js.map +1 -0
  222. package/dist/serializers/codecs/yaml.d.ts +24 -0
  223. package/dist/serializers/codecs/yaml.d.ts.map +1 -0
  224. package/dist/serializers/codecs/yaml.js +31 -0
  225. package/dist/serializers/codecs/yaml.js.map +1 -0
  226. package/dist/serializers/format-codec.d.ts +53 -0
  227. package/dist/serializers/format-codec.d.ts.map +1 -0
  228. package/dist/serializers/format-codec.js +148 -0
  229. package/dist/serializers/format-codec.js.map +1 -0
  230. package/dist/serializers/presets.d.ts +48 -0
  231. package/dist/serializers/presets.d.ts.map +1 -0
  232. package/dist/serializers/presets.js +72 -0
  233. package/dist/serializers/presets.js.map +1 -0
  234. package/dist/serializers/serializer-service.d.ts +11 -0
  235. package/dist/serializers/serializer-service.d.ts.map +1 -0
  236. package/dist/serializers/serializer-service.js +4 -0
  237. package/dist/serializers/serializer-service.js.map +1 -0
  238. package/dist/state/collection-state.d.ts +19 -0
  239. package/dist/state/collection-state.d.ts.map +1 -0
  240. package/dist/state/collection-state.js +15 -0
  241. package/dist/state/collection-state.js.map +1 -0
  242. package/dist/state/state-operations.d.ts +38 -0
  243. package/dist/state/state-operations.d.ts.map +1 -0
  244. package/dist/state/state-operations.js +65 -0
  245. package/dist/state/state-operations.js.map +1 -0
  246. package/dist/storage/in-memory-adapter-layer.d.ts +16 -0
  247. package/dist/storage/in-memory-adapter-layer.d.ts.map +1 -0
  248. package/dist/storage/in-memory-adapter-layer.js +81 -0
  249. package/dist/storage/in-memory-adapter-layer.js.map +1 -0
  250. package/dist/storage/persistence-effect.d.ts +244 -0
  251. package/dist/storage/persistence-effect.d.ts.map +1 -0
  252. package/dist/storage/persistence-effect.js +551 -0
  253. package/dist/storage/persistence-effect.js.map +1 -0
  254. package/dist/storage/storage-service.d.ts +22 -0
  255. package/dist/storage/storage-service.d.ts.map +1 -0
  256. package/dist/storage/storage-service.js +4 -0
  257. package/dist/storage/storage-service.js.map +1 -0
  258. package/dist/storage/transforms.d.ts +183 -0
  259. package/dist/storage/transforms.d.ts.map +1 -0
  260. package/dist/storage/transforms.js +263 -0
  261. package/dist/storage/transforms.js.map +1 -0
  262. package/dist/transactions/transaction.d.ts +87 -0
  263. package/dist/transactions/transaction.d.ts.map +1 -0
  264. package/dist/transactions/transaction.js +240 -0
  265. package/dist/transactions/transaction.js.map +1 -0
  266. package/dist/types/aggregate-types.d.ts +73 -0
  267. package/dist/types/aggregate-types.d.ts.map +1 -0
  268. package/dist/types/aggregate-types.js +14 -0
  269. package/dist/types/aggregate-types.js.map +1 -0
  270. package/dist/types/computed-types.d.ts +71 -0
  271. package/dist/types/computed-types.d.ts.map +1 -0
  272. package/dist/types/computed-types.js +8 -0
  273. package/dist/types/computed-types.js.map +1 -0
  274. package/dist/types/crud-relationship-types.d.ts +180 -0
  275. package/dist/types/crud-relationship-types.d.ts.map +1 -0
  276. package/dist/types/crud-relationship-types.js +17 -0
  277. package/dist/types/crud-relationship-types.js.map +1 -0
  278. package/dist/types/crud-types.d.ts +343 -0
  279. package/dist/types/crud-types.d.ts.map +1 -0
  280. package/dist/types/crud-types.js +43 -0
  281. package/dist/types/crud-types.js.map +1 -0
  282. package/dist/types/cursor-types.d.ts +52 -0
  283. package/dist/types/cursor-types.d.ts.map +1 -0
  284. package/dist/types/cursor-types.js +2 -0
  285. package/dist/types/cursor-types.js.map +1 -0
  286. package/dist/types/database-config-types.d.ts +196 -0
  287. package/dist/types/database-config-types.d.ts.map +1 -0
  288. package/dist/types/database-config-types.js +11 -0
  289. package/dist/types/database-config-types.js.map +1 -0
  290. package/dist/types/hook-types.d.ts +158 -0
  291. package/dist/types/hook-types.d.ts.map +1 -0
  292. package/dist/types/hook-types.js +6 -0
  293. package/dist/types/hook-types.js.map +1 -0
  294. package/dist/types/index-types.d.ts +42 -0
  295. package/dist/types/index-types.d.ts.map +1 -0
  296. package/dist/types/index-types.js +8 -0
  297. package/dist/types/index-types.js.map +1 -0
  298. package/dist/types/operators.d.ts +5 -0
  299. package/dist/types/operators.d.ts.map +1 -0
  300. package/dist/types/operators.js +297 -0
  301. package/dist/types/operators.js.map +1 -0
  302. package/dist/types/query-overloads.d.ts +54 -0
  303. package/dist/types/query-overloads.d.ts.map +1 -0
  304. package/dist/types/query-overloads.js +3 -0
  305. package/dist/types/query-overloads.js.map +1 -0
  306. package/dist/types/reactive-types.d.ts +75 -0
  307. package/dist/types/reactive-types.d.ts.map +1 -0
  308. package/dist/types/reactive-types.js +7 -0
  309. package/dist/types/reactive-types.js.map +1 -0
  310. package/dist/types/schema-types.d.ts +56 -0
  311. package/dist/types/schema-types.d.ts.map +1 -0
  312. package/dist/types/schema-types.js +8 -0
  313. package/dist/types/schema-types.js.map +1 -0
  314. package/dist/types/search-types.d.ts +82 -0
  315. package/dist/types/search-types.d.ts.map +1 -0
  316. package/dist/types/search-types.js +110 -0
  317. package/dist/types/search-types.js.map +1 -0
  318. package/dist/types/types.d.ts +286 -0
  319. package/dist/types/types.d.ts.map +1 -0
  320. package/dist/types/types.js +2 -0
  321. package/dist/types/types.js.map +1 -0
  322. package/dist/utils/id-generator.d.ts +97 -0
  323. package/dist/utils/id-generator.d.ts.map +1 -0
  324. package/dist/utils/id-generator.js +247 -0
  325. package/dist/utils/id-generator.js.map +1 -0
  326. package/dist/utils/nested-path.d.ts +56 -0
  327. package/dist/utils/nested-path.d.ts.map +1 -0
  328. package/dist/utils/nested-path.js +119 -0
  329. package/dist/utils/nested-path.js.map +1 -0
  330. package/dist/utils/path.d.ts +16 -0
  331. package/dist/utils/path.d.ts.map +1 -0
  332. package/dist/utils/path.js +24 -0
  333. package/dist/utils/path.js.map +1 -0
  334. package/dist/validators/foreign-key.d.ts +49 -0
  335. package/dist/validators/foreign-key.d.ts.map +1 -0
  336. package/dist/validators/foreign-key.js +153 -0
  337. package/dist/validators/foreign-key.js.map +1 -0
  338. package/dist/validators/schema-validator.d.ts +19 -0
  339. package/dist/validators/schema-validator.d.ts.map +1 -0
  340. package/dist/validators/schema-validator.js +34 -0
  341. package/dist/validators/schema-validator.js.map +1 -0
  342. package/package.json +57 -0
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Effect-based upsert operations for entities.
3
+ *
4
+ * Uses Ref<ReadonlyMap> for atomic state mutation, Effect Schema for validation,
5
+ * and typed errors (ValidationError, ForeignKeyError).
6
+ *
7
+ * Upsert = find by `where` clause → update if exists, create if not.
8
+ */
9
+ import { Effect, PubSub, Ref } from "effect";
10
+ import { runAfterCreateHooks, runAfterUpdateHooks, runBeforeCreateHooks, runBeforeUpdateHooks, runOnChangeHooks, } from "../../hooks/hook-runner.js";
11
+ import { addManyToIndex, addToIndex, updateInIndex, } from "../../indexes/index-manager.js";
12
+ import { addToSearchIndex, updateInSearchIndex, } from "../../indexes/search-index.js";
13
+ import { generateId } from "../../utils/id-generator.js";
14
+ import { validateForeignKeysEffect } from "../../validators/foreign-key.js";
15
+ import { validateEntity } from "../../validators/schema-validator.js";
16
+ import { checkBatchUniqueConstraints, checkUniqueConstraints, validateUpsertWhere, } from "./unique-check.js";
17
+ import { applyUpdates } from "./update.js";
18
+ // ============================================================================
19
+ // Find by Where Clause
20
+ // ============================================================================
21
+ /**
22
+ * Find an entity in a ReadonlyMap matching all fields in the where clause.
23
+ * If `where` contains `id`, uses O(1) lookup. Otherwise scans all values.
24
+ */
25
+ const findByWhere = (map, where) => {
26
+ // Fast path: if where has `id`, use direct lookup
27
+ if ("id" in where && typeof where.id === "string") {
28
+ const candidate = map.get(where.id);
29
+ if (candidate === undefined)
30
+ return undefined;
31
+ // Verify all other where fields match
32
+ for (const [key, value] of Object.entries(where)) {
33
+ if (candidate[key] !== value) {
34
+ return undefined;
35
+ }
36
+ }
37
+ return candidate;
38
+ }
39
+ // Slow path: scan all entities
40
+ for (const entity of map.values()) {
41
+ let matches = true;
42
+ for (const [key, value] of Object.entries(where)) {
43
+ if (entity[key] !== value) {
44
+ matches = false;
45
+ break;
46
+ }
47
+ }
48
+ if (matches)
49
+ return entity;
50
+ }
51
+ return undefined;
52
+ };
53
+ // ============================================================================
54
+ // Upsert Single Entity
55
+ // ============================================================================
56
+ /**
57
+ * Upsert a single entity: find by `where`, update if exists, create if not.
58
+ *
59
+ * Steps:
60
+ * 1. Look up entity by where clause in Ref state
61
+ * 2a. If found: run beforeUpdate hooks, apply update operators, validate, update in state, run afterUpdate/onChange
62
+ * 2b. If not found: merge where + create data, generate ID/timestamps, validate, run beforeCreate hooks, add to state, run afterCreate/onChange
63
+ * 3. Validate foreign key constraints
64
+ * 4. Return entity with __action metadata
65
+ */
66
+ export const upsert = (collectionName, schema, relationships, ref, stateRefs, indexes, hooks, uniqueFields = [], searchIndexRef, searchIndexFields, changePubSub) => (input) => Effect.gen(function* () {
67
+ const where = input.where;
68
+ // Validate that the where clause targets a unique field or id
69
+ yield* validateUpsertWhere(where, uniqueFields, collectionName);
70
+ const currentMap = yield* Ref.get(ref);
71
+ const existing = findByWhere(currentMap, where);
72
+ if (existing !== undefined) {
73
+ // === UPDATE PATH ===
74
+ // Run beforeUpdate hooks (can transform the update payload)
75
+ const transformedUpdates = yield* runBeforeUpdateHooks(hooks?.beforeUpdate, {
76
+ operation: "update",
77
+ collection: collectionName,
78
+ id: existing.id,
79
+ existing,
80
+ update: input.update,
81
+ });
82
+ const updated = applyUpdates(existing, transformedUpdates);
83
+ // Validate through Effect Schema
84
+ const validated = yield* validateEntity(schema, updated);
85
+ // Validate foreign keys if relationship fields were updated
86
+ const relationshipFields = Object.keys(relationships).map((field) => relationships[field].foreignKey || `${field}Id`);
87
+ const hasRelationshipUpdate = Object.keys(transformedUpdates).some((key) => relationshipFields.includes(key));
88
+ if (hasRelationshipUpdate) {
89
+ yield* validateForeignKeysEffect(validated, collectionName, relationships, stateRefs);
90
+ }
91
+ // Atomically update in state
92
+ yield* Ref.update(ref, (map) => {
93
+ const next = new Map(map);
94
+ next.set(existing.id, validated);
95
+ return next;
96
+ });
97
+ // Update indexes if provided
98
+ if (indexes && indexes.size > 0) {
99
+ yield* updateInIndex(indexes, existing, validated);
100
+ }
101
+ // Update search index if configured
102
+ if (searchIndexRef &&
103
+ searchIndexFields &&
104
+ searchIndexFields.length > 0) {
105
+ yield* updateInSearchIndex(searchIndexRef, existing, validated, searchIndexFields);
106
+ }
107
+ // Run afterUpdate hooks (fire-and-forget, errors swallowed)
108
+ yield* runAfterUpdateHooks(hooks?.afterUpdate, {
109
+ operation: "update",
110
+ collection: collectionName,
111
+ id: existing.id,
112
+ previous: existing,
113
+ current: validated,
114
+ update: transformedUpdates,
115
+ });
116
+ // Run onChange hooks with type: "update" (fire-and-forget, errors swallowed)
117
+ yield* runOnChangeHooks(hooks?.onChange, {
118
+ type: "update",
119
+ collection: collectionName,
120
+ id: existing.id,
121
+ previous: existing,
122
+ current: validated,
123
+ });
124
+ // Publish change event to reactive subscribers
125
+ if (changePubSub) {
126
+ yield* PubSub.publish(changePubSub, {
127
+ collection: collectionName,
128
+ operation: "update",
129
+ });
130
+ }
131
+ return { ...validated, __action: "updated" };
132
+ }
133
+ // === CREATE PATH ===
134
+ const id = (typeof where.id === "string" ? where.id : undefined) || generateId();
135
+ const now = new Date().toISOString();
136
+ const createData = {
137
+ ...where,
138
+ ...input.create,
139
+ id,
140
+ createdAt: now,
141
+ updatedAt: now,
142
+ };
143
+ // Validate through Effect Schema
144
+ const validated = yield* validateEntity(schema, createData);
145
+ // Run beforeCreate hooks (can transform the entity)
146
+ const entity = yield* runBeforeCreateHooks(hooks?.beforeCreate, {
147
+ operation: "create",
148
+ collection: collectionName,
149
+ data: validated,
150
+ });
151
+ // Check unique constraints
152
+ yield* checkUniqueConstraints(entity, currentMap, uniqueFields, collectionName);
153
+ // Validate foreign keys
154
+ yield* validateForeignKeysEffect(entity, collectionName, relationships, stateRefs);
155
+ // Atomically add to state
156
+ yield* Ref.update(ref, (map) => {
157
+ const next = new Map(map);
158
+ next.set(id, entity);
159
+ return next;
160
+ });
161
+ // Update indexes if provided
162
+ if (indexes && indexes.size > 0) {
163
+ yield* addToIndex(indexes, entity);
164
+ }
165
+ // Update search index if configured
166
+ if (searchIndexRef && searchIndexFields && searchIndexFields.length > 0) {
167
+ yield* addToSearchIndex(searchIndexRef, entity, searchIndexFields);
168
+ }
169
+ // Run afterCreate hooks (fire-and-forget, errors swallowed)
170
+ yield* runAfterCreateHooks(hooks?.afterCreate, {
171
+ operation: "create",
172
+ collection: collectionName,
173
+ entity,
174
+ });
175
+ // Run onChange hooks with type: "create" (fire-and-forget, errors swallowed)
176
+ yield* runOnChangeHooks(hooks?.onChange, {
177
+ type: "create",
178
+ collection: collectionName,
179
+ entity,
180
+ });
181
+ // Publish change event to reactive subscribers
182
+ if (changePubSub) {
183
+ yield* PubSub.publish(changePubSub, {
184
+ collection: collectionName,
185
+ operation: "create",
186
+ });
187
+ }
188
+ return { ...entity, __action: "created" };
189
+ });
190
+ // ============================================================================
191
+ // Upsert Multiple Entities
192
+ // ============================================================================
193
+ /**
194
+ * Upsert multiple entities efficiently.
195
+ *
196
+ * For each input:
197
+ * - If entity matches where clause: apply updates (or skip if unchanged)
198
+ * - If no match: create new entity
199
+ *
200
+ * All changes validated and applied atomically.
201
+ * Returns categorized results: created, updated, unchanged.
202
+ *
203
+ * Runs hooks per entity:
204
+ * - Create path: beforeCreate → mutation → afterCreate → onChange("create")
205
+ * - Update path: beforeUpdate → mutation → afterUpdate → onChange("update")
206
+ */
207
+ export const upsertMany = (collectionName, schema, relationships, ref, stateRefs, indexes, hooks, uniqueFields = [], searchIndexRef, searchIndexFields, changePubSub) => (inputs) => Effect.gen(function* () {
208
+ // Validate all where clauses target unique fields or id
209
+ for (const input of inputs) {
210
+ const where = input.where;
211
+ yield* validateUpsertWhere(where, uniqueFields, collectionName);
212
+ }
213
+ const currentMap = yield* Ref.get(ref);
214
+ const created = [];
215
+ const updated = [];
216
+ const unchanged = [];
217
+ const now = new Date().toISOString();
218
+ // Phase 1: Process all inputs, validate, run before-hooks, and categorize
219
+ const toCreate = [];
220
+ const toUpdate = [];
221
+ for (let i = 0; i < inputs.length; i++) {
222
+ const input = inputs[i];
223
+ if (!input)
224
+ continue;
225
+ const where = input.where;
226
+ const existing = findByWhere(currentMap, where);
227
+ if (existing !== undefined) {
228
+ // === UPDATE PATH ===
229
+ // Check if update would change anything
230
+ const wouldChange = Object.keys(input.update).some((key) => {
231
+ const updateValue = input.update[key];
232
+ const currentValue = existing[key];
233
+ // Operator-based updates always cause a change
234
+ if (typeof updateValue === "object" &&
235
+ updateValue !== null &&
236
+ !Array.isArray(updateValue)) {
237
+ return true;
238
+ }
239
+ return updateValue !== currentValue;
240
+ });
241
+ if (!wouldChange) {
242
+ unchanged.push(existing);
243
+ continue;
244
+ }
245
+ // Run beforeUpdate hooks (can transform the update payload)
246
+ const transformedUpdates = yield* runBeforeUpdateHooks(hooks?.beforeUpdate, {
247
+ operation: "update",
248
+ collection: collectionName,
249
+ id: existing.id,
250
+ existing,
251
+ update: input.update,
252
+ });
253
+ // Apply updates with (possibly transformed) payload
254
+ const updatedEntity = applyUpdates(existing, transformedUpdates);
255
+ // Validate
256
+ const validated = yield* validateEntity(schema, updatedEntity);
257
+ toUpdate.push({
258
+ oldEntity: existing,
259
+ newEntity: validated,
260
+ transformedUpdates,
261
+ });
262
+ }
263
+ else {
264
+ // === CREATE PATH ===
265
+ const id = (typeof where.id === "string" ? where.id : undefined) ||
266
+ generateId();
267
+ const createData = {
268
+ ...where,
269
+ ...input.create,
270
+ id,
271
+ createdAt: now,
272
+ updatedAt: now,
273
+ };
274
+ // Validate through schema first
275
+ const validated = yield* validateEntity(schema, createData);
276
+ // Run beforeCreate hooks (can transform the entity)
277
+ const entity = yield* runBeforeCreateHooks(hooks?.beforeCreate, {
278
+ operation: "create",
279
+ collection: collectionName,
280
+ data: validated,
281
+ });
282
+ toCreate.push(entity);
283
+ }
284
+ }
285
+ // Phase 2: Check unique constraints for entities being created
286
+ // This checks against existing data and also inter-batch conflicts
287
+ if (toCreate.length > 0) {
288
+ yield* checkBatchUniqueConstraints(toCreate, currentMap, uniqueFields, collectionName);
289
+ }
290
+ // Phase 3: Validate foreign keys for all entities being created or updated
291
+ for (const entity of toCreate) {
292
+ yield* validateForeignKeysEffect(entity, collectionName, relationships, stateRefs);
293
+ }
294
+ for (const { newEntity } of toUpdate) {
295
+ yield* validateForeignKeysEffect(newEntity, collectionName, relationships, stateRefs);
296
+ }
297
+ // Phase 4: Atomically apply all changes to state
298
+ if (toCreate.length > 0 || toUpdate.length > 0) {
299
+ yield* Ref.update(ref, (map) => {
300
+ const next = new Map(map);
301
+ for (const entity of toCreate) {
302
+ next.set(entity.id, entity);
303
+ }
304
+ for (const { newEntity } of toUpdate) {
305
+ next.set(newEntity.id, newEntity);
306
+ }
307
+ return next;
308
+ });
309
+ }
310
+ // Phase 5: Update indexes if provided
311
+ if (indexes && indexes.size > 0) {
312
+ // Use batch operation for created entities
313
+ if (toCreate.length > 0) {
314
+ yield* addManyToIndex(indexes, toCreate);
315
+ }
316
+ // Update indexes for updated entities
317
+ for (const { oldEntity, newEntity } of toUpdate) {
318
+ yield* updateInIndex(indexes, oldEntity, newEntity);
319
+ }
320
+ }
321
+ // Update search index if configured
322
+ if (searchIndexRef && searchIndexFields && searchIndexFields.length > 0) {
323
+ // Add created entities to search index
324
+ for (const entity of toCreate) {
325
+ yield* addToSearchIndex(searchIndexRef, entity, searchIndexFields);
326
+ }
327
+ // Update search index for updated entities
328
+ for (const { oldEntity, newEntity } of toUpdate) {
329
+ yield* updateInSearchIndex(searchIndexRef, oldEntity, newEntity, searchIndexFields);
330
+ }
331
+ }
332
+ // Phase 6: Run after-hooks and onChange hooks for created entities
333
+ for (const entity of toCreate) {
334
+ // Run afterCreate hooks (fire-and-forget, errors swallowed)
335
+ yield* runAfterCreateHooks(hooks?.afterCreate, {
336
+ operation: "create",
337
+ collection: collectionName,
338
+ entity,
339
+ });
340
+ // Run onChange hooks with type: "create" (fire-and-forget, errors swallowed)
341
+ yield* runOnChangeHooks(hooks?.onChange, {
342
+ type: "create",
343
+ collection: collectionName,
344
+ entity,
345
+ });
346
+ }
347
+ // Phase 7: Run after-hooks and onChange hooks for updated entities
348
+ for (const { oldEntity, newEntity, transformedUpdates } of toUpdate) {
349
+ // Run afterUpdate hooks (fire-and-forget, errors swallowed)
350
+ yield* runAfterUpdateHooks(hooks?.afterUpdate, {
351
+ operation: "update",
352
+ collection: collectionName,
353
+ id: newEntity.id,
354
+ previous: oldEntity,
355
+ current: newEntity,
356
+ update: transformedUpdates,
357
+ });
358
+ // Run onChange hooks with type: "update" (fire-and-forget, errors swallowed)
359
+ yield* runOnChangeHooks(hooks?.onChange, {
360
+ type: "update",
361
+ collection: collectionName,
362
+ id: newEntity.id,
363
+ previous: oldEntity,
364
+ current: newEntity,
365
+ });
366
+ }
367
+ created.push(...toCreate);
368
+ updated.push(...toUpdate.map(({ newEntity }) => newEntity));
369
+ // Publish change events to reactive subscribers
370
+ // Publish a "create" event if any entities were created
371
+ if (changePubSub && toCreate.length > 0) {
372
+ yield* PubSub.publish(changePubSub, {
373
+ collection: collectionName,
374
+ operation: "create",
375
+ });
376
+ }
377
+ // Publish an "update" event if any entities were updated
378
+ if (changePubSub && toUpdate.length > 0) {
379
+ yield* PubSub.publish(changePubSub, {
380
+ collection: collectionName,
381
+ operation: "update",
382
+ });
383
+ }
384
+ return { created, updated, unchanged };
385
+ });
386
+ //# sourceMappingURL=upsert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upsert.js","sourceRoot":"","sources":["../../../src/operations/crud/upsert.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Aggregate operations for computing scalar values from entity collections.
3
+ *
4
+ * Implements count, sum, avg, min, max aggregations in a single pass.
5
+ * All aggregates are computed simultaneously for efficiency.
6
+ */
7
+ import type { AggregateResult, GroupedAggregateConfig, GroupedAggregateResult, ScalarAggregateConfig } from "../../types/aggregate-types.js";
8
+ /**
9
+ * Compute scalar aggregates over an array of entities.
10
+ *
11
+ * Performs a single-pass reduction computing all requested aggregates simultaneously.
12
+ * This is O(n * k) where n is entities and k is aggregate operations — effectively O(n).
13
+ *
14
+ * @param entities - Array of entities to aggregate
15
+ * @param config - Aggregate configuration specifying which operations to perform
16
+ * @returns AggregateResult with only the requested aggregations
17
+ */
18
+ export declare const computeAggregates: (entities: ReadonlyArray<Record<string, unknown>>, config: ScalarAggregateConfig) => AggregateResult;
19
+ /**
20
+ * Compute grouped aggregates over an array of entities.
21
+ *
22
+ * Partitions entities into groups based on groupBy fields, then applies
23
+ * computeAggregates within each group. Groups are ordered by first encounter.
24
+ *
25
+ * @param entities - Array of entities to aggregate
26
+ * @param config - Grouped aggregate configuration specifying groupBy and operations
27
+ * @returns GroupedAggregateResult array with group objects
28
+ */
29
+ export declare const computeGroupedAggregates: (entities: ReadonlyArray<Record<string, unknown>>, config: GroupedAggregateConfig) => GroupedAggregateResult;
30
+ //# sourceMappingURL=aggregate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../../src/operations/query/aggregate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACX,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EAEtB,qBAAqB,EACrB,MAAM,gCAAgC,CAAC;AAwNxC;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAC7B,UAAU,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAChD,QAAQ,qBAAqB,KAC3B,eASF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,GACpC,UAAU,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAChD,QAAQ,sBAAsB,KAC5B,sBA8CF,CAAC"}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Aggregate operations for computing scalar values from entity collections.
3
+ *
4
+ * Implements count, sum, avg, min, max aggregations in a single pass.
5
+ * All aggregates are computed simultaneously for efficiency.
6
+ */
7
+ import { getNestedValue } from "../../utils/nested-path.js";
8
+ /**
9
+ * Normalize a field spec (string or array) to an array.
10
+ */
11
+ const normalizeFields = (fields) => {
12
+ if (fields === undefined)
13
+ return [];
14
+ if (typeof fields === "string")
15
+ return [fields];
16
+ return fields;
17
+ };
18
+ /**
19
+ * Sentinel value to distinguish undefined from null in group keys.
20
+ * JSON.stringify converts both null and undefined to "null", so we need
21
+ * a custom approach to maintain strict equality semantics (null !== undefined).
22
+ */
23
+ const UNDEFINED_SENTINEL = "__PTDB_UNDEFINED__";
24
+ /**
25
+ * Create a group key from field values that distinguishes null from undefined.
26
+ * Uses a sentinel value to represent undefined since JSON.stringify([undefined])
27
+ * produces "[null]" which would collide with JSON.stringify([null]).
28
+ */
29
+ const createGroupKey = (values) => {
30
+ const mapped = values.map((v) => (v === undefined ? UNDEFINED_SENTINEL : v));
31
+ return JSON.stringify(mapped);
32
+ };
33
+ /**
34
+ * Parse a group key back into field values, restoring undefined from sentinel.
35
+ */
36
+ const parseGroupKey = (key) => {
37
+ const values = JSON.parse(key);
38
+ return values.map((v) => (v === UNDEFINED_SENTINEL ? undefined : v));
39
+ };
40
+ /**
41
+ * Check if a value is numeric (finite number, not NaN).
42
+ */
43
+ const isNumeric = (value) => typeof value === "number" && Number.isFinite(value);
44
+ /**
45
+ * Check if a value is valid for comparison (not null/undefined).
46
+ */
47
+ const isComparable = (value) => value !== null && value !== undefined;
48
+ /**
49
+ * Create initial accumulators based on config.
50
+ */
51
+ const createAccumulators = (config) => {
52
+ const sumFields = normalizeFields(config.sum);
53
+ const avgFields = normalizeFields(config.avg);
54
+ const minFields = normalizeFields(config.min);
55
+ const maxFields = normalizeFields(config.max);
56
+ const sum = {};
57
+ for (const field of sumFields) {
58
+ sum[field] = 0;
59
+ }
60
+ const avg = {};
61
+ for (const field of avgFields) {
62
+ avg[field] = { sum: 0, count: 0 };
63
+ }
64
+ const min = {};
65
+ for (const field of minFields) {
66
+ min[field] = undefined;
67
+ }
68
+ const max = {};
69
+ for (const field of maxFields) {
70
+ max[field] = undefined;
71
+ }
72
+ return { count: 0, sum, avg, min, max };
73
+ };
74
+ /**
75
+ * Update accumulators with a single entity.
76
+ */
77
+ const updateAccumulators = (acc, entity, config) => {
78
+ const sumFields = normalizeFields(config.sum);
79
+ const avgFields = normalizeFields(config.avg);
80
+ const minFields = normalizeFields(config.min);
81
+ const maxFields = normalizeFields(config.max);
82
+ // Count
83
+ const newCount = config.count ? acc.count + 1 : acc.count;
84
+ // Sum: accumulate numeric values, skip non-numeric
85
+ const newSum = { ...acc.sum };
86
+ for (const field of sumFields) {
87
+ const value = getNestedValue(entity, field);
88
+ if (isNumeric(value)) {
89
+ newSum[field] = (newSum[field] ?? 0) + value;
90
+ }
91
+ }
92
+ // Avg: track sum and count of numeric values
93
+ const newAvg = { ...acc.avg };
94
+ for (const field of avgFields) {
95
+ const value = getNestedValue(entity, field);
96
+ if (isNumeric(value)) {
97
+ const current = newAvg[field] ?? { sum: 0, count: 0 };
98
+ newAvg[field] = {
99
+ sum: current.sum + value,
100
+ count: current.count + 1,
101
+ };
102
+ }
103
+ }
104
+ // Min: track minimum comparable value
105
+ const newMin = { ...acc.min };
106
+ for (const field of minFields) {
107
+ const value = getNestedValue(entity, field);
108
+ if (isComparable(value)) {
109
+ const current = newMin[field];
110
+ // Type-safe comparison: we check current is undefined or compare as primitives
111
+ if (current === undefined ||
112
+ value < current) {
113
+ newMin[field] = value;
114
+ }
115
+ }
116
+ }
117
+ // Max: track maximum comparable value
118
+ const newMax = { ...acc.max };
119
+ for (const field of maxFields) {
120
+ const value = getNestedValue(entity, field);
121
+ if (isComparable(value)) {
122
+ const current = newMax[field];
123
+ // Type-safe comparison: we check current is undefined or compare as primitives
124
+ if (current === undefined ||
125
+ value > current) {
126
+ newMax[field] = value;
127
+ }
128
+ }
129
+ }
130
+ return {
131
+ count: newCount,
132
+ sum: newSum,
133
+ avg: newAvg,
134
+ min: newMin,
135
+ max: newMax,
136
+ };
137
+ };
138
+ /**
139
+ * Convert accumulators to final result.
140
+ */
141
+ const accumulatorsToResult = (acc, config) => {
142
+ const result = {};
143
+ // Include only requested aggregations
144
+ if (config.count) {
145
+ result.count = acc.count;
146
+ }
147
+ if (config.sum !== undefined) {
148
+ result.sum = acc.sum;
149
+ }
150
+ if (config.avg !== undefined) {
151
+ const avgResult = {};
152
+ for (const [field, { sum, count }] of Object.entries(acc.avg)) {
153
+ avgResult[field] = count > 0 ? sum / count : null;
154
+ }
155
+ result.avg = avgResult;
156
+ }
157
+ if (config.min !== undefined) {
158
+ result.min = acc.min;
159
+ }
160
+ if (config.max !== undefined) {
161
+ result.max = acc.max;
162
+ }
163
+ return result;
164
+ };
165
+ /**
166
+ * Compute scalar aggregates over an array of entities.
167
+ *
168
+ * Performs a single-pass reduction computing all requested aggregates simultaneously.
169
+ * This is O(n * k) where n is entities and k is aggregate operations — effectively O(n).
170
+ *
171
+ * @param entities - Array of entities to aggregate
172
+ * @param config - Aggregate configuration specifying which operations to perform
173
+ * @returns AggregateResult with only the requested aggregations
174
+ */
175
+ export const computeAggregates = (entities, config) => {
176
+ const initialAcc = createAccumulators(config);
177
+ const finalAcc = entities.reduce((acc, entity) => updateAccumulators(acc, entity, config), initialAcc);
178
+ return accumulatorsToResult(finalAcc, config);
179
+ };
180
+ /**
181
+ * Compute grouped aggregates over an array of entities.
182
+ *
183
+ * Partitions entities into groups based on groupBy fields, then applies
184
+ * computeAggregates within each group. Groups are ordered by first encounter.
185
+ *
186
+ * @param entities - Array of entities to aggregate
187
+ * @param config - Grouped aggregate configuration specifying groupBy and operations
188
+ * @returns GroupedAggregateResult array with group objects
189
+ */
190
+ export const computeGroupedAggregates = (entities, config) => {
191
+ // Normalize groupBy to array
192
+ const groupByFields = normalizeFields(config.groupBy);
193
+ // Partition entities into groups using Map for first-encounter ordering
194
+ const groups = new Map();
195
+ for (const entity of entities) {
196
+ // Build group key from grouping field values
197
+ const groupKey = createGroupKey(groupByFields.map((f) => getNestedValue(entity, f)));
198
+ const existing = groups.get(groupKey);
199
+ if (existing !== undefined) {
200
+ existing.push(entity);
201
+ }
202
+ else {
203
+ groups.set(groupKey, [entity]);
204
+ }
205
+ }
206
+ // Build result for each group
207
+ const result = [];
208
+ for (const [groupKey, groupEntities] of groups) {
209
+ // Parse back the group values
210
+ const groupValues = parseGroupKey(groupKey);
211
+ // Build the group field object
212
+ const group = {};
213
+ for (let i = 0; i < groupByFields.length; i++) {
214
+ group[groupByFields[i]] = groupValues[i];
215
+ }
216
+ // Compute aggregates for this group (exclude groupBy from config passed to computeAggregates)
217
+ const { groupBy: _, ...scalarConfig } = config;
218
+ const aggregates = computeAggregates(groupEntities, scalarConfig);
219
+ // Combine group info with aggregates
220
+ result.push({
221
+ group,
222
+ ...aggregates,
223
+ });
224
+ }
225
+ return result;
226
+ };
227
+ //# sourceMappingURL=aggregate.js.map