@lossless.org/client 1.14.0 → 1.16.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 +87 -31
- package/dist_ts/nosqldb/classes.doc.js +107 -36
- package/dist_ts/objectstorage/classes.bucket.d.ts +16 -0
- package/dist_ts/objectstorage/classes.bucket.js +77 -3
- package/dist_ts/objectstorage/classes.objectrangeerror.d.ts +5 -0
- package/dist_ts/objectstorage/classes.objectrangeerror.js +9 -0
- package/dist_ts/objectstorage/index.d.ts +1 -0
- package/dist_ts/objectstorage/index.js +2 -1
- package/dist_ts/objectstorage/interfaces.d.ts +33 -0
- package/dist_ts/objectstorage/internal.bucketrequests.d.ts +4 -0
- package/dist_ts/objectstorage/internal.bucketrequests.js +2 -2
- package/package.json +2 -2
- package/readme.md +8 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/nosqldb/classes.collection.ts +13 -2
- package/ts/nosqldb/classes.doc.ts +258 -70
- package/ts/objectstorage/classes.bucket.ts +122 -2
- package/ts/objectstorage/classes.objectrangeerror.ts +11 -0
- package/ts/objectstorage/index.ts +1 -0
- package/ts/objectstorage/interfaces.ts +37 -0
- package/ts/objectstorage/internal.bucketrequests.ts +1 -1
|
@@ -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,52 @@ 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
|
+
* Aggregates per element of one array instead of per document: names a
|
|
821
|
+
* declared top-level array field or a nested path declared through
|
|
822
|
+
* `@svDb({ arrayPaths })`, which is itself a `groupBy` or `sums` path or
|
|
823
|
+
* holds the element fields that `groupBy` and `sums` paths name below it
|
|
824
|
+
* (`lines.accountNumber` below `unwind: 'lines'`). Element fields are
|
|
825
|
+
* declared nested paths like any other, through `@svDb({ atomicPaths })`
|
|
826
|
+
* on the array's field. Every element contributes on its own, so `count`
|
|
827
|
+
* counts elements and a document contributes its `sums` once per element;
|
|
828
|
+
* a document whose array is missing, null or empty contributes nothing.
|
|
829
|
+
* The `filter` still selects whole documents before the array is unwound,
|
|
830
|
+
* and no `groupBy` or `sums` path may contain the unwound array.
|
|
788
831
|
*/
|
|
789
|
-
|
|
832
|
+
unwind?: TUnwind;
|
|
833
|
+
/**
|
|
834
|
+
* Declared numeric top-level fields, or declared nested paths holding
|
|
835
|
+
* numbers, to sum per group.
|
|
836
|
+
*/
|
|
837
|
+
sums?: ReadonlyArray<
|
|
838
|
+
TSmartdataNumericDocumentPath<T> | TSmartdataAtomicNestedDocumentPath<T>
|
|
839
|
+
>;
|
|
790
840
|
filter?: MongoFilter<T>;
|
|
791
841
|
/**
|
|
792
842
|
* Required bound on the number of returned groups (1..10000). The group
|
|
@@ -807,18 +857,37 @@ export interface ISmartdataGroupedTotalsOptions<T> {
|
|
|
807
857
|
session?: TSmartdataOrdinarySession;
|
|
808
858
|
}
|
|
809
859
|
|
|
810
|
-
export interface ISmartdataGroupedTotalsRow<
|
|
860
|
+
export interface ISmartdataGroupedTotalsRow<
|
|
861
|
+
T,
|
|
862
|
+
TUnwind extends TSmartdataGroupedTotalsUnwindPath<T> = never,
|
|
863
|
+
> {
|
|
811
864
|
/**
|
|
812
|
-
* The group's key values
|
|
813
|
-
* holding an explicit null merge into
|
|
865
|
+
* The group's key values, keyed by the `groupBy` paths as given. Documents
|
|
866
|
+
* missing a group path and documents holding an explicit null merge into
|
|
867
|
+
* one group reported as null. An unwound top-level array reports the
|
|
868
|
+
* element; nested paths report the stored value untyped.
|
|
814
869
|
*/
|
|
815
|
-
group:
|
|
870
|
+
group:
|
|
871
|
+
& {
|
|
872
|
+
[TKey in TSmartdataDocumentPath<T>]?:
|
|
873
|
+
| (TKey extends TUnwind ? TSmartdataAtomicArrayElement<T[TKey]> : T[TKey])
|
|
874
|
+
| null;
|
|
875
|
+
}
|
|
876
|
+
& Partial<Record<TSmartdataAtomicNestedDocumentPath<T>, unknown>>;
|
|
816
877
|
count: number;
|
|
817
|
-
sums: Partial<
|
|
878
|
+
sums: Partial<
|
|
879
|
+
Record<
|
|
880
|
+
TSmartdataNumericDocumentPath<T> | TSmartdataAtomicNestedDocumentPath<T>,
|
|
881
|
+
number
|
|
882
|
+
>
|
|
883
|
+
>;
|
|
818
884
|
}
|
|
819
885
|
|
|
820
|
-
export interface ISmartdataGroupedTotalsPage<
|
|
821
|
-
|
|
886
|
+
export interface ISmartdataGroupedTotalsPage<
|
|
887
|
+
T,
|
|
888
|
+
TUnwind extends TSmartdataGroupedTotalsUnwindPath<T> = never,
|
|
889
|
+
> {
|
|
890
|
+
rows: Array<ISmartdataGroupedTotalsRow<T, TUnwind>>;
|
|
822
891
|
hasMore: boolean;
|
|
823
892
|
}
|
|
824
893
|
|
|
@@ -830,12 +899,19 @@ export type TSmartdataAtomicArrayPath<T> = {
|
|
|
830
899
|
type TSmartdataAtomicArrayElement<TValue> =
|
|
831
900
|
NonNullable<TValue> extends ReadonlyArray<infer TElement> ? TElement : never;
|
|
832
901
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
902
|
+
/**
|
|
903
|
+
* Array operators take a declared top-level array field, typed by its element,
|
|
904
|
+
* or a nested path below a declared field. A nested path is accepted at run
|
|
905
|
+
* time only when the model declares it through `@svDb({ arrayPaths })`.
|
|
906
|
+
*/
|
|
907
|
+
type TSmartdataAtomicArrayOperation<T> =
|
|
908
|
+
& Partial<{
|
|
909
|
+
[TKey in TSmartdataAtomicArrayPath<T>]: TSmartdataAtomicArrayElement<T[TKey]>;
|
|
910
|
+
}>
|
|
911
|
+
& Partial<Record<TSmartdataAtomicNestedDocumentPath<T>, unknown>>;
|
|
836
912
|
|
|
837
|
-
|
|
838
|
-
|
|
913
|
+
/** The scalar equality operands `$pull` accepts. */
|
|
914
|
+
type TSmartdataAtomicPullScalarValue =
|
|
839
915
|
| string
|
|
840
916
|
| number
|
|
841
917
|
| boolean
|
|
@@ -847,14 +923,22 @@ type TSmartdataAtomicPullScalar<TValue> = Extract<
|
|
|
847
923
|
| plugins.mongodb.Long
|
|
848
924
|
| plugins.mongodb.Int32
|
|
849
925
|
| plugins.mongodb.Double
|
|
850
|
-
| plugins.mongodb.Timestamp
|
|
926
|
+
| plugins.mongodb.Timestamp;
|
|
927
|
+
|
|
928
|
+
type TSmartdataAtomicPullScalar<TValue> = Extract<
|
|
929
|
+
TValue,
|
|
930
|
+
TSmartdataAtomicPullScalarValue
|
|
851
931
|
>;
|
|
852
932
|
|
|
853
|
-
type TSmartdataAtomicArrayPullOperation<T> =
|
|
854
|
-
|
|
855
|
-
|
|
933
|
+
type TSmartdataAtomicArrayPullOperation<T> =
|
|
934
|
+
& Partial<{
|
|
935
|
+
[TKey in TSmartdataAtomicArrayPath<T>]: TSmartdataAtomicPullScalar<
|
|
936
|
+
TSmartdataAtomicArrayElement<T[TKey]>
|
|
937
|
+
>;
|
|
938
|
+
}>
|
|
939
|
+
& Partial<
|
|
940
|
+
Record<TSmartdataAtomicNestedDocumentPath<T>, TSmartdataAtomicPullScalarValue>
|
|
856
941
|
>;
|
|
857
|
-
}>;
|
|
858
942
|
|
|
859
943
|
export interface ISmartdataAtomicUpdate<T> {
|
|
860
944
|
$set?: TSmartdataAtomicSet<T>;
|
|
@@ -876,19 +960,21 @@ export interface ISmartdataAtomicUpdate<T> {
|
|
|
876
960
|
$min?: TSmartdataAtomicMonotonic<T>;
|
|
877
961
|
$setOnInsert?: TSmartdataAtomicSet<T>;
|
|
878
962
|
/**
|
|
879
|
-
* Appends one element to a declared top-level array field
|
|
880
|
-
*
|
|
963
|
+
* Appends one element to a declared top-level array field or a nested
|
|
964
|
+
* path declared through `@svDb({ arrayPaths })`. Modifier documents
|
|
965
|
+
* ($each, $position, $slice, $sort) are rejected.
|
|
881
966
|
*/
|
|
882
967
|
$push?: TSmartdataAtomicArrayOperation<T>;
|
|
883
968
|
/**
|
|
884
|
-
* Adds one element to a declared top-level array field
|
|
885
|
-
* element is already present. Modifier
|
|
969
|
+
* Adds one element to a declared top-level array field or a declared
|
|
970
|
+
* nested array path unless an equal element is already present. Modifier
|
|
971
|
+
* documents are rejected.
|
|
886
972
|
*/
|
|
887
973
|
$addToSet?: TSmartdataAtomicArrayOperation<T>;
|
|
888
974
|
/**
|
|
889
975
|
* Removes every element equal to a scalar operand from a declared
|
|
890
|
-
* top-level array field
|
|
891
|
-
* regular expressions are rejected.
|
|
976
|
+
* top-level array field or a declared nested array path. Operator
|
|
977
|
+
* documents, plain objects, arrays, and regular expressions are rejected.
|
|
892
978
|
*/
|
|
893
979
|
$pull?: TSmartdataAtomicArrayPullOperation<T>;
|
|
894
980
|
}
|
|
@@ -3051,6 +3137,8 @@ const normalizeAtomicUpdate = <T>(
|
|
|
3051
3137
|
updateArg: ISmartdataAtomicUpdate<T>,
|
|
3052
3138
|
declaredRootsArg: Set<string>,
|
|
3053
3139
|
declaredNestedPathsArg: Set<string>,
|
|
3140
|
+
/** Nested paths declared through `@svDb({ arrayPaths })`; array operators may target them. */
|
|
3141
|
+
declaredArrayPathsArg: Set<string>,
|
|
3054
3142
|
uniqueRootsArg: Set<string>,
|
|
3055
3143
|
serializedRootsArg: Set<string>,
|
|
3056
3144
|
/**
|
|
@@ -3164,10 +3252,13 @@ const normalizeAtomicUpdate = <T>(
|
|
|
3164
3252
|
}
|
|
3165
3253
|
occupiedPaths.push({ path, operator });
|
|
3166
3254
|
if (arrayOperators.has(operator)) {
|
|
3167
|
-
if (path !== root) {
|
|
3255
|
+
if (path !== root && !declaredArrayPathsArg.has(path)) {
|
|
3256
|
+
// The shape of a nested value is known only from its declaration, so
|
|
3257
|
+
// a nested path that is not declared as an array is refused rather
|
|
3258
|
+
// than left to fail on the stored value.
|
|
3168
3259
|
throw new SmartdataPersistenceError(
|
|
3169
3260
|
'invalid_argument',
|
|
3170
|
-
`${operator} may only target declared top-level array
|
|
3261
|
+
`${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
3262
|
);
|
|
3172
3263
|
}
|
|
3173
3264
|
if (value === undefined) {
|
|
@@ -3903,21 +3994,41 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
3903
3994
|
return keySets;
|
|
3904
3995
|
}
|
|
3905
3996
|
|
|
3997
|
+
/**
|
|
3998
|
+
* Every nested path the model declares below a persisted field, through
|
|
3999
|
+
* `@svDb({ atomicPaths })` or `@svDb({ arrayPaths })`.
|
|
4000
|
+
*/
|
|
3906
4001
|
private static getDeclaredAtomicPaths(): Set<string> {
|
|
4002
|
+
return new Set([
|
|
4003
|
+
...(this as any).readDeclaredNestedPaths('atomicPaths') as Set<string>,
|
|
4004
|
+
...(this as any).readDeclaredNestedPaths('arrayPaths') as Set<string>,
|
|
4005
|
+
]);
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
/** The nested paths declared through `@svDb({ arrayPaths })` as holding arrays. */
|
|
4009
|
+
private static getDeclaredArrayPaths(): Set<string> {
|
|
4010
|
+
return (this as any).readDeclaredNestedPaths('arrayPaths') as Set<string>;
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
private static readDeclaredNestedPaths(
|
|
4014
|
+
declarationArg: 'atomicPaths' | 'arrayPaths',
|
|
4015
|
+
): Set<string> {
|
|
3907
4016
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
3908
4017
|
const uniqueRoots = (this as any).getDeclaredUniqueRoots() as Set<string>;
|
|
3909
4018
|
const options = (this as any)._svDbOptions as
|
|
3910
4019
|
| Record<string, SvDbOptions>
|
|
3911
4020
|
| undefined;
|
|
3912
|
-
const
|
|
4021
|
+
const label = declarationArg === 'atomicPaths' ? 'Atomic' : 'Array';
|
|
4022
|
+
const nestedPaths = new Set<string>();
|
|
3913
4023
|
for (const [root, fieldOptions] of Object.entries(options || {})) {
|
|
3914
|
-
|
|
4024
|
+
const relativePaths = fieldOptions[declarationArg];
|
|
4025
|
+
if (relativePaths === undefined) {
|
|
3915
4026
|
continue;
|
|
3916
4027
|
}
|
|
3917
|
-
if (!Array.isArray(
|
|
4028
|
+
if (!Array.isArray(relativePaths)) {
|
|
3918
4029
|
throw new SmartdataPersistenceError(
|
|
3919
4030
|
'invalid_configuration',
|
|
3920
|
-
|
|
4031
|
+
`${label} paths for persisted field "${root}" must be an array.`,
|
|
3921
4032
|
);
|
|
3922
4033
|
}
|
|
3923
4034
|
if (
|
|
@@ -3926,20 +4037,20 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
3926
4037
|
) {
|
|
3927
4038
|
throw new SmartdataPersistenceError(
|
|
3928
4039
|
'invalid_configuration',
|
|
3929
|
-
`Serialized persisted field "${root}" may not declare
|
|
4040
|
+
`Serialized persisted field "${root}" may not declare ${label.toLowerCase()} nested paths.`,
|
|
3930
4041
|
);
|
|
3931
4042
|
}
|
|
3932
4043
|
if (uniqueRoots.has(root)) {
|
|
3933
4044
|
throw new SmartdataPersistenceError(
|
|
3934
4045
|
'invalid_configuration',
|
|
3935
|
-
`Immutable @unI() field "${root}" may not declare
|
|
4046
|
+
`Immutable @unI() field "${root}" may not declare ${label.toLowerCase()} nested paths.`,
|
|
3936
4047
|
);
|
|
3937
4048
|
}
|
|
3938
|
-
for (const relativePath of
|
|
4049
|
+
for (const relativePath of relativePaths) {
|
|
3939
4050
|
if (typeof relativePath !== 'string' || relativePath.length === 0) {
|
|
3940
4051
|
throw new SmartdataPersistenceError(
|
|
3941
4052
|
'invalid_configuration',
|
|
3942
|
-
|
|
4053
|
+
`${label} paths for persisted field "${root}" must be non-empty strings.`,
|
|
3943
4054
|
);
|
|
3944
4055
|
}
|
|
3945
4056
|
const fullPath = `${root}.${relativePath}`;
|
|
@@ -3947,20 +4058,20 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
3947
4058
|
requireDeclaredPath(
|
|
3948
4059
|
fullPath,
|
|
3949
4060
|
declaredRoots,
|
|
3950
|
-
|
|
4061
|
+
`${label} path declaration`,
|
|
3951
4062
|
new Set([fullPath]),
|
|
3952
4063
|
);
|
|
3953
4064
|
} catch (errorArg) {
|
|
3954
4065
|
throw new SmartdataPersistenceError(
|
|
3955
4066
|
'invalid_configuration',
|
|
3956
|
-
|
|
4067
|
+
`${label} path declaration "${fullPath}" is invalid.`,
|
|
3957
4068
|
{ cause: errorArg },
|
|
3958
4069
|
);
|
|
3959
4070
|
}
|
|
3960
|
-
|
|
4071
|
+
nestedPaths.add(fullPath);
|
|
3961
4072
|
}
|
|
3962
4073
|
}
|
|
3963
|
-
return
|
|
4074
|
+
return nestedPaths;
|
|
3964
4075
|
}
|
|
3965
4076
|
|
|
3966
4077
|
private static getSerializedPersistedRoots(): Set<string> {
|
|
@@ -4297,6 +4408,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4297
4408
|
}
|
|
4298
4409
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
4299
4410
|
const declaredAtomicPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
|
|
4411
|
+
const declaredArrayPaths = (this as any).getDeclaredArrayPaths() as Set<string>;
|
|
4300
4412
|
const identityValueTypes = (this as any).getDeclaredIdentityValueTypes() as Map<
|
|
4301
4413
|
string,
|
|
4302
4414
|
TSmartdataIdentityValueType
|
|
@@ -4332,6 +4444,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4332
4444
|
updateArg,
|
|
4333
4445
|
declaredRoots,
|
|
4334
4446
|
declaredAtomicPaths,
|
|
4447
|
+
declaredArrayPaths,
|
|
4335
4448
|
uniqueRoots,
|
|
4336
4449
|
serializedRoots,
|
|
4337
4450
|
upserting ? { identityValueTypes } : undefined,
|
|
@@ -4407,6 +4520,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4407
4520
|
const policy = getOrdinaryPersistencePolicy(this);
|
|
4408
4521
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
4409
4522
|
const declaredAtomicPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
|
|
4523
|
+
const declaredArrayPaths = (this as any).getDeclaredArrayPaths() as Set<string>;
|
|
4410
4524
|
const identityValueTypes = (this as any).getDeclaredIdentityValueTypes() as Map<
|
|
4411
4525
|
string,
|
|
4412
4526
|
TSmartdataIdentityValueType
|
|
@@ -4433,6 +4547,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4433
4547
|
updateArg,
|
|
4434
4548
|
declaredRoots,
|
|
4435
4549
|
declaredAtomicPaths,
|
|
4550
|
+
declaredArrayPaths,
|
|
4436
4551
|
uniqueRoots,
|
|
4437
4552
|
serializedRoots,
|
|
4438
4553
|
normalizedOptions.upsert ? { identityValueTypes } : undefined,
|
|
@@ -4763,6 +4878,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4763
4878
|
const timeoutMS = requireBoundedInteger(opts?.timeoutMS, 'Atomic update-many timeoutMS', 120_000);
|
|
4764
4879
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
4765
4880
|
const declaredAtomicPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
|
|
4881
|
+
const declaredArrayPaths = (this as any).getDeclaredArrayPaths() as Set<string>;
|
|
4766
4882
|
const uniqueRoots = (this as any).getDeclaredUniqueRoots() as Set<string>;
|
|
4767
4883
|
const identityValueTypes = (this as any).getDeclaredIdentityValueTypes() as Map<
|
|
4768
4884
|
string,
|
|
@@ -4783,6 +4899,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4783
4899
|
updateArg,
|
|
4784
4900
|
declaredRoots,
|
|
4785
4901
|
declaredAtomicPaths,
|
|
4902
|
+
declaredArrayPaths,
|
|
4786
4903
|
uniqueRoots,
|
|
4787
4904
|
serializedRoots,
|
|
4788
4905
|
);
|
|
@@ -4873,6 +4990,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4873
4990
|
);
|
|
4874
4991
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
4875
4992
|
const declaredAtomicPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
|
|
4993
|
+
const declaredArrayPaths = (this as any).getDeclaredArrayPaths() as Set<string>;
|
|
4876
4994
|
const identityValueTypes = (this as any).getDeclaredIdentityValueTypes() as Map<
|
|
4877
4995
|
string,
|
|
4878
4996
|
TSmartdataIdentityValueType
|
|
@@ -4921,6 +5039,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4921
5039
|
operation.update as ISmartdataAtomicUpdate<T>,
|
|
4922
5040
|
declaredRoots,
|
|
4923
5041
|
declaredAtomicPaths,
|
|
5042
|
+
declaredArrayPaths,
|
|
4924
5043
|
uniqueRoots,
|
|
4925
5044
|
serializedRoots,
|
|
4926
5045
|
{ identityValueTypes },
|
|
@@ -4976,18 +5095,25 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4976
5095
|
|
|
4977
5096
|
/**
|
|
4978
5097
|
* Computes bounded grouped counts and optional numeric sums server-side.
|
|
4979
|
-
* Groups by one or two declared top-level fields
|
|
4980
|
-
*
|
|
4981
|
-
*
|
|
4982
|
-
*
|
|
4983
|
-
*
|
|
5098
|
+
* Groups by one or two declared paths — top-level fields or nested paths
|
|
5099
|
+
* declared through `@svDb({ atomicPaths })` or `@svDb({ arrayPaths })` —
|
|
5100
|
+
* optionally per element of one declared array (`unwind`), whose element
|
|
5101
|
+
* fields `groupBy` and `sums` may then name, always returns a `count` per
|
|
5102
|
+
* group, and sums only declared numeric paths. The
|
|
5103
|
+
* `$limit` follows the group stage without a sort, so when more groups
|
|
5104
|
+
* exist than the limit the returned subset is arbitrary. Use
|
|
5105
|
+
* `getGroupedTotalsPage()` when the caller must detect overflow. The
|
|
5106
|
+
* aggregation cursor never leaves SmartData.
|
|
4984
5107
|
*/
|
|
4985
|
-
public static async getGroupedTotals<
|
|
5108
|
+
public static async getGroupedTotals<
|
|
5109
|
+
T,
|
|
5110
|
+
TUnwind extends TSmartdataGroupedTotalsUnwindPath<T> = never,
|
|
5111
|
+
>(
|
|
4986
5112
|
this: plugins.tsclass.typeFest.Class<T>,
|
|
4987
|
-
optionsArg: ISmartdataGroupedTotalsOptions<T>,
|
|
4988
|
-
): Promise<Array<ISmartdataGroupedTotalsRow<T>>> {
|
|
5113
|
+
optionsArg: ISmartdataGroupedTotalsOptions<T, TUnwind>,
|
|
5114
|
+
): Promise<Array<ISmartdataGroupedTotalsRow<T, TUnwind>>> {
|
|
4989
5115
|
const page = await (this as any).getGroupedTotalsPage(optionsArg) as
|
|
4990
|
-
ISmartdataGroupedTotalsPage<T>;
|
|
5116
|
+
ISmartdataGroupedTotalsPage<T, TUnwind>;
|
|
4991
5117
|
return page.rows;
|
|
4992
5118
|
}
|
|
4993
5119
|
|
|
@@ -4996,10 +5122,13 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
4996
5122
|
* reports whether at least one additional group exists beyond the requested
|
|
4997
5123
|
* row limit.
|
|
4998
5124
|
*/
|
|
4999
|
-
public static async getGroupedTotalsPage<
|
|
5125
|
+
public static async getGroupedTotalsPage<
|
|
5126
|
+
T,
|
|
5127
|
+
TUnwind extends TSmartdataGroupedTotalsUnwindPath<T> = never,
|
|
5128
|
+
>(
|
|
5000
5129
|
this: plugins.tsclass.typeFest.Class<T>,
|
|
5001
|
-
optionsArg: ISmartdataGroupedTotalsOptions<T>,
|
|
5002
|
-
): Promise<ISmartdataGroupedTotalsPage<T>> {
|
|
5130
|
+
optionsArg: ISmartdataGroupedTotalsOptions<T, TUnwind>,
|
|
5131
|
+
): Promise<ISmartdataGroupedTotalsPage<T, TUnwind>> {
|
|
5003
5132
|
if ((this as any)[exactPersistencePolicySymbol]) {
|
|
5004
5133
|
throw new SmartdataPersistenceError(
|
|
5005
5134
|
'unsupported_operation',
|
|
@@ -5019,6 +5148,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5019
5148
|
}
|
|
5020
5149
|
const allowedKeys = new Set([
|
|
5021
5150
|
'groupBy',
|
|
5151
|
+
'unwind',
|
|
5022
5152
|
'sums',
|
|
5023
5153
|
'filter',
|
|
5024
5154
|
'limit',
|
|
@@ -5047,6 +5177,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5047
5177
|
}
|
|
5048
5178
|
}
|
|
5049
5179
|
const groupByOption = optionsArg.groupBy;
|
|
5180
|
+
const unwindOption = optionsArg.unwind;
|
|
5050
5181
|
const sumsOption = optionsArg.sums;
|
|
5051
5182
|
const filterOption = optionsArg.filter;
|
|
5052
5183
|
const limitOption = optionsArg.limit;
|
|
@@ -5059,6 +5190,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5059
5190
|
signalOption?.throwIfAborted();
|
|
5060
5191
|
const sessionOption = optionsArg.session;
|
|
5061
5192
|
const declaredRoots = (this as any).getDeclaredPersistedRoots() as Set<string>;
|
|
5193
|
+
const declaredNestedPaths = (this as any).getDeclaredAtomicPaths() as Set<string>;
|
|
5062
5194
|
const serializedRoots = (this as any).getSerializedPersistedRoots() as Set<string>;
|
|
5063
5195
|
const requireDeclaredGroupField = (
|
|
5064
5196
|
fieldArg: unknown,
|
|
@@ -5067,23 +5199,22 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5067
5199
|
if (typeof fieldArg !== 'string') {
|
|
5068
5200
|
throw new SmartdataPersistenceError(
|
|
5069
5201
|
'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.`,
|
|
5202
|
+
`${labelArg} must name a declared top-level field or a declared nested path.`,
|
|
5078
5203
|
);
|
|
5079
5204
|
}
|
|
5205
|
+
const root = requireDeclaredPath(
|
|
5206
|
+
fieldArg,
|
|
5207
|
+
declaredRoots,
|
|
5208
|
+
labelArg,
|
|
5209
|
+
declaredNestedPaths,
|
|
5210
|
+
);
|
|
5080
5211
|
if (serializedRoots.has(root)) {
|
|
5081
5212
|
throw new SmartdataPersistenceError(
|
|
5082
5213
|
'unsupported_operation',
|
|
5083
5214
|
`${labelArg} cannot target serialized field "${root}".`,
|
|
5084
5215
|
);
|
|
5085
5216
|
}
|
|
5086
|
-
return
|
|
5217
|
+
return fieldArg;
|
|
5087
5218
|
};
|
|
5088
5219
|
if (
|
|
5089
5220
|
!Array.isArray(groupByOption)
|
|
@@ -5101,11 +5232,14 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5101
5232
|
if (
|
|
5102
5233
|
groupByFields.length < 1
|
|
5103
5234
|
|| groupByFields.length > 2
|
|
5104
|
-
||
|
|
5235
|
+
|| (
|
|
5236
|
+
groupByFields.length === 2
|
|
5237
|
+
&& pathsConflict(groupByFields[0], groupByFields[1])
|
|
5238
|
+
)
|
|
5105
5239
|
) {
|
|
5106
5240
|
throw new SmartdataPersistenceError(
|
|
5107
5241
|
'invalid_argument',
|
|
5108
|
-
'Grouped totals groupBy fields must be one or two distinct fields.',
|
|
5242
|
+
'Grouped totals groupBy fields must be one or two distinct fields, neither nested in the other.',
|
|
5109
5243
|
);
|
|
5110
5244
|
}
|
|
5111
5245
|
if (sumsOption !== undefined && !Array.isArray(sumsOption)) {
|
|
@@ -5123,6 +5257,53 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5123
5257
|
'Grouped totals sum fields must be distinct.',
|
|
5124
5258
|
);
|
|
5125
5259
|
}
|
|
5260
|
+
let unwindField: string | undefined;
|
|
5261
|
+
if (unwindOption !== undefined) {
|
|
5262
|
+
if (typeof unwindOption !== 'string') {
|
|
5263
|
+
throw new SmartdataPersistenceError(
|
|
5264
|
+
'invalid_argument',
|
|
5265
|
+
'Grouped totals unwind must name a declared array path.',
|
|
5266
|
+
);
|
|
5267
|
+
}
|
|
5268
|
+
requireDeclaredGroupField(unwindOption, 'Grouped totals unwind path');
|
|
5269
|
+
const aggregatedFields = [...groupByFields, ...sumFields];
|
|
5270
|
+
// The unwound array either is a groupBy or sum path, or holds the
|
|
5271
|
+
// element fields a groupBy or sum path names below it.
|
|
5272
|
+
if (
|
|
5273
|
+
!aggregatedFields.some((fieldArg) =>
|
|
5274
|
+
fieldArg === unwindOption || fieldArg.startsWith(unwindOption + '.'),
|
|
5275
|
+
)
|
|
5276
|
+
) {
|
|
5277
|
+
throw new SmartdataPersistenceError(
|
|
5278
|
+
'invalid_argument',
|
|
5279
|
+
'Grouped totals unwind must name a groupBy or sum path, or the array holding one of them.',
|
|
5280
|
+
);
|
|
5281
|
+
}
|
|
5282
|
+
// A path that contains the unwound array would read a document whose
|
|
5283
|
+
// array has been replaced by one element: neither the document's value
|
|
5284
|
+
// nor the element's.
|
|
5285
|
+
const containingField = aggregatedFields.find((fieldArg) =>
|
|
5286
|
+
unwindOption.startsWith(fieldArg + '.'),
|
|
5287
|
+
);
|
|
5288
|
+
if (containingField !== undefined) {
|
|
5289
|
+
throw new SmartdataPersistenceError(
|
|
5290
|
+
'invalid_argument',
|
|
5291
|
+
`Grouped totals path "${containingField}" contains the unwound array "${unwindOption}".`,
|
|
5292
|
+
);
|
|
5293
|
+
}
|
|
5294
|
+
if (
|
|
5295
|
+
unwindOption.includes('.')
|
|
5296
|
+
&& !((this as any).getDeclaredArrayPaths() as Set<string>).has(unwindOption)
|
|
5297
|
+
) {
|
|
5298
|
+
// A nested value's shape is known only from its declaration; unwinding
|
|
5299
|
+
// a path not declared as an array would count scalars as elements.
|
|
5300
|
+
throw new SmartdataPersistenceError(
|
|
5301
|
+
'invalid_argument',
|
|
5302
|
+
`Grouped totals unwind path "${unwindOption}" is not declared through @svDb({ arrayPaths }).`,
|
|
5303
|
+
);
|
|
5304
|
+
}
|
|
5305
|
+
unwindField = unwindOption;
|
|
5306
|
+
}
|
|
5126
5307
|
const limit = requireBoundedInteger(
|
|
5127
5308
|
limitOption,
|
|
5128
5309
|
'Grouped totals limit',
|
|
@@ -5154,11 +5335,17 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5154
5335
|
+ `call, such as groupBy: ["${groupByFields[0]}"].`,
|
|
5155
5336
|
);
|
|
5156
5337
|
}
|
|
5338
|
+
// A composite key names its members by position: a group path may be
|
|
5339
|
+
// dotted, and an expression object's field names may not contain a dot.
|
|
5340
|
+
const compositeKeyName = (indexArg: number): string => 'k' + indexArg;
|
|
5157
5341
|
const groupStage: Record<string, unknown> = {
|
|
5158
5342
|
_id: groupByFields.length === 1
|
|
5159
5343
|
? '$' + groupByFields[0]
|
|
5160
5344
|
: Object.fromEntries(
|
|
5161
|
-
groupByFields.map((fieldArg) => [
|
|
5345
|
+
groupByFields.map((fieldArg, indexArg) => [
|
|
5346
|
+
compositeKeyName(indexArg),
|
|
5347
|
+
'$' + fieldArg,
|
|
5348
|
+
]),
|
|
5162
5349
|
),
|
|
5163
5350
|
__groupedTotalsCount: { $sum: 1 },
|
|
5164
5351
|
};
|
|
@@ -5176,6 +5363,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5176
5363
|
...(maxTimeMS !== undefined ? { maxTimeMS } : {}),
|
|
5177
5364
|
...(timeoutMS !== undefined ? { timeoutMS } : {}),
|
|
5178
5365
|
...(signalOption !== undefined ? { signal: signalOption } : {}),
|
|
5366
|
+
...(unwindField !== undefined ? { unwindPath: unwindField } : {}),
|
|
5179
5367
|
},
|
|
5180
5368
|
);
|
|
5181
5369
|
const hasMore = rows.length > limit;
|
|
@@ -5198,10 +5386,10 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5198
5386
|
group[groupByFields[0]] = rowArg._id ?? null;
|
|
5199
5387
|
} else {
|
|
5200
5388
|
const compoundKey = (rowArg._id ?? {}) as Record<string, unknown>;
|
|
5201
|
-
for (const fieldArg of groupByFields) {
|
|
5389
|
+
for (const [index, fieldArg] of groupByFields.entries()) {
|
|
5202
5390
|
// Missing and explicit-null values merge into one group that is
|
|
5203
5391
|
// reported as null in both the single-field and compound shapes.
|
|
5204
|
-
group[fieldArg] = compoundKey[
|
|
5392
|
+
group[fieldArg] = compoundKey[compositeKeyName(index)] ?? null;
|
|
5205
5393
|
}
|
|
5206
5394
|
}
|
|
5207
5395
|
const sums: Record<string, number> = {};
|
|
@@ -5218,7 +5406,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
|
|
5218
5406
|
'Grouped totals count',
|
|
5219
5407
|
),
|
|
5220
5408
|
sums,
|
|
5221
|
-
} as ISmartdataGroupedTotalsRow<T>;
|
|
5409
|
+
} as ISmartdataGroupedTotalsRow<T, TUnwind>;
|
|
5222
5410
|
});
|
|
5223
5411
|
return {
|
|
5224
5412
|
rows: mappedRows,
|