@diister/quick-permission 0.9.0-beta.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/LICENSE +21 -0
- package/README.md +747 -0
- package/aggregation.ts +114 -0
- package/core/filtering.ts +70 -0
- package/core/matching.ts +82 -0
- package/core/merging.ts +143 -0
- package/dist/aggregation.d.ts +62 -0
- package/dist/aggregation.d.ts.map +1 -0
- package/dist/aggregation.js +97 -0
- package/dist/aggregation.js.map +1 -0
- package/dist/core/filtering.d.ts +35 -0
- package/dist/core/filtering.d.ts.map +1 -0
- package/dist/core/filtering.js +62 -0
- package/dist/core/filtering.js.map +1 -0
- package/dist/core/matching.d.ts +31 -0
- package/dist/core/matching.d.ts.map +1 -0
- package/dist/core/matching.js +75 -0
- package/dist/core/matching.js.map +1 -0
- package/dist/core/merging.d.ts +29 -0
- package/dist/core/merging.d.ts.map +1 -0
- package/dist/core/merging.js +124 -0
- package/dist/core/merging.js.map +1 -0
- package/dist/indirect-aggregation.d.ts +41 -0
- package/dist/indirect-aggregation.d.ts.map +1 -0
- package/dist/indirect-aggregation.js +185 -0
- package/dist/indirect-aggregation.js.map +1 -0
- package/dist/indirect-resource.d.ts +126 -0
- package/dist/indirect-resource.d.ts.map +1 -0
- package/dist/indirect-resource.js +109 -0
- package/dist/indirect-resource.js.map +1 -0
- package/dist/mod.d.ts +25 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +25 -0
- package/dist/mod.js.map +1 -0
- package/dist/mongo-query.d.ts +38 -0
- package/dist/mongo-query.d.ts.map +1 -0
- package/dist/mongo-query.js +88 -0
- package/dist/mongo-query.js.map +1 -0
- package/dist/permission.d.ts +57 -0
- package/dist/permission.d.ts.map +1 -0
- package/dist/permission.js +60 -0
- package/dist/permission.js.map +1 -0
- package/dist/resource.d.ts +48 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +298 -0
- package/dist/resource.js.map +1 -0
- package/dist/rules.d.ts +106 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +183 -0
- package/dist/rules.js.map +1 -0
- package/dist/sift/core.d.ts +104 -0
- package/dist/sift/core.d.ts.map +1 -0
- package/dist/sift/core.js +248 -0
- package/dist/sift/core.js.map +1 -0
- package/dist/sift/index.d.ts +10 -0
- package/dist/sift/index.d.ts.map +1 -0
- package/dist/sift/index.js +18 -0
- package/dist/sift/index.js.map +1 -0
- package/dist/sift/operations.d.ts +87 -0
- package/dist/sift/operations.d.ts.map +1 -0
- package/dist/sift/operations.js +257 -0
- package/dist/sift/operations.js.map +1 -0
- package/dist/sift/utils.d.ts +12 -0
- package/dist/sift/utils.d.ts.map +1 -0
- package/dist/sift/utils.js +80 -0
- package/dist/sift/utils.js.map +1 -0
- package/dist/system.d.ts +113 -0
- package/dist/system.d.ts.map +1 -0
- package/dist/system.js +712 -0
- package/dist/system.js.map +1 -0
- package/dist/target.d.ts +18 -0
- package/dist/target.d.ts.map +1 -0
- package/dist/target.js +41 -0
- package/dist/target.js.map +1 -0
- package/dist/types.d.ts +345 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/indirect-aggregation.ts +216 -0
- package/indirect-resource.ts +205 -0
- package/mod.ts +81 -0
- package/mongo-query.ts +94 -0
- package/package.json +58 -0
- package/permission.ts +88 -0
- package/resource.ts +352 -0
- package/rules.ts +241 -0
- package/sift/MIT-LICENSE.txt +20 -0
- package/sift/core.ts +551 -0
- package/sift/index.ts +62 -0
- package/sift/operations.ts +449 -0
- package/sift/utils.ts +96 -0
- package/system.ts +974 -0
- package/target.ts +84 -0
- package/types.ts +408 -0
package/sift/core.ts
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
// Vendored from sift.js (https://github.com/crcn/sift.js) — MIT licensed.
|
|
2
|
+
// `$where` operator removed (security: arbitrary code execution).
|
|
3
|
+
//
|
|
4
|
+
// Deviates from upstream in one mechanical way: constructor parameter
|
|
5
|
+
// properties are written out as explicit fields. Node's type stripping cannot
|
|
6
|
+
// erase that syntax, so upstream's form made this file unloadable as raw
|
|
7
|
+
// TypeScript there — and the suite runs on Node to cover a second engine.
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
comparable,
|
|
11
|
+
type Comparator,
|
|
12
|
+
equals,
|
|
13
|
+
isArray,
|
|
14
|
+
isProperty,
|
|
15
|
+
isVanillaObject,
|
|
16
|
+
type Key,
|
|
17
|
+
} from "./utils.ts";
|
|
18
|
+
|
|
19
|
+
export interface Operation<TItem> {
|
|
20
|
+
readonly keep: boolean;
|
|
21
|
+
readonly done: boolean;
|
|
22
|
+
propop: boolean;
|
|
23
|
+
reset(): void;
|
|
24
|
+
next(
|
|
25
|
+
item: TItem,
|
|
26
|
+
key?: Key,
|
|
27
|
+
owner?: unknown,
|
|
28
|
+
root?: boolean,
|
|
29
|
+
leaf?: boolean,
|
|
30
|
+
): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type Tester = (
|
|
34
|
+
item: unknown,
|
|
35
|
+
key?: Key,
|
|
36
|
+
owner?: unknown,
|
|
37
|
+
root?: boolean,
|
|
38
|
+
leaf?: boolean,
|
|
39
|
+
) => boolean;
|
|
40
|
+
|
|
41
|
+
export interface NamedOperation {
|
|
42
|
+
name: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type OperationCreator<TItem> = (
|
|
46
|
+
params: unknown,
|
|
47
|
+
parentQuery: unknown,
|
|
48
|
+
options: Options,
|
|
49
|
+
name: string,
|
|
50
|
+
) => Operation<TItem>;
|
|
51
|
+
|
|
52
|
+
export type BasicValueQuery<TValue> = {
|
|
53
|
+
$eq?: TValue;
|
|
54
|
+
$ne?: TValue;
|
|
55
|
+
$lt?: TValue;
|
|
56
|
+
$gt?: TValue;
|
|
57
|
+
$lte?: TValue;
|
|
58
|
+
$gte?: TValue;
|
|
59
|
+
$in?: TValue[];
|
|
60
|
+
$nin?: TValue[];
|
|
61
|
+
$all?: TValue[];
|
|
62
|
+
$mod?: [number, number];
|
|
63
|
+
$exists?: boolean;
|
|
64
|
+
$regex?: string | RegExp;
|
|
65
|
+
$size?: number;
|
|
66
|
+
$options?: "i" | "g" | "m" | "u";
|
|
67
|
+
// deno-lint-ignore ban-types
|
|
68
|
+
$type?: Function;
|
|
69
|
+
$not?: NestedQuery<TValue>;
|
|
70
|
+
$or?: NestedQuery<TValue>[];
|
|
71
|
+
$nor?: NestedQuery<TValue>[];
|
|
72
|
+
$and?: NestedQuery<TValue>[];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type ArrayValueQuery<TValue> = {
|
|
76
|
+
$elemMatch?: Query<TValue>;
|
|
77
|
+
} & BasicValueQuery<TValue>;
|
|
78
|
+
|
|
79
|
+
type Unpacked<T> = T extends (infer U)[] ? U : T;
|
|
80
|
+
|
|
81
|
+
export type ValueQuery<TValue> =
|
|
82
|
+
TValue extends Array<unknown>
|
|
83
|
+
? ArrayValueQuery<Unpacked<TValue>>
|
|
84
|
+
: BasicValueQuery<TValue>;
|
|
85
|
+
|
|
86
|
+
type NotObject = string | number | Date | boolean | Array<unknown>;
|
|
87
|
+
export type ShapeQuery<TItemSchema> = TItemSchema extends NotObject
|
|
88
|
+
? // deno-lint-ignore ban-types
|
|
89
|
+
{}
|
|
90
|
+
: { [k in keyof TItemSchema]?: TItemSchema[k] | ValueQuery<TItemSchema[k]> };
|
|
91
|
+
|
|
92
|
+
export type NestedQuery<TItemSchema> = ValueQuery<TItemSchema> &
|
|
93
|
+
ShapeQuery<TItemSchema>;
|
|
94
|
+
|
|
95
|
+
export type Query<TItemSchema> =
|
|
96
|
+
| TItemSchema
|
|
97
|
+
| RegExp
|
|
98
|
+
| NestedQuery<TItemSchema>;
|
|
99
|
+
|
|
100
|
+
export type QueryOperators<TValue = unknown> = keyof ValueQuery<TValue>;
|
|
101
|
+
|
|
102
|
+
// Walks each value at a key path for nested ops, e.g. { "person.address": { $eq: "x" } }.
|
|
103
|
+
const walkKeyPathValues = (
|
|
104
|
+
item: unknown,
|
|
105
|
+
keyPath: Key[],
|
|
106
|
+
next: Tester,
|
|
107
|
+
depth: number,
|
|
108
|
+
key: Key | undefined,
|
|
109
|
+
owner: unknown,
|
|
110
|
+
): boolean | void => {
|
|
111
|
+
const currentKey = keyPath[depth];
|
|
112
|
+
|
|
113
|
+
// If array and currentKey is non-numeric / not own prop, fan out across elements.
|
|
114
|
+
// Falls through for cases like { $eq: [1, 2, 3] } against [1, 2, 3].
|
|
115
|
+
if (
|
|
116
|
+
isArray(item) &&
|
|
117
|
+
isNaN(Number(currentKey)) &&
|
|
118
|
+
!isProperty(item as object, currentKey)
|
|
119
|
+
) {
|
|
120
|
+
for (let i = 0, { length } = item; i < length; i++) {
|
|
121
|
+
// Returning false from `next` terminates the walk — for operations,
|
|
122
|
+
// that means the search criterion was met.
|
|
123
|
+
if (!walkKeyPathValues(item[i], keyPath, next, depth, i, item)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (depth === keyPath.length || item == null) {
|
|
130
|
+
return next(item, key, owner, depth === 0, depth === keyPath.length);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return walkKeyPathValues(
|
|
134
|
+
(item as Record<string, unknown>)[currentKey as string],
|
|
135
|
+
keyPath,
|
|
136
|
+
next,
|
|
137
|
+
depth + 1,
|
|
138
|
+
currentKey,
|
|
139
|
+
item,
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export abstract class BaseOperation<TParams, TItem = unknown>
|
|
144
|
+
implements Operation<TItem>
|
|
145
|
+
{
|
|
146
|
+
keep = false;
|
|
147
|
+
done = false;
|
|
148
|
+
abstract propop: boolean;
|
|
149
|
+
readonly params: TParams;
|
|
150
|
+
readonly owneryQuery: unknown;
|
|
151
|
+
readonly options: Options;
|
|
152
|
+
readonly name?: string;
|
|
153
|
+
// Fields are assigned manually (not via parameter properties) so they're
|
|
154
|
+
// available inside init(), which runs from this constructor before subclass
|
|
155
|
+
// field initializers fire under TS's `useDefineForClassFields` semantics.
|
|
156
|
+
constructor(
|
|
157
|
+
params: TParams,
|
|
158
|
+
owneryQuery: unknown,
|
|
159
|
+
options: Options,
|
|
160
|
+
name?: string,
|
|
161
|
+
) {
|
|
162
|
+
this.params = params;
|
|
163
|
+
this.owneryQuery = owneryQuery;
|
|
164
|
+
this.options = options;
|
|
165
|
+
this.name = name;
|
|
166
|
+
this.init();
|
|
167
|
+
}
|
|
168
|
+
protected init(): void {}
|
|
169
|
+
reset(): void {
|
|
170
|
+
this.done = false;
|
|
171
|
+
this.keep = false;
|
|
172
|
+
}
|
|
173
|
+
abstract next(
|
|
174
|
+
item: TItem,
|
|
175
|
+
key?: Key,
|
|
176
|
+
parent?: unknown,
|
|
177
|
+
root?: boolean,
|
|
178
|
+
leaf?: boolean,
|
|
179
|
+
): void;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
abstract class GroupOperation extends BaseOperation<unknown> {
|
|
183
|
+
public readonly children: Operation<unknown>[];
|
|
184
|
+
|
|
185
|
+
constructor(
|
|
186
|
+
params: unknown,
|
|
187
|
+
owneryQuery: unknown,
|
|
188
|
+
options: Options,
|
|
189
|
+
children: Operation<unknown>[],
|
|
190
|
+
) {
|
|
191
|
+
super(params, owneryQuery, options);
|
|
192
|
+
this.children = children;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
override reset(): void {
|
|
196
|
+
this.keep = false;
|
|
197
|
+
this.done = false;
|
|
198
|
+
for (let i = 0, { length } = this.children; i < length; i++) {
|
|
199
|
+
this.children[i].reset();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
abstract override next(
|
|
204
|
+
item: unknown,
|
|
205
|
+
key?: Key,
|
|
206
|
+
owner?: unknown,
|
|
207
|
+
root?: boolean,
|
|
208
|
+
leaf?: boolean,
|
|
209
|
+
): void;
|
|
210
|
+
|
|
211
|
+
protected childrenNext(
|
|
212
|
+
item: unknown,
|
|
213
|
+
key: Key | undefined,
|
|
214
|
+
owner: unknown,
|
|
215
|
+
root: boolean,
|
|
216
|
+
leaf?: boolean,
|
|
217
|
+
): void {
|
|
218
|
+
let done = true;
|
|
219
|
+
let keep = true;
|
|
220
|
+
for (let i = 0, { length } = this.children; i < length; i++) {
|
|
221
|
+
const childOperation = this.children[i];
|
|
222
|
+
if (!childOperation.done) {
|
|
223
|
+
childOperation.next(item, key, owner, root, leaf);
|
|
224
|
+
}
|
|
225
|
+
if (!childOperation.keep) {
|
|
226
|
+
keep = false;
|
|
227
|
+
}
|
|
228
|
+
if (childOperation.done) {
|
|
229
|
+
if (!childOperation.keep) {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
done = false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
this.done = done;
|
|
237
|
+
this.keep = keep;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export abstract class NamedGroupOperation
|
|
242
|
+
extends GroupOperation
|
|
243
|
+
implements NamedOperation
|
|
244
|
+
{
|
|
245
|
+
abstract override propop: boolean;
|
|
246
|
+
override readonly name: string;
|
|
247
|
+
|
|
248
|
+
constructor(
|
|
249
|
+
params: unknown,
|
|
250
|
+
owneryQuery: unknown,
|
|
251
|
+
options: Options,
|
|
252
|
+
children: Operation<unknown>[],
|
|
253
|
+
name: string,
|
|
254
|
+
) {
|
|
255
|
+
super(params, owneryQuery, options, children);
|
|
256
|
+
this.name = name;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export class QueryOperation<TItem> extends GroupOperation {
|
|
261
|
+
readonly propop = true;
|
|
262
|
+
|
|
263
|
+
override next(
|
|
264
|
+
item: TItem,
|
|
265
|
+
key?: Key,
|
|
266
|
+
parent?: unknown,
|
|
267
|
+
root?: boolean,
|
|
268
|
+
): void {
|
|
269
|
+
this.childrenNext(item, key, parent, root ?? false);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export class NestedOperation extends GroupOperation {
|
|
274
|
+
readonly propop = true;
|
|
275
|
+
readonly keyPath: Key[];
|
|
276
|
+
|
|
277
|
+
constructor(
|
|
278
|
+
keyPath: Key[],
|
|
279
|
+
params: unknown,
|
|
280
|
+
owneryQuery: unknown,
|
|
281
|
+
options: Options,
|
|
282
|
+
children: Operation<unknown>[],
|
|
283
|
+
) {
|
|
284
|
+
super(params, owneryQuery, options, children);
|
|
285
|
+
this.keyPath = keyPath;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
override next(item: unknown, key?: Key, parent?: unknown): void {
|
|
289
|
+
walkKeyPathValues(
|
|
290
|
+
item,
|
|
291
|
+
this.keyPath,
|
|
292
|
+
this._nextNestedValue,
|
|
293
|
+
0,
|
|
294
|
+
key,
|
|
295
|
+
parent,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private _nextNestedValue = (
|
|
300
|
+
value: unknown,
|
|
301
|
+
key: Key | undefined,
|
|
302
|
+
owner: unknown,
|
|
303
|
+
root: boolean | undefined,
|
|
304
|
+
leaf: boolean | undefined,
|
|
305
|
+
): boolean => {
|
|
306
|
+
this.childrenNext(value, key, owner, root ?? false, leaf);
|
|
307
|
+
return !this.done;
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export const createTester = (a: unknown, compare: Comparator): Tester => {
|
|
312
|
+
if (a instanceof Function) {
|
|
313
|
+
return a as Tester;
|
|
314
|
+
}
|
|
315
|
+
if (a instanceof RegExp) {
|
|
316
|
+
return (b: unknown) => {
|
|
317
|
+
const result = typeof b === "string" && a.test(b);
|
|
318
|
+
a.lastIndex = 0;
|
|
319
|
+
return result;
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const comparableA = comparable(a);
|
|
323
|
+
return (b: unknown) => compare(comparableA, comparable(b));
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
export class EqualsOperation<TParam> extends BaseOperation<TParam> {
|
|
327
|
+
readonly propop = true;
|
|
328
|
+
// `declare` so the field has no runtime initializer that would clobber
|
|
329
|
+
// the value set by init() in the parent constructor.
|
|
330
|
+
declare private _test: Tester;
|
|
331
|
+
override init(): void {
|
|
332
|
+
// Params can be a raw value, RegExp, or a custom tester function.
|
|
333
|
+
this._test = createTester(this.params as unknown, this.options.compare);
|
|
334
|
+
}
|
|
335
|
+
next(item: unknown, key?: Key, parent?: unknown): void {
|
|
336
|
+
if (
|
|
337
|
+
!Array.isArray(parent) ||
|
|
338
|
+
Object.prototype.hasOwnProperty.call(parent, key as PropertyKey)
|
|
339
|
+
) {
|
|
340
|
+
if (this._test(item, key, parent)) {
|
|
341
|
+
this.done = true;
|
|
342
|
+
this.keep = true;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export const createEqualsOperation = (
|
|
349
|
+
params: unknown,
|
|
350
|
+
owneryQuery: unknown,
|
|
351
|
+
options: Options,
|
|
352
|
+
): EqualsOperation<unknown> =>
|
|
353
|
+
new EqualsOperation(params, owneryQuery, options);
|
|
354
|
+
|
|
355
|
+
export const numericalOperationCreator =
|
|
356
|
+
(
|
|
357
|
+
createNumericalOperation: OperationCreator<unknown>,
|
|
358
|
+
): OperationCreator<unknown> =>
|
|
359
|
+
(params: unknown, owneryQuery: unknown, options: Options, name: string) => {
|
|
360
|
+
return createNumericalOperation(params, owneryQuery, options, name);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export const numericalOperation = (
|
|
364
|
+
createTester: (value: unknown) => Tester,
|
|
365
|
+
): OperationCreator<unknown> =>
|
|
366
|
+
numericalOperationCreator(
|
|
367
|
+
(params: unknown, owneryQuery: unknown, options: Options, name: string) => {
|
|
368
|
+
const typeofParams = typeof comparable(params);
|
|
369
|
+
const test = createTester(params);
|
|
370
|
+
return new EqualsOperation(
|
|
371
|
+
((b: unknown) => {
|
|
372
|
+
const actualValue = b == null ? null : b;
|
|
373
|
+
return (
|
|
374
|
+
typeof comparable(actualValue) === typeofParams && test(actualValue)
|
|
375
|
+
);
|
|
376
|
+
// EqualsOperation accepts a Tester as params; cast retains that contract.
|
|
377
|
+
}) as unknown,
|
|
378
|
+
owneryQuery,
|
|
379
|
+
options,
|
|
380
|
+
name,
|
|
381
|
+
);
|
|
382
|
+
},
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
export type Options = {
|
|
386
|
+
operations: {
|
|
387
|
+
// Heterogeneous registry: each operator returns its own Operation subtype.
|
|
388
|
+
// deno-lint-ignore no-explicit-any
|
|
389
|
+
[identifier: string]: OperationCreator<any>;
|
|
390
|
+
};
|
|
391
|
+
compare: (a: unknown, b: unknown) => boolean;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const createNamedOperation = (
|
|
395
|
+
name: string,
|
|
396
|
+
params: unknown,
|
|
397
|
+
parentQuery: unknown,
|
|
398
|
+
options: Options,
|
|
399
|
+
): Operation<unknown> | null => {
|
|
400
|
+
const operationCreator = options.operations[name];
|
|
401
|
+
if (!operationCreator) {
|
|
402
|
+
throwUnsupportedOperation(name);
|
|
403
|
+
}
|
|
404
|
+
return operationCreator(params, parentQuery, options, name);
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const throwUnsupportedOperation = (name: string): never => {
|
|
408
|
+
throw new Error(`Unsupported operation: ${name}`);
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
export const containsOperation = (
|
|
412
|
+
query: unknown,
|
|
413
|
+
options: Options,
|
|
414
|
+
): boolean => {
|
|
415
|
+
if (query == null || typeof query !== "object") return false;
|
|
416
|
+
for (const key in query as object) {
|
|
417
|
+
if (
|
|
418
|
+
Object.prototype.hasOwnProperty.call(options.operations, key) ||
|
|
419
|
+
key.charAt(0) === "$"
|
|
420
|
+
) {
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const createNestedOperation = (
|
|
428
|
+
keyPath: Key[],
|
|
429
|
+
nestedQuery: unknown,
|
|
430
|
+
parentKey: string,
|
|
431
|
+
owneryQuery: unknown,
|
|
432
|
+
options: Options,
|
|
433
|
+
): NestedOperation => {
|
|
434
|
+
if (containsOperation(nestedQuery, options)) {
|
|
435
|
+
const [selfOperations, nestedOperations] = createQueryOperations(
|
|
436
|
+
nestedQuery,
|
|
437
|
+
parentKey,
|
|
438
|
+
options,
|
|
439
|
+
);
|
|
440
|
+
if (nestedOperations.length) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`Property queries must contain only operations, or exact objects.`,
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
return new NestedOperation(
|
|
446
|
+
keyPath,
|
|
447
|
+
nestedQuery,
|
|
448
|
+
owneryQuery,
|
|
449
|
+
options,
|
|
450
|
+
selfOperations,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
return new NestedOperation(keyPath, nestedQuery, owneryQuery, options, [
|
|
454
|
+
new EqualsOperation(nestedQuery, owneryQuery, options),
|
|
455
|
+
]);
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
export const createQueryOperation = <TItem, TSchema = TItem>(
|
|
459
|
+
query: Query<TSchema>,
|
|
460
|
+
owneryQuery: unknown = null,
|
|
461
|
+
{ compare, operations }: Partial<Options> = {},
|
|
462
|
+
): QueryOperation<TItem> => {
|
|
463
|
+
const options: Options = {
|
|
464
|
+
compare: compare || equals,
|
|
465
|
+
operations: Object.assign({}, operations || {}),
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const [selfOperations, nestedOperations] = createQueryOperations(
|
|
469
|
+
query,
|
|
470
|
+
null,
|
|
471
|
+
options,
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
const ops: Operation<unknown>[] = [];
|
|
475
|
+
|
|
476
|
+
if (selfOperations.length) {
|
|
477
|
+
ops.push(
|
|
478
|
+
new NestedOperation([], query, owneryQuery, options, selfOperations),
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
ops.push(...nestedOperations);
|
|
483
|
+
|
|
484
|
+
if (ops.length === 1) {
|
|
485
|
+
return ops[0] as QueryOperation<TItem>;
|
|
486
|
+
}
|
|
487
|
+
return new QueryOperation<TItem>(query, owneryQuery, options, ops);
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const createQueryOperations = (
|
|
491
|
+
query: unknown,
|
|
492
|
+
parentKey: string | null,
|
|
493
|
+
options: Options,
|
|
494
|
+
): [Operation<unknown>[], NestedOperation[]] => {
|
|
495
|
+
const selfOperations: Operation<unknown>[] = [];
|
|
496
|
+
const nestedOperations: NestedOperation[] = [];
|
|
497
|
+
if (!isVanillaObject(query)) {
|
|
498
|
+
selfOperations.push(new EqualsOperation(query, query, options));
|
|
499
|
+
return [selfOperations, nestedOperations];
|
|
500
|
+
}
|
|
501
|
+
const queryObj = query as Record<string, unknown>;
|
|
502
|
+
for (const key in queryObj) {
|
|
503
|
+
if (Object.prototype.hasOwnProperty.call(options.operations, key)) {
|
|
504
|
+
const op = createNamedOperation(key, queryObj[key], queryObj, options);
|
|
505
|
+
|
|
506
|
+
if (op) {
|
|
507
|
+
if (!op.propop && parentKey && !options.operations[parentKey]) {
|
|
508
|
+
throw new Error(
|
|
509
|
+
`Malformed query. ${key} cannot be matched against property.`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// A null op may just be a flag for another operation (like $options).
|
|
515
|
+
if (op != null) {
|
|
516
|
+
selfOperations.push(op);
|
|
517
|
+
}
|
|
518
|
+
} else if (key.charAt(0) === "$") {
|
|
519
|
+
throwUnsupportedOperation(key);
|
|
520
|
+
} else {
|
|
521
|
+
nestedOperations.push(
|
|
522
|
+
createNestedOperation(
|
|
523
|
+
key.split("."),
|
|
524
|
+
queryObj[key],
|
|
525
|
+
key,
|
|
526
|
+
queryObj,
|
|
527
|
+
options,
|
|
528
|
+
),
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return [selfOperations, nestedOperations];
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
export const createOperationTester =
|
|
537
|
+
<TItem>(operation: Operation<TItem>) =>
|
|
538
|
+
(item: TItem, key?: Key, owner?: unknown): boolean => {
|
|
539
|
+
operation.reset();
|
|
540
|
+
operation.next(item, key, owner);
|
|
541
|
+
return operation.keep;
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
export const createQueryTester = <TItem, TSchema = TItem>(
|
|
545
|
+
query: Query<TSchema>,
|
|
546
|
+
options: Partial<Options> = {},
|
|
547
|
+
): ((item: TItem, key?: Key, owner?: unknown) => boolean) => {
|
|
548
|
+
return createOperationTester(
|
|
549
|
+
createQueryOperation<TItem, TSchema>(query, null, options),
|
|
550
|
+
);
|
|
551
|
+
};
|
package/sift/index.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Vendored from sift.js (https://github.com/crcn/sift.js) — MIT licensed.
|
|
2
|
+
// `$where` operator removed (security: arbitrary code execution).
|
|
3
|
+
|
|
4
|
+
import * as defaultOperations from "./operations.ts";
|
|
5
|
+
import {
|
|
6
|
+
createEqualsOperation,
|
|
7
|
+
createOperationTester,
|
|
8
|
+
createQueryOperation,
|
|
9
|
+
createQueryTester,
|
|
10
|
+
EqualsOperation,
|
|
11
|
+
type Options,
|
|
12
|
+
} from "./core.ts";
|
|
13
|
+
import type {
|
|
14
|
+
ArrayValueQuery,
|
|
15
|
+
BasicValueQuery,
|
|
16
|
+
NestedQuery,
|
|
17
|
+
Query,
|
|
18
|
+
QueryOperators,
|
|
19
|
+
ShapeQuery,
|
|
20
|
+
ValueQuery,
|
|
21
|
+
} from "./core.ts";
|
|
22
|
+
import type { Key } from "./utils.ts";
|
|
23
|
+
|
|
24
|
+
const createDefaultQueryOperation = <TItem, TSchema extends TItem = TItem>(
|
|
25
|
+
query: Query<TSchema>,
|
|
26
|
+
ownerQuery: unknown,
|
|
27
|
+
{ compare, operations }: Partial<Options> = {},
|
|
28
|
+
) => {
|
|
29
|
+
return createQueryOperation<TItem, TSchema>(query, ownerQuery, {
|
|
30
|
+
compare,
|
|
31
|
+
operations: Object.assign({}, defaultOperations, operations || {}),
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const createDefaultQueryTester = <TItem, TSchema extends TItem = TItem>(
|
|
36
|
+
query: Query<TSchema>,
|
|
37
|
+
options: Partial<Options> = {},
|
|
38
|
+
): ((item: TItem, key?: Key, owner?: unknown) => boolean) => {
|
|
39
|
+
const op = createDefaultQueryOperation<TItem, TSchema>(query, null, options);
|
|
40
|
+
return createOperationTester(op);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
createDefaultQueryOperation,
|
|
45
|
+
createEqualsOperation,
|
|
46
|
+
createOperationTester,
|
|
47
|
+
createQueryOperation,
|
|
48
|
+
createQueryTester,
|
|
49
|
+
EqualsOperation,
|
|
50
|
+
};
|
|
51
|
+
export type {
|
|
52
|
+
ArrayValueQuery,
|
|
53
|
+
BasicValueQuery,
|
|
54
|
+
NestedQuery,
|
|
55
|
+
Query,
|
|
56
|
+
QueryOperators,
|
|
57
|
+
ShapeQuery,
|
|
58
|
+
ValueQuery,
|
|
59
|
+
};
|
|
60
|
+
export * from "./operations.ts";
|
|
61
|
+
|
|
62
|
+
export default createDefaultQueryTester;
|