@lossless.org/client 1.14.0 → 1.15.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/nosqldb/classes.collection.d.ts +4 -1
- package/dist_ts/nosqldb/classes.collection.js +7 -2
- package/dist_ts/nosqldb/classes.doc.d.ts +81 -30
- package/dist_ts/nosqldb/classes.doc.js +92 -36
- package/package.json +2 -2
- package/readme.md +4 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/nosqldb/classes.collection.ts +13 -2
- package/ts/nosqldb/classes.doc.ts +227 -70
|
@@ -222,6 +222,13 @@ export interface SvDbOptions {
|
|
|
222
222
|
* below this persisted field.
|
|
223
223
|
*/
|
|
224
224
|
atomicPaths?: readonly string[];
|
|
225
|
+
/**
|
|
226
|
+
* Explicit relative nested paths below this persisted field that hold
|
|
227
|
+
* arrays. Each one is a declared nested path exactly like an `atomicPaths`
|
|
228
|
+
* entry, and in addition the array operators `$push`, `$addToSet` and
|
|
229
|
+
* `$pull` and the grouped-totals `unwind` may target it.
|
|
230
|
+
*/
|
|
231
|
+
arrayPaths?: readonly string[];
|
|
225
232
|
/**
|
|
226
233
|
* Declares this persisted top-level field as numeric for strict atomic
|
|
227
234
|
* expression references.
|
|
@@ -286,6 +293,13 @@ export function svDb(options?: SvDbOptions) {
|
|
|
286
293
|
: options.atomicPaths,
|
|
287
294
|
}
|
|
288
295
|
: {}),
|
|
296
|
+
...(options.arrayPaths !== undefined
|
|
297
|
+
? {
|
|
298
|
+
arrayPaths: Array.isArray(options.arrayPaths)
|
|
299
|
+
? [...options.arrayPaths]
|
|
300
|
+
: options.arrayPaths,
|
|
301
|
+
}
|
|
302
|
+
: {}),
|
|
289
303
|
}
|
|
290
304
|
: undefined;
|
|
291
305
|
return (value: undefined, context: ClassFieldDecoratorContext) => {
|
|
@@ -777,16 +791,48 @@ export interface ISmartdataAtomicNumericExpression<T> {
|
|
|
777
791
|
];
|
|
778
792
|
}
|
|
779
793
|
|
|
780
|
-
|
|
794
|
+
/**
|
|
795
|
+
* A path grouped totals may group by or sum: a declared top-level field or a
|
|
796
|
+
* nested path declared through `@svDb({ atomicPaths })` or
|
|
797
|
+
* `@svDb({ arrayPaths })`.
|
|
798
|
+
*/
|
|
799
|
+
export type TSmartdataGroupedTotalsPath<T> = TSmartdataAtomicDocumentPath<T>;
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* A path grouped totals may unwind: a declared top-level array field or a
|
|
803
|
+
* nested path declared through `@svDb({ arrayPaths })`.
|
|
804
|
+
*/
|
|
805
|
+
export type TSmartdataGroupedTotalsUnwindPath<T> =
|
|
806
|
+
| TSmartdataAtomicArrayPath<T>
|
|
807
|
+
| TSmartdataAtomicNestedDocumentPath<T>;
|
|
808
|
+
|
|
809
|
+
export interface ISmartdataGroupedTotalsOptions<
|
|
810
|
+
T,
|
|
811
|
+
TUnwind extends TSmartdataGroupedTotalsUnwindPath<T> = never,
|
|
812
|
+
> {
|
|
781
813
|
/**
|
|
782
|
-
* One or two declared
|
|
783
|
-
*
|
|
814
|
+
* One or two distinct declared paths whose value combinations form the
|
|
815
|
+
* groups: declared top-level fields or nested paths declared through
|
|
816
|
+
* `@svDb({ atomicPaths })` or `@svDb({ arrayPaths })`.
|
|
784
817
|
*/
|
|
785
|
-
groupBy: ReadonlyArray<
|
|
818
|
+
groupBy: ReadonlyArray<TSmartdataGroupedTotalsPath<T>>;
|
|
786
819
|
/**
|
|
787
|
-
*
|
|
820
|
+
* Groups by each element of one array instead of by the array: names
|
|
821
|
+
* exactly one `groupBy` path, which must be a declared top-level array
|
|
822
|
+
* field or a nested path declared through `@svDb({ arrayPaths })`. Every
|
|
823
|
+
* element forms its own row, so `count` counts elements and a document
|
|
824
|
+
* contributes its `sums` once per element; a document whose array is
|
|
825
|
+
* missing, null or empty contributes nothing. The `filter` still selects
|
|
826
|
+
* whole documents before the array is unwound.
|
|
788
827
|
*/
|
|
789
|
-
|
|
828
|
+
unwind?: TUnwind;
|
|
829
|
+
/**
|
|
830
|
+
* Declared numeric top-level fields, or declared nested paths holding
|
|
831
|
+
* numbers, to sum per group.
|
|
832
|
+
*/
|
|
833
|
+
sums?: ReadonlyArray<
|
|
834
|
+
TSmartdataNumericDocumentPath<T> | TSmartdataAtomicNestedDocumentPath<T>
|
|
835
|
+
>;
|
|
790
836
|
filter?: MongoFilter<T>;
|
|
791
837
|
/**
|
|
792
838
|
* Required bound on the number of returned groups (1..10000). The group
|
|
@@ -807,18 +853,37 @@ export interface ISmartdataGroupedTotalsOptions<T> {
|
|
|
807
853
|
session?: TSmartdataOrdinarySession;
|
|
808
854
|
}
|
|
809
855
|
|
|
810
|
-
export interface ISmartdataGroupedTotalsRow<
|
|
856
|
+
export interface ISmartdataGroupedTotalsRow<
|
|
857
|
+
T,
|
|
858
|
+
TUnwind extends TSmartdataGroupedTotalsUnwindPath<T> = never,
|
|
859
|
+
> {
|
|
811
860
|
/**
|
|
812
|
-
* The group's key values
|
|
813
|
-
* holding an explicit null merge into
|
|
861
|
+
* The group's key values, keyed by the `groupBy` paths as given. Documents
|
|
862
|
+
* missing a group path and documents holding an explicit null merge into
|
|
863
|
+
* one group reported as null. An unwound top-level array reports the
|
|
864
|
+
* element; nested paths report the stored value untyped.
|
|
814
865
|
*/
|
|
815
|
-
group:
|
|
866
|
+
group:
|
|
867
|
+
& {
|
|
868
|
+
[TKey in TSmartdataDocumentPath<T>]?:
|
|
869
|
+
| (TKey extends TUnwind ? TSmartdataAtomicArrayElement<T[TKey]> : T[TKey])
|
|
870
|
+
| null;
|
|
871
|
+
}
|
|
872
|
+
& Partial<Record<TSmartdataAtomicNestedDocumentPath<T>, unknown>>;
|
|
816
873
|
count: number;
|
|
817
|
-
sums: Partial<
|
|
874
|
+
sums: Partial<
|
|
875
|
+
Record<
|
|
876
|
+
TSmartdataNumericDocumentPath<T> | TSmartdataAtomicNestedDocumentPath<T>,
|
|
877
|
+
number
|
|
878
|
+
>
|
|
879
|
+
>;
|
|
818
880
|
}
|
|
819
881
|
|
|
820
|
-
export interface ISmartdataGroupedTotalsPage<
|
|
821
|
-
|
|
882
|
+
export interface ISmartdataGroupedTotalsPage<
|
|
883
|
+
T,
|
|
884
|
+
TUnwind extends TSmartdataGroupedTotalsUnwindPath<T> = never,
|
|
885
|
+
> {
|
|
886
|
+
rows: Array<ISmartdataGroupedTotalsRow<T, TUnwind>>;
|
|
822
887
|
hasMore: boolean;
|
|
823
888
|
}
|
|
824
889
|
|
|
@@ -830,12 +895,19 @@ export type TSmartdataAtomicArrayPath<T> = {
|
|
|
830
895
|
type TSmartdataAtomicArrayElement<TValue> =
|
|
831
896
|
NonNullable<TValue> extends ReadonlyArray<infer TElement> ? TElement : never;
|
|
832
897
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
898
|
+
/**
|
|
899
|
+
* Array operators take a declared top-level array field, typed by its element,
|
|
900
|
+
* or a nested path below a declared field. A nested path is accepted at run
|
|
901
|
+
* time only when the model declares it through `@svDb({ arrayPaths })`.
|
|
902
|
+
*/
|
|
903
|
+
type TSmartdataAtomicArrayOperation<T> =
|
|
904
|
+
& Partial<{
|
|
905
|
+
[TKey in TSmartdataAtomicArrayPath<T>]: TSmartdataAtomicArrayElement<T[TKey]>;
|
|
906
|
+
}>
|
|
907
|
+
& Partial<Record<TSmartdataAtomicNestedDocumentPath<T>, unknown>>;
|
|
836
908
|
|
|
837
|
-
|
|
838
|
-
|
|
909
|
+
/** The scalar equality operands `$pull` accepts. */
|
|
910
|
+
type TSmartdataAtomicPullScalarValue =
|
|
839
911
|
| string
|
|
840
912
|
| number
|
|
841
913
|
| boolean
|
|
@@ -847,14 +919,22 @@ type TSmartdataAtomicPullScalar<TValue> = Extract<
|
|
|
847
919
|
| plugins.mongodb.Long
|
|
848
920
|
| plugins.mongodb.Int32
|
|
849
921
|
| plugins.mongodb.Double
|
|
850
|
-
| plugins.mongodb.Timestamp
|
|
922
|
+
| plugins.mongodb.Timestamp;
|
|
923
|
+
|
|
924
|
+
type TSmartdataAtomicPullScalar<TValue> = Extract<
|
|
925
|
+
TValue,
|
|
926
|
+
TSmartdataAtomicPullScalarValue
|
|
851
927
|
>;
|
|
852
928
|
|
|
853
|
-
type TSmartdataAtomicArrayPullOperation<T> =
|
|
854
|
-
|
|
855
|
-
|
|
929
|
+
type TSmartdataAtomicArrayPullOperation<T> =
|
|
930
|
+
& Partial<{
|
|
931
|
+
[TKey in TSmartdataAtomicArrayPath<T>]: TSmartdataAtomicPullScalar<
|
|
932
|
+
TSmartdataAtomicArrayElement<T[TKey]>
|
|
933
|
+
>;
|
|
934
|
+
}>
|
|
935
|
+
& Partial<
|
|
936
|
+
Record<TSmartdataAtomicNestedDocumentPath<T>, TSmartdataAtomicPullScalarValue>
|
|
856
937
|
>;
|
|
857
|
-
}>;
|
|
858
938
|
|
|
859
939
|
export interface ISmartdataAtomicUpdate<T> {
|
|
860
940
|
$set?: TSmartdataAtomicSet<T>;
|
|
@@ -876,19 +956,21 @@ export interface ISmartdataAtomicUpdate<T> {
|
|
|
876
956
|
$min?: TSmartdataAtomicMonotonic<T>;
|
|
877
957
|
$setOnInsert?: TSmartdataAtomicSet<T>;
|
|
878
958
|
/**
|
|
879
|
-
* Appends one element to a declared top-level array field
|
|
880
|
-
*
|
|
959
|
+
* Appends one element to a declared top-level array field or a nested
|
|
960
|
+
* path declared through `@svDb({ arrayPaths })`. Modifier documents
|
|
961
|
+
* ($each, $position, $slice, $sort) are rejected.
|
|
881
962
|
*/
|
|
882
963
|
$push?: TSmartdataAtomicArrayOperation<T>;
|
|
883
964
|
/**
|
|
884
|
-
* Adds one element to a declared top-level array field
|
|
885
|
-
* element is already present. Modifier
|
|
965
|
+
* Adds one element to a declared top-level array field or a declared
|
|
966
|
+
* nested array path unless an equal element is already present. Modifier
|
|
967
|
+
* documents are rejected.
|
|
886
968
|
*/
|
|
887
969
|
$addToSet?: TSmartdataAtomicArrayOperation<T>;
|
|
888
970
|
/**
|
|
889
971
|
* Removes every element equal to a scalar operand from a declared
|
|
890
|
-
* top-level array field
|
|
891
|
-
* regular expressions are rejected.
|
|
972
|
+
* top-level array field or a declared nested array path. Operator
|
|
973
|
+
* documents, plain objects, arrays, and regular expressions are rejected.
|
|
892
974
|
*/
|
|
893
975
|
$pull?: TSmartdataAtomicArrayPullOperation<T>;
|
|
894
976
|
}
|
|
@@ -3051,6 +3133,8 @@ const normalizeAtomicUpdate = <T>(
|
|
|
3051
3133
|
updateArg: ISmartdataAtomicUpdate<T>,
|
|
3052
3134
|
declaredRootsArg: Set<string>,
|
|
3053
3135
|
declaredNestedPathsArg: Set<string>,
|
|
3136
|
+
/** Nested paths declared through `@svDb({ arrayPaths })`; array operators may target them. */
|
|
3137
|
+
declaredArrayPathsArg: Set<string>,
|
|
3054
3138
|
uniqueRootsArg: Set<string>,
|
|
3055
3139
|
serializedRootsArg: Set<string>,
|
|
3056
3140
|
/**
|
|
@@ -3164,10 +3248,13 @@ const normalizeAtomicUpdate = <T>(
|
|
|
3164
3248
|
}
|
|
3165
3249
|
occupiedPaths.push({ path, operator });
|
|
3166
3250
|
if (arrayOperators.has(operator)) {
|
|
3167
|
-
if (path !== root) {
|
|
3251
|
+
if (path !== root && !declaredArrayPathsArg.has(path)) {
|
|
3252
|
+
// The shape of a nested value is known only from its declaration, so
|
|
3253
|
+
// a nested path that is not declared as an array is refused rather
|
|
3254
|
+
// than left to fail on the stored value.
|
|
3168
3255
|
throw new SmartdataPersistenceError(
|
|
3169
3256
|
'invalid_argument',
|
|
3170
|
-
`${operator} may only target declared top-level array
|
|
3257
|
+
`${operator} may only target a declared top-level array field or a nested path declared through @svDb({ arrayPaths }); "${path}" is not a declared array path.`,
|
|
3171
3258
|
);
|
|
3172
3259
|
}
|
|
3173
3260
|
if (value === undefined) {
|
|
@@ -3903,21 +3990,41 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
3903
3990
|
return keySets;
|
|
3904
3991
|
}
|
|
3905
3992
|
|
|
3993
|
+
/**
|
|
3994
|
+
* Every nested path the model declares below a persisted field, through
|
|
3995
|
+
* `@svDb({ atomicPaths })` or `@svDb({ arrayPaths })`.
|
|
3996
|
+
*/
|
|
3906
3997
|
private static getDeclaredAtomicPaths(): Set<string> {
|
|
3998
|
+
return new Set([
|
|
3999
|
+
...(this as any).readDeclaredNestedPaths('atomicPaths') as Set<string>,
|
|
4000
|
+
...(this as any).readDeclaredNestedPaths('arrayPaths') as Set<string>,
|
|
4001
|
+
]);
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
/** The nested paths declared through `@svDb({ arrayPaths })` as holding arrays. */
|
|
4005
|
+
private static getDeclaredArrayPaths(): Set<string> {
|
|
4006
|
+
return (this as any).readDeclaredNestedPaths('arrayPaths') as Set<string>;
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
private static readDeclaredNestedPaths(
|
|
4010
|
+
declarationArg: 'atomicPaths' | 'arrayPaths',
|
|
4011
|
+
): Set<string> {
|
|
3907
4012
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
3908
4013
|
const uniqueRoots = (this as any).getDeclaredUniqueRoots() as Set<string>;
|
|
3909
4014
|
const options = (this as any)._svDbOptions as
|
|
3910
4015
|
| Record<string, SvDbOptions>
|
|
3911
4016
|
| undefined;
|
|
3912
|
-
const
|
|
4017
|
+
const label = declarationArg === 'atomicPaths' ? 'Atomic' : 'Array';
|
|
4018
|
+
const nestedPaths = new Set<string>();
|
|
3913
4019
|
for (const [root, fieldOptions] of Object.entries(options || {})) {
|
|
3914
|
-
|
|
4020
|
+
const relativePaths = fieldOptions[declarationArg];
|
|
4021
|
+
if (relativePaths === undefined) {
|
|
3915
4022
|
continue;
|
|
3916
4023
|
}
|
|
3917
|
-
if (!Array.isArray(
|
|
4024
|
+
if (!Array.isArray(relativePaths)) {
|
|
3918
4025
|
throw new SmartdataPersistenceError(
|
|
3919
4026
|
'invalid_configuration',
|
|
3920
|
-
|
|
4027
|
+
`${label} paths for persisted field "${root}" must be an array.`,
|
|
3921
4028
|
);
|
|
3922
4029
|
}
|
|
3923
4030
|
if (
|
|
@@ -3926,20 +4033,20 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
3926
4033
|
) {
|
|
3927
4034
|
throw new SmartdataPersistenceError(
|
|
3928
4035
|
'invalid_configuration',
|
|
3929
|
-
`Serialized persisted field "${root}" may not declare
|
|
4036
|
+
`Serialized persisted field "${root}" may not declare ${label.toLowerCase()} nested paths.`,
|
|
3930
4037
|
);
|
|
3931
4038
|
}
|
|
3932
4039
|
if (uniqueRoots.has(root)) {
|
|
3933
4040
|
throw new SmartdataPersistenceError(
|
|
3934
4041
|
'invalid_configuration',
|
|
3935
|
-
`Immutable @unI() field "${root}" may not declare
|
|
4042
|
+
`Immutable @unI() field "${root}" may not declare ${label.toLowerCase()} nested paths.`,
|
|
3936
4043
|
);
|
|
3937
4044
|
}
|
|
3938
|
-
for (const relativePath of
|
|
4045
|
+
for (const relativePath of relativePaths) {
|
|
3939
4046
|
if (typeof relativePath !== 'string' || relativePath.length === 0) {
|
|
3940
4047
|
throw new SmartdataPersistenceError(
|
|
3941
4048
|
'invalid_configuration',
|
|
3942
|
-
|
|
4049
|
+
`${label} paths for persisted field "${root}" must be non-empty strings.`,
|
|
3943
4050
|
);
|
|
3944
4051
|
}
|
|
3945
4052
|
const fullPath = `${root}.${relativePath}`;
|
|
@@ -3947,20 +4054,20 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
3947
4054
|
requireDeclaredPath(
|
|
3948
4055
|
fullPath,
|
|
3949
4056
|
declaredRoots,
|
|
3950
|
-
|
|
4057
|
+
`${label} path declaration`,
|
|
3951
4058
|
new Set([fullPath]),
|
|
3952
4059
|
);
|
|
3953
4060
|
} catch (errorArg) {
|
|
3954
4061
|
throw new SmartdataPersistenceError(
|
|
3955
4062
|
'invalid_configuration',
|
|
3956
|
-
|
|
4063
|
+
`${label} path declaration "${fullPath}" is invalid.`,
|
|
3957
4064
|
{ cause: errorArg },
|
|
3958
4065
|
);
|
|
3959
4066
|
}
|
|
3960
|
-
|
|
4067
|
+
nestedPaths.add(fullPath);
|
|
3961
4068
|
}
|
|
3962
4069
|
}
|
|
3963
|
-
return
|
|
4070
|
+
return nestedPaths;
|
|
3964
4071
|
}
|
|
3965
4072
|
|
|
3966
4073
|
private static getSerializedPersistedRoots(): Set<string> {
|
|
@@ -4297,6 +4404,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4297
4404
|
}
|
|
4298
4405
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
4299
4406
|
const declaredAtomicPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
|
|
4407
|
+
const declaredArrayPaths = (this as any).getDeclaredArrayPaths() as Set<string>;
|
|
4300
4408
|
const identityValueTypes = (this as any).getDeclaredIdentityValueTypes() as Map<
|
|
4301
4409
|
string,
|
|
4302
4410
|
TSmartdataIdentityValueType
|
|
@@ -4332,6 +4440,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4332
4440
|
updateArg,
|
|
4333
4441
|
declaredRoots,
|
|
4334
4442
|
declaredAtomicPaths,
|
|
4443
|
+
declaredArrayPaths,
|
|
4335
4444
|
uniqueRoots,
|
|
4336
4445
|
serializedRoots,
|
|
4337
4446
|
upserting ? { identityValueTypes } : undefined,
|
|
@@ -4407,6 +4516,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4407
4516
|
const policy = getOrdinaryPersistencePolicy(this);
|
|
4408
4517
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
4409
4518
|
const declaredAtomicPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
|
|
4519
|
+
const declaredArrayPaths = (this as any).getDeclaredArrayPaths() as Set<string>;
|
|
4410
4520
|
const identityValueTypes = (this as any).getDeclaredIdentityValueTypes() as Map<
|
|
4411
4521
|
string,
|
|
4412
4522
|
TSmartdataIdentityValueType
|
|
@@ -4433,6 +4543,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4433
4543
|
updateArg,
|
|
4434
4544
|
declaredRoots,
|
|
4435
4545
|
declaredAtomicPaths,
|
|
4546
|
+
declaredArrayPaths,
|
|
4436
4547
|
uniqueRoots,
|
|
4437
4548
|
serializedRoots,
|
|
4438
4549
|
normalizedOptions.upsert ? { identityValueTypes } : undefined,
|
|
@@ -4763,6 +4874,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4763
4874
|
const timeoutMS = requireBoundedInteger(opts?.timeoutMS, 'Atomic update-many timeoutMS', 120_000);
|
|
4764
4875
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
4765
4876
|
const declaredAtomicPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
|
|
4877
|
+
const declaredArrayPaths = (this as any).getDeclaredArrayPaths() as Set<string>;
|
|
4766
4878
|
const uniqueRoots = (this as any).getDeclaredUniqueRoots() as Set<string>;
|
|
4767
4879
|
const identityValueTypes = (this as any).getDeclaredIdentityValueTypes() as Map<
|
|
4768
4880
|
string,
|
|
@@ -4783,6 +4895,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4783
4895
|
updateArg,
|
|
4784
4896
|
declaredRoots,
|
|
4785
4897
|
declaredAtomicPaths,
|
|
4898
|
+
declaredArrayPaths,
|
|
4786
4899
|
uniqueRoots,
|
|
4787
4900
|
serializedRoots,
|
|
4788
4901
|
);
|
|
@@ -4873,6 +4986,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4873
4986
|
);
|
|
4874
4987
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
4875
4988
|
const declaredAtomicPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
|
|
4989
|
+
const declaredArrayPaths = (this as any).getDeclaredArrayPaths() as Set<string>;
|
|
4876
4990
|
const identityValueTypes = (this as any).getDeclaredIdentityValueTypes() as Map<
|
|
4877
4991
|
string,
|
|
4878
4992
|
TSmartdataIdentityValueType
|
|
@@ -4921,6 +5035,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4921
5035
|
operation.update as ISmartdataAtomicUpdate<T>,
|
|
4922
5036
|
declaredRoots,
|
|
4923
5037
|
declaredAtomicPaths,
|
|
5038
|
+
declaredArrayPaths,
|
|
4924
5039
|
uniqueRoots,
|
|
4925
5040
|
serializedRoots,
|
|
4926
5041
|
{ identityValueTypes },
|
|
@@ -4976,18 +5091,24 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4976
5091
|
|
|
4977
5092
|
/**
|
|
4978
5093
|
* Computes bounded grouped counts and optional numeric sums server-side.
|
|
4979
|
-
* Groups by one or two declared top-level fields
|
|
4980
|
-
*
|
|
4981
|
-
*
|
|
4982
|
-
*
|
|
4983
|
-
*
|
|
5094
|
+
* Groups by one or two declared paths — top-level fields or nested paths
|
|
5095
|
+
* declared through `@svDb({ atomicPaths })` or `@svDb({ arrayPaths })` —
|
|
5096
|
+
* optionally by each element of one declared array (`unwind`), always
|
|
5097
|
+
* returns a `count` per group, and sums only declared numeric paths. The
|
|
5098
|
+
* `$limit` follows the group stage without a sort, so when more groups
|
|
5099
|
+
* exist than the limit the returned subset is arbitrary. Use
|
|
5100
|
+
* `getGroupedTotalsPage()` when the caller must detect overflow. The
|
|
5101
|
+
* aggregation cursor never leaves SmartData.
|
|
4984
5102
|
*/
|
|
4985
|
-
public static async getGroupedTotals<
|
|
5103
|
+
public static async getGroupedTotals<
|
|
5104
|
+
T,
|
|
5105
|
+
TUnwind extends TSmartdataGroupedTotalsUnwindPath<T> = never,
|
|
5106
|
+
>(
|
|
4986
5107
|
this: plugins.tsclass.typeFest.Class<T>,
|
|
4987
|
-
optionsArg: ISmartdataGroupedTotalsOptions<T>,
|
|
4988
|
-
): Promise<Array<ISmartdataGroupedTotalsRow<T>>> {
|
|
5108
|
+
optionsArg: ISmartdataGroupedTotalsOptions<T, TUnwind>,
|
|
5109
|
+
): Promise<Array<ISmartdataGroupedTotalsRow<T, TUnwind>>> {
|
|
4989
5110
|
const page = await (this as any).getGroupedTotalsPage(optionsArg) as
|
|
4990
|
-
ISmartdataGroupedTotalsPage<T>;
|
|
5111
|
+
ISmartdataGroupedTotalsPage<T, TUnwind>;
|
|
4991
5112
|
return page.rows;
|
|
4992
5113
|
}
|
|
4993
5114
|
|
|
@@ -4996,10 +5117,13 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4996
5117
|
* reports whether at least one additional group exists beyond the requested
|
|
4997
5118
|
* row limit.
|
|
4998
5119
|
*/
|
|
4999
|
-
public static async getGroupedTotalsPage<
|
|
5120
|
+
public static async getGroupedTotalsPage<
|
|
5121
|
+
T,
|
|
5122
|
+
TUnwind extends TSmartdataGroupedTotalsUnwindPath<T> = never,
|
|
5123
|
+
>(
|
|
5000
5124
|
this: plugins.tsclass.typeFest.Class<T>,
|
|
5001
|
-
optionsArg: ISmartdataGroupedTotalsOptions<T>,
|
|
5002
|
-
): Promise<ISmartdataGroupedTotalsPage<T>> {
|
|
5125
|
+
optionsArg: ISmartdataGroupedTotalsOptions<T, TUnwind>,
|
|
5126
|
+
): Promise<ISmartdataGroupedTotalsPage<T, TUnwind>> {
|
|
5003
5127
|
if ((this as any)[exactPersistencePolicySymbol]) {
|
|
5004
5128
|
throw new SmartdataPersistenceError(
|
|
5005
5129
|
'unsupported_operation',
|
|
@@ -5019,6 +5143,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5019
5143
|
}
|
|
5020
5144
|
const allowedKeys = new Set([
|
|
5021
5145
|
'groupBy',
|
|
5146
|
+
'unwind',
|
|
5022
5147
|
'sums',
|
|
5023
5148
|
'filter',
|
|
5024
5149
|
'limit',
|
|
@@ -5047,6 +5172,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5047
5172
|
}
|
|
5048
5173
|
}
|
|
5049
5174
|
const groupByOption = optionsArg.groupBy;
|
|
5175
|
+
const unwindOption = optionsArg.unwind;
|
|
5050
5176
|
const sumsOption = optionsArg.sums;
|
|
5051
5177
|
const filterOption = optionsArg.filter;
|
|
5052
5178
|
const limitOption = optionsArg.limit;
|
|
@@ -5059,6 +5185,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5059
5185
|
signalOption?.throwIfAborted();
|
|
5060
5186
|
const sessionOption = optionsArg.session;
|
|
5061
5187
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
5188
|
+
const declaredNestedPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
|
|
5062
5189
|
const serializedRoots = (this as any).getSerializedPersistedRoots() as Set<string>;
|
|
5063
5190
|
const requireDeclaredGroupField = (
|
|
5064
5191
|
fieldArg: unknown,
|
|
@@ -5067,23 +5194,22 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5067
5194
|
if (typeof fieldArg !== 'string') {
|
|
5068
5195
|
throw new SmartdataPersistenceError(
|
|
5069
5196
|
'invalid_argument',
|
|
5070
|
-
`${labelArg} must name a declared top-level field.`,
|
|
5071
|
-
);
|
|
5072
|
-
}
|
|
5073
|
-
const root = requireDeclaredPath(fieldArg, declaredRoots, labelArg);
|
|
5074
|
-
if (root !== fieldArg) {
|
|
5075
|
-
throw new SmartdataPersistenceError(
|
|
5076
|
-
'invalid_argument',
|
|
5077
|
-
`${labelArg} must name a declared top-level field, not a nested path.`,
|
|
5197
|
+
`${labelArg} must name a declared top-level field or a declared nested path.`,
|
|
5078
5198
|
);
|
|
5079
5199
|
}
|
|
5200
|
+
const root = requireDeclaredPath(
|
|
5201
|
+
fieldArg,
|
|
5202
|
+
declaredRoots,
|
|
5203
|
+
labelArg,
|
|
5204
|
+
declaredNestedPaths,
|
|
5205
|
+
);
|
|
5080
5206
|
if (serializedRoots.has(root)) {
|
|
5081
5207
|
throw new SmartdataPersistenceError(
|
|
5082
5208
|
'unsupported_operation',
|
|
5083
5209
|
`${labelArg} cannot target serialized field "${root}".`,
|
|
5084
5210
|
);
|
|
5085
5211
|
}
|
|
5086
|
-
return
|
|
5212
|
+
return fieldArg;
|
|
5087
5213
|
};
|
|
5088
5214
|
if (
|
|
5089
5215
|
!Array.isArray(groupByOption)
|
|
@@ -5101,11 +5227,14 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5101
5227
|
if (
|
|
5102
5228
|
groupByFields.length < 1
|
|
5103
5229
|
|| groupByFields.length > 2
|
|
5104
|
-
||
|
|
5230
|
+
|| (
|
|
5231
|
+
groupByFields.length === 2
|
|
5232
|
+
&& pathsConflict(groupByFields[0], groupByFields[1])
|
|
5233
|
+
)
|
|
5105
5234
|
) {
|
|
5106
5235
|
throw new SmartdataPersistenceError(
|
|
5107
5236
|
'invalid_argument',
|
|
5108
|
-
'Grouped totals groupBy fields must be one or two distinct fields.',
|
|
5237
|
+
'Grouped totals groupBy fields must be one or two distinct fields, neither nested in the other.',
|
|
5109
5238
|
);
|
|
5110
5239
|
}
|
|
5111
5240
|
if (sumsOption !== undefined && !Array.isArray(sumsOption)) {
|
|
@@ -5123,6 +5252,27 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5123
5252
|
'Grouped totals sum fields must be distinct.',
|
|
5124
5253
|
);
|
|
5125
5254
|
}
|
|
5255
|
+
let unwindField: string | undefined;
|
|
5256
|
+
if (unwindOption !== undefined) {
|
|
5257
|
+
if (typeof unwindOption !== 'string' || !groupByFields.includes(unwindOption)) {
|
|
5258
|
+
throw new SmartdataPersistenceError(
|
|
5259
|
+
'invalid_argument',
|
|
5260
|
+
'Grouped totals unwind must name one of the groupBy paths.',
|
|
5261
|
+
);
|
|
5262
|
+
}
|
|
5263
|
+
if (
|
|
5264
|
+
unwindOption.includes('.')
|
|
5265
|
+
&& !((this as any).getDeclaredArrayPaths() as Set<string>).has(unwindOption)
|
|
5266
|
+
) {
|
|
5267
|
+
// A nested value's shape is known only from its declaration; unwinding
|
|
5268
|
+
// a path not declared as an array would count scalars as elements.
|
|
5269
|
+
throw new SmartdataPersistenceError(
|
|
5270
|
+
'invalid_argument',
|
|
5271
|
+
`Grouped totals unwind path "${unwindOption}" is not declared through @svDb({ arrayPaths }).`,
|
|
5272
|
+
);
|
|
5273
|
+
}
|
|
5274
|
+
unwindField = unwindOption;
|
|
5275
|
+
}
|
|
5126
5276
|
const limit = requireBoundedInteger(
|
|
5127
5277
|
limitOption,
|
|
5128
5278
|
'Grouped totals limit',
|
|
@@ -5154,11 +5304,17 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5154
5304
|
+ `call, such as groupBy: ["${groupByFields[0]}"].`,
|
|
5155
5305
|
);
|
|
5156
5306
|
}
|
|
5307
|
+
// A composite key names its members by position: a group path may be
|
|
5308
|
+
// dotted, and an expression object's field names may not contain a dot.
|
|
5309
|
+
const compositeKeyName = (indexArg: number): string => 'k' + indexArg;
|
|
5157
5310
|
const groupStage: Record<string, unknown> = {
|
|
5158
5311
|
_id: groupByFields.length === 1
|
|
5159
5312
|
? '$' + groupByFields[0]
|
|
5160
5313
|
: Object.fromEntries(
|
|
5161
|
-
groupByFields.map((fieldArg) => [
|
|
5314
|
+
groupByFields.map((fieldArg, indexArg) => [
|
|
5315
|
+
compositeKeyName(indexArg),
|
|
5316
|
+
'$' + fieldArg,
|
|
5317
|
+
]),
|
|
5162
5318
|
),
|
|
5163
5319
|
__groupedTotalsCount: { $sum: 1 },
|
|
5164
5320
|
};
|
|
@@ -5176,6 +5332,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5176
5332
|
...(maxTimeMS !== undefined ? { maxTimeMS } : {}),
|
|
5177
5333
|
...(timeoutMS !== undefined ? { timeoutMS } : {}),
|
|
5178
5334
|
...(signalOption !== undefined ? { signal: signalOption } : {}),
|
|
5335
|
+
...(unwindField !== undefined ? { unwindPath: unwindField } : {}),
|
|
5179
5336
|
},
|
|
5180
5337
|
);
|
|
5181
5338
|
const hasMore = rows.length > limit;
|
|
@@ -5198,10 +5355,10 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5198
5355
|
group[groupByFields[0]] = rowArg._id ?? null;
|
|
5199
5356
|
} else {
|
|
5200
5357
|
const compoundKey = (rowArg._id ?? {}) as Record<string, unknown>;
|
|
5201
|
-
for (const fieldArg of groupByFields) {
|
|
5358
|
+
for (const [index, fieldArg] of groupByFields.entries()) {
|
|
5202
5359
|
// Missing and explicit-null values merge into one group that is
|
|
5203
5360
|
// reported as null in both the single-field and compound shapes.
|
|
5204
|
-
group[fieldArg] = compoundKey[
|
|
5361
|
+
group[fieldArg] = compoundKey[compositeKeyName(index)] ?? null;
|
|
5205
5362
|
}
|
|
5206
5363
|
}
|
|
5207
5364
|
const sums: Record<string, number> = {};
|
|
@@ -5218,7 +5375,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5218
5375
|
'Grouped totals count',
|
|
5219
5376
|
),
|
|
5220
5377
|
sums,
|
|
5221
|
-
} as ISmartdataGroupedTotalsRow<T>;
|
|
5378
|
+
} as ISmartdataGroupedTotalsRow<T, TUnwind>;
|
|
5222
5379
|
});
|
|
5223
5380
|
return {
|
|
5224
5381
|
rows: mappedRows,
|