@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,179 @@
1
+ /**
2
+ * Search index management for full-text search.
3
+ *
4
+ * Provides functions for building and maintaining an inverted index
5
+ * that maps tokens to entity IDs for fast text search queries.
6
+ *
7
+ * Follows the same Ref-based pattern as index-manager.ts for consistency.
8
+ */
9
+ import { Effect, Ref } from "effect";
10
+ import type { SearchIndexMap } from "../types/search-types.js";
11
+ /**
12
+ * Entity constraint: must have a readonly string `id` field.
13
+ */
14
+ type HasId = {
15
+ readonly id: string;
16
+ };
17
+ /**
18
+ * Build a search index from entities for the specified fields.
19
+ *
20
+ * Creates an inverted index mapping tokens to sets of entity IDs.
21
+ * For each entity, tokenizes the values of the specified fields
22
+ * and adds the entity's ID to each token's set.
23
+ *
24
+ * @param fields - The fields to index for full-text search
25
+ * @param entities - All entities in the collection
26
+ * @returns Effect producing a Ref containing the SearchIndexMap
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const books = [
31
+ * { id: "1", title: "Dune", author: "Frank Herbert" },
32
+ * { id: "2", title: "Neuromancer", author: "William Gibson" },
33
+ * ]
34
+ *
35
+ * const indexRef = yield* buildSearchIndex(["title", "author"], books)
36
+ * // Index structure:
37
+ * // "dune" -> Set(["1"])
38
+ * // "frank" -> Set(["1"])
39
+ * // "herbert" -> Set(["1"])
40
+ * // "neuromancer" -> Set(["2"])
41
+ * // "william" -> Set(["2"])
42
+ * // "gibson" -> Set(["2"])
43
+ * ```
44
+ */
45
+ export declare const buildSearchIndex: <T extends HasId>(fields: ReadonlyArray<string>, entities: ReadonlyArray<T>) => Effect.Effect<Ref.Ref<SearchIndexMap>>;
46
+ /**
47
+ * Lookup candidate entity IDs from the search index for a given query.
48
+ *
49
+ * For each query token:
50
+ * - Finds exact matches in the index
51
+ * - Finds prefix matches (index tokens that start with the query token)
52
+ *
53
+ * Returns the intersection of ID sets across all query tokens (AND semantics).
54
+ * If a query token has no matches, returns an empty set.
55
+ * If queryTokens is empty, returns an empty set.
56
+ *
57
+ * @param indexRef - Ref containing the SearchIndexMap
58
+ * @param queryTokens - Tokenized query terms to search for
59
+ * @returns Effect producing a Set of candidate entity IDs
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * // Index: "dune" -> Set(["1"]), "frank" -> Set(["1"]), "neuromancer" -> Set(["2"])
64
+ *
65
+ * // Exact match
66
+ * const ids = yield* lookupSearchIndex(indexRef, ["dune"])
67
+ * // → Set(["1"])
68
+ *
69
+ * // Prefix match
70
+ * const ids = yield* lookupSearchIndex(indexRef, ["neuro"])
71
+ * // → Set(["2"]) (matches "neuromancer")
72
+ *
73
+ * // Multi-token (AND semantics)
74
+ * const ids = yield* lookupSearchIndex(indexRef, ["dune", "frank"])
75
+ * // → Set(["1"]) (intersection)
76
+ *
77
+ * // No match
78
+ * const ids = yield* lookupSearchIndex(indexRef, ["xyz"])
79
+ * // → Set([])
80
+ * ```
81
+ */
82
+ export declare const lookupSearchIndex: (indexRef: Ref.Ref<SearchIndexMap>, queryTokens: ReadonlyArray<string>) => Effect.Effect<Set<string>>;
83
+ /**
84
+ * Resolve candidate entities using the search index when a search query is present.
85
+ *
86
+ * Checks if the where clause contains a $search operator (field-level or top-level).
87
+ * If found and the search index covers the queried fields, uses the index to
88
+ * narrow the candidate set before full filtering.
89
+ *
90
+ * Returns undefined if:
91
+ * - No $search operator is present
92
+ * - The search index doesn't cover the queried fields
93
+ * - The search index is empty
94
+ *
95
+ * @param where - The where clause from the query
96
+ * @param searchIndexRef - The search index Ref (or undefined if not configured)
97
+ * @param searchIndexFields - The fields covered by the search index (or undefined)
98
+ * @param map - The entity data map (id -> entity)
99
+ * @returns Effect producing Array<T> if index was used, undefined if no usable index
100
+ */
101
+ export declare const resolveWithSearchIndex: <T extends HasId>(where: Record<string, unknown> | undefined, searchIndexRef: Ref.Ref<SearchIndexMap> | undefined, searchIndexFields: ReadonlyArray<string> | undefined, map: ReadonlyMap<string, T>) => Effect.Effect<ReadonlyArray<T> | undefined>;
102
+ /**
103
+ * Add an entity to the search index.
104
+ *
105
+ * Tokenizes the entity's indexed fields and adds the entity ID to each
106
+ * token's set in the inverted index. This should be called after creating
107
+ * a new entity to keep the search index up to date.
108
+ *
109
+ * @param indexRef - Ref containing the SearchIndexMap
110
+ * @param entity - The entity to add to the index
111
+ * @param fields - The fields to index for full-text search
112
+ * @returns Effect that completes when the entity is added
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * const newBook = { id: "5", title: "Snow Crash", author: "Neal Stephenson" }
117
+ * yield* addToSearchIndex(indexRef, newBook, ["title", "author"])
118
+ * // Index now contains:
119
+ * // "snow" -> Set([..., "5"])
120
+ * // "crash" -> Set([..., "5"])
121
+ * // "neal" -> Set([..., "5"])
122
+ * // "stephenson" -> Set([..., "5"])
123
+ * ```
124
+ */
125
+ export declare const addToSearchIndex: <T extends HasId>(indexRef: Ref.Ref<SearchIndexMap>, entity: T, fields: ReadonlyArray<string>) => Effect.Effect<void>;
126
+ /**
127
+ * Remove an entity from the search index.
128
+ *
129
+ * Tokenizes the entity's indexed fields and removes the entity ID from each
130
+ * token's set in the inverted index. Cleans up empty sets to avoid memory
131
+ * leaks. This should be called after deleting an entity to keep the search
132
+ * index up to date.
133
+ *
134
+ * @param indexRef - Ref containing the SearchIndexMap
135
+ * @param entity - The entity to remove from the index
136
+ * @param fields - The fields that were indexed for full-text search
137
+ * @returns Effect that completes when the entity is removed
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * const bookToDelete = { id: "5", title: "Snow Crash", author: "Neal Stephenson" }
142
+ * yield* removeFromSearchIndex(indexRef, bookToDelete, ["title", "author"])
143
+ * // Index now has entity "5" removed from:
144
+ * // "snow", "crash", "neal", "stephenson" sets
145
+ * // Empty sets are deleted from the index
146
+ * ```
147
+ */
148
+ export declare const removeFromSearchIndex: <T extends HasId>(indexRef: Ref.Ref<SearchIndexMap>, entity: T, fields: ReadonlyArray<string>) => Effect.Effect<void>;
149
+ /**
150
+ * Update an entity in the search index.
151
+ *
152
+ * Efficiently handles updates by only reindexing fields that have changed.
153
+ * For changed fields, removes old tokens and adds new tokens. This should
154
+ * be called after updating an entity to keep the search index up to date.
155
+ *
156
+ * Optimization: If a field's value hasn't changed, no index operations are
157
+ * performed for that field. This is more efficient than a full remove+add
158
+ * when only some indexed fields are modified.
159
+ *
160
+ * @param indexRef - Ref containing the SearchIndexMap
161
+ * @param oldEntity - The entity before the update
162
+ * @param newEntity - The entity after the update
163
+ * @param fields - The fields that are indexed for full-text search
164
+ * @returns Effect that completes when the index is updated
165
+ *
166
+ * @example
167
+ * ```ts
168
+ * const oldBook = { id: "5", title: "Snow Crash", author: "Neal Stephenson" }
169
+ * const newBook = { id: "5", title: "Snow Crash (Revised)", author: "Neal Stephenson" }
170
+ * yield* updateInSearchIndex(indexRef, oldBook, newBook, ["title", "author"])
171
+ * // Only "title" changed, so:
172
+ * // - Removes "5" from "snow", "crash"
173
+ * // - Adds "5" to "snow", "crash", "revised"
174
+ * // - "author" field is unchanged, no operations needed
175
+ * ```
176
+ */
177
+ export declare const updateInSearchIndex: <T extends HasId>(indexRef: Ref.Ref<SearchIndexMap>, oldEntity: T, newEntity: T, fields: ReadonlyArray<string>) => Effect.Effect<void>;
178
+ export {};
179
+ //# sourceMappingURL=search-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-index.d.ts","sourceRoot":"","sources":["../../src/indexes/search-index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAErC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG/D;;GAEG;AACH,KAAK,KAAK,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,KAAK,EAC/C,QAAQ,aAAa,CAAC,MAAM,CAAC,EAC7B,UAAU,aAAa,CAAC,CAAC,CAAC,KACxB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CASrC,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,iBAAiB,GAC7B,UAAU,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,EACjC,aAAa,aAAa,CAAC,MAAM,CAAC,KAChC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CA+CzB,CAAC;AAEJ;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,sBAAsB,GAAI,CAAC,SAAS,KAAK,EACrD,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,gBAAgB,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,SAAS,EACnD,mBAAmB,aAAa,CAAC,MAAM,CAAC,GAAG,SAAS,EACpD,KAAK,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,KACzB,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,CA8C1C,CAAC;AA0GJ;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,KAAK,EAC/C,UAAU,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,EACjC,QAAQ,CAAC,EACT,QAAQ,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,CAMlB,CAAC;AA2CJ;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,SAAS,KAAK,EACpD,UAAU,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,EACjC,QAAQ,CAAC,EACT,QAAQ,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,CAUlB,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS,KAAK,EAClD,UAAU,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,EACjC,WAAW,CAAC,EACZ,WAAW,CAAC,EACZ,QAAQ,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,CAgClB,CAAC"}
@@ -0,0 +1,405 @@
1
+ /**
2
+ * Search index management for full-text search.
3
+ *
4
+ * Provides functions for building and maintaining an inverted index
5
+ * that maps tokens to entity IDs for fast text search queries.
6
+ *
7
+ * Follows the same Ref-based pattern as index-manager.ts for consistency.
8
+ */
9
+ import { Effect, Ref } from "effect";
10
+ import { tokenize } from "../operations/query/search.js";
11
+ import { getNestedValue } from "../utils/nested-path.js";
12
+ /**
13
+ * Build a search index from entities for the specified fields.
14
+ *
15
+ * Creates an inverted index mapping tokens to sets of entity IDs.
16
+ * For each entity, tokenizes the values of the specified fields
17
+ * and adds the entity's ID to each token's set.
18
+ *
19
+ * @param fields - The fields to index for full-text search
20
+ * @param entities - All entities in the collection
21
+ * @returns Effect producing a Ref containing the SearchIndexMap
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const books = [
26
+ * { id: "1", title: "Dune", author: "Frank Herbert" },
27
+ * { id: "2", title: "Neuromancer", author: "William Gibson" },
28
+ * ]
29
+ *
30
+ * const indexRef = yield* buildSearchIndex(["title", "author"], books)
31
+ * // Index structure:
32
+ * // "dune" -> Set(["1"])
33
+ * // "frank" -> Set(["1"])
34
+ * // "herbert" -> Set(["1"])
35
+ * // "neuromancer" -> Set(["2"])
36
+ * // "william" -> Set(["2"])
37
+ * // "gibson" -> Set(["2"])
38
+ * ```
39
+ */
40
+ export const buildSearchIndex = (fields, entities) => Effect.gen(function* () {
41
+ const searchIndex = new Map();
42
+ for (const entity of entities) {
43
+ addEntityToIndexMut(searchIndex, entity, fields);
44
+ }
45
+ return yield* Ref.make(searchIndex);
46
+ });
47
+ /**
48
+ * Lookup candidate entity IDs from the search index for a given query.
49
+ *
50
+ * For each query token:
51
+ * - Finds exact matches in the index
52
+ * - Finds prefix matches (index tokens that start with the query token)
53
+ *
54
+ * Returns the intersection of ID sets across all query tokens (AND semantics).
55
+ * If a query token has no matches, returns an empty set.
56
+ * If queryTokens is empty, returns an empty set.
57
+ *
58
+ * @param indexRef - Ref containing the SearchIndexMap
59
+ * @param queryTokens - Tokenized query terms to search for
60
+ * @returns Effect producing a Set of candidate entity IDs
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * // Index: "dune" -> Set(["1"]), "frank" -> Set(["1"]), "neuromancer" -> Set(["2"])
65
+ *
66
+ * // Exact match
67
+ * const ids = yield* lookupSearchIndex(indexRef, ["dune"])
68
+ * // → Set(["1"])
69
+ *
70
+ * // Prefix match
71
+ * const ids = yield* lookupSearchIndex(indexRef, ["neuro"])
72
+ * // → Set(["2"]) (matches "neuromancer")
73
+ *
74
+ * // Multi-token (AND semantics)
75
+ * const ids = yield* lookupSearchIndex(indexRef, ["dune", "frank"])
76
+ * // → Set(["1"]) (intersection)
77
+ *
78
+ * // No match
79
+ * const ids = yield* lookupSearchIndex(indexRef, ["xyz"])
80
+ * // → Set([])
81
+ * ```
82
+ */
83
+ export const lookupSearchIndex = (indexRef, queryTokens) => Effect.gen(function* () {
84
+ // Empty query returns empty set
85
+ if (queryTokens.length === 0) {
86
+ return new Set();
87
+ }
88
+ const index = yield* Ref.get(indexRef);
89
+ // For each query token, find all matching IDs (exact + prefix)
90
+ const tokenMatchSets = [];
91
+ for (const queryToken of queryTokens) {
92
+ const matchingIds = new Set();
93
+ // Check each token in the index for exact or prefix match
94
+ for (const [indexToken, entityIds] of index) {
95
+ if (indexToken === queryToken || indexToken.startsWith(queryToken)) {
96
+ // Union all matching entity IDs for this query token
97
+ for (const id of entityIds) {
98
+ matchingIds.add(id);
99
+ }
100
+ }
101
+ }
102
+ tokenMatchSets.push(matchingIds);
103
+ }
104
+ // Intersect all token match sets (AND semantics)
105
+ // If any token has no matches, the result is empty
106
+ if (tokenMatchSets.length === 0) {
107
+ return new Set();
108
+ }
109
+ // Start with the first set and intersect with the rest
110
+ const result = new Set(tokenMatchSets[0]);
111
+ for (let i = 1; i < tokenMatchSets.length; i++) {
112
+ const currentSet = tokenMatchSets[i];
113
+ for (const id of result) {
114
+ if (!currentSet.has(id)) {
115
+ result.delete(id);
116
+ }
117
+ }
118
+ }
119
+ return result;
120
+ });
121
+ /**
122
+ * Resolve candidate entities using the search index when a search query is present.
123
+ *
124
+ * Checks if the where clause contains a $search operator (field-level or top-level).
125
+ * If found and the search index covers the queried fields, uses the index to
126
+ * narrow the candidate set before full filtering.
127
+ *
128
+ * Returns undefined if:
129
+ * - No $search operator is present
130
+ * - The search index doesn't cover the queried fields
131
+ * - The search index is empty
132
+ *
133
+ * @param where - The where clause from the query
134
+ * @param searchIndexRef - The search index Ref (or undefined if not configured)
135
+ * @param searchIndexFields - The fields covered by the search index (or undefined)
136
+ * @param map - The entity data map (id -> entity)
137
+ * @returns Effect producing Array<T> if index was used, undefined if no usable index
138
+ */
139
+ export const resolveWithSearchIndex = (where, searchIndexRef, searchIndexFields, map) => Effect.gen(function* () {
140
+ // No where clause or no search index configured
141
+ if (!where ||
142
+ !searchIndexRef ||
143
+ !searchIndexFields ||
144
+ searchIndexFields.length === 0) {
145
+ return undefined;
146
+ }
147
+ // Extract search query from where clause
148
+ const searchInfo = extractSearchFromWhere(where, searchIndexFields);
149
+ if (!searchInfo) {
150
+ return undefined;
151
+ }
152
+ const { queryTokens, queriedFields } = searchInfo;
153
+ // Check if the search index covers all queried fields
154
+ const indexCovered = queriedFields.every((field) => searchIndexFields.includes(field));
155
+ if (!indexCovered) {
156
+ return undefined;
157
+ }
158
+ // Use the search index to get candidate entity IDs
159
+ const candidateIds = yield* lookupSearchIndex(searchIndexRef, queryTokens);
160
+ // Empty result set means no candidates
161
+ if (candidateIds.size === 0) {
162
+ return [];
163
+ }
164
+ // Load candidate entities from the map
165
+ const entities = [];
166
+ for (const id of candidateIds) {
167
+ const entity = map.get(id);
168
+ if (entity !== undefined) {
169
+ entities.push(entity);
170
+ }
171
+ }
172
+ return entities;
173
+ });
174
+ /**
175
+ * Extract search query info from a where clause.
176
+ *
177
+ * Looks for:
178
+ * 1. Top-level $search: { query: "...", fields?: [...] }
179
+ * 2. Field-level $search: { fieldName: { $search: "..." } }
180
+ *
181
+ * Returns the tokenized query and the fields being searched, or undefined if no search.
182
+ */
183
+ const extractSearchFromWhere = (where, defaultFields) => {
184
+ // Check for top-level $search
185
+ if ("$search" in where) {
186
+ const searchValue = where.$search;
187
+ if (searchValue !== null && typeof searchValue === "object") {
188
+ const config = searchValue;
189
+ if (typeof config.query === "string") {
190
+ const queryTokens = tokenize(config.query);
191
+ if (queryTokens.length === 0) {
192
+ return undefined; // Empty query matches everything, no index help
193
+ }
194
+ const queriedFields = config.fields && config.fields.length > 0
195
+ ? config.fields
196
+ : defaultFields;
197
+ return { queryTokens, queriedFields };
198
+ }
199
+ }
200
+ }
201
+ // Check for field-level $search on any field
202
+ for (const [field, value] of Object.entries(where)) {
203
+ // Skip logical operators
204
+ if (field === "$or" ||
205
+ field === "$and" ||
206
+ field === "$not" ||
207
+ field === "$search") {
208
+ continue;
209
+ }
210
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
211
+ const obj = value;
212
+ if ("$search" in obj && typeof obj.$search === "string") {
213
+ const queryTokens = tokenize(obj.$search);
214
+ if (queryTokens.length === 0) {
215
+ continue; // Empty query, skip
216
+ }
217
+ // Field-level search applies to just this field
218
+ return { queryTokens, queriedFields: [field] };
219
+ }
220
+ }
221
+ }
222
+ return undefined;
223
+ };
224
+ /**
225
+ * Helper function to add a single entity to a search index map.
226
+ *
227
+ * Tokenizes the values of the specified fields and adds the entity's ID
228
+ * to each token's set in the index.
229
+ *
230
+ * @param index - The search index map to mutate
231
+ * @param entity - The entity to add
232
+ * @param fields - The fields to index
233
+ */
234
+ const addEntityToIndexMut = (index, entity, fields) => {
235
+ const entityRecord = entity;
236
+ const entityId = entity.id;
237
+ for (const field of fields) {
238
+ const fieldValue = getNestedValue(entityRecord, field);
239
+ // Only index string fields
240
+ if (typeof fieldValue !== "string") {
241
+ continue;
242
+ }
243
+ // Tokenize the field value and add to index
244
+ const tokens = tokenize(fieldValue);
245
+ for (const token of tokens) {
246
+ const existingSet = index.get(token);
247
+ if (existingSet) {
248
+ existingSet.add(entityId);
249
+ }
250
+ else {
251
+ index.set(token, new Set([entityId]));
252
+ }
253
+ }
254
+ }
255
+ };
256
+ /**
257
+ * Add an entity to the search index.
258
+ *
259
+ * Tokenizes the entity's indexed fields and adds the entity ID to each
260
+ * token's set in the inverted index. This should be called after creating
261
+ * a new entity to keep the search index up to date.
262
+ *
263
+ * @param indexRef - Ref containing the SearchIndexMap
264
+ * @param entity - The entity to add to the index
265
+ * @param fields - The fields to index for full-text search
266
+ * @returns Effect that completes when the entity is added
267
+ *
268
+ * @example
269
+ * ```ts
270
+ * const newBook = { id: "5", title: "Snow Crash", author: "Neal Stephenson" }
271
+ * yield* addToSearchIndex(indexRef, newBook, ["title", "author"])
272
+ * // Index now contains:
273
+ * // "snow" -> Set([..., "5"])
274
+ * // "crash" -> Set([..., "5"])
275
+ * // "neal" -> Set([..., "5"])
276
+ * // "stephenson" -> Set([..., "5"])
277
+ * ```
278
+ */
279
+ export const addToSearchIndex = (indexRef, entity, fields) => Ref.update(indexRef, (index) => {
280
+ // Clone the index to avoid mutating the original
281
+ const newIndex = new Map(index);
282
+ addEntityToIndexMut(newIndex, entity, fields);
283
+ return newIndex;
284
+ });
285
+ /**
286
+ * Helper function to remove a single entity from a search index map.
287
+ *
288
+ * Tokenizes the values of the specified fields and removes the entity's ID
289
+ * from each token's set in the index. Cleans up empty sets.
290
+ *
291
+ * @param index - The search index map to mutate
292
+ * @param entity - The entity to remove
293
+ * @param fields - The fields that were indexed
294
+ */
295
+ const removeEntityFromIndexMut = (index, entity, fields) => {
296
+ const entityRecord = entity;
297
+ const entityId = entity.id;
298
+ for (const field of fields) {
299
+ const fieldValue = getNestedValue(entityRecord, field);
300
+ // Only process string fields
301
+ if (typeof fieldValue !== "string") {
302
+ continue;
303
+ }
304
+ // Tokenize the field value and remove from index
305
+ const tokens = tokenize(fieldValue);
306
+ for (const token of tokens) {
307
+ const existingSet = index.get(token);
308
+ if (existingSet) {
309
+ existingSet.delete(entityId);
310
+ // Clean up empty sets
311
+ if (existingSet.size === 0) {
312
+ index.delete(token);
313
+ }
314
+ }
315
+ }
316
+ }
317
+ };
318
+ /**
319
+ * Remove an entity from the search index.
320
+ *
321
+ * Tokenizes the entity's indexed fields and removes the entity ID from each
322
+ * token's set in the inverted index. Cleans up empty sets to avoid memory
323
+ * leaks. This should be called after deleting an entity to keep the search
324
+ * index up to date.
325
+ *
326
+ * @param indexRef - Ref containing the SearchIndexMap
327
+ * @param entity - The entity to remove from the index
328
+ * @param fields - The fields that were indexed for full-text search
329
+ * @returns Effect that completes when the entity is removed
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * const bookToDelete = { id: "5", title: "Snow Crash", author: "Neal Stephenson" }
334
+ * yield* removeFromSearchIndex(indexRef, bookToDelete, ["title", "author"])
335
+ * // Index now has entity "5" removed from:
336
+ * // "snow", "crash", "neal", "stephenson" sets
337
+ * // Empty sets are deleted from the index
338
+ * ```
339
+ */
340
+ export const removeFromSearchIndex = (indexRef, entity, fields) => Ref.update(indexRef, (index) => {
341
+ // Clone the index to avoid mutating the original
342
+ const newIndex = new Map(index);
343
+ // Also clone the sets that we'll modify to maintain immutability
344
+ for (const [token, idSet] of index) {
345
+ newIndex.set(token, new Set(idSet));
346
+ }
347
+ removeEntityFromIndexMut(newIndex, entity, fields);
348
+ return newIndex;
349
+ });
350
+ /**
351
+ * Update an entity in the search index.
352
+ *
353
+ * Efficiently handles updates by only reindexing fields that have changed.
354
+ * For changed fields, removes old tokens and adds new tokens. This should
355
+ * be called after updating an entity to keep the search index up to date.
356
+ *
357
+ * Optimization: If a field's value hasn't changed, no index operations are
358
+ * performed for that field. This is more efficient than a full remove+add
359
+ * when only some indexed fields are modified.
360
+ *
361
+ * @param indexRef - Ref containing the SearchIndexMap
362
+ * @param oldEntity - The entity before the update
363
+ * @param newEntity - The entity after the update
364
+ * @param fields - The fields that are indexed for full-text search
365
+ * @returns Effect that completes when the index is updated
366
+ *
367
+ * @example
368
+ * ```ts
369
+ * const oldBook = { id: "5", title: "Snow Crash", author: "Neal Stephenson" }
370
+ * const newBook = { id: "5", title: "Snow Crash (Revised)", author: "Neal Stephenson" }
371
+ * yield* updateInSearchIndex(indexRef, oldBook, newBook, ["title", "author"])
372
+ * // Only "title" changed, so:
373
+ * // - Removes "5" from "snow", "crash"
374
+ * // - Adds "5" to "snow", "crash", "revised"
375
+ * // - "author" field is unchanged, no operations needed
376
+ * ```
377
+ */
378
+ export const updateInSearchIndex = (indexRef, oldEntity, newEntity, fields) => Ref.update(indexRef, (index) => {
379
+ const oldRecord = oldEntity;
380
+ const newRecord = newEntity;
381
+ // Find which fields have actually changed
382
+ const changedFields = [];
383
+ for (const field of fields) {
384
+ const oldValue = getNestedValue(oldRecord, field);
385
+ const newValue = getNestedValue(newRecord, field);
386
+ if (oldValue !== newValue) {
387
+ changedFields.push(field);
388
+ }
389
+ }
390
+ // If no indexed fields changed, return the original index unchanged
391
+ if (changedFields.length === 0) {
392
+ return index;
393
+ }
394
+ // Clone the index to avoid mutating the original
395
+ const newIndex = new Map(index);
396
+ // Also clone the sets that we'll modify to maintain immutability
397
+ for (const [token, idSet] of index) {
398
+ newIndex.set(token, new Set(idSet));
399
+ }
400
+ // Remove old tokens and add new tokens for changed fields only
401
+ removeEntityFromIndexMut(newIndex, oldEntity, changedFields);
402
+ addEntityToIndexMut(newIndex, newEntity, changedFields);
403
+ return newIndex;
404
+ });
405
+ //# sourceMappingURL=search-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-index.js","sourceRoot":"","sources":["../../src/indexes/search-index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Migration Runner
3
+ *
4
+ * Core migration logic: validate migration registries, run migrations,
5
+ * and preview migrations via dry-run.
6
+ */
7
+ import { Effect, type Ref } from "effect";
8
+ import { MigrationError } from "../errors/migration-errors.js";
9
+ import type { SerializationError, StorageError, UnsupportedFormatError } from "../errors/storage-errors.js";
10
+ import { SerializerRegistry } from "../serializers/serializer-service.js";
11
+ import { StorageAdapter } from "../storage/storage-service.js";
12
+ import type { DatabaseConfig } from "../types/database-config-types.js";
13
+ import type { DryRunResult, Migration } from "./migration-types.js";
14
+ /**
15
+ * Run migrations to transform data from one version to another.
16
+ *
17
+ * Filters migrations to only those applicable for the version transition,
18
+ * then runs each transform in order, piping the output of one to the input
19
+ * of the next.
20
+ *
21
+ * @param data - The raw entity map to migrate
22
+ * @param fileVersion - The version of the data (from file's _version, or 0 if absent)
23
+ * @param targetVersion - The target schema version from collection config
24
+ * @param migrations - The full migration registry for this collection
25
+ * @param collectionName - Name of the collection (for error messages)
26
+ * @returns Effect<Record<string, unknown>, MigrationError> - the migrated data
27
+ */
28
+ export declare const runMigrations: (data: Record<string, unknown>, fileVersion: number, targetVersion: number, migrations: ReadonlyArray<Migration>, collectionName: string) => Effect.Effect<Record<string, unknown>, MigrationError>;
29
+ /**
30
+ * Validate that a migration registry forms a valid, contiguous chain.
31
+ *
32
+ * Validation rules:
33
+ * - Migrations must form a contiguous chain (no gaps in `from`/`to`)
34
+ * - Each migration's `to` must equal `from + 1`
35
+ * - No duplicate `from` values
36
+ * - The last migration's `to` must equal the collection's `version`
37
+ * - Version 0 with no migrations is valid
38
+ * - Version > 0 with empty migrations is invalid
39
+ *
40
+ * @param collectionName - Name of the collection (for error messages)
41
+ * @param version - Target schema version from collection config
42
+ * @param migrations - Array of migrations to validate
43
+ * @returns Effect<void, MigrationError> - succeeds if valid, fails with MigrationError if invalid
44
+ */
45
+ export declare const validateMigrationRegistry: (collectionName: string, version: number, migrations: ReadonlyArray<Migration>) => Effect.Effect<void, MigrationError>;
46
+ /**
47
+ * Internal Ref map type for cross-collection access.
48
+ */
49
+ type HasId = {
50
+ readonly id: string;
51
+ };
52
+ type StateRefs = Record<string, Ref.Ref<ReadonlyMap<string, HasId>>>;
53
+ /**
54
+ * Preview which files need migration and what transforms would apply.
55
+ *
56
+ * For each versioned collection with a file path:
57
+ * - Read the file and extract `_version`
58
+ * - Compare to config `version`
59
+ * - List which migrations would apply (without running transforms)
60
+ * - Report status
61
+ *
62
+ * No transforms are executed. No files are written.
63
+ *
64
+ * @param config - The database configuration
65
+ * @param _stateRefs - State refs (unused, but kept for API consistency)
66
+ * @returns Effect<DryRunResult, MigrationError | StorageError | SerializationError | UnsupportedFormatError>
67
+ */
68
+ export declare const dryRunMigrations: (config: DatabaseConfig, _stateRefs: StateRefs) => Effect.Effect<DryRunResult, MigrationError | StorageError | SerializationError | UnsupportedFormatError, StorageAdapter | SerializerRegistry>;
69
+ export {};
70
+ //# sourceMappingURL=migration-runner.d.ts.map