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