@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.
- package/LICENSE +21 -0
- package/dist/errors/crud-errors.d.ts +98 -0
- package/dist/errors/crud-errors.d.ts.map +1 -0
- package/dist/errors/crud-errors.js +23 -0
- package/dist/errors/crud-errors.js.map +1 -0
- package/dist/errors/index.d.ts +16 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +12 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/migration-errors.d.ts +22 -0
- package/dist/errors/migration-errors.d.ts.map +1 -0
- package/dist/errors/migration-errors.js +14 -0
- package/dist/errors/migration-errors.js.map +1 -0
- package/dist/errors/plugin-errors.d.ts +15 -0
- package/dist/errors/plugin-errors.d.ts.map +1 -0
- package/dist/errors/plugin-errors.js +11 -0
- package/dist/errors/plugin-errors.js.map +1 -0
- package/dist/errors/query-errors.d.ts +31 -0
- package/dist/errors/query-errors.d.ts.map +1 -0
- package/dist/errors/query-errors.js +11 -0
- package/dist/errors/query-errors.js.map +1 -0
- package/dist/errors/storage-errors.d.ts +30 -0
- package/dist/errors/storage-errors.d.ts.map +1 -0
- package/dist/errors/storage-errors.js +11 -0
- package/dist/errors/storage-errors.js.map +1 -0
- package/dist/factories/crud-factory-with-relationships.d.ts +28 -0
- package/dist/factories/crud-factory-with-relationships.d.ts.map +1 -0
- package/dist/factories/crud-factory-with-relationships.js +8 -0
- package/dist/factories/crud-factory-with-relationships.js.map +1 -0
- package/dist/factories/crud-factory.d.ts +25 -0
- package/dist/factories/crud-factory.d.ts.map +1 -0
- package/dist/factories/crud-factory.js +8 -0
- package/dist/factories/crud-factory.js.map +1 -0
- package/dist/factories/database-effect.d.ts +241 -0
- package/dist/factories/database-effect.d.ts.map +1 -0
- package/dist/factories/database-effect.js +859 -0
- package/dist/factories/database-effect.js.map +1 -0
- package/dist/hooks/hook-runner.d.ts +60 -0
- package/dist/hooks/hook-runner.d.ts.map +1 -0
- package/dist/hooks/hook-runner.js +107 -0
- package/dist/hooks/hook-runner.js.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/indexes/index-lookup.d.ts +33 -0
- package/dist/indexes/index-lookup.d.ts.map +1 -0
- package/dist/indexes/index-lookup.js +180 -0
- package/dist/indexes/index-lookup.js.map +1 -0
- package/dist/indexes/index-manager.d.ts +118 -0
- package/dist/indexes/index-manager.d.ts.map +1 -0
- package/dist/indexes/index-manager.js +345 -0
- package/dist/indexes/index-manager.js.map +1 -0
- package/dist/indexes/search-index.d.ts +179 -0
- package/dist/indexes/search-index.d.ts.map +1 -0
- package/dist/indexes/search-index.js +405 -0
- package/dist/indexes/search-index.js.map +1 -0
- package/dist/migrations/migration-runner.d.ts +70 -0
- package/dist/migrations/migration-runner.d.ts.map +1 -0
- package/dist/migrations/migration-runner.js +271 -0
- package/dist/migrations/migration-runner.js.map +1 -0
- package/dist/migrations/migration-types.d.ts +63 -0
- package/dist/migrations/migration-types.d.ts.map +1 -0
- package/dist/migrations/migration-types.js +5 -0
- package/dist/migrations/migration-types.js.map +1 -0
- package/dist/operations/crud/create-with-relationships.d.ts +44 -0
- package/dist/operations/crud/create-with-relationships.d.ts.map +1 -0
- package/dist/operations/crud/create-with-relationships.js +483 -0
- package/dist/operations/crud/create-with-relationships.js.map +1 -0
- package/dist/operations/crud/create.d.ts +48 -0
- package/dist/operations/crud/create.d.ts.map +1 -0
- package/dist/operations/crud/create.js +333 -0
- package/dist/operations/crud/create.js.map +1 -0
- package/dist/operations/crud/delete-with-relationships.d.ts +63 -0
- package/dist/operations/crud/delete-with-relationships.d.ts.map +1 -0
- package/dist/operations/crud/delete-with-relationships.js +395 -0
- package/dist/operations/crud/delete-with-relationships.js.map +1 -0
- package/dist/operations/crud/delete.d.ts +58 -0
- package/dist/operations/crud/delete.d.ts.map +1 -0
- package/dist/operations/crud/delete.js +267 -0
- package/dist/operations/crud/delete.js.map +1 -0
- package/dist/operations/crud/unique-check.d.ts +114 -0
- package/dist/operations/crud/unique-check.d.ts.map +1 -0
- package/dist/operations/crud/unique-check.js +383 -0
- package/dist/operations/crud/unique-check.js.map +1 -0
- package/dist/operations/crud/update-with-relationships.d.ts +45 -0
- package/dist/operations/crud/update-with-relationships.d.ts.map +1 -0
- package/dist/operations/crud/update-with-relationships.js +516 -0
- package/dist/operations/crud/update-with-relationships.js.map +1 -0
- package/dist/operations/crud/update.d.ts +91 -0
- package/dist/operations/crud/update.d.ts.map +1 -0
- package/dist/operations/crud/update.js +505 -0
- package/dist/operations/crud/update.js.map +1 -0
- package/dist/operations/crud/upsert.d.ts +52 -0
- package/dist/operations/crud/upsert.d.ts.map +1 -0
- package/dist/operations/crud/upsert.js +386 -0
- package/dist/operations/crud/upsert.js.map +1 -0
- package/dist/operations/query/aggregate.d.ts +30 -0
- package/dist/operations/query/aggregate.d.ts.map +1 -0
- package/dist/operations/query/aggregate.js +227 -0
- package/dist/operations/query/aggregate.js.map +1 -0
- package/dist/operations/query/cursor-stream.d.ts +18 -0
- package/dist/operations/query/cursor-stream.d.ts.map +1 -0
- package/dist/operations/query/cursor-stream.js +199 -0
- package/dist/operations/query/cursor-stream.js.map +1 -0
- package/dist/operations/query/filter-stream.d.ts +12 -0
- package/dist/operations/query/filter-stream.d.ts.map +1 -0
- package/dist/operations/query/filter-stream.js +167 -0
- package/dist/operations/query/filter-stream.js.map +1 -0
- package/dist/operations/query/filter.d.ts +13 -0
- package/dist/operations/query/filter.d.ts.map +1 -0
- package/dist/operations/query/filter.js +267 -0
- package/dist/operations/query/filter.js.map +1 -0
- package/dist/operations/query/paginate-stream.d.ts +11 -0
- package/dist/operations/query/paginate-stream.d.ts.map +1 -0
- package/dist/operations/query/paginate-stream.js +22 -0
- package/dist/operations/query/paginate-stream.js.map +1 -0
- package/dist/operations/query/query-helpers.d.ts +14 -0
- package/dist/operations/query/query-helpers.d.ts.map +1 -0
- package/dist/operations/query/query-helpers.js +22 -0
- package/dist/operations/query/query-helpers.js.map +1 -0
- package/dist/operations/query/resolve-computed.d.ts +142 -0
- package/dist/operations/query/resolve-computed.d.ts.map +1 -0
- package/dist/operations/query/resolve-computed.js +197 -0
- package/dist/operations/query/resolve-computed.js.map +1 -0
- package/dist/operations/query/search.d.ts +110 -0
- package/dist/operations/query/search.d.ts.map +1 -0
- package/dist/operations/query/search.js +188 -0
- package/dist/operations/query/search.js.map +1 -0
- package/dist/operations/query/select-stream.d.ts +27 -0
- package/dist/operations/query/select-stream.d.ts.map +1 -0
- package/dist/operations/query/select-stream.js +88 -0
- package/dist/operations/query/select-stream.js.map +1 -0
- package/dist/operations/query/select.d.ts +54 -0
- package/dist/operations/query/select.d.ts.map +1 -0
- package/dist/operations/query/select.js +159 -0
- package/dist/operations/query/select.js.map +1 -0
- package/dist/operations/query/sort-stream.d.ts +46 -0
- package/dist/operations/query/sort-stream.d.ts.map +1 -0
- package/dist/operations/query/sort-stream.js +158 -0
- package/dist/operations/query/sort-stream.js.map +1 -0
- package/dist/operations/query/sort.d.ts +9 -0
- package/dist/operations/query/sort.d.ts.map +1 -0
- package/dist/operations/query/sort.js +58 -0
- package/dist/operations/query/sort.js.map +1 -0
- package/dist/operations/relationships/populate-stream.d.ts +29 -0
- package/dist/operations/relationships/populate-stream.d.ts.map +1 -0
- package/dist/operations/relationships/populate-stream.js +159 -0
- package/dist/operations/relationships/populate-stream.js.map +1 -0
- package/dist/operations/relationships/populate.d.ts +15 -0
- package/dist/operations/relationships/populate.d.ts.map +1 -0
- package/dist/operations/relationships/populate.js +228 -0
- package/dist/operations/relationships/populate.js.map +1 -0
- package/dist/plugins/plugin-hooks.d.ts +25 -0
- package/dist/plugins/plugin-hooks.d.ts.map +1 -0
- package/dist/plugins/plugin-hooks.js +64 -0
- package/dist/plugins/plugin-hooks.js.map +1 -0
- package/dist/plugins/plugin-registry.d.ts +26 -0
- package/dist/plugins/plugin-registry.d.ts.map +1 -0
- package/dist/plugins/plugin-registry.js +150 -0
- package/dist/plugins/plugin-registry.js.map +1 -0
- package/dist/plugins/plugin-types.d.ts +95 -0
- package/dist/plugins/plugin-types.d.ts.map +1 -0
- package/dist/plugins/plugin-types.js +6 -0
- package/dist/plugins/plugin-types.js.map +1 -0
- package/dist/plugins/plugin-validation.d.ts +49 -0
- package/dist/plugins/plugin-validation.d.ts.map +1 -0
- package/dist/plugins/plugin-validation.js +295 -0
- package/dist/plugins/plugin-validation.js.map +1 -0
- package/dist/reactive/change-event.d.ts +44 -0
- package/dist/reactive/change-event.d.ts.map +1 -0
- package/dist/reactive/change-event.js +49 -0
- package/dist/reactive/change-event.js.map +1 -0
- package/dist/reactive/change-pubsub.d.ts +32 -0
- package/dist/reactive/change-pubsub.d.ts.map +1 -0
- package/dist/reactive/change-pubsub.js +31 -0
- package/dist/reactive/change-pubsub.js.map +1 -0
- package/dist/reactive/evaluate-query.d.ts +62 -0
- package/dist/reactive/evaluate-query.d.ts.map +1 -0
- package/dist/reactive/evaluate-query.js +57 -0
- package/dist/reactive/evaluate-query.js.map +1 -0
- package/dist/reactive/watch-by-id.d.ts +53 -0
- package/dist/reactive/watch-by-id.d.ts.map +1 -0
- package/dist/reactive/watch-by-id.js +55 -0
- package/dist/reactive/watch-by-id.js.map +1 -0
- package/dist/reactive/watch.d.ts +78 -0
- package/dist/reactive/watch.d.ts.map +1 -0
- package/dist/reactive/watch.js +133 -0
- package/dist/reactive/watch.js.map +1 -0
- package/dist/serializers/codecs/hjson.d.ts +33 -0
- package/dist/serializers/codecs/hjson.d.ts.map +1 -0
- package/dist/serializers/codecs/hjson.js +40 -0
- package/dist/serializers/codecs/hjson.js.map +1 -0
- package/dist/serializers/codecs/json.d.ts +22 -0
- package/dist/serializers/codecs/json.d.ts.map +1 -0
- package/dist/serializers/codecs/json.js +28 -0
- package/dist/serializers/codecs/json.js.map +1 -0
- package/dist/serializers/codecs/json5.d.ts +26 -0
- package/dist/serializers/codecs/json5.d.ts.map +1 -0
- package/dist/serializers/codecs/json5.js +33 -0
- package/dist/serializers/codecs/json5.js.map +1 -0
- package/dist/serializers/codecs/jsonc.d.ts +29 -0
- package/dist/serializers/codecs/jsonc.d.ts.map +1 -0
- package/dist/serializers/codecs/jsonc.js +38 -0
- package/dist/serializers/codecs/jsonc.js.map +1 -0
- package/dist/serializers/codecs/jsonl.d.ts +17 -0
- package/dist/serializers/codecs/jsonl.d.ts.map +1 -0
- package/dist/serializers/codecs/jsonl.js +31 -0
- package/dist/serializers/codecs/jsonl.js.map +1 -0
- package/dist/serializers/codecs/prose.d.ts +419 -0
- package/dist/serializers/codecs/prose.d.ts.map +1 -0
- package/dist/serializers/codecs/prose.js +1060 -0
- package/dist/serializers/codecs/prose.js.map +1 -0
- package/dist/serializers/codecs/toml.d.ts +23 -0
- package/dist/serializers/codecs/toml.d.ts.map +1 -0
- package/dist/serializers/codecs/toml.js +66 -0
- package/dist/serializers/codecs/toml.js.map +1 -0
- package/dist/serializers/codecs/toon.d.ts +20 -0
- package/dist/serializers/codecs/toon.d.ts.map +1 -0
- package/dist/serializers/codecs/toon.js +33 -0
- package/dist/serializers/codecs/toon.js.map +1 -0
- package/dist/serializers/codecs/yaml.d.ts +24 -0
- package/dist/serializers/codecs/yaml.d.ts.map +1 -0
- package/dist/serializers/codecs/yaml.js +31 -0
- package/dist/serializers/codecs/yaml.js.map +1 -0
- package/dist/serializers/format-codec.d.ts +53 -0
- package/dist/serializers/format-codec.d.ts.map +1 -0
- package/dist/serializers/format-codec.js +148 -0
- package/dist/serializers/format-codec.js.map +1 -0
- package/dist/serializers/presets.d.ts +48 -0
- package/dist/serializers/presets.d.ts.map +1 -0
- package/dist/serializers/presets.js +72 -0
- package/dist/serializers/presets.js.map +1 -0
- package/dist/serializers/serializer-service.d.ts +11 -0
- package/dist/serializers/serializer-service.d.ts.map +1 -0
- package/dist/serializers/serializer-service.js +4 -0
- package/dist/serializers/serializer-service.js.map +1 -0
- package/dist/state/collection-state.d.ts +19 -0
- package/dist/state/collection-state.d.ts.map +1 -0
- package/dist/state/collection-state.js +15 -0
- package/dist/state/collection-state.js.map +1 -0
- package/dist/state/state-operations.d.ts +38 -0
- package/dist/state/state-operations.d.ts.map +1 -0
- package/dist/state/state-operations.js +65 -0
- package/dist/state/state-operations.js.map +1 -0
- package/dist/storage/in-memory-adapter-layer.d.ts +16 -0
- package/dist/storage/in-memory-adapter-layer.d.ts.map +1 -0
- package/dist/storage/in-memory-adapter-layer.js +81 -0
- package/dist/storage/in-memory-adapter-layer.js.map +1 -0
- package/dist/storage/persistence-effect.d.ts +244 -0
- package/dist/storage/persistence-effect.d.ts.map +1 -0
- package/dist/storage/persistence-effect.js +551 -0
- package/dist/storage/persistence-effect.js.map +1 -0
- package/dist/storage/storage-service.d.ts +22 -0
- package/dist/storage/storage-service.d.ts.map +1 -0
- package/dist/storage/storage-service.js +4 -0
- package/dist/storage/storage-service.js.map +1 -0
- package/dist/storage/transforms.d.ts +183 -0
- package/dist/storage/transforms.d.ts.map +1 -0
- package/dist/storage/transforms.js +263 -0
- package/dist/storage/transforms.js.map +1 -0
- package/dist/transactions/transaction.d.ts +87 -0
- package/dist/transactions/transaction.d.ts.map +1 -0
- package/dist/transactions/transaction.js +240 -0
- package/dist/transactions/transaction.js.map +1 -0
- package/dist/types/aggregate-types.d.ts +73 -0
- package/dist/types/aggregate-types.d.ts.map +1 -0
- package/dist/types/aggregate-types.js +14 -0
- package/dist/types/aggregate-types.js.map +1 -0
- package/dist/types/computed-types.d.ts +71 -0
- package/dist/types/computed-types.d.ts.map +1 -0
- package/dist/types/computed-types.js +8 -0
- package/dist/types/computed-types.js.map +1 -0
- package/dist/types/crud-relationship-types.d.ts +180 -0
- package/dist/types/crud-relationship-types.d.ts.map +1 -0
- package/dist/types/crud-relationship-types.js +17 -0
- package/dist/types/crud-relationship-types.js.map +1 -0
- package/dist/types/crud-types.d.ts +343 -0
- package/dist/types/crud-types.d.ts.map +1 -0
- package/dist/types/crud-types.js +43 -0
- package/dist/types/crud-types.js.map +1 -0
- package/dist/types/cursor-types.d.ts +52 -0
- package/dist/types/cursor-types.d.ts.map +1 -0
- package/dist/types/cursor-types.js +2 -0
- package/dist/types/cursor-types.js.map +1 -0
- package/dist/types/database-config-types.d.ts +196 -0
- package/dist/types/database-config-types.d.ts.map +1 -0
- package/dist/types/database-config-types.js +11 -0
- package/dist/types/database-config-types.js.map +1 -0
- package/dist/types/hook-types.d.ts +158 -0
- package/dist/types/hook-types.d.ts.map +1 -0
- package/dist/types/hook-types.js +6 -0
- package/dist/types/hook-types.js.map +1 -0
- package/dist/types/index-types.d.ts +42 -0
- package/dist/types/index-types.d.ts.map +1 -0
- package/dist/types/index-types.js +8 -0
- package/dist/types/index-types.js.map +1 -0
- package/dist/types/operators.d.ts +5 -0
- package/dist/types/operators.d.ts.map +1 -0
- package/dist/types/operators.js +297 -0
- package/dist/types/operators.js.map +1 -0
- package/dist/types/query-overloads.d.ts +54 -0
- package/dist/types/query-overloads.d.ts.map +1 -0
- package/dist/types/query-overloads.js +3 -0
- package/dist/types/query-overloads.js.map +1 -0
- package/dist/types/reactive-types.d.ts +75 -0
- package/dist/types/reactive-types.d.ts.map +1 -0
- package/dist/types/reactive-types.js +7 -0
- package/dist/types/reactive-types.js.map +1 -0
- package/dist/types/schema-types.d.ts +56 -0
- package/dist/types/schema-types.d.ts.map +1 -0
- package/dist/types/schema-types.js +8 -0
- package/dist/types/schema-types.js.map +1 -0
- package/dist/types/search-types.d.ts +82 -0
- package/dist/types/search-types.d.ts.map +1 -0
- package/dist/types/search-types.js +110 -0
- package/dist/types/search-types.js.map +1 -0
- package/dist/types/types.d.ts +286 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +2 -0
- package/dist/types/types.js.map +1 -0
- package/dist/utils/id-generator.d.ts +97 -0
- package/dist/utils/id-generator.d.ts.map +1 -0
- package/dist/utils/id-generator.js +247 -0
- package/dist/utils/id-generator.js.map +1 -0
- package/dist/utils/nested-path.d.ts +56 -0
- package/dist/utils/nested-path.d.ts.map +1 -0
- package/dist/utils/nested-path.js +119 -0
- package/dist/utils/nested-path.js.map +1 -0
- package/dist/utils/path.d.ts +16 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +24 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/validators/foreign-key.d.ts +49 -0
- package/dist/validators/foreign-key.d.ts.map +1 -0
- package/dist/validators/foreign-key.js +153 -0
- package/dist/validators/foreign-key.js.map +1 -0
- package/dist/validators/schema-validator.d.ts +19 -0
- package/dist/validators/schema-validator.d.ts.map +1 -0
- package/dist/validators/schema-validator.js +34 -0
- package/dist/validators/schema-validator.js.map +1 -0
- 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
|