@aws-amplify/datastore 3.14.5-unstable.2 → 3.14.5
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/CHANGELOG.md +19 -0
- package/build.js +5 -0
- package/dist/aws-amplify-datastore.js +92853 -0
- package/dist/aws-amplify-datastore.js.map +1 -0
- package/dist/aws-amplify-datastore.min.js +65 -0
- package/dist/aws-amplify-datastore.min.js.map +1 -0
- package/index.js +7 -0
- package/lib/authModeStrategies/multiAuthStrategy.js +64 -6
- package/lib/authModeStrategies/multiAuthStrategy.js.map +1 -1
- package/lib/datastore/datastore.js +297 -703
- package/lib/datastore/datastore.js.map +1 -1
- package/lib/index.js +4 -6
- package/lib/index.js.map +1 -1
- package/lib/predicates/index.js +6 -127
- package/lib/predicates/index.js.map +1 -1
- package/lib/predicates/sort.js +4 -10
- package/lib/predicates/sort.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageAdapter.js +381 -138
- package/lib/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib/storage/adapter/AsyncStorageDatabase.js +98 -37
- package/lib/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib/storage/adapter/InMemoryStore.js +67 -16
- package/lib/storage/adapter/InMemoryStore.js.map +1 -1
- package/lib/storage/adapter/InMemoryStore.native.js +4 -2
- package/lib/storage/adapter/InMemoryStore.native.js.map +1 -1
- package/lib/storage/adapter/IndexedDBAdapter.js +420 -272
- package/lib/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib/storage/adapter/getDefaultAdapter/index.js +5 -3
- package/lib/storage/adapter/getDefaultAdapter/index.js.map +1 -1
- package/lib/storage/adapter/getDefaultAdapter/index.native.js +4 -2
- package/lib/storage/adapter/getDefaultAdapter/index.native.js.map +1 -1
- package/lib/storage/storage.js +143 -72
- package/lib/storage/storage.js.map +1 -1
- package/lib/sync/datastoreConnectivity.js +55 -6
- package/lib/sync/datastoreConnectivity.js.map +1 -1
- package/lib/sync/datastoreReachability/index.native.js +4 -2
- package/lib/sync/datastoreReachability/index.native.js.map +1 -1
- package/lib/sync/index.js +124 -49
- package/lib/sync/index.js.map +1 -1
- package/lib/sync/merger.js +74 -8
- package/lib/sync/merger.js.map +1 -1
- package/lib/sync/outbox.js +97 -24
- package/lib/sync/outbox.js.map +1 -1
- package/lib/sync/processors/errorMaps.js +35 -5
- package/lib/sync/processors/errorMaps.js.map +1 -1
- package/lib/sync/processors/mutation.js +131 -47
- package/lib/sync/processors/mutation.js.map +1 -1
- package/lib/sync/processors/subscription.js +102 -29
- package/lib/sync/processors/subscription.js.map +1 -1
- package/lib/sync/processors/sync.js +102 -26
- package/lib/sync/processors/sync.js.map +1 -1
- package/lib/sync/utils.js +103 -40
- package/lib/sync/utils.js.map +1 -1
- package/lib/types.js +39 -9
- package/lib/types.js.map +1 -1
- package/lib/util.js +188 -192
- package/lib/util.js.map +1 -1
- package/lib-esm/authModeStrategies/multiAuthStrategy.js +57 -2
- package/lib-esm/authModeStrategies/multiAuthStrategy.js.map +1 -1
- package/lib-esm/datastore/datastore.d.ts +8 -59
- package/lib-esm/datastore/datastore.js +234 -642
- package/lib-esm/datastore/datastore.js.map +1 -1
- package/lib-esm/index.d.ts +2 -3
- package/lib-esm/index.js +1 -2
- package/lib-esm/index.js.map +1 -1
- package/lib-esm/predicates/index.d.ts +2 -16
- package/lib-esm/predicates/index.js +7 -128
- package/lib-esm/predicates/index.js.map +1 -1
- package/lib-esm/predicates/sort.js +4 -10
- package/lib-esm/predicates/sort.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageAdapter.d.ts +1 -2
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js +349 -109
- package/lib-esm/storage/adapter/AsyncStorageAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js +68 -7
- package/lib-esm/storage/adapter/AsyncStorageDatabase.js.map +1 -1
- package/lib-esm/storage/adapter/InMemoryStore.d.ts +1 -1
- package/lib-esm/storage/adapter/InMemoryStore.js +52 -1
- package/lib-esm/storage/adapter/InMemoryStore.js.map +1 -1
- package/lib-esm/storage/adapter/IndexedDBAdapter.d.ts +2 -4
- package/lib-esm/storage/adapter/IndexedDBAdapter.js +368 -227
- package/lib-esm/storage/adapter/IndexedDBAdapter.js.map +1 -1
- package/lib-esm/storage/adapter/getDefaultAdapter/index.js.map +1 -1
- package/lib-esm/storage/storage.d.ts +6 -7
- package/lib-esm/storage/storage.js +101 -33
- package/lib-esm/storage/storage.js.map +1 -1
- package/lib-esm/sync/datastoreConnectivity.js +47 -1
- package/lib-esm/sync/datastoreConnectivity.js.map +1 -1
- package/lib-esm/sync/index.js +76 -4
- package/lib-esm/sync/index.js.map +1 -1
- package/lib-esm/sync/merger.js +67 -1
- package/lib-esm/sync/merger.js.map +1 -1
- package/lib-esm/sync/outbox.js +74 -1
- package/lib-esm/sync/outbox.js.map +1 -1
- package/lib-esm/sync/processors/errorMaps.js +32 -2
- package/lib-esm/sync/processors/errorMaps.js.map +1 -1
- package/lib-esm/sync/processors/mutation.js +93 -12
- package/lib-esm/sync/processors/mutation.js.map +1 -1
- package/lib-esm/sync/processors/subscription.js +69 -6
- package/lib-esm/sync/processors/subscription.js.map +1 -1
- package/lib-esm/sync/processors/sync.js +75 -2
- package/lib-esm/sync/processors/sync.js.map +1 -1
- package/lib-esm/sync/utils.d.ts +1 -1
- package/lib-esm/sync/utils.js +95 -32
- package/lib-esm/sync/utils.js.map +1 -1
- package/lib-esm/types.d.ts +10 -63
- package/lib-esm/types.js +38 -7
- package/lib-esm/types.js.map +1 -1
- package/lib-esm/util.d.ts +6 -39
- package/lib-esm/util.js +171 -171
- package/lib-esm/util.js.map +1 -1
- package/package.json +14 -21
- package/src/authModeStrategies/multiAuthStrategy.ts +2 -2
- package/src/datastore/datastore.ts +206 -699
- package/src/index.ts +0 -4
- package/src/predicates/index.ts +17 -143
- package/src/predicates/sort.ts +2 -8
- package/src/storage/adapter/AsyncStorageAdapter.ts +178 -56
- package/src/storage/adapter/AsyncStorageDatabase.ts +15 -16
- package/src/storage/adapter/InMemoryStore.ts +2 -5
- package/src/storage/adapter/IndexedDBAdapter.ts +191 -166
- package/src/storage/adapter/getDefaultAdapter/index.ts +2 -2
- package/src/storage/storage.ts +37 -56
- package/src/sync/datastoreConnectivity.ts +4 -4
- package/src/sync/index.ts +28 -22
- package/src/sync/merger.ts +1 -1
- package/src/sync/outbox.ts +6 -6
- package/src/sync/processors/errorMaps.ts +1 -1
- package/src/sync/processors/mutation.ts +19 -23
- package/src/sync/processors/subscription.ts +16 -20
- package/src/sync/processors/sync.ts +17 -17
- package/src/sync/utils.ts +48 -42
- package/src/types.ts +16 -128
- package/src/util.ts +150 -108
- package/webpack.config.dev.js +6 -0
- package/lib/authModeStrategies/defaultAuthStrategy.d.ts +0 -2
- package/lib/authModeStrategies/index.d.ts +0 -2
- package/lib/authModeStrategies/multiAuthStrategy.d.ts +0 -13
- package/lib/datastore/datastore.d.ts +0 -207
- package/lib/index.d.ts +0 -16
- package/lib/predicates/index.d.ts +0 -30
- package/lib/predicates/next.d.ts +0 -301
- package/lib/predicates/next.js +0 -816
- package/lib/predicates/next.js.map +0 -1
- package/lib/predicates/sort.d.ts +0 -8
- package/lib/ssr/index.d.ts +0 -3
- package/lib/storage/adapter/AsyncStorageAdapter.d.ts +0 -42
- package/lib/storage/adapter/AsyncStorageDatabase.d.ts +0 -39
- package/lib/storage/adapter/InMemoryStore.d.ts +0 -11
- package/lib/storage/adapter/InMemoryStore.native.d.ts +0 -1
- package/lib/storage/adapter/IndexedDBAdapter.d.ts +0 -61
- package/lib/storage/adapter/getDefaultAdapter/index.d.ts +0 -3
- package/lib/storage/adapter/getDefaultAdapter/index.native.d.ts +0 -3
- package/lib/storage/adapter/index.d.ts +0 -9
- package/lib/storage/relationship.d.ts +0 -140
- package/lib/storage/relationship.js +0 -335
- package/lib/storage/relationship.js.map +0 -1
- package/lib/storage/storage.d.ts +0 -50
- package/lib/sync/datastoreConnectivity.d.ts +0 -16
- package/lib/sync/datastoreReachability/index.d.ts +0 -3
- package/lib/sync/datastoreReachability/index.native.d.ts +0 -3
- package/lib/sync/index.d.ts +0 -89
- package/lib/sync/merger.d.ts +0 -17
- package/lib/sync/outbox.d.ts +0 -27
- package/lib/sync/processors/errorMaps.d.ts +0 -17
- package/lib/sync/processors/mutation.d.ts +0 -58
- package/lib/sync/processors/subscription.d.ts +0 -33
- package/lib/sync/processors/sync.d.ts +0 -28
- package/lib/sync/utils.d.ts +0 -42
- package/lib/types.d.ts +0 -554
- package/lib/util.d.ts +0 -189
- package/lib-esm/predicates/next.d.ts +0 -301
- package/lib-esm/predicates/next.js +0 -812
- package/lib-esm/predicates/next.js.map +0 -1
- package/lib-esm/storage/relationship.d.ts +0 -140
- package/lib-esm/storage/relationship.js +0 -333
- package/lib-esm/storage/relationship.js.map +0 -1
- package/src/predicates/next.ts +0 -967
- package/src/storage/relationship.ts +0 -272
package/src/predicates/next.ts
DELETED
|
@@ -1,967 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Scalar,
|
|
3
|
-
PersistentModel,
|
|
4
|
-
ModelFieldType,
|
|
5
|
-
ModelMeta,
|
|
6
|
-
AllOperators,
|
|
7
|
-
PredicateFieldType,
|
|
8
|
-
ModelPredicate as StoragePredicate,
|
|
9
|
-
} from '../types';
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
ModelPredicateCreator as FlatModelPredicateCreator,
|
|
13
|
-
comparisonKeys,
|
|
14
|
-
} from './index';
|
|
15
|
-
import { ExclusiveStorage as StorageAdapter } from '../storage/storage';
|
|
16
|
-
import { ModelRelationship } from '../storage/relationship';
|
|
17
|
-
import { asyncSome, asyncEvery } from '../util';
|
|
18
|
-
|
|
19
|
-
type MatchableTypes =
|
|
20
|
-
| string
|
|
21
|
-
| string[]
|
|
22
|
-
| number
|
|
23
|
-
| number[]
|
|
24
|
-
| boolean
|
|
25
|
-
| boolean[];
|
|
26
|
-
|
|
27
|
-
type AllFieldOperators = keyof AllOperators;
|
|
28
|
-
|
|
29
|
-
const ops = [...comparisonKeys] as AllFieldOperators[];
|
|
30
|
-
|
|
31
|
-
type NonNeverKeys<T> = {
|
|
32
|
-
[K in keyof T]: T[K] extends never ? never : K;
|
|
33
|
-
}[keyof T];
|
|
34
|
-
|
|
35
|
-
type WithoutNevers<T> = Pick<T, NonNeverKeys<T>>;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* A function that accepts a RecursiveModelPrecicate<T>, which it must use to
|
|
39
|
-
* return a final condition.
|
|
40
|
-
*
|
|
41
|
-
* This is used in `DataStore.query()`, `DataStore.observe()`, and
|
|
42
|
-
* `DataStore.observeQuery()` as the second argument. E.g.,
|
|
43
|
-
*
|
|
44
|
-
* ```
|
|
45
|
-
* DataStore.query(MyModel, model => model.field.eq('some value'))
|
|
46
|
-
* ```
|
|
47
|
-
*
|
|
48
|
-
* More complex queries should also be supported. E.g.,
|
|
49
|
-
*
|
|
50
|
-
* ```
|
|
51
|
-
* DataStore.query(MyModel, model => model.and(m => [
|
|
52
|
-
* m.relatedEntity.or(relative => [
|
|
53
|
-
* relative.relativeField.eq('whatever'),
|
|
54
|
-
* relative.relativeField.eq('whatever else')
|
|
55
|
-
* ]),
|
|
56
|
-
* m.myModelField.ne('something')
|
|
57
|
-
* ]))
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
export type RecursiveModelPredicateExtender<RT extends PersistentModel> = (
|
|
61
|
-
lambda: RecursiveModelPredicate<RT>
|
|
62
|
-
) => PredicateInternalsKey;
|
|
63
|
-
|
|
64
|
-
export type RecursiveModelPredicateAggregateExtender<
|
|
65
|
-
RT extends PersistentModel
|
|
66
|
-
> = (lambda: RecursiveModelPredicate<RT>) => PredicateInternalsKey[];
|
|
67
|
-
|
|
68
|
-
type RecursiveModelPredicateOperator<RT extends PersistentModel> = (
|
|
69
|
-
predicates: RecursiveModelPredicateAggregateExtender<RT>
|
|
70
|
-
) => PredicateInternalsKey;
|
|
71
|
-
|
|
72
|
-
type RecursiveModelPredicateNegation<RT extends PersistentModel> = (
|
|
73
|
-
predicate: RecursiveModelPredicateExtender<RT>
|
|
74
|
-
) => PredicateInternalsKey;
|
|
75
|
-
|
|
76
|
-
export type RecursiveModelPredicate<RT extends PersistentModel> = {
|
|
77
|
-
[K in keyof RT]-?: PredicateFieldType<RT[K]> extends PersistentModel
|
|
78
|
-
? RecursiveModelPredicate<PredicateFieldType<RT[K]>>
|
|
79
|
-
: ValuePredicate<RT, RT[K]>;
|
|
80
|
-
} & {
|
|
81
|
-
or: RecursiveModelPredicateOperator<RT>;
|
|
82
|
-
and: RecursiveModelPredicateOperator<RT>;
|
|
83
|
-
not: RecursiveModelPredicateNegation<RT>;
|
|
84
|
-
} & PredicateInternalsKey;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* A function that accepts a ModelPrecicate<T>, which it must use to return a
|
|
88
|
-
* final condition.
|
|
89
|
-
*
|
|
90
|
-
* This is used as predicates in `DataStore.save()`, `DataStore.delete()`, and
|
|
91
|
-
* DataStore sync expressions.
|
|
92
|
-
*
|
|
93
|
-
* ```
|
|
94
|
-
* DataStore.save(record, model => model.field.eq('some value'))
|
|
95
|
-
* ```
|
|
96
|
-
*
|
|
97
|
-
* Logical operators are supported. But, condtiions are related records are
|
|
98
|
-
* NOT supported. E.g.,
|
|
99
|
-
*
|
|
100
|
-
* ```
|
|
101
|
-
* DataStore.delete(record, model => model.or(m => [
|
|
102
|
-
* m.field.eq('whatever'),
|
|
103
|
-
* m.field.eq('whatever else')
|
|
104
|
-
* ]))
|
|
105
|
-
* ```
|
|
106
|
-
*/
|
|
107
|
-
export type ModelPredicateExtender<RT extends PersistentModel> = (
|
|
108
|
-
lambda: ModelPredicate<RT>
|
|
109
|
-
) => PredicateInternalsKey;
|
|
110
|
-
|
|
111
|
-
export type ModelPredicateAggregateExtender<RT extends PersistentModel> = (
|
|
112
|
-
lambda: ModelPredicate<RT>
|
|
113
|
-
) => PredicateInternalsKey[];
|
|
114
|
-
|
|
115
|
-
type ValuePredicate<RT extends PersistentModel, MT extends MatchableTypes> = {
|
|
116
|
-
[K in AllFieldOperators]: K extends 'between'
|
|
117
|
-
? (
|
|
118
|
-
inclusiveLowerBound: Scalar<MT>,
|
|
119
|
-
inclusiveUpperBound: Scalar<MT>
|
|
120
|
-
) => PredicateInternalsKey
|
|
121
|
-
: (operand: Scalar<MT>) => PredicateInternalsKey;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export type ModelPredicate<RT extends PersistentModel> = WithoutNevers<{
|
|
125
|
-
[K in keyof RT]-?: PredicateFieldType<RT[K]> extends PersistentModel
|
|
126
|
-
? never
|
|
127
|
-
: ValuePredicate<RT, RT[K]>;
|
|
128
|
-
}> & {
|
|
129
|
-
or: ModelPredicateOperator<RT>;
|
|
130
|
-
and: ModelPredicateOperator<RT>;
|
|
131
|
-
not: ModelPredicateNegation<RT>;
|
|
132
|
-
} & PredicateInternalsKey;
|
|
133
|
-
|
|
134
|
-
type ModelPredicateOperator<RT extends PersistentModel> = (
|
|
135
|
-
predicates: ModelPredicateAggregateExtender<RT>
|
|
136
|
-
) => PredicateInternalsKey;
|
|
137
|
-
|
|
138
|
-
type ModelPredicateNegation<RT extends PersistentModel> = (
|
|
139
|
-
predicate: ModelPredicateExtender<RT>
|
|
140
|
-
) => PredicateInternalsKey;
|
|
141
|
-
|
|
142
|
-
type GroupOperator = 'and' | 'or' | 'not';
|
|
143
|
-
|
|
144
|
-
type UntypedCondition = {
|
|
145
|
-
fetch: (storage: StorageAdapter) => Promise<Record<string, any>[]>;
|
|
146
|
-
matches: (item: Record<string, any>) => Promise<boolean>;
|
|
147
|
-
copy(extract: GroupCondition): [UntypedCondition, GroupCondition | undefined];
|
|
148
|
-
toAST(): any;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* A pointer used by DataStore internally to lookup predicate details
|
|
153
|
-
* that should not be exposed on public customer interfaces.
|
|
154
|
-
*/
|
|
155
|
-
export class PredicateInternalsKey {
|
|
156
|
-
private __isPredicateInternalsKeySentinel: boolean = true;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* A map from keys (exposed to customers) to the internal predicate data
|
|
161
|
-
* structures invoking code should not muck with.
|
|
162
|
-
*/
|
|
163
|
-
const predicateInternalsMap = new Map<PredicateInternalsKey, GroupCondition>();
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Creates a link between a key (and generates a key if needed) and an internal
|
|
167
|
-
* `GroupCondition`, which allows us to return a key object instead of the gory
|
|
168
|
-
* conditions details to customers/invoking code.
|
|
169
|
-
*
|
|
170
|
-
* @param condition The internal condition to keep hidden.
|
|
171
|
-
* @param key The object DataStore will use to find the internal condition.
|
|
172
|
-
* If no key is given, an empty one is created.
|
|
173
|
-
*/
|
|
174
|
-
const registerPredicateInternals = (condition: GroupCondition, key?: any) => {
|
|
175
|
-
const finalKey = key || new PredicateInternalsKey();
|
|
176
|
-
predicateInternalsMap.set(finalKey, condition);
|
|
177
|
-
return finalKey;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Takes a key object from `registerPredicateInternals()` to fetch an internal
|
|
182
|
-
* `GroupCondition` object, which can then be used to query storage or
|
|
183
|
-
* test/match objects.
|
|
184
|
-
*
|
|
185
|
-
* This indirection exists to hide `GroupCondition` from public interfaces, since
|
|
186
|
-
* `GroupCondition` contains extra methods and properties that public callers
|
|
187
|
-
* should not use.
|
|
188
|
-
*
|
|
189
|
-
* @param key A key object previously returned by `registerPredicateInternals()`
|
|
190
|
-
*/
|
|
191
|
-
export const internals = (key: any) => {
|
|
192
|
-
if (!predicateInternalsMap.has(key)) {
|
|
193
|
-
throw new Error(
|
|
194
|
-
"Invalid predicate. Terminate your predicate with a valid condition (e.g., `p => p.field.eq('value')`) or pass `Predicates.ALL`."
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
return predicateInternalsMap.get(key)!;
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Maps operators to negated operators.
|
|
202
|
-
* Used to facilitate propagation of negation down a tree of conditions.
|
|
203
|
-
*/
|
|
204
|
-
const negations = {
|
|
205
|
-
and: 'or',
|
|
206
|
-
or: 'and',
|
|
207
|
-
not: 'and',
|
|
208
|
-
eq: 'ne',
|
|
209
|
-
ne: 'eq',
|
|
210
|
-
gt: 'le',
|
|
211
|
-
ge: 'lt',
|
|
212
|
-
lt: 'ge',
|
|
213
|
-
le: 'gt',
|
|
214
|
-
contains: 'notContains',
|
|
215
|
-
notContains: 'contains',
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Given a V1 predicate "seed", applies a list of V2 field-level conditions
|
|
220
|
-
* to the predicate, returning a new/final V1 predicate chain link.
|
|
221
|
-
* @param predicate The base/seed V1 predicate to build on
|
|
222
|
-
* @param conditions The V2 conditions to add to the predicate chain.
|
|
223
|
-
* @param negateChildren Whether the conditions should be negated first.
|
|
224
|
-
* @returns A V1 predicate, with conditions incorporated.
|
|
225
|
-
*/
|
|
226
|
-
function applyConditionsToV1Predicate<T>(
|
|
227
|
-
predicate: T,
|
|
228
|
-
conditions: FieldCondition[],
|
|
229
|
-
negateChildren: boolean
|
|
230
|
-
): T {
|
|
231
|
-
let p = predicate;
|
|
232
|
-
const finalConditions: FieldCondition[] = [];
|
|
233
|
-
|
|
234
|
-
for (const c of conditions) {
|
|
235
|
-
if (negateChildren) {
|
|
236
|
-
if (c.operator === 'between') {
|
|
237
|
-
finalConditions.push(
|
|
238
|
-
new FieldCondition(c.field, 'lt', [c.operands[0]]),
|
|
239
|
-
new FieldCondition(c.field, 'gt', [c.operands[1]])
|
|
240
|
-
);
|
|
241
|
-
} else {
|
|
242
|
-
finalConditions.push(
|
|
243
|
-
new FieldCondition(c.field, negations[c.operator], c.operands)
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
} else {
|
|
247
|
-
finalConditions.push(c);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
for (const c of finalConditions) {
|
|
252
|
-
p = p[c.field](
|
|
253
|
-
c.operator as never,
|
|
254
|
-
(c.operator === 'between' ? c.operands : c.operands[0]) as never
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
return p;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* A condition that can operate against a single "primitive" field of a model or item.
|
|
262
|
-
* @member field The field of *some record* to test against.
|
|
263
|
-
* @member operator The equality or comparison operator to use.
|
|
264
|
-
* @member operands The operands for the equality/comparison check.
|
|
265
|
-
*/
|
|
266
|
-
export class FieldCondition {
|
|
267
|
-
constructor(
|
|
268
|
-
public field: string,
|
|
269
|
-
public operator: string,
|
|
270
|
-
public operands: string[]
|
|
271
|
-
) {
|
|
272
|
-
this.validate();
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Creates a copy of self.
|
|
277
|
-
* @param extract Not used. Present only to fulfill the `UntypedCondition` interface.
|
|
278
|
-
* @returns A new, identitical `FieldCondition`.
|
|
279
|
-
*/
|
|
280
|
-
copy(extract: GroupCondition): [FieldCondition, GroupCondition | undefined] {
|
|
281
|
-
return [
|
|
282
|
-
new FieldCondition(this.field, this.operator, [...this.operands]),
|
|
283
|
-
undefined,
|
|
284
|
-
];
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
toAST() {
|
|
288
|
-
return {
|
|
289
|
-
[this.field]: {
|
|
290
|
-
[this.operator]:
|
|
291
|
-
this.operator === 'between'
|
|
292
|
-
? [this.operands[0], this.operands[1]]
|
|
293
|
-
: this.operands[0],
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Not implemented. Not needed. GroupCondition instead consumes FieldConditions and
|
|
300
|
-
* transforms them into legacy predicates. (*For now.*)
|
|
301
|
-
* @param storage N/A. If ever implemented, the storage adapter to query.
|
|
302
|
-
* @returns N/A. If ever implemented, return items from `storage` that match.
|
|
303
|
-
*/
|
|
304
|
-
async fetch(storage: StorageAdapter): Promise<Record<string, any>[]> {
|
|
305
|
-
return Promise.reject('No implementation needed [yet].');
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Determins whether a given item matches the expressed condition.
|
|
310
|
-
* @param item The item to test.
|
|
311
|
-
* @returns `Promise<boolean>`, `true` if matches; `false` otherwise.
|
|
312
|
-
*/
|
|
313
|
-
async matches(item: Record<string, any>): Promise<boolean> {
|
|
314
|
-
const v = String(item[this.field]);
|
|
315
|
-
const operations = {
|
|
316
|
-
eq: () => v === this.operands[0],
|
|
317
|
-
ne: () => v !== this.operands[0],
|
|
318
|
-
gt: () => v > this.operands[0],
|
|
319
|
-
ge: () => v >= this.operands[0],
|
|
320
|
-
lt: () => v < this.operands[0],
|
|
321
|
-
le: () => v <= this.operands[0],
|
|
322
|
-
contains: () => v.indexOf(this.operands[0]) > -1,
|
|
323
|
-
notContains: () => v.indexOf(this.operands[0]) === -1,
|
|
324
|
-
beginsWith: () => v.startsWith(this.operands[0]),
|
|
325
|
-
between: () => v >= this.operands[0] && v <= this.operands[1],
|
|
326
|
-
};
|
|
327
|
-
const operation = operations[this.operator as keyof typeof operations];
|
|
328
|
-
if (operation) {
|
|
329
|
-
return operation();
|
|
330
|
-
} else {
|
|
331
|
-
throw new Error(`Invalid operator given: ${this.operator}`);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Checks `this.operands` for compatibility with `this.operator`.
|
|
337
|
-
*/
|
|
338
|
-
validate(): void {
|
|
339
|
-
/**
|
|
340
|
-
* Creates a validator that checks for a particular `operands` count.
|
|
341
|
-
* Throws an exception if the `count` disagrees with `operands.length`.
|
|
342
|
-
* @param count The number of `operands` expected.
|
|
343
|
-
*/
|
|
344
|
-
const argumentCount = count => {
|
|
345
|
-
const argsClause = count === 1 ? 'argument is' : 'arguments are';
|
|
346
|
-
return () => {
|
|
347
|
-
if (this.operands.length !== count) {
|
|
348
|
-
return `Exactly ${count} ${argsClause} required.`;
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
// NOTE: validations should return a message on failure.
|
|
354
|
-
// hence, they should be "joined" together with logical OR's
|
|
355
|
-
// as seen in the `between:` entry.
|
|
356
|
-
const validations = {
|
|
357
|
-
eq: argumentCount(1),
|
|
358
|
-
ne: argumentCount(1),
|
|
359
|
-
gt: argumentCount(1),
|
|
360
|
-
ge: argumentCount(1),
|
|
361
|
-
lt: argumentCount(1),
|
|
362
|
-
le: argumentCount(1),
|
|
363
|
-
contains: argumentCount(1),
|
|
364
|
-
notContains: argumentCount(1),
|
|
365
|
-
beginsWith: argumentCount(1),
|
|
366
|
-
between: () =>
|
|
367
|
-
argumentCount(2)() ||
|
|
368
|
-
(this.operands[0] > this.operands[1]
|
|
369
|
-
? 'The first argument must be less than or equal to the second argument.'
|
|
370
|
-
: null),
|
|
371
|
-
};
|
|
372
|
-
const validate = validations[this.operator as keyof typeof validations];
|
|
373
|
-
if (validate) {
|
|
374
|
-
const e = validate();
|
|
375
|
-
if (typeof e === 'string')
|
|
376
|
-
throw new Error(`Incorrect usage of \`${this.operator}()\`: ${e}`);
|
|
377
|
-
} else {
|
|
378
|
-
throw new Error(`Non-existent operator: \`${this.operator}()\``);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Small utility function to generate a monotonically increasing ID.
|
|
385
|
-
* Used by GroupCondition to help keep track of which group is doing what,
|
|
386
|
-
* when, and where during troubleshooting.
|
|
387
|
-
*/
|
|
388
|
-
const getGroupId = (() => {
|
|
389
|
-
let seed = 1;
|
|
390
|
-
return () => `group_${seed++}`;
|
|
391
|
-
})();
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* A set of sub-conditions to operate against a model, optionally scoped to
|
|
395
|
-
* a specific field, combined with the given operator (one of `and`, `or`, or `not`).
|
|
396
|
-
* @member groupId Used to distinguish between GroupCondition instances for
|
|
397
|
-
* debugging and troublehsooting.
|
|
398
|
-
* @member model A metadata object that tells GroupCondition what to query and how.
|
|
399
|
-
* @member field The field on the model that the sub-conditions apply to.
|
|
400
|
-
* @member operator How to group child conditions together.
|
|
401
|
-
* @member operands The child conditions.
|
|
402
|
-
*/
|
|
403
|
-
export class GroupCondition {
|
|
404
|
-
// `groupId` was used for development/debugging.
|
|
405
|
-
// Should we leave this in for future troubleshooting?
|
|
406
|
-
public groupId = getGroupId();
|
|
407
|
-
|
|
408
|
-
constructor(
|
|
409
|
-
/**
|
|
410
|
-
* The `ModelMeta` of the model to query and/or filter against.
|
|
411
|
-
* Expected to contain:
|
|
412
|
-
*
|
|
413
|
-
* ```js
|
|
414
|
-
* {
|
|
415
|
-
* builder: ModelConstructor,
|
|
416
|
-
* schema: SchemaModel,
|
|
417
|
-
* pkField: string[]
|
|
418
|
-
* }
|
|
419
|
-
* ```
|
|
420
|
-
*/
|
|
421
|
-
public model: ModelMeta<any>,
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* If populated, this group specifices a condition on a relationship.
|
|
425
|
-
*
|
|
426
|
-
* If `field` does *not* point to a related model, that's an error. It
|
|
427
|
-
* could indicate that the `GroupCondition` was instantiated with bad
|
|
428
|
-
* data, or that the model metadata is incorrect.
|
|
429
|
-
*/
|
|
430
|
-
public field: string | undefined,
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* If a `field` is given, whether the relationship is a `HAS_ONE`,
|
|
434
|
-
* 'HAS_MANY`, or `BELONGS_TO`.
|
|
435
|
-
*
|
|
436
|
-
* TODO: Remove this and replace with derivation using
|
|
437
|
-
* `ModelRelationship.from(this.model, this.field).relationship`;
|
|
438
|
-
*/
|
|
439
|
-
public relationshipType: string | undefined,
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
*
|
|
443
|
-
*/
|
|
444
|
-
public operator: GroupOperator,
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
*
|
|
448
|
-
*/
|
|
449
|
-
public operands: UntypedCondition[]
|
|
450
|
-
) {}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Returns a copy of a GroupCondition, which also returns the copy of a
|
|
454
|
-
* given reference node to "extract".
|
|
455
|
-
* @param extract A node of interest. Its copy will *also* be returned if the node exists.
|
|
456
|
-
* @returns [The full copy, the copy of `extract` | undefined]
|
|
457
|
-
*/
|
|
458
|
-
copy(extract: GroupCondition): [GroupCondition, GroupCondition | undefined] {
|
|
459
|
-
const copied = new GroupCondition(
|
|
460
|
-
this.model,
|
|
461
|
-
this.field,
|
|
462
|
-
this.relationshipType,
|
|
463
|
-
this.operator,
|
|
464
|
-
[]
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
let extractedCopy: GroupCondition | undefined =
|
|
468
|
-
extract === this ? copied : undefined;
|
|
469
|
-
|
|
470
|
-
this.operands.forEach(o => {
|
|
471
|
-
const [operandCopy, extractedFromOperand] = o.copy(extract);
|
|
472
|
-
copied.operands.push(operandCopy);
|
|
473
|
-
extractedCopy = extractedCopy || extractedFromOperand;
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
return [copied, extractedCopy];
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Fetches matching records from a given storage adapter using legacy predicates (for now).
|
|
481
|
-
* @param storage The storage adapter this predicate will query against.
|
|
482
|
-
* @param breadcrumb For debugging/troubleshooting. A list of the `groupId`'s this
|
|
483
|
-
* GroupdCondition.fetch is nested within.
|
|
484
|
-
* @param negate Whether to match on the `NOT` of `this`.
|
|
485
|
-
* @returns An `Promise` of `any[]` from `storage` matching the child conditions.
|
|
486
|
-
*/
|
|
487
|
-
async fetch(
|
|
488
|
-
storage: StorageAdapter,
|
|
489
|
-
breadcrumb: string[] = [],
|
|
490
|
-
negate = false
|
|
491
|
-
): Promise<Record<string, any>[]> {
|
|
492
|
-
const resultGroups: Array<Record<string, any>[]> = [];
|
|
493
|
-
|
|
494
|
-
const operator = (negate ? negations[this.operator] : this.operator) as
|
|
495
|
-
| 'or'
|
|
496
|
-
| 'and'
|
|
497
|
-
| 'not';
|
|
498
|
-
|
|
499
|
-
const negateChildren = negate !== (this.operator === 'not');
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Conditions that must be branched out and used to generate a base, "candidate"
|
|
503
|
-
* result set.
|
|
504
|
-
*
|
|
505
|
-
* If `field` is populated, these groups select *related* records, and the base,
|
|
506
|
-
* candidate results are selected to match those.
|
|
507
|
-
*/
|
|
508
|
-
const groups = this.operands.filter(
|
|
509
|
-
op => op instanceof GroupCondition
|
|
510
|
-
) as GroupCondition[];
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Simple conditions that must match the target model of `this`.
|
|
514
|
-
*/
|
|
515
|
-
const conditions = this.operands.filter(
|
|
516
|
-
op => op instanceof FieldCondition
|
|
517
|
-
) as FieldCondition[];
|
|
518
|
-
|
|
519
|
-
for (const g of groups) {
|
|
520
|
-
const relatives = await g.fetch(
|
|
521
|
-
storage,
|
|
522
|
-
[...breadcrumb, this.groupId],
|
|
523
|
-
negateChildren
|
|
524
|
-
);
|
|
525
|
-
|
|
526
|
-
// no relatives -> no need to attempt to perform a "join" query for
|
|
527
|
-
// candidate results:
|
|
528
|
-
//
|
|
529
|
-
// select a.* from a,b where b.id in EMPTY_SET ==> EMPTY_SET
|
|
530
|
-
//
|
|
531
|
-
// Additionally, the entire (sub)-query can be short-circuited if
|
|
532
|
-
// the operator is `AND`. Illustrated in SQL:
|
|
533
|
-
//
|
|
534
|
-
// select a.* from a where
|
|
535
|
-
// id in [a,b,c]
|
|
536
|
-
// AND <
|
|
537
|
-
// id in EMTPY_SET <<< Look!
|
|
538
|
-
// AND <
|
|
539
|
-
// id in [x,y,z]
|
|
540
|
-
//
|
|
541
|
-
// YIELDS: EMPTY_SET // <-- Easy peasy. Lemon squeezy.
|
|
542
|
-
//
|
|
543
|
-
if (relatives.length === 0) {
|
|
544
|
-
// aggressively short-circuit as soon as we know the group condition will fail
|
|
545
|
-
if (operator === 'and') {
|
|
546
|
-
return [];
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// less aggressive short-circuit if we know the relatives will produce no
|
|
550
|
-
// candidate results; but aren't sure yet how this affects the group condition.
|
|
551
|
-
resultGroups.push([]);
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (g.field) {
|
|
556
|
-
// `relatives` are actual relatives. We'll skim them for FK query values.
|
|
557
|
-
// Use the relatives to add candidate result sets (`resultGroups`)
|
|
558
|
-
|
|
559
|
-
const relationship = ModelRelationship.from(this.model, g.field);
|
|
560
|
-
|
|
561
|
-
if (relationship) {
|
|
562
|
-
const relativesPredicates: ((
|
|
563
|
-
p: RecursiveModelPredicate<any>
|
|
564
|
-
) => RecursiveModelPredicate<any>)[] = [];
|
|
565
|
-
for (const relative of relatives) {
|
|
566
|
-
const individualRowJoinConditions: FieldCondition[] = [];
|
|
567
|
-
|
|
568
|
-
for (let i = 0; i < relationship.localJoinFields.length; i++) {
|
|
569
|
-
// rightHandValue
|
|
570
|
-
individualRowJoinConditions.push(
|
|
571
|
-
new FieldCondition(relationship.localJoinFields[i], 'eq', [
|
|
572
|
-
relative[relationship.remoteJoinFields[i]],
|
|
573
|
-
])
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const predicate = p =>
|
|
578
|
-
applyConditionsToV1Predicate(
|
|
579
|
-
p,
|
|
580
|
-
individualRowJoinConditions,
|
|
581
|
-
false
|
|
582
|
-
);
|
|
583
|
-
relativesPredicates.push(predicate as any);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const predicate = FlatModelPredicateCreator.createGroupFromExisting(
|
|
587
|
-
this.model.schema,
|
|
588
|
-
'or',
|
|
589
|
-
relativesPredicates as any
|
|
590
|
-
);
|
|
591
|
-
|
|
592
|
-
resultGroups.push(
|
|
593
|
-
await storage.query(this.model.builder, predicate as any)
|
|
594
|
-
);
|
|
595
|
-
} else {
|
|
596
|
-
throw new Error('Missing field metadata.');
|
|
597
|
-
}
|
|
598
|
-
} else {
|
|
599
|
-
// relatives are not actually relatives. they're candidate results.
|
|
600
|
-
resultGroups.push(relatives);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// if conditions is empty at this point, child predicates found no matches.
|
|
605
|
-
// i.e., we can stop looking and return empty.
|
|
606
|
-
if (conditions.length > 0) {
|
|
607
|
-
const predicate = FlatModelPredicateCreator.createFromExisting(
|
|
608
|
-
this.model.schema,
|
|
609
|
-
p =>
|
|
610
|
-
p[operator](c =>
|
|
611
|
-
applyConditionsToV1Predicate(c, conditions, negateChildren)
|
|
612
|
-
)
|
|
613
|
-
);
|
|
614
|
-
resultGroups.push(
|
|
615
|
-
await storage.query(this.model.builder, predicate as any)
|
|
616
|
-
);
|
|
617
|
-
} else if (conditions.length === 0 && resultGroups.length === 0) {
|
|
618
|
-
resultGroups.push(await storage.query(this.model.builder));
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// PK might be a single field, like `id`, or it might be several fields.
|
|
622
|
-
// so, we'll need to extract the list of PK fields from an object
|
|
623
|
-
// and stringify the list for easy comparison / merging.
|
|
624
|
-
const getPKValue = item =>
|
|
625
|
-
JSON.stringify(this.model.pkField.map(name => item[name]));
|
|
626
|
-
|
|
627
|
-
// will be used for intersecting or unioning results
|
|
628
|
-
let resultIndex: Map<string, Record<string, any>> | undefined;
|
|
629
|
-
|
|
630
|
-
if (operator === 'and') {
|
|
631
|
-
if (resultGroups.length === 0) {
|
|
632
|
-
return [];
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// for each group, we intersect, removing items from the result index
|
|
636
|
-
// that aren't present in each subsequent group.
|
|
637
|
-
for (const group of resultGroups) {
|
|
638
|
-
if (resultIndex === undefined) {
|
|
639
|
-
resultIndex = new Map(group.map(item => [getPKValue(item), item]));
|
|
640
|
-
} else {
|
|
641
|
-
const intersectWith = new Map<string, Record<string, any>>(
|
|
642
|
-
group.map(item => [getPKValue(item), item])
|
|
643
|
-
);
|
|
644
|
-
for (const k of resultIndex.keys()) {
|
|
645
|
-
if (!intersectWith.has(k)) {
|
|
646
|
-
resultIndex.delete(k);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
} else if (operator === 'or' || operator === 'not') {
|
|
652
|
-
// it's OK to handle NOT here, because NOT must always only negate
|
|
653
|
-
// a single child predicate. NOT logic will have been distributed down
|
|
654
|
-
// to the leaf conditions already.
|
|
655
|
-
|
|
656
|
-
resultIndex = new Map();
|
|
657
|
-
|
|
658
|
-
// just merge the groups, performing DISTINCT-ification by ID.
|
|
659
|
-
for (const group of resultGroups) {
|
|
660
|
-
for (const item of group) {
|
|
661
|
-
resultIndex.set(getPKValue(item), item);
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
return Array.from(resultIndex?.values() || []);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* Determines whether a single item matches the conditions of `this`.
|
|
671
|
-
* When checking the target `item`'s properties, each property will be `await`'d
|
|
672
|
-
* to ensure lazy-loading is respected where applicable.
|
|
673
|
-
* @param item The item to match against.
|
|
674
|
-
* @param ignoreFieldName Tells `match()` that the field name has already been dereferenced.
|
|
675
|
-
* (Used for iterating over children on HAS_MANY checks.)
|
|
676
|
-
* @returns A boolean (promise): `true` if matched, `false` otherwise.
|
|
677
|
-
*/
|
|
678
|
-
async matches(
|
|
679
|
-
item: Record<string, any>,
|
|
680
|
-
ignoreFieldName: boolean = false
|
|
681
|
-
): Promise<boolean> {
|
|
682
|
-
const itemToCheck =
|
|
683
|
-
this.field && !ignoreFieldName ? await item[this.field] : item;
|
|
684
|
-
|
|
685
|
-
// if there is no item to check, we can stop recursing immediately.
|
|
686
|
-
// a condition cannot match against an item that does not exist. this
|
|
687
|
-
// can occur when `item.field` is optional in the schema.
|
|
688
|
-
if (!itemToCheck) {
|
|
689
|
-
return false;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
if (
|
|
693
|
-
this.relationshipType === 'HAS_MANY' &&
|
|
694
|
-
typeof itemToCheck[Symbol.asyncIterator] === 'function'
|
|
695
|
-
) {
|
|
696
|
-
for await (const singleItem of itemToCheck) {
|
|
697
|
-
if (await this.matches(singleItem, true)) {
|
|
698
|
-
return true;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
return false;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
if (this.operator === 'or') {
|
|
705
|
-
return asyncSome(this.operands, c => c.matches(itemToCheck));
|
|
706
|
-
} else if (this.operator === 'and') {
|
|
707
|
-
return asyncEvery(this.operands, c => c.matches(itemToCheck));
|
|
708
|
-
} else if (this.operator === 'not') {
|
|
709
|
-
if (this.operands.length !== 1) {
|
|
710
|
-
throw new Error(
|
|
711
|
-
'Invalid arguments! `not()` accepts exactly one predicate expression.'
|
|
712
|
-
);
|
|
713
|
-
}
|
|
714
|
-
return !(await this.operands[0].matches(itemToCheck));
|
|
715
|
-
} else {
|
|
716
|
-
throw new Error('Invalid group operator!');
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* Tranfsorm to a AppSync GraphQL compatible AST.
|
|
722
|
-
* (Does not support filtering in nested types.)
|
|
723
|
-
*/
|
|
724
|
-
toAST() {
|
|
725
|
-
if (this.field)
|
|
726
|
-
throw new Error('Nested type conditions are not supported!');
|
|
727
|
-
|
|
728
|
-
return {
|
|
729
|
-
[this.operator]: this.operands.map(operand => operand.toAST()),
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
toStoragePredicate<T>(
|
|
734
|
-
baseCondition?: StoragePredicate<T>
|
|
735
|
-
): StoragePredicate<T> {
|
|
736
|
-
return FlatModelPredicateCreator.createFromAST(
|
|
737
|
-
this.model.schema,
|
|
738
|
-
this.toAST()
|
|
739
|
-
) as unknown as StoragePredicate<T>;
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
/**
|
|
744
|
-
* Creates a "seed" predicate that can be used to build an executable condition.
|
|
745
|
-
* This is used in `query()`, for example, to seed customer- E.g.,
|
|
746
|
-
*
|
|
747
|
-
* ```
|
|
748
|
-
* const p = predicateFor({builder: modelConstructor, schema: modelSchema, pkField: string[]});
|
|
749
|
-
* p.and(child => [
|
|
750
|
-
* child.field.eq('whatever'),
|
|
751
|
-
* child.childModel.childField.eq('whatever else'),
|
|
752
|
-
* child.childModel.or(child => [
|
|
753
|
-
* child.otherField.contains('x'),
|
|
754
|
-
* child.otherField.contains('y'),
|
|
755
|
-
* child.otherField.contains('z'),
|
|
756
|
-
* ])
|
|
757
|
-
* ])
|
|
758
|
-
* ```
|
|
759
|
-
*
|
|
760
|
-
* `predicateFor()` returns objecst with recursive getters. To facilitate this,
|
|
761
|
-
* a `query` and `tail` can be provided to "accumulate" nested conditions.
|
|
762
|
-
*
|
|
763
|
-
* TODO: the sortof-immutable algorithm was originally done to support legacy style
|
|
764
|
-
* predicate branching (`p => p.x.eq(value).y.eq(value)`). i'm not sure this is
|
|
765
|
-
* necessary or beneficial at this point, since we decided that each field condition
|
|
766
|
-
* must flly terminate a branch. is the strong mutation barrier between chain links
|
|
767
|
-
* still necessary or helpful?
|
|
768
|
-
*
|
|
769
|
-
* @param ModelType The ModelMeta used to build child properties.
|
|
770
|
-
* @param field Scopes the query branch to a field.
|
|
771
|
-
* @param query A base query to build on. Omit to start a new query.
|
|
772
|
-
* @param tail The point in an existing `query` to attach new conditions to.
|
|
773
|
-
* @returns A ModelPredicate (builder) that customers can create queries with.
|
|
774
|
-
* (As shown in function description.)
|
|
775
|
-
*/
|
|
776
|
-
export function recursivePredicateFor<T extends PersistentModel>(
|
|
777
|
-
ModelType: ModelMeta<T>,
|
|
778
|
-
allowRecursion: boolean = true,
|
|
779
|
-
field?: string,
|
|
780
|
-
query?: GroupCondition,
|
|
781
|
-
tail?: GroupCondition
|
|
782
|
-
): RecursiveModelPredicate<T> & PredicateInternalsKey {
|
|
783
|
-
// to be used if we don't have a base query or tail to build onto
|
|
784
|
-
const starter = new GroupCondition(ModelType, field, undefined, 'and', []);
|
|
785
|
-
|
|
786
|
-
const baseCondition = query && tail ? query : starter;
|
|
787
|
-
const tailCondition = query && tail ? tail : starter;
|
|
788
|
-
|
|
789
|
-
// our eventual return object, which can be built upon.
|
|
790
|
-
// next steps will be to add or(), and(), not(), and field.op() methods.
|
|
791
|
-
const link = {} as any;
|
|
792
|
-
|
|
793
|
-
registerPredicateInternals(baseCondition, link);
|
|
794
|
-
|
|
795
|
-
const copyLink = () => {
|
|
796
|
-
const [query, newTail] = baseCondition.copy(tailCondition);
|
|
797
|
-
const newLink = recursivePredicateFor(
|
|
798
|
-
ModelType,
|
|
799
|
-
allowRecursion,
|
|
800
|
-
undefined,
|
|
801
|
-
query,
|
|
802
|
-
newTail
|
|
803
|
-
);
|
|
804
|
-
return { query, newTail, newLink };
|
|
805
|
-
};
|
|
806
|
-
|
|
807
|
-
// Adds .or() and .and() methods to the link.
|
|
808
|
-
// TODO: If revisiting this code, consider writing a Proxy instead.
|
|
809
|
-
['and', 'or'].forEach(op => {
|
|
810
|
-
(link as any)[op] = (
|
|
811
|
-
builder: RecursiveModelPredicateAggregateExtender<T>
|
|
812
|
-
) => {
|
|
813
|
-
// or() and and() will return a copy of the original link
|
|
814
|
-
// to head off mutability concerns.
|
|
815
|
-
const { query, newTail } = copyLink();
|
|
816
|
-
|
|
817
|
-
const childConditions = builder(
|
|
818
|
-
recursivePredicateFor(ModelType, allowRecursion)
|
|
819
|
-
);
|
|
820
|
-
if (!Array.isArray(childConditions)) {
|
|
821
|
-
throw new Error(
|
|
822
|
-
`Invalid predicate. \`${op}\` groups must return an array of child conditions.`
|
|
823
|
-
);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// the customer will supply a child predicate, which apply to the `model.field`
|
|
827
|
-
// of the tail GroupCondition.
|
|
828
|
-
newTail?.operands.push(
|
|
829
|
-
new GroupCondition(
|
|
830
|
-
ModelType,
|
|
831
|
-
field,
|
|
832
|
-
undefined,
|
|
833
|
-
op as 'and' | 'or',
|
|
834
|
-
childConditions.map(c => internals(c))
|
|
835
|
-
)
|
|
836
|
-
);
|
|
837
|
-
|
|
838
|
-
// FinalPredicate
|
|
839
|
-
return registerPredicateInternals(query);
|
|
840
|
-
};
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
// TODO: If revisiting this code, consider proxy.
|
|
844
|
-
link.not = (
|
|
845
|
-
builder: RecursiveModelPredicateExtender<T>
|
|
846
|
-
): PredicateInternalsKey => {
|
|
847
|
-
// not() will return a copy of the original link
|
|
848
|
-
// to head off mutability concerns.
|
|
849
|
-
const { query, newTail } = copyLink();
|
|
850
|
-
|
|
851
|
-
// unlike and() and or(), the customer will supply a "singular" child predicate.
|
|
852
|
-
// the difference being: not() does not accept an array of predicate-like objects.
|
|
853
|
-
// it negates only a *single* predicate subtree.
|
|
854
|
-
newTail?.operands.push(
|
|
855
|
-
new GroupCondition(ModelType, field, undefined, 'not', [
|
|
856
|
-
internals(builder(recursivePredicateFor(ModelType, allowRecursion))),
|
|
857
|
-
])
|
|
858
|
-
);
|
|
859
|
-
|
|
860
|
-
// A `FinalModelPredicate`.
|
|
861
|
-
// Return a thing that can no longer be extended, but instead used to `async filter(items)`
|
|
862
|
-
// or query storage: `.__query.fetch(storage)`.
|
|
863
|
-
return registerPredicateInternals(query);
|
|
864
|
-
};
|
|
865
|
-
|
|
866
|
-
// For each field on the model schema, we want to add a getter
|
|
867
|
-
// that creates the appropriate new `link` in the query chain.
|
|
868
|
-
// TODO: If revisiting, consider a proxy.
|
|
869
|
-
for (const fieldName in ModelType.schema.fields) {
|
|
870
|
-
Object.defineProperty(link, fieldName, {
|
|
871
|
-
enumerable: true,
|
|
872
|
-
get: () => {
|
|
873
|
-
const def = ModelType.schema.fields[fieldName];
|
|
874
|
-
|
|
875
|
-
if (!def.association) {
|
|
876
|
-
// we're looking at a value field. we need to return a
|
|
877
|
-
// "field matcher object", which contains all of the comparison
|
|
878
|
-
// functions ('eq', 'ne', 'gt', etc.), scoped to operate
|
|
879
|
-
// against the target field (fieldName).
|
|
880
|
-
return ops.reduce((fieldMatcher, operator) => {
|
|
881
|
-
return {
|
|
882
|
-
...fieldMatcher,
|
|
883
|
-
|
|
884
|
-
// each operator on the fieldMatcher objcect is a function.
|
|
885
|
-
// when the customer calls the function, it returns a new link
|
|
886
|
-
// in the chain -- for now -- this is the "leaf" link that
|
|
887
|
-
// cannot be further extended.
|
|
888
|
-
[operator]: (...operands: any[]) => {
|
|
889
|
-
// build off a fresh copy of the existing `link`, just in case
|
|
890
|
-
// the same link is being used elsewhere by the customer.
|
|
891
|
-
const { query, newTail } = copyLink();
|
|
892
|
-
|
|
893
|
-
// add the given condition to the link's TAIL node.
|
|
894
|
-
// remember: the base link might go N nodes deep! e.g.,
|
|
895
|
-
newTail?.operands.push(
|
|
896
|
-
new FieldCondition(fieldName, operator, operands)
|
|
897
|
-
);
|
|
898
|
-
|
|
899
|
-
// A `FinalModelPredicate`.
|
|
900
|
-
// Return a thing that can no longer be extended, but instead used to `async filter(items)`
|
|
901
|
-
// or query storage: `.__query.fetch(storage)`.
|
|
902
|
-
return registerPredicateInternals(query);
|
|
903
|
-
},
|
|
904
|
-
};
|
|
905
|
-
}, {});
|
|
906
|
-
} else {
|
|
907
|
-
if (!allowRecursion) {
|
|
908
|
-
throw new Error(
|
|
909
|
-
'Predication on releated models is not supported in this context.'
|
|
910
|
-
);
|
|
911
|
-
} else if (
|
|
912
|
-
def.association.connectionType === 'BELONGS_TO' ||
|
|
913
|
-
def.association.connectionType === 'HAS_ONE' ||
|
|
914
|
-
def.association.connectionType === 'HAS_MANY'
|
|
915
|
-
) {
|
|
916
|
-
// the use has just typed '.someRelatedModel'. we need to given them
|
|
917
|
-
// back a predicate chain.
|
|
918
|
-
|
|
919
|
-
const relatedMeta = (def.type as ModelFieldType).modelConstructor;
|
|
920
|
-
if (!relatedMeta) {
|
|
921
|
-
throw new Error(
|
|
922
|
-
'Related model metadata is missing. This is a bug! Please report it.'
|
|
923
|
-
);
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
// `Model.reletedModelField` returns a copy of the original link,
|
|
927
|
-
// and will contains copies of internal GroupConditions
|
|
928
|
-
// to head off mutability concerns.
|
|
929
|
-
const [newquery, oldtail] = baseCondition.copy(tailCondition);
|
|
930
|
-
const newtail = new GroupCondition(
|
|
931
|
-
relatedMeta,
|
|
932
|
-
fieldName,
|
|
933
|
-
def.association.connectionType,
|
|
934
|
-
'and',
|
|
935
|
-
[]
|
|
936
|
-
);
|
|
937
|
-
|
|
938
|
-
// `oldtail` here refers to the *copy* of the old tail.
|
|
939
|
-
// so, it's safe to modify at this point. and we need to modify
|
|
940
|
-
// it to push the *new* tail onto the end of it.
|
|
941
|
-
(oldtail as GroupCondition).operands.push(newtail);
|
|
942
|
-
const newlink = recursivePredicateFor(
|
|
943
|
-
relatedMeta,
|
|
944
|
-
allowRecursion,
|
|
945
|
-
undefined,
|
|
946
|
-
newquery,
|
|
947
|
-
newtail
|
|
948
|
-
);
|
|
949
|
-
return newlink;
|
|
950
|
-
} else {
|
|
951
|
-
throw new Error(
|
|
952
|
-
"Related model definition doesn't have a typedef. This is a bug! Please report it."
|
|
953
|
-
);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
},
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
return link;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
export function predicateFor<T extends PersistentModel>(
|
|
964
|
-
ModelType: ModelMeta<T>
|
|
965
|
-
): ModelPredicate<T> & PredicateInternalsKey {
|
|
966
|
-
return recursivePredicateFor(ModelType, false) as any as ModelPredicate<T>;
|
|
967
|
-
}
|