@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,197 @@
|
|
|
1
|
+
import { Stream } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve computed fields for a single entity.
|
|
4
|
+
*
|
|
5
|
+
* Iterates over the computed config, calling each derivation function with the entity,
|
|
6
|
+
* and returns a new object with the original entity fields plus computed field values.
|
|
7
|
+
*
|
|
8
|
+
* @template T - The entity type (stored fields, possibly with populated relationships)
|
|
9
|
+
* @template C - The computed fields config type
|
|
10
|
+
*
|
|
11
|
+
* @param entity - The entity to resolve computed fields for
|
|
12
|
+
* @param config - The computed fields configuration (field name → derivation function)
|
|
13
|
+
* @returns A new object with entity fields plus computed fields attached
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const book = { id: "1", title: "Dune", year: 1965 }
|
|
18
|
+
* const config = {
|
|
19
|
+
* displayName: (b) => `${b.title} (${b.year})`,
|
|
20
|
+
* isClassic: (b) => b.year < 1980,
|
|
21
|
+
* }
|
|
22
|
+
* const result = resolveComputedFields(book, config)
|
|
23
|
+
* // { id: "1", title: "Dune", year: 1965, displayName: "Dune (1965)", isClassic: true }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export const resolveComputedFields = (entity, config) => {
|
|
27
|
+
const computedValues = {};
|
|
28
|
+
for (const key of Object.keys(config)) {
|
|
29
|
+
const derivationFn = config[key];
|
|
30
|
+
if (typeof derivationFn === "function") {
|
|
31
|
+
computedValues[key] = derivationFn(entity);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { ...entity, ...computedValues };
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Apply computed field resolution as a Stream combinator.
|
|
38
|
+
* Returns a function that transforms Stream<T> → Stream<WithComputed<T, C>>,
|
|
39
|
+
* mapping the resolution function over each entity.
|
|
40
|
+
*
|
|
41
|
+
* When the config is undefined or has no keys, returns the stream unchanged
|
|
42
|
+
* (no resolution overhead).
|
|
43
|
+
*
|
|
44
|
+
* @template T - The entity type (stored fields, possibly with populated relationships)
|
|
45
|
+
* @template C - The computed fields config type
|
|
46
|
+
*
|
|
47
|
+
* @param config - The computed fields configuration (field name → derivation function), or undefined
|
|
48
|
+
* @returns A stream combinator function
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const config = {
|
|
53
|
+
* displayName: (b) => `${b.title} (${b.year})`,
|
|
54
|
+
* isClassic: (b) => b.year < 1980,
|
|
55
|
+
* }
|
|
56
|
+
* const enrichedStream = stream.pipe(resolveComputedStream(config))
|
|
57
|
+
* // Each entity in the resulting stream has displayName and isClassic attached
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export const resolveComputedStream = (config) => (stream) => {
|
|
61
|
+
// When config is empty or undefined, return stream unchanged
|
|
62
|
+
if (config === undefined || Object.keys(config).length === 0) {
|
|
63
|
+
return stream;
|
|
64
|
+
}
|
|
65
|
+
return Stream.map(stream, (entity) => resolveComputedFields(entity, config));
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Strip computed field keys from an entity object.
|
|
69
|
+
* Used as a safety net before persistence to ensure computed fields
|
|
70
|
+
* are never written to storage.
|
|
71
|
+
*
|
|
72
|
+
* @template T - The original entity type (stored fields only)
|
|
73
|
+
* @template C - The computed fields config type
|
|
74
|
+
*
|
|
75
|
+
* @param entity - The entity (possibly with computed fields attached)
|
|
76
|
+
* @param config - The computed fields configuration that defines which keys to strip
|
|
77
|
+
* @returns A new object with only stored fields (computed fields removed)
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const entityWithComputed = {
|
|
82
|
+
* id: "1",
|
|
83
|
+
* title: "Dune",
|
|
84
|
+
* year: 1965,
|
|
85
|
+
* displayName: "Dune (1965)", // computed
|
|
86
|
+
* isClassic: true, // computed
|
|
87
|
+
* }
|
|
88
|
+
* const config = {
|
|
89
|
+
* displayName: (b) => `${b.title} (${b.year})`,
|
|
90
|
+
* isClassic: (b) => b.year < 1980,
|
|
91
|
+
* }
|
|
92
|
+
* const stored = stripComputedFields(entityWithComputed, config)
|
|
93
|
+
* // { id: "1", title: "Dune", year: 1965 }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export const stripComputedFields = (entity, config) => {
|
|
97
|
+
// When config is empty or undefined, return entity unchanged
|
|
98
|
+
if (config === undefined || Object.keys(config).length === 0) {
|
|
99
|
+
return entity;
|
|
100
|
+
}
|
|
101
|
+
const computedKeys = new Set(Object.keys(config));
|
|
102
|
+
const result = {};
|
|
103
|
+
for (const key of Object.keys(entity)) {
|
|
104
|
+
if (!computedKeys.has(key)) {
|
|
105
|
+
result[key] = entity[key];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Check if any computed fields are selected based on the select configuration.
|
|
112
|
+
*
|
|
113
|
+
* @param computedConfig - The computed fields configuration
|
|
114
|
+
* @param select - The select configuration (object with true values for selected fields)
|
|
115
|
+
* @returns true if any computed field is selected (or select is undefined meaning all fields)
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const computedConfig = {
|
|
120
|
+
* displayName: (b) => `${b.title} (${b.year})`,
|
|
121
|
+
* isClassic: (b) => b.year < 1980,
|
|
122
|
+
* }
|
|
123
|
+
*
|
|
124
|
+
* // No select = all fields including computed
|
|
125
|
+
* hasSelectedComputedFields(computedConfig, undefined) // true
|
|
126
|
+
*
|
|
127
|
+
* // Select includes computed field
|
|
128
|
+
* hasSelectedComputedFields(computedConfig, { title: true, displayName: true }) // true
|
|
129
|
+
*
|
|
130
|
+
* // Select excludes all computed fields
|
|
131
|
+
* hasSelectedComputedFields(computedConfig, { title: true, year: true }) // false
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export const hasSelectedComputedFields = (computedConfig, select) => {
|
|
135
|
+
// No computed config means no computed fields to select
|
|
136
|
+
if (computedConfig === undefined ||
|
|
137
|
+
Object.keys(computedConfig).length === 0) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
// No select means all fields are selected (including computed)
|
|
141
|
+
if (select === undefined) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
// Check if any computed field key is in the select config
|
|
145
|
+
const computedKeys = Object.keys(computedConfig);
|
|
146
|
+
const selectKeys = Object.keys(select);
|
|
147
|
+
for (const computedKey of computedKeys) {
|
|
148
|
+
if (selectKeys.includes(computedKey) && select[computedKey] === true) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Apply computed field resolution with lazy skip optimization.
|
|
156
|
+
*
|
|
157
|
+
* When `select` is provided and has no intersection with computed field keys,
|
|
158
|
+
* bypasses resolution entirely by returning the stream unchanged. This avoids
|
|
159
|
+
* unnecessary computation when only stored fields are needed.
|
|
160
|
+
*
|
|
161
|
+
* @template T - The entity type (stored fields, possibly with populated relationships)
|
|
162
|
+
* @template C - The computed fields config type
|
|
163
|
+
*
|
|
164
|
+
* @param config - The computed fields configuration (field name → derivation function), or undefined
|
|
165
|
+
* @param select - The select configuration (object with true values for selected fields), or undefined
|
|
166
|
+
* @returns A stream combinator function
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```ts
|
|
170
|
+
* const config = {
|
|
171
|
+
* displayName: (b) => `${b.title} (${b.year})`,
|
|
172
|
+
* isClassic: (b) => b.year < 1980,
|
|
173
|
+
* }
|
|
174
|
+
*
|
|
175
|
+
* // When select includes computed fields, resolution is applied
|
|
176
|
+
* const enrichedStream = stream.pipe(
|
|
177
|
+
* resolveComputedStreamWithLazySkip(config, { title: true, displayName: true })
|
|
178
|
+
* )
|
|
179
|
+
*
|
|
180
|
+
* // When select excludes all computed fields, resolution is skipped
|
|
181
|
+
* const storedOnlyStream = stream.pipe(
|
|
182
|
+
* resolveComputedStreamWithLazySkip(config, { title: true, year: true })
|
|
183
|
+
* )
|
|
184
|
+
* // → stream returned unchanged, no resolution overhead
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
export const resolveComputedStreamWithLazySkip = (config, select) => (stream) => {
|
|
188
|
+
// Check if any computed fields are selected
|
|
189
|
+
// Cast to unknown first to bypass contravariance check - we're only checking keys
|
|
190
|
+
if (!hasSelectedComputedFields(config, select)) {
|
|
191
|
+
// No computed fields selected, return stream unchanged
|
|
192
|
+
return stream;
|
|
193
|
+
}
|
|
194
|
+
// Computed fields are selected, apply resolution
|
|
195
|
+
return Stream.map(stream, (entity) => resolveComputedFields(entity, config));
|
|
196
|
+
};
|
|
197
|
+
//# sourceMappingURL=resolve-computed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-computed.js","sourceRoot":"","sources":["../../../src/operations/query/resolve-computed.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-text search operations: tokenization, matching, and relevance scoring.
|
|
3
|
+
*
|
|
4
|
+
* Provides pure functions for text search functionality including
|
|
5
|
+
* tokenization, stop word filtering, and relevance-based ranking.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Tokenize text into normalized tokens.
|
|
9
|
+
*
|
|
10
|
+
* Splits on whitespace, strips leading/trailing punctuation characters,
|
|
11
|
+
* lowercases all tokens, and filters out empty strings.
|
|
12
|
+
*
|
|
13
|
+
* @param text - The text to tokenize
|
|
14
|
+
* @returns Array of normalized tokens
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* tokenize("Gibson, William")
|
|
19
|
+
* // => ["gibson", "william"]
|
|
20
|
+
*
|
|
21
|
+
* tokenize("The Left Hand of Darkness")
|
|
22
|
+
* // => ["the", "left", "hand", "of", "darkness"]
|
|
23
|
+
*
|
|
24
|
+
* tokenize(" ")
|
|
25
|
+
* // => []
|
|
26
|
+
*
|
|
27
|
+
* tokenize("")
|
|
28
|
+
* // => []
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function tokenize(text: string): ReadonlyArray<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Tokenize text with optional stop word filtering.
|
|
34
|
+
*
|
|
35
|
+
* Calls `tokenize` then optionally filters out common stop words
|
|
36
|
+
* (articles, prepositions, conjunctions, etc.) that typically don't
|
|
37
|
+
* contribute to search relevance.
|
|
38
|
+
*
|
|
39
|
+
* @param text - The text to tokenize
|
|
40
|
+
* @param removeStopWords - Whether to filter out stop words
|
|
41
|
+
* @returns Array of normalized tokens, optionally without stop words
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* tokenizeWithStopWords("The Left Hand of Darkness", false)
|
|
46
|
+
* // => ["the", "left", "hand", "of", "darkness"]
|
|
47
|
+
*
|
|
48
|
+
* tokenizeWithStopWords("The Left Hand of Darkness", true)
|
|
49
|
+
* // => ["left", "hand", "darkness"]
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function tokenizeWithStopWords(text: string, removeStopWords: boolean): ReadonlyArray<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Compute the relevance score for a single field value against query tokens.
|
|
55
|
+
*
|
|
56
|
+
* Scoring uses three factors combined multiplicatively:
|
|
57
|
+
* 1. **Term coverage**: Fraction of query tokens that matched (0..1)
|
|
58
|
+
* 2. **Term frequency (TF)**: How many times query tokens appear in the field
|
|
59
|
+
* 3. **Field length normalization**: Shorter fields score higher for same match
|
|
60
|
+
*
|
|
61
|
+
* Prefix matches (via startsWith) count toward matching but score slightly lower
|
|
62
|
+
* than exact matches to preserve ranking quality.
|
|
63
|
+
*
|
|
64
|
+
* @param fieldValue - The field value to score
|
|
65
|
+
* @param queryTokens - The tokenized search query
|
|
66
|
+
* @returns Relevance score (0 if no tokens match)
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* computeFieldScore("Dune", ["dune"])
|
|
71
|
+
* // => ~1.44 (exact match, short field)
|
|
72
|
+
*
|
|
73
|
+
* computeFieldScore("The Left Hand of Darkness", ["left", "hand"])
|
|
74
|
+
* // => ~0.29 (2/2 coverage, longer field)
|
|
75
|
+
*
|
|
76
|
+
* computeFieldScore("Neuromancer", ["neuro"])
|
|
77
|
+
* // => ~0.72 (prefix match, reduced score)
|
|
78
|
+
*
|
|
79
|
+
* computeFieldScore("Dune", ["xyz"])
|
|
80
|
+
* // => 0 (no match)
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function computeFieldScore(fieldValue: string, queryTokens: ReadonlyArray<string>): number;
|
|
84
|
+
/**
|
|
85
|
+
* Compute the total relevance score for an entity across multiple fields.
|
|
86
|
+
*
|
|
87
|
+
* Sums the field scores for all specified fields. Fields that are not
|
|
88
|
+
* present on the entity or are not strings are skipped (score 0).
|
|
89
|
+
*
|
|
90
|
+
* @param entity - The entity to score
|
|
91
|
+
* @param queryTokens - The tokenized search query
|
|
92
|
+
* @param fields - The fields to search across
|
|
93
|
+
* @returns Total relevance score (0 if no fields match)
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const book = { title: "Dune", author: "Frank Herbert", year: 1965 }
|
|
98
|
+
*
|
|
99
|
+
* computeSearchScore(book, ["dune"], ["title", "author"])
|
|
100
|
+
* // => ~1.44 (matches in title only)
|
|
101
|
+
*
|
|
102
|
+
* computeSearchScore(book, ["herbert", "dune"], ["title", "author"])
|
|
103
|
+
* // => ~2.88 (matches in both fields)
|
|
104
|
+
*
|
|
105
|
+
* computeSearchScore(book, ["xyz"], ["title", "author"])
|
|
106
|
+
* // => 0 (no matches)
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export declare function computeSearchScore(entity: Record<string, unknown>, queryTokens: ReadonlyArray<string>, fields: ReadonlyArray<string>): number;
|
|
110
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/operations/query/search.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAM5D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,OAAO,GACtB,aAAa,CAAC,MAAM,CAAC,CAMvB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAChC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,GAChC,MAAM,CAqDR;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,EAClC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,GAC3B,MAAM,CAoBR"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-text search operations: tokenization, matching, and relevance scoring.
|
|
3
|
+
*
|
|
4
|
+
* Provides pure functions for text search functionality including
|
|
5
|
+
* tokenization, stop word filtering, and relevance-based ranking.
|
|
6
|
+
*/
|
|
7
|
+
import { STOP_WORDS } from "../../types/search-types.js";
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Tokenization
|
|
10
|
+
// ============================================================================
|
|
11
|
+
/**
|
|
12
|
+
* Tokenize text into normalized tokens.
|
|
13
|
+
*
|
|
14
|
+
* Splits on whitespace, strips leading/trailing punctuation characters,
|
|
15
|
+
* lowercases all tokens, and filters out empty strings.
|
|
16
|
+
*
|
|
17
|
+
* @param text - The text to tokenize
|
|
18
|
+
* @returns Array of normalized tokens
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* tokenize("Gibson, William")
|
|
23
|
+
* // => ["gibson", "william"]
|
|
24
|
+
*
|
|
25
|
+
* tokenize("The Left Hand of Darkness")
|
|
26
|
+
* // => ["the", "left", "hand", "of", "darkness"]
|
|
27
|
+
*
|
|
28
|
+
* tokenize(" ")
|
|
29
|
+
* // => []
|
|
30
|
+
*
|
|
31
|
+
* tokenize("")
|
|
32
|
+
* // => []
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function tokenize(text) {
|
|
36
|
+
return text
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.split(/\s+/)
|
|
39
|
+
.map((t) => t.replace(/^[^\w]+|[^\w]+$/g, ""))
|
|
40
|
+
.filter((t) => t.length > 0);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Tokenize text with optional stop word filtering.
|
|
44
|
+
*
|
|
45
|
+
* Calls `tokenize` then optionally filters out common stop words
|
|
46
|
+
* (articles, prepositions, conjunctions, etc.) that typically don't
|
|
47
|
+
* contribute to search relevance.
|
|
48
|
+
*
|
|
49
|
+
* @param text - The text to tokenize
|
|
50
|
+
* @param removeStopWords - Whether to filter out stop words
|
|
51
|
+
* @returns Array of normalized tokens, optionally without stop words
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* tokenizeWithStopWords("The Left Hand of Darkness", false)
|
|
56
|
+
* // => ["the", "left", "hand", "of", "darkness"]
|
|
57
|
+
*
|
|
58
|
+
* tokenizeWithStopWords("The Left Hand of Darkness", true)
|
|
59
|
+
* // => ["left", "hand", "darkness"]
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function tokenizeWithStopWords(text, removeStopWords) {
|
|
63
|
+
const tokens = tokenize(text);
|
|
64
|
+
if (!removeStopWords) {
|
|
65
|
+
return tokens;
|
|
66
|
+
}
|
|
67
|
+
return tokens.filter((token) => !STOP_WORDS.has(token));
|
|
68
|
+
}
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Relevance Scoring
|
|
71
|
+
// ============================================================================
|
|
72
|
+
/**
|
|
73
|
+
* Compute the relevance score for a single field value against query tokens.
|
|
74
|
+
*
|
|
75
|
+
* Scoring uses three factors combined multiplicatively:
|
|
76
|
+
* 1. **Term coverage**: Fraction of query tokens that matched (0..1)
|
|
77
|
+
* 2. **Term frequency (TF)**: How many times query tokens appear in the field
|
|
78
|
+
* 3. **Field length normalization**: Shorter fields score higher for same match
|
|
79
|
+
*
|
|
80
|
+
* Prefix matches (via startsWith) count toward matching but score slightly lower
|
|
81
|
+
* than exact matches to preserve ranking quality.
|
|
82
|
+
*
|
|
83
|
+
* @param fieldValue - The field value to score
|
|
84
|
+
* @param queryTokens - The tokenized search query
|
|
85
|
+
* @returns Relevance score (0 if no tokens match)
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* computeFieldScore("Dune", ["dune"])
|
|
90
|
+
* // => ~1.44 (exact match, short field)
|
|
91
|
+
*
|
|
92
|
+
* computeFieldScore("The Left Hand of Darkness", ["left", "hand"])
|
|
93
|
+
* // => ~0.29 (2/2 coverage, longer field)
|
|
94
|
+
*
|
|
95
|
+
* computeFieldScore("Neuromancer", ["neuro"])
|
|
96
|
+
* // => ~0.72 (prefix match, reduced score)
|
|
97
|
+
*
|
|
98
|
+
* computeFieldScore("Dune", ["xyz"])
|
|
99
|
+
* // => 0 (no match)
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export function computeFieldScore(fieldValue, queryTokens) {
|
|
103
|
+
// Handle edge cases
|
|
104
|
+
if (queryTokens.length === 0) {
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
const fieldTokens = tokenize(fieldValue);
|
|
108
|
+
if (fieldTokens.length === 0) {
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
// Count matched tokens and compute term frequency
|
|
112
|
+
let matchedTermCount = 0;
|
|
113
|
+
let termFrequencySum = 0;
|
|
114
|
+
for (const queryToken of queryTokens) {
|
|
115
|
+
let tokenMatched = false;
|
|
116
|
+
let tokenFrequency = 0;
|
|
117
|
+
for (const fieldToken of fieldTokens) {
|
|
118
|
+
if (fieldToken === queryToken) {
|
|
119
|
+
// Exact match: full weight
|
|
120
|
+
tokenFrequency += 1;
|
|
121
|
+
tokenMatched = true;
|
|
122
|
+
}
|
|
123
|
+
else if (fieldToken.startsWith(queryToken)) {
|
|
124
|
+
// Prefix match: reduced weight (0.5x)
|
|
125
|
+
tokenFrequency += 0.5;
|
|
126
|
+
tokenMatched = true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (tokenMatched) {
|
|
130
|
+
matchedTermCount += 1;
|
|
131
|
+
termFrequencySum += tokenFrequency;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// No matches means score of 0
|
|
135
|
+
if (matchedTermCount === 0) {
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
// Compute score using the three factors:
|
|
139
|
+
// 1. Coverage: fraction of query tokens that matched
|
|
140
|
+
const coverage = matchedTermCount / queryTokens.length;
|
|
141
|
+
// 2. TF boost: more occurrences = higher score
|
|
142
|
+
const tfBoost = 1 + termFrequencySum / fieldTokens.length;
|
|
143
|
+
// 3. Length normalization: shorter fields score higher
|
|
144
|
+
const lengthNorm = 1 / Math.log(1 + fieldTokens.length);
|
|
145
|
+
return coverage * tfBoost * lengthNorm;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Compute the total relevance score for an entity across multiple fields.
|
|
149
|
+
*
|
|
150
|
+
* Sums the field scores for all specified fields. Fields that are not
|
|
151
|
+
* present on the entity or are not strings are skipped (score 0).
|
|
152
|
+
*
|
|
153
|
+
* @param entity - The entity to score
|
|
154
|
+
* @param queryTokens - The tokenized search query
|
|
155
|
+
* @param fields - The fields to search across
|
|
156
|
+
* @returns Total relevance score (0 if no fields match)
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* const book = { title: "Dune", author: "Frank Herbert", year: 1965 }
|
|
161
|
+
*
|
|
162
|
+
* computeSearchScore(book, ["dune"], ["title", "author"])
|
|
163
|
+
* // => ~1.44 (matches in title only)
|
|
164
|
+
*
|
|
165
|
+
* computeSearchScore(book, ["herbert", "dune"], ["title", "author"])
|
|
166
|
+
* // => ~2.88 (matches in both fields)
|
|
167
|
+
*
|
|
168
|
+
* computeSearchScore(book, ["xyz"], ["title", "author"])
|
|
169
|
+
* // => 0 (no matches)
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export function computeSearchScore(entity, queryTokens, fields) {
|
|
173
|
+
// Handle edge cases
|
|
174
|
+
if (queryTokens.length === 0 || fields.length === 0) {
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
let totalScore = 0;
|
|
178
|
+
for (const field of fields) {
|
|
179
|
+
const fieldValue = entity[field];
|
|
180
|
+
// Skip non-string fields
|
|
181
|
+
if (typeof fieldValue !== "string") {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
totalScore += computeFieldScore(fieldValue, queryTokens);
|
|
185
|
+
}
|
|
186
|
+
return totalScore;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../../src/operations/query/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Stream } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* A select configuration can be either:
|
|
4
|
+
* - Object-based: `{ name: true, email: true, company: { name: true } }`
|
|
5
|
+
* - Array-based: `["name", "email"]` (picks those fields)
|
|
6
|
+
*/
|
|
7
|
+
type SelectConfig = Record<string, unknown> | ReadonlyArray<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Apply a field selection as a Stream combinator.
|
|
10
|
+
* Returns a function that transforms Stream<T> → Stream<T>, projecting each entity
|
|
11
|
+
* to only the selected fields.
|
|
12
|
+
*
|
|
13
|
+
* Supports both object-based (`{ name: true, email: true }`) and
|
|
14
|
+
* array-based (`["name", "email"]`) selection configs.
|
|
15
|
+
* Nested selection on populated relationships is supported via object-based config:
|
|
16
|
+
* `{ name: true, company: { name: true } }`.
|
|
17
|
+
*/
|
|
18
|
+
export declare const applySelect: <T extends Record<string, unknown>>(select: SelectConfig | undefined) => <E, R>(stream: Stream.Stream<T, E, R>) => Stream.Stream<T, E, R>;
|
|
19
|
+
/**
|
|
20
|
+
* Apply a field selection to an array of items.
|
|
21
|
+
*
|
|
22
|
+
* This is used by cursor pagination where items are collected before selection.
|
|
23
|
+
* Supports both object-based and array-based selection configs.
|
|
24
|
+
*/
|
|
25
|
+
export declare const applySelectToArray: <T extends Record<string, unknown>>(items: ReadonlyArray<T>, select: SelectConfig | undefined) => ReadonlyArray<T>;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=select-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-stream.d.ts","sourceRoot":"","sources":["../../../src/operations/query/select-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;;;GAIG;AACH,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;AAsDpE;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GACtB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,YAAY,GAAG,SAAS,MACnE,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAmB5D,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,OAAO,aAAa,CAAC,CAAC,CAAC,EACvB,QAAQ,YAAY,GAAG,SAAS,KAC9B,aAAa,CAAC,CAAC,CAcjB,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Stream } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* Type guard to check if a value is a record (non-null, non-array object).
|
|
4
|
+
*/
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Apply object-based field selection to a single item.
|
|
10
|
+
* Handles nested selection for populated relationships (both objects and arrays).
|
|
11
|
+
*/
|
|
12
|
+
function applyObjectSelect(item, selection) {
|
|
13
|
+
const result = {};
|
|
14
|
+
for (const [key, value] of Object.entries(selection)) {
|
|
15
|
+
if (!(key in item))
|
|
16
|
+
continue;
|
|
17
|
+
if (value === true) {
|
|
18
|
+
result[key] = item[key];
|
|
19
|
+
}
|
|
20
|
+
else if (isRecord(value)) {
|
|
21
|
+
// Nested selection for populated relationships
|
|
22
|
+
const nestedData = item[key];
|
|
23
|
+
if (Array.isArray(nestedData)) {
|
|
24
|
+
result[key] = nestedData
|
|
25
|
+
.filter(isRecord)
|
|
26
|
+
.map((nested) => applyObjectSelect(nested, value));
|
|
27
|
+
}
|
|
28
|
+
else if (isRecord(nestedData)) {
|
|
29
|
+
result[key] = applyObjectSelect(nestedData, value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Convert an array-based select config to an object-based one.
|
|
37
|
+
* `["name", "email"]` becomes `{ name: true, email: true }`.
|
|
38
|
+
*/
|
|
39
|
+
function arraySelectToObject(fields) {
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const field of fields) {
|
|
42
|
+
result[field] = true;
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Apply a field selection as a Stream combinator.
|
|
48
|
+
* Returns a function that transforms Stream<T> → Stream<T>, projecting each entity
|
|
49
|
+
* to only the selected fields.
|
|
50
|
+
*
|
|
51
|
+
* Supports both object-based (`{ name: true, email: true }`) and
|
|
52
|
+
* array-based (`["name", "email"]`) selection configs.
|
|
53
|
+
* Nested selection on populated relationships is supported via object-based config:
|
|
54
|
+
* `{ name: true, company: { name: true } }`.
|
|
55
|
+
*/
|
|
56
|
+
export const applySelect = (select) => (stream) => {
|
|
57
|
+
if (select === undefined || select === null)
|
|
58
|
+
return stream;
|
|
59
|
+
if (Array.isArray(select)) {
|
|
60
|
+
if (select.length === 0)
|
|
61
|
+
return stream;
|
|
62
|
+
const objectSelect = arraySelectToObject(select);
|
|
63
|
+
return Stream.map(stream, (item) => applyObjectSelect(item, objectSelect));
|
|
64
|
+
}
|
|
65
|
+
if (isRecord(select) && Object.keys(select).length === 0)
|
|
66
|
+
return stream;
|
|
67
|
+
return Stream.map(stream, (item) => applyObjectSelect(item, select));
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Apply a field selection to an array of items.
|
|
71
|
+
*
|
|
72
|
+
* This is used by cursor pagination where items are collected before selection.
|
|
73
|
+
* Supports both object-based and array-based selection configs.
|
|
74
|
+
*/
|
|
75
|
+
export const applySelectToArray = (items, select) => {
|
|
76
|
+
if (select === undefined || select === null)
|
|
77
|
+
return items;
|
|
78
|
+
if (Array.isArray(select)) {
|
|
79
|
+
if (select.length === 0)
|
|
80
|
+
return items;
|
|
81
|
+
const objectSelect = arraySelectToObject(select);
|
|
82
|
+
return items.map((item) => applyObjectSelect(item, objectSelect));
|
|
83
|
+
}
|
|
84
|
+
if (isRecord(select) && Object.keys(select).length === 0)
|
|
85
|
+
return items;
|
|
86
|
+
return items.map((item) => applyObjectSelect(item, select));
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=select-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-stream.js","sourceRoot":"","sources":["../../../src/operations/query/select-stream.ts"],"names":[],"mappings":"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"}
|