@wxn0brp/db-core 0.12.11 → 0.12.13
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/customFileCpu.js +9 -1
- package/dist/db/multiStorage.js +15 -24
- package/dist/db/transaction.d.ts +3 -3
- package/dist/db/transaction.js +5 -5
- package/dist/db/valthera.d.ts +5 -5
- package/dist/db/valthera.js +47 -29
- package/dist/helpers/addId.js +12 -0
- package/dist/helpers/assignDataPush.js +7 -1
- package/dist/helpers/collection.d.ts +1 -0
- package/dist/helpers/collection.js +3 -0
- package/dist/helpers/forge.js +15 -2
- package/dist/helpers/plugin.d.ts +2 -0
- package/dist/helpers/plugin.js +9 -0
- package/dist/helpers/relation.js +20 -7
- package/dist/types/plugin.d.ts +5 -0
- package/dist/types/query.d.ts +3 -0
- package/dist/types/valthera.d.ts +3 -3
- package/dist/utils/action.js +31 -15
- package/dist/utils/hasFields.js +1 -1
- package/dist/utils/hasFieldsAdvanced.js +16 -9
- package/dist/utils/merge.js +7 -1
- package/dist/utils/opKind.d.ts +9 -0
- package/dist/utils/opKind.js +48 -0
- package/dist/utils/security.d.ts +2 -0
- package/dist/utils/security.js +11 -0
- package/dist/utils/updateFindObject.js +9 -4
- package/dist/utils/updateObject.js +29 -12
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/customFileCpu.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { QueryValidationError } from "./errors.js";
|
|
1
2
|
import { matchObj, updateObj } from "./utils/process.js";
|
|
2
3
|
import { updateFindObject } from "./utils/updateFindObject.js";
|
|
3
4
|
export function pathRepair(path) {
|
|
4
|
-
|
|
5
|
+
const repaired = path.replace(/\/+/g, "/");
|
|
6
|
+
if (repaired.split(/[/\\]+/).includes("..")) {
|
|
7
|
+
throw new QueryValidationError("Path traversal is not allowed", {
|
|
8
|
+
op: "pathRepair",
|
|
9
|
+
path,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return repaired;
|
|
5
13
|
}
|
|
6
14
|
export class CustomFileCpu {
|
|
7
15
|
requireClone;
|
package/dist/db/multiStorage.js
CHANGED
|
@@ -33,15 +33,12 @@ export class MultiBackend extends ActionsBase {
|
|
|
33
33
|
}
|
|
34
34
|
async findOne(config) {
|
|
35
35
|
for (const backend of this.backends) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
36
|
+
const result = await backend.findOne({
|
|
37
|
+
...config,
|
|
38
|
+
});
|
|
39
|
+
if (result !== null && result !== undefined) {
|
|
40
|
+
return result;
|
|
43
41
|
}
|
|
44
|
-
catch { }
|
|
45
42
|
}
|
|
46
43
|
return null;
|
|
47
44
|
}
|
|
@@ -53,14 +50,11 @@ export class MultiBackend extends ActionsBase {
|
|
|
53
50
|
}
|
|
54
51
|
async updateOne(config) {
|
|
55
52
|
for (const backend of this.backends) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
catch (error) { }
|
|
53
|
+
const result = await backend.updateOne({
|
|
54
|
+
...config,
|
|
55
|
+
});
|
|
56
|
+
if (result)
|
|
57
|
+
return result;
|
|
64
58
|
}
|
|
65
59
|
return null;
|
|
66
60
|
}
|
|
@@ -72,14 +66,11 @@ export class MultiBackend extends ActionsBase {
|
|
|
72
66
|
}
|
|
73
67
|
async removeOne(config) {
|
|
74
68
|
for (const backend of this.backends) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
catch (error) { }
|
|
69
|
+
const result = await backend.removeOne({
|
|
70
|
+
...config,
|
|
71
|
+
});
|
|
72
|
+
if (result)
|
|
73
|
+
return result;
|
|
83
74
|
}
|
|
84
75
|
return null;
|
|
85
76
|
}
|
package/dist/db/transaction.d.ts
CHANGED
|
@@ -23,10 +23,10 @@ export declare class Transaction {
|
|
|
23
23
|
updateOneOrAdd<T = Data>(q: VQueryT.UpdateOneOrAdd<T>): Promise<VQueryT.UpdateOneOrAddResult<T>>;
|
|
24
24
|
toggleOne<T = Data>(q: VQueryT.ToggleOne<T>): Promise<VQueryT.ToggleOneResult<T>>;
|
|
25
25
|
count<T = Data>(q: VQueryT.Count<T>): Promise<number>;
|
|
26
|
-
ensureCollection(collection: string): Promise<boolean>;
|
|
27
|
-
issetCollection(collection: string): Promise<boolean>;
|
|
26
|
+
ensureCollection(collection: string | VQueryT.Collection): Promise<boolean>;
|
|
27
|
+
issetCollection(collection: string | VQueryT.Collection): Promise<boolean>;
|
|
28
28
|
getCollections(): Promise<string[]>;
|
|
29
|
-
removeCollection(
|
|
29
|
+
removeCollection(target: string | VQueryT.Collection): Promise<boolean>;
|
|
30
30
|
commit(): Promise<void>;
|
|
31
31
|
rollback(): Promise<void>;
|
|
32
32
|
}
|
package/dist/db/transaction.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { wrapAdapterError } from "../errors.js";
|
|
2
|
-
import { Collection } from "../helpers/collection.js";
|
|
2
|
+
import { Collection, resolveCollection } from "../helpers/collection.js";
|
|
3
3
|
import { applyAddDefaults, applyBulkAddDefaults, applyCountDefaults, applyFindDefaults, applyFindOneDefaults, applyRemoveDefaults, applyToggleOneDefaults, applyUpdateDefaults, applyUpdateOneOrAddDefaults, } from "../helpers/queryDefaults.js";
|
|
4
4
|
/**
|
|
5
5
|
* Transaction context. Operations are executed directly through the parent
|
|
@@ -60,16 +60,16 @@ export class Transaction {
|
|
|
60
60
|
return this._parent.execute("count", q, this.handle);
|
|
61
61
|
}
|
|
62
62
|
ensureCollection(collection) {
|
|
63
|
-
return this._parent.execute("ensureCollection", collection, this.handle);
|
|
63
|
+
return this._parent.execute("ensureCollection", resolveCollection(collection), this.handle);
|
|
64
64
|
}
|
|
65
65
|
issetCollection(collection) {
|
|
66
|
-
return this._parent.execute("issetCollection", collection, this.handle);
|
|
66
|
+
return this._parent.execute("issetCollection", resolveCollection(collection), this.handle);
|
|
67
67
|
}
|
|
68
68
|
getCollections() {
|
|
69
69
|
return this._parent.execute("getCollections", {}, this.handle);
|
|
70
70
|
}
|
|
71
|
-
removeCollection(
|
|
72
|
-
return this._parent.execute("removeCollection",
|
|
71
|
+
removeCollection(target) {
|
|
72
|
+
return this._parent.execute("removeCollection", resolveCollection(target), this.handle);
|
|
73
73
|
}
|
|
74
74
|
async commit() {
|
|
75
75
|
try {
|
package/dist/db/valthera.d.ts
CHANGED
|
@@ -20,9 +20,9 @@ export declare class ValtheraClass implements ValtheraCompatible {
|
|
|
20
20
|
adapter: ActionsBase;
|
|
21
21
|
executor: ExecutorInterface;
|
|
22
22
|
emitter: VEE<{
|
|
23
|
-
[K in keyof Omit<ValtheraCompatible, "c">]: (query: VQuery, result: Awaited<ReturnType<ValtheraCompatible[K]
|
|
23
|
+
[K in keyof Omit<ValtheraCompatible, "c">]: (query: VQuery, result: Awaited<ReturnType<ValtheraCompatible[K]>>, collection?: string) => void;
|
|
24
24
|
} & {
|
|
25
|
-
"*": (name: keyof Omit<ValtheraCompatible, "c">, query: VQuery, result: any) => void;
|
|
25
|
+
"*": (name: keyof Omit<ValtheraCompatible, "c">, query: VQuery, result: any, collection?: string) => void;
|
|
26
26
|
}>;
|
|
27
27
|
version: string;
|
|
28
28
|
_plugins: ValtheraPlugin[];
|
|
@@ -43,11 +43,11 @@ export declare class ValtheraClass implements ValtheraCompatible {
|
|
|
43
43
|
/**
|
|
44
44
|
* Check and create the specified collection if it doesn't exist.
|
|
45
45
|
*/
|
|
46
|
-
ensureCollection(collection: string): Promise<boolean>;
|
|
46
|
+
ensureCollection(collection: string | VQueryT.Collection): Promise<boolean>;
|
|
47
47
|
/**
|
|
48
48
|
* Check if a collection exists.
|
|
49
49
|
*/
|
|
50
|
-
issetCollection(collection: string): Promise<boolean>;
|
|
50
|
+
issetCollection(collection: string | VQueryT.Collection): Promise<boolean>;
|
|
51
51
|
/**
|
|
52
52
|
* Add data to a database.
|
|
53
53
|
*/
|
|
@@ -88,7 +88,7 @@ export declare class ValtheraClass implements ValtheraCompatible {
|
|
|
88
88
|
/**
|
|
89
89
|
* Removes a database collection from the database.
|
|
90
90
|
*/
|
|
91
|
-
removeCollection(
|
|
91
|
+
removeCollection(target: string | VQueryT.Collection): Promise<boolean>;
|
|
92
92
|
/**
|
|
93
93
|
* Create an index on a collection.
|
|
94
94
|
*/
|
package/dist/db/valthera.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { VEE } from "@wxn0brp/event-emitter";
|
|
2
|
-
import
|
|
3
|
-
import { Collection } from "../helpers/collection.js";
|
|
2
|
+
import * as errors from "../errors.js";
|
|
3
|
+
import { Collection, resolveCollection } from "../helpers/collection.js";
|
|
4
4
|
import { SmartExecutor } from "../helpers/executor.js";
|
|
5
5
|
import { genId } from "../helpers/gen.js";
|
|
6
|
-
import {
|
|
6
|
+
import { rewriteOp } from "../helpers/plugin.js";
|
|
7
|
+
import * as queryDefaults from "../helpers/queryDefaults.js";
|
|
8
|
+
import { getOpKind, isKnownOp } from "../utils/opKind.js";
|
|
7
9
|
import { version } from "../version.js";
|
|
8
10
|
import { Transaction } from "./transaction.js";
|
|
9
11
|
/**
|
|
@@ -50,9 +52,9 @@ export class ValtheraClass {
|
|
|
50
52
|
return;
|
|
51
53
|
const self = this;
|
|
52
54
|
const failInit = (err) => {
|
|
53
|
-
if (isValtheraError(err))
|
|
55
|
+
if (errors.isValtheraError(err))
|
|
54
56
|
throw err;
|
|
55
|
-
throw new AdapterInitError(err, {
|
|
57
|
+
throw new errors.AdapterInitError(err, {
|
|
56
58
|
adapter: self.adapter?.version,
|
|
57
59
|
});
|
|
58
60
|
};
|
|
@@ -99,9 +101,9 @@ export class ValtheraClass {
|
|
|
99
101
|
await self.adapter.close(...args);
|
|
100
102
|
}
|
|
101
103
|
catch (err) {
|
|
102
|
-
if (isValtheraError(err))
|
|
104
|
+
if (errors.isValtheraError(err))
|
|
103
105
|
throw err;
|
|
104
|
-
throw new AdapterCloseError(err, {
|
|
106
|
+
throw new errors.AdapterCloseError(err, {
|
|
105
107
|
adapter: self.adapter?.version,
|
|
106
108
|
});
|
|
107
109
|
}
|
|
@@ -112,7 +114,7 @@ export class ValtheraClass {
|
|
|
112
114
|
await this.init();
|
|
113
115
|
const collection = typeof query === "string" ? query : query?.collection;
|
|
114
116
|
if (!collection && name !== "getCollections")
|
|
115
|
-
throw new QueryValidationError(`Missing "collection" for operation "${String(name)}"`, {
|
|
117
|
+
throw new errors.QueryValidationError(`Missing "collection" for operation "${String(name)}"`, {
|
|
116
118
|
op: name,
|
|
117
119
|
});
|
|
118
120
|
if (txHandle && query && typeof query === "object")
|
|
@@ -122,26 +124,41 @@ export class ValtheraClass {
|
|
|
122
124
|
let idx = 0;
|
|
123
125
|
const ctx = {
|
|
124
126
|
op: name,
|
|
127
|
+
kind: getOpKind(name),
|
|
128
|
+
collection,
|
|
125
129
|
query,
|
|
126
130
|
next: async () => {
|
|
127
131
|
if (idx < plugins.length)
|
|
128
132
|
return plugins[idx++].execute(ctx);
|
|
133
|
+
if (txHandle && ctx.query && typeof ctx.query === "object")
|
|
134
|
+
ctx.query.transaction = txHandle;
|
|
135
|
+
if (!isKnownOp(ctx.op))
|
|
136
|
+
throw new errors.QueryValidationError(`Unknown operation "${ctx.op}"`, {
|
|
137
|
+
op: ctx.op,
|
|
138
|
+
});
|
|
129
139
|
try {
|
|
130
140
|
if (txHandle)
|
|
131
|
-
return await self.adapter[ctx.op](query, txHandle);
|
|
141
|
+
return await self.adapter[ctx.op](ctx.query, txHandle);
|
|
132
142
|
return await self.executor.addOp(self.adapter[ctx.op].bind(self.adapter), ctx.query, typeof ctx.query === "string" ? ctx.query : ctx.query.collection);
|
|
133
143
|
}
|
|
134
144
|
catch (err) {
|
|
135
|
-
throw wrapAdapterError(err, {
|
|
145
|
+
throw errors.wrapAdapterError(err, {
|
|
136
146
|
op: ctx.op,
|
|
137
|
-
collection,
|
|
147
|
+
collection: typeof ctx.query === "string" ? ctx.query : ctx.query?.collection,
|
|
138
148
|
adapter: self.adapter?.version,
|
|
139
149
|
});
|
|
140
150
|
}
|
|
141
151
|
},
|
|
152
|
+
setOp(op, q) {
|
|
153
|
+
rewriteOp(ctx, op, q);
|
|
154
|
+
},
|
|
155
|
+
setQuery(q) {
|
|
156
|
+
rewriteOp(ctx, ctx.op, q);
|
|
157
|
+
},
|
|
142
158
|
};
|
|
143
159
|
const result = await ctx.next();
|
|
144
|
-
|
|
160
|
+
const emittedCollection = typeof ctx.query === "string" ? ctx.query : ctx.query?.collection;
|
|
161
|
+
this.emitter.emit(ctx.op, ctx.query, result, emittedCollection);
|
|
145
162
|
return result;
|
|
146
163
|
}
|
|
147
164
|
/**
|
|
@@ -164,85 +181,86 @@ export class ValtheraClass {
|
|
|
164
181
|
* Check and create the specified collection if it doesn't exist.
|
|
165
182
|
*/
|
|
166
183
|
async ensureCollection(collection) {
|
|
167
|
-
return await this.execute("ensureCollection", collection);
|
|
184
|
+
return await this.execute("ensureCollection", resolveCollection(collection));
|
|
168
185
|
}
|
|
169
186
|
/**
|
|
170
187
|
* Check if a collection exists.
|
|
171
188
|
*/
|
|
172
189
|
async issetCollection(collection) {
|
|
173
|
-
return await this.execute("issetCollection", collection);
|
|
190
|
+
return await this.execute("issetCollection", resolveCollection(collection));
|
|
174
191
|
}
|
|
175
192
|
/**
|
|
176
193
|
* Add data to a database.
|
|
177
194
|
*/
|
|
178
195
|
add(query) {
|
|
179
|
-
applyAddDefaults(query);
|
|
196
|
+
queryDefaults.applyAddDefaults(query);
|
|
180
197
|
return this.execute("add", query);
|
|
181
198
|
}
|
|
182
199
|
bulkAdd(query) {
|
|
183
|
-
applyBulkAddDefaults(query);
|
|
200
|
+
queryDefaults.applyBulkAddDefaults(query);
|
|
184
201
|
return this.execute("bulkAdd", query);
|
|
185
202
|
}
|
|
186
203
|
/**
|
|
187
204
|
* Find data in a database.
|
|
188
205
|
*/
|
|
189
206
|
find(query) {
|
|
190
|
-
applyFindDefaults(query);
|
|
207
|
+
queryDefaults.applyFindDefaults(query);
|
|
191
208
|
return this.execute("find", query);
|
|
192
209
|
}
|
|
193
210
|
/**
|
|
194
211
|
* Find one data entry in a database.
|
|
195
212
|
*/
|
|
196
213
|
findOne(query) {
|
|
197
|
-
applyFindOneDefaults(query);
|
|
214
|
+
queryDefaults.applyFindOneDefaults(query);
|
|
198
215
|
return this.execute("findOne", query);
|
|
199
216
|
}
|
|
200
217
|
/**
|
|
201
218
|
* Update data in a database.
|
|
202
219
|
*/
|
|
203
220
|
update(query) {
|
|
204
|
-
applyUpdateDefaults(query);
|
|
221
|
+
queryDefaults.applyUpdateDefaults(query);
|
|
205
222
|
return this.execute("update", query);
|
|
206
223
|
}
|
|
207
224
|
/**
|
|
208
225
|
* Update one data entry in a database.
|
|
209
226
|
*/
|
|
210
227
|
updateOne(query) {
|
|
211
|
-
applyUpdateDefaults(query);
|
|
228
|
+
queryDefaults.applyUpdateDefaults(query);
|
|
212
229
|
return this.execute("updateOne", query);
|
|
213
230
|
}
|
|
214
231
|
/**
|
|
215
232
|
* Remove data from a database.
|
|
216
233
|
*/
|
|
217
234
|
remove(query) {
|
|
218
|
-
applyRemoveDefaults(query);
|
|
235
|
+
queryDefaults.applyRemoveDefaults(query);
|
|
219
236
|
return this.execute("remove", query);
|
|
220
237
|
}
|
|
221
238
|
/**
|
|
222
239
|
* Remove one data entry from a database.
|
|
223
240
|
*/
|
|
224
241
|
removeOne(query) {
|
|
225
|
-
applyRemoveDefaults(query);
|
|
242
|
+
queryDefaults.applyRemoveDefaults(query);
|
|
226
243
|
return this.execute("removeOne", query);
|
|
227
244
|
}
|
|
228
245
|
/**
|
|
229
246
|
* Asynchronously updates one entry in a database or adds a new one if it doesn't exist.
|
|
230
247
|
*/
|
|
231
248
|
updateOneOrAdd(query) {
|
|
232
|
-
applyUpdateOneOrAddDefaults(query);
|
|
249
|
+
queryDefaults.applyUpdateOneOrAddDefaults(query);
|
|
233
250
|
return this.execute("updateOneOrAdd", query);
|
|
234
251
|
}
|
|
235
252
|
/**
|
|
236
253
|
* Asynchronously removes one entry in a database or adds a new one if it doesn't exist. Usage e.g. for toggling a flag.
|
|
237
254
|
*/
|
|
238
255
|
toggleOne(query) {
|
|
239
|
-
applyToggleOneDefaults(query);
|
|
256
|
+
queryDefaults.applyToggleOneDefaults(query);
|
|
240
257
|
return this.execute("toggleOne", query);
|
|
241
258
|
}
|
|
242
259
|
/**
|
|
243
260
|
* Removes a database collection from the database.
|
|
244
261
|
*/
|
|
245
|
-
removeCollection(
|
|
262
|
+
removeCollection(target) {
|
|
263
|
+
const collection = resolveCollection(target);
|
|
246
264
|
this._collections.delete(collection);
|
|
247
265
|
return this.execute("removeCollection", collection);
|
|
248
266
|
}
|
|
@@ -274,7 +292,7 @@ export class ValtheraClass {
|
|
|
274
292
|
});
|
|
275
293
|
}
|
|
276
294
|
count(query) {
|
|
277
|
-
applyCountDefaults(query);
|
|
295
|
+
queryDefaults.applyCountDefaults(query);
|
|
278
296
|
return this.execute("count", query);
|
|
279
297
|
}
|
|
280
298
|
/**
|
|
@@ -319,7 +337,7 @@ export class ValtheraClass {
|
|
|
319
337
|
handle = await this.adapter.beginTransaction(genId(), collections);
|
|
320
338
|
}
|
|
321
339
|
catch (err) {
|
|
322
|
-
throw wrapAdapterError(err, {
|
|
340
|
+
throw errors.wrapAdapterError(err, {
|
|
323
341
|
op: "beginTransaction",
|
|
324
342
|
adapter: this.adapter?.version,
|
|
325
343
|
});
|
|
@@ -335,8 +353,8 @@ export class ValtheraClass {
|
|
|
335
353
|
await tx.rollback();
|
|
336
354
|
}
|
|
337
355
|
catch (rollbackErr) {
|
|
338
|
-
throw new TransactionError("Transaction failed and rollback failed", {
|
|
339
|
-
code: ErrorCodes.TRANSACTION_ROLLBACK_FAILED,
|
|
356
|
+
throw new errors.TransactionError("Transaction failed and rollback failed", {
|
|
357
|
+
code: errors.ErrorCodes.TRANSACTION_ROLLBACK_FAILED,
|
|
340
358
|
cause: new AggregateError([
|
|
341
359
|
err,
|
|
342
360
|
rollbackErr,
|
package/dist/helpers/addId.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
import { QueryValidationError } from "../errors.js";
|
|
2
|
+
import { isUnsafeKey } from "../utils/security.js";
|
|
1
3
|
import { genId } from "./gen.js";
|
|
4
|
+
function assertSafeIdKey(idKey) {
|
|
5
|
+
if (!isUnsafeKey(idKey))
|
|
6
|
+
return;
|
|
7
|
+
throw new QueryValidationError("Unsafe idKey", {
|
|
8
|
+
op: "addId",
|
|
9
|
+
idKey,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
2
12
|
export async function addId(query, actions, defaultGen = true) {
|
|
3
13
|
const collection = query.collection;
|
|
4
14
|
const data = query.data;
|
|
@@ -6,6 +16,7 @@ export async function addId(query, actions, defaultGen = true) {
|
|
|
6
16
|
const { idKey = "_id", numberId } = actions.adapterOpts;
|
|
7
17
|
if (!id_gen)
|
|
8
18
|
return data;
|
|
19
|
+
assertSafeIdKey(idKey);
|
|
9
20
|
if (data[idKey])
|
|
10
21
|
return data;
|
|
11
22
|
if (!numberId) {
|
|
@@ -45,6 +56,7 @@ export async function addIdBulk(query, actions, defaultGen = true) {
|
|
|
45
56
|
const { idKey = "_id", numberId } = actions.adapterOpts;
|
|
46
57
|
if (!id_gen)
|
|
47
58
|
return;
|
|
59
|
+
assertSafeIdKey(idKey);
|
|
48
60
|
if (!numberId) {
|
|
49
61
|
for (const data of query.datas) {
|
|
50
62
|
if (!data[idKey])
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isUnsafeKey } from "../utils/security.js";
|
|
1
2
|
export function assignDataPush(data) {
|
|
2
3
|
if (!data)
|
|
3
4
|
return {};
|
|
@@ -7,6 +8,8 @@ export function assignDataPush(data) {
|
|
|
7
8
|
return {};
|
|
8
9
|
const obj = {};
|
|
9
10
|
for (const key of Object.keys(data)) {
|
|
11
|
+
if (isUnsafeKey(key))
|
|
12
|
+
continue;
|
|
10
13
|
if (!key.startsWith("$")) {
|
|
11
14
|
obj[key] = data[key];
|
|
12
15
|
continue;
|
|
@@ -14,7 +17,10 @@ export function assignDataPush(data) {
|
|
|
14
17
|
const dk = data[key];
|
|
15
18
|
if (Array.isArray(dk))
|
|
16
19
|
continue;
|
|
17
|
-
Object.keys(dk).forEach(k =>
|
|
20
|
+
Object.keys(dk).forEach(k => {
|
|
21
|
+
if (!isUnsafeKey(k))
|
|
22
|
+
obj[k] = dk[k];
|
|
23
|
+
});
|
|
18
24
|
}
|
|
19
25
|
return obj;
|
|
20
26
|
}
|
|
@@ -6,6 +6,7 @@ import { DbFindOpts, FindOpts } from "../types/options.js";
|
|
|
6
6
|
import { VQueryT } from "../types/query.js";
|
|
7
7
|
import { VContext } from "../types/types.js";
|
|
8
8
|
import { ValtheraCompatible } from "../types/valthera.js";
|
|
9
|
+
export declare function resolveCollection(target: string | VQueryT.Collection): string;
|
|
9
10
|
export declare class Collection<D = Data> {
|
|
10
11
|
db: ValtheraCompatible;
|
|
11
12
|
collection: string;
|
package/dist/helpers/forge.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import { isUnsafeKey } from "../utils/security.js";
|
|
2
|
+
function hasApiProperty(target, prop) {
|
|
3
|
+
if (isUnsafeKey(prop))
|
|
4
|
+
return false;
|
|
5
|
+
let current = target;
|
|
6
|
+
while (current && current !== Object.prototype) {
|
|
7
|
+
if (Object.hasOwn(current, prop))
|
|
8
|
+
return true;
|
|
9
|
+
current = Object.getPrototypeOf(current);
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
1
13
|
export function forgeTypedValthera(target) {
|
|
2
14
|
return new Proxy(target, {
|
|
3
15
|
get(target, prop, receiver) {
|
|
4
|
-
if (prop
|
|
16
|
+
if (typeof prop === "symbol")
|
|
17
|
+
return Reflect.get(target, prop, receiver);
|
|
18
|
+
if (hasApiProperty(target, prop))
|
|
5
19
|
return Reflect.get(target, prop, receiver);
|
|
6
|
-
}
|
|
7
20
|
return target.c(prop);
|
|
8
21
|
},
|
|
9
22
|
set(target, prop, value, receiver) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getOpKind } from "../utils/opKind.js";
|
|
2
|
+
export function rewriteOp(ctx, op, query) {
|
|
3
|
+
ctx.op = op;
|
|
4
|
+
if (query !== undefined)
|
|
5
|
+
ctx.query = query;
|
|
6
|
+
ctx.kind = getOpKind(op);
|
|
7
|
+
const q = ctx.query;
|
|
8
|
+
ctx.collection = typeof q === "string" ? q : q?.collection;
|
|
9
|
+
}
|
package/dist/helpers/relation.js
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
import { QueryValidationError } from "../errors.js";
|
|
2
|
+
import { hasUnsafeSegment, isUnsafeKey } from "../utils/security.js";
|
|
2
3
|
export function pickByPath(obj, paths) {
|
|
3
4
|
const result = {};
|
|
4
5
|
for (const path of paths) {
|
|
6
|
+
if (hasUnsafeSegment(path))
|
|
7
|
+
continue;
|
|
5
8
|
let src = obj;
|
|
6
9
|
let dst = result;
|
|
7
10
|
for (let i = 0; i < path.length; i++) {
|
|
8
11
|
const k = path[i];
|
|
9
12
|
if (src == null)
|
|
10
13
|
break;
|
|
14
|
+
const isOwn = Object.hasOwn(src, k);
|
|
15
|
+
if (!isOwn && k in Object(src))
|
|
16
|
+
break;
|
|
11
17
|
if (i === path.length - 1) {
|
|
12
18
|
dst[k] = src[k];
|
|
19
|
+
break;
|
|
13
20
|
}
|
|
14
|
-
|
|
15
|
-
dst[k]
|
|
16
|
-
dst
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
if (!Object.hasOwn(dst, k) ||
|
|
22
|
+
typeof dst[k] !== "object" ||
|
|
23
|
+
dst[k] === null ||
|
|
24
|
+
Array.isArray(dst[k]))
|
|
25
|
+
dst[k] = {};
|
|
26
|
+
if (!isOwn)
|
|
27
|
+
break;
|
|
28
|
+
dst = dst[k];
|
|
29
|
+
src = src[k];
|
|
19
30
|
}
|
|
20
31
|
}
|
|
21
32
|
return result;
|
|
@@ -71,10 +82,12 @@ export async function processRelations(dbs, cfg, data, parentList = null) {
|
|
|
71
82
|
if (targets.length === 0)
|
|
72
83
|
return;
|
|
73
84
|
for (const key in cfg) {
|
|
74
|
-
if (!Object.hasOwn(cfg, key))
|
|
85
|
+
if (!Object.hasOwn(cfg, key) || isUnsafeKey(key))
|
|
75
86
|
continue;
|
|
76
87
|
const rel = cfg[key];
|
|
77
88
|
const { pk = "_id", fk = "_id", type = "1", path, as = key, select, dbFindOpts = {}, through, } = rel;
|
|
89
|
+
if (isUnsafeKey(as))
|
|
90
|
+
continue;
|
|
78
91
|
const [dbKey, collection] = path;
|
|
79
92
|
const db = dbs[dbKey];
|
|
80
93
|
if (type === "1") {
|
|
@@ -150,7 +163,7 @@ export async function processRelations(dbs, cfg, data, parentList = null) {
|
|
|
150
163
|
acc[id] ||= [];
|
|
151
164
|
acc[id].push(row);
|
|
152
165
|
return acc;
|
|
153
|
-
},
|
|
166
|
+
}, Object.create(null));
|
|
154
167
|
for (const item of targets)
|
|
155
168
|
item[as] = grouped[item[pk]] || [];
|
|
156
169
|
if (rel.relations)
|
package/dist/types/plugin.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { ValtheraClass } from "../db/valthera.js";
|
|
2
|
+
import type { PluginOpKind } from "../utils/opKind.js";
|
|
2
3
|
export interface PluginContext {
|
|
3
4
|
op: string;
|
|
5
|
+
kind: PluginOpKind;
|
|
6
|
+
collection?: string;
|
|
4
7
|
query: any;
|
|
5
8
|
next: () => Promise<any>;
|
|
9
|
+
setOp(op: string, query?: any): void;
|
|
10
|
+
setQuery(query: any): void;
|
|
6
11
|
}
|
|
7
12
|
export interface ValtheraPlugin {
|
|
8
13
|
name: string;
|
package/dist/types/query.d.ts
CHANGED
package/dist/types/valthera.d.ts
CHANGED
|
@@ -6,8 +6,8 @@ import { VQueryT } from "./query.js";
|
|
|
6
6
|
export interface ValtheraCompatible {
|
|
7
7
|
c<T = Data>(collection: string): Collection<T>;
|
|
8
8
|
getCollections(): Promise<string[]>;
|
|
9
|
-
ensureCollection(collection: string): Promise<boolean>;
|
|
10
|
-
issetCollection(collection: string): Promise<boolean>;
|
|
9
|
+
ensureCollection(collection: string | VQueryT.Collection): Promise<boolean>;
|
|
10
|
+
issetCollection(collection: string | VQueryT.Collection): Promise<boolean>;
|
|
11
11
|
add<T = Data>(query: VQueryT.Add<T>): Promise<T>;
|
|
12
12
|
find<T = Data>(query: VQueryT.Find<T>): Promise<T[]>;
|
|
13
13
|
findOne<T = Data>(query: VQueryT.FindOne<T>): Promise<T | null>;
|
|
@@ -15,7 +15,7 @@ export interface ValtheraCompatible {
|
|
|
15
15
|
updateOne<T = Data>(query: VQueryT.Update<T>): Promise<T | null>;
|
|
16
16
|
remove<T = Data>(query: VQueryT.Remove<T>): Promise<T[]>;
|
|
17
17
|
removeOne<T = Data>(query: VQueryT.Remove<T>): Promise<T | null>;
|
|
18
|
-
removeCollection(collection: string): Promise<boolean>;
|
|
18
|
+
removeCollection(collection: string | VQueryT.Collection): Promise<boolean>;
|
|
19
19
|
updateOneOrAdd<T = Data>(query: VQueryT.UpdateOneOrAdd<T>): Promise<VQueryT.UpdateOneOrAddResult<T>>;
|
|
20
20
|
toggleOne<T = Data>(query: VQueryT.ToggleOne<T>): Promise<VQueryT.ToggleOneResult<T>>;
|
|
21
21
|
createIndex(collection: string, fields: string[], opts?: IndexOpts): Promise<void>;
|
package/dist/utils/action.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { compareSafe } from "./compare.js";
|
|
2
|
+
import { isUnsafeKey } from "./security.js";
|
|
3
|
+
function getOwnValue(obj, key) {
|
|
4
|
+
return Object.hasOwn(obj, key) ? obj[key] : undefined;
|
|
5
|
+
}
|
|
2
6
|
export async function findUtil(query, fileCpuOrData, files, fileCpuOpts) {
|
|
3
7
|
const { dbFindOpts = {} } = query;
|
|
4
8
|
const { reverse = false, offset = 0, sortBy, sortAsc = true, min, max, avg, sum, distinct, groupBy, count, } = dbFindOpts;
|
|
@@ -69,18 +73,18 @@ export async function findUtil(query, fileCpuOrData, files, fileCpuOpts) {
|
|
|
69
73
|
const hasAggregations = min || max || avg || sum || groupBy || count;
|
|
70
74
|
if (hasAggregations) {
|
|
71
75
|
const groups = new Map();
|
|
72
|
-
const groupKeys = Array.isArray(groupBy)
|
|
76
|
+
const groupKeys = (Array.isArray(groupBy)
|
|
73
77
|
? groupBy
|
|
74
78
|
: groupBy
|
|
75
79
|
? [
|
|
76
80
|
groupBy,
|
|
77
81
|
]
|
|
78
|
-
: [];
|
|
82
|
+
: []).filter(key => !isUnsafeKey(key));
|
|
79
83
|
if (groupKeys.length) {
|
|
80
84
|
for (const data of datas) {
|
|
81
|
-
let key = String(data
|
|
85
|
+
let key = String(getOwnValue(data, groupKeys[0]) ?? "");
|
|
82
86
|
for (let i = 1; i < groupKeys.length; i++) {
|
|
83
|
-
key += "|" + String(data
|
|
87
|
+
key += "|" + String(getOwnValue(data, groupKeys[i]) ?? "");
|
|
84
88
|
}
|
|
85
89
|
let group = groups.get(key);
|
|
86
90
|
if (!group) {
|
|
@@ -98,14 +102,18 @@ export async function findUtil(query, fileCpuOrData, files, fileCpuOpts) {
|
|
|
98
102
|
const result = {};
|
|
99
103
|
if (groupKeys.length) {
|
|
100
104
|
const sample = groupItems[0];
|
|
101
|
-
for (const k of groupKeys)
|
|
102
|
-
|
|
105
|
+
for (const k of groupKeys) {
|
|
106
|
+
if (Object.hasOwn(sample, k))
|
|
107
|
+
result[k] = sample[k];
|
|
108
|
+
}
|
|
103
109
|
}
|
|
104
110
|
if (count) {
|
|
105
111
|
for (const [outKey, srcKey] of Object.entries(count)) {
|
|
112
|
+
if (isUnsafeKey(outKey))
|
|
113
|
+
continue;
|
|
106
114
|
let c = 0;
|
|
107
115
|
for (let i = 0; i < groupItems.length; i++) {
|
|
108
|
-
const v = groupItems[i]
|
|
116
|
+
const v = getOwnValue(groupItems[i], srcKey);
|
|
109
117
|
if (v !== undefined && v !== null)
|
|
110
118
|
c++;
|
|
111
119
|
}
|
|
@@ -118,9 +126,11 @@ export async function findUtil(query, fileCpuOrData, files, fileCpuOpts) {
|
|
|
118
126
|
const sumEntries = sum ? Object.entries(sum) : null;
|
|
119
127
|
if (minEntries) {
|
|
120
128
|
for (const [outKey, srcField] of minEntries) {
|
|
129
|
+
if (isUnsafeKey(outKey))
|
|
130
|
+
continue;
|
|
121
131
|
let best = null;
|
|
122
132
|
for (let i = 0; i < groupItems.length; i++) {
|
|
123
|
-
const v = groupItems[i]
|
|
133
|
+
const v = getOwnValue(groupItems[i], srcField);
|
|
124
134
|
if (typeof v === "number") {
|
|
125
135
|
if (best === null || v < best)
|
|
126
136
|
best = v;
|
|
@@ -131,9 +141,11 @@ export async function findUtil(query, fileCpuOrData, files, fileCpuOpts) {
|
|
|
131
141
|
}
|
|
132
142
|
if (maxEntries) {
|
|
133
143
|
for (const [outKey, srcField] of maxEntries) {
|
|
144
|
+
if (isUnsafeKey(outKey))
|
|
145
|
+
continue;
|
|
134
146
|
let best = null;
|
|
135
147
|
for (let i = 0; i < groupItems.length; i++) {
|
|
136
|
-
const v = groupItems[i]
|
|
148
|
+
const v = getOwnValue(groupItems[i], srcField);
|
|
137
149
|
if (typeof v === "number") {
|
|
138
150
|
if (best === null || v > best)
|
|
139
151
|
best = v;
|
|
@@ -144,10 +156,12 @@ export async function findUtil(query, fileCpuOrData, files, fileCpuOpts) {
|
|
|
144
156
|
}
|
|
145
157
|
if (avgEntries) {
|
|
146
158
|
for (const [outKey, srcField] of avgEntries) {
|
|
159
|
+
if (isUnsafeKey(outKey))
|
|
160
|
+
continue;
|
|
147
161
|
let total = 0;
|
|
148
162
|
let c = 0;
|
|
149
163
|
for (let i = 0; i < groupItems.length; i++) {
|
|
150
|
-
const v = groupItems[i]
|
|
164
|
+
const v = getOwnValue(groupItems[i], srcField);
|
|
151
165
|
if (typeof v === "number") {
|
|
152
166
|
total += v;
|
|
153
167
|
c++;
|
|
@@ -158,10 +172,12 @@ export async function findUtil(query, fileCpuOrData, files, fileCpuOpts) {
|
|
|
158
172
|
}
|
|
159
173
|
if (sumEntries) {
|
|
160
174
|
for (const [outKey, srcField] of sumEntries) {
|
|
175
|
+
if (isUnsafeKey(outKey))
|
|
176
|
+
continue;
|
|
161
177
|
let total = 0;
|
|
162
178
|
let c = 0;
|
|
163
179
|
for (let i = 0; i < groupItems.length; i++) {
|
|
164
|
-
const v = groupItems[i]
|
|
180
|
+
const v = getOwnValue(groupItems[i], srcField);
|
|
165
181
|
if (typeof v === "number") {
|
|
166
182
|
total += v;
|
|
167
183
|
c++;
|
|
@@ -174,11 +190,11 @@ export async function findUtil(query, fileCpuOrData, files, fileCpuOpts) {
|
|
|
174
190
|
}
|
|
175
191
|
datas = aggregated;
|
|
176
192
|
}
|
|
177
|
-
if (distinct) {
|
|
193
|
+
if (distinct && !isUnsafeKey(distinct)) {
|
|
178
194
|
const seen = new Set();
|
|
179
195
|
const result = [];
|
|
180
196
|
for (let i = 0; i < datas.length; i++) {
|
|
181
|
-
const val = datas[i]
|
|
197
|
+
const val = getOwnValue(datas[i], distinct);
|
|
182
198
|
const key = typeof val === "object" && val !== null ? JSON.stringify(val) : val;
|
|
183
199
|
if (!seen.has(key)) {
|
|
184
200
|
seen.add(key);
|
|
@@ -195,7 +211,7 @@ export async function findUtil(query, fileCpuOrData, files, fileCpuOpts) {
|
|
|
195
211
|
datas.sort((a, b) => {
|
|
196
212
|
for (const { field, asc = true } of sortBy) {
|
|
197
213
|
const dir = asc ? 1 : -1;
|
|
198
|
-
const cmp = compareSafe(a
|
|
214
|
+
const cmp = compareSafe(getOwnValue(a, field), getOwnValue(b, field)) * dir;
|
|
199
215
|
if (cmp !== 0)
|
|
200
216
|
return cmp;
|
|
201
217
|
}
|
|
@@ -204,7 +220,7 @@ export async function findUtil(query, fileCpuOrData, files, fileCpuOpts) {
|
|
|
204
220
|
}
|
|
205
221
|
else {
|
|
206
222
|
const dir = sortAsc ? 1 : -1;
|
|
207
|
-
datas.sort((a, b) => compareSafe(a
|
|
223
|
+
datas.sort((a, b) => compareSafe(getOwnValue(a, sortBy), getOwnValue(b, sortBy)) * dir);
|
|
208
224
|
}
|
|
209
225
|
}
|
|
210
226
|
if (offset > 0 || limit !== -1) {
|
package/dist/utils/hasFields.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export function hasFields(obj, fields) {
|
|
8
8
|
const keys = Object.keys(fields);
|
|
9
9
|
return keys.every(key => {
|
|
10
|
-
if (obj
|
|
10
|
+
if (Object.hasOwn(obj, key)) {
|
|
11
11
|
if (typeof fields[key] === "object" && fields[key] !== null) {
|
|
12
12
|
return hasFields(obj[key], fields[key]);
|
|
13
13
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { QueryValidationError } from "../errors.js";
|
|
2
2
|
import { hasFields } from "./hasFields.js";
|
|
3
3
|
import { compareIds } from "./id.js";
|
|
4
|
+
import { isUnsafeKey } from "./security.js";
|
|
4
5
|
/**
|
|
5
6
|
* Checks if an object meets the criteria specified in the fields with operators.
|
|
6
7
|
*/
|
|
@@ -16,6 +17,8 @@ export function hasFieldsAdvanced(obj, fields) {
|
|
|
16
17
|
const subsetFields = {};
|
|
17
18
|
let hasSubset = false;
|
|
18
19
|
for (const key in fields) {
|
|
20
|
+
if (!Object.hasOwn(fields, key) || isUnsafeKey(key))
|
|
21
|
+
continue;
|
|
19
22
|
if (key.charCodeAt(0) === 36) {
|
|
20
23
|
$fields[key.toLowerCase()] = fields[key];
|
|
21
24
|
}
|
|
@@ -31,17 +34,17 @@ export function hasFieldsAdvanced(obj, fields) {
|
|
|
31
34
|
if (Object.keys($fields).length === 0)
|
|
32
35
|
return true;
|
|
33
36
|
// Subset check
|
|
34
|
-
if ("$subset"
|
|
37
|
+
if (Object.hasOwn($fields, "$subset") && !hasFields(obj, $fields["$subset"]))
|
|
35
38
|
return false;
|
|
36
39
|
// Advanced operators
|
|
37
40
|
if (!mainCheck(obj, $fields))
|
|
38
41
|
return false;
|
|
39
42
|
if (!checkNot(obj, $fields))
|
|
40
43
|
return false;
|
|
41
|
-
if ("$and"
|
|
44
|
+
if (Object.hasOwn($fields, "$and")) {
|
|
42
45
|
return $fields["$and"].every((subFields) => hasFieldsAdvanced(obj, subFields));
|
|
43
46
|
}
|
|
44
|
-
if ("$or"
|
|
47
|
+
if (Object.hasOwn($fields, "$or")) {
|
|
45
48
|
return $fields["$or"].some((subFields) => hasFieldsAdvanced(obj, subFields));
|
|
46
49
|
}
|
|
47
50
|
return true;
|
|
@@ -57,9 +60,9 @@ function mainCheck(obj, fields) {
|
|
|
57
60
|
nin: (data, value) => !value.includes(data),
|
|
58
61
|
type: (data, value) => typeof data === value,
|
|
59
62
|
exists: (targetObj, shouldExist, key) => {
|
|
60
|
-
if (shouldExist && !(key
|
|
63
|
+
if (shouldExist && !Object.hasOwn(targetObj, key))
|
|
61
64
|
return false;
|
|
62
|
-
if (!shouldExist && key
|
|
65
|
+
if (!shouldExist && Object.hasOwn(targetObj, key))
|
|
63
66
|
return false;
|
|
64
67
|
return true;
|
|
65
68
|
},
|
|
@@ -93,7 +96,7 @@ function mainCheck(obj, fields) {
|
|
|
93
96
|
});
|
|
94
97
|
}
|
|
95
98
|
function checkNot(obj, fields) {
|
|
96
|
-
if ("$not"
|
|
99
|
+
if (Object.hasOwn(fields, "$not")) {
|
|
97
100
|
return !hasFieldsAdvanced(obj, fields["$not"]);
|
|
98
101
|
}
|
|
99
102
|
return true;
|
|
@@ -101,11 +104,13 @@ function checkNot(obj, fields) {
|
|
|
101
104
|
function _for(fields, obj, opts) {
|
|
102
105
|
for (const [field, value] of Object.entries(fields)) {
|
|
103
106
|
const fieldRaw = field.slice(1);
|
|
104
|
-
const fieldFn = opts[fieldRaw];
|
|
107
|
+
const fieldFn = Object.hasOwn(opts, fieldRaw) ? opts[fieldRaw] : undefined;
|
|
105
108
|
if (!fieldFn)
|
|
106
109
|
continue;
|
|
107
110
|
for (const [key, val] of Object.entries(value)) {
|
|
108
|
-
|
|
111
|
+
if (isUnsafeKey(key))
|
|
112
|
+
continue;
|
|
113
|
+
const targetValue = obj != null && Object.hasOwn(obj, key) ? obj[key] : undefined;
|
|
109
114
|
if (typeof val === "object" &&
|
|
110
115
|
val !== null &&
|
|
111
116
|
!Array.isArray(val) &&
|
|
@@ -131,7 +136,9 @@ function deepCheck(valueObj, targetObj, fieldFn, fieldRaw) {
|
|
|
131
136
|
if (typeof targetObj !== "object" || targetObj === null)
|
|
132
137
|
return false;
|
|
133
138
|
for (const [k, v] of Object.entries(valueObj)) {
|
|
134
|
-
|
|
139
|
+
if (isUnsafeKey(k))
|
|
140
|
+
continue;
|
|
141
|
+
const targetValue = Object.hasOwn(targetObj, k) ? targetObj[k] : undefined;
|
|
135
142
|
if (typeof v === "object" &&
|
|
136
143
|
v !== null &&
|
|
137
144
|
!Array.isArray(v) &&
|
package/dist/utils/merge.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isUnsafeKey } from "./security.js";
|
|
1
2
|
export function _deepMerge(target, ...sources) {
|
|
2
3
|
if (!sources.length)
|
|
3
4
|
return target;
|
|
@@ -6,6 +7,8 @@ export function _deepMerge(target, ...sources) {
|
|
|
6
7
|
target = {};
|
|
7
8
|
if (isObject(target) && isObject(source)) {
|
|
8
9
|
for (const key in source) {
|
|
10
|
+
if (!Object.hasOwn(source, key) || isUnsafeKey(key))
|
|
11
|
+
continue;
|
|
9
12
|
if (isObject(source[key])) {
|
|
10
13
|
if (!target[key])
|
|
11
14
|
Object.assign(target, {
|
|
@@ -30,8 +33,11 @@ function fastClone(obj) {
|
|
|
30
33
|
if (obj instanceof Date)
|
|
31
34
|
return new Date(obj.getTime());
|
|
32
35
|
const result = {};
|
|
33
|
-
for (const key in obj)
|
|
36
|
+
for (const key in obj) {
|
|
37
|
+
if (!Object.hasOwn(obj, key) || isUnsafeKey(key))
|
|
38
|
+
continue;
|
|
34
39
|
result[key] = fastClone(obj[key]);
|
|
40
|
+
}
|
|
35
41
|
return result;
|
|
36
42
|
}
|
|
37
43
|
export function deepMerge(target, ...sources) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type PluginOpKind = "read" | "write" | "delete" | "collection" | "index" | "meta" | "control";
|
|
2
|
+
export declare function getOpKind(op: string): PluginOpKind;
|
|
3
|
+
export declare function isKnownOp(op: string): boolean;
|
|
4
|
+
export declare function isMutation(kind: PluginOpKind): kind is "delete" | "write";
|
|
5
|
+
export declare function isCollectionOp(op: string): boolean;
|
|
6
|
+
export declare function isIndexOp(op: string): boolean;
|
|
7
|
+
export declare function isMetaOp(op: string): boolean;
|
|
8
|
+
export declare function isControlOp(op: string): boolean;
|
|
9
|
+
export declare function isNoQueryOp(op: string): boolean;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const OP_KINDS = {
|
|
2
|
+
find: "read",
|
|
3
|
+
findOne: "read",
|
|
4
|
+
count: "read",
|
|
5
|
+
add: "write",
|
|
6
|
+
bulkAdd: "write",
|
|
7
|
+
update: "write",
|
|
8
|
+
updateOne: "write",
|
|
9
|
+
updateOneOrAdd: "write",
|
|
10
|
+
toggleOne: "write",
|
|
11
|
+
remove: "delete",
|
|
12
|
+
removeOne: "delete",
|
|
13
|
+
ensureCollection: "collection",
|
|
14
|
+
issetCollection: "collection",
|
|
15
|
+
removeCollection: "collection",
|
|
16
|
+
createIndex: "index",
|
|
17
|
+
removeIndex: "index",
|
|
18
|
+
getCollections: "meta",
|
|
19
|
+
capabilities: "meta",
|
|
20
|
+
beginTransaction: "control",
|
|
21
|
+
commitTransaction: "control",
|
|
22
|
+
rollbackTransaction: "control",
|
|
23
|
+
};
|
|
24
|
+
export function getOpKind(op) {
|
|
25
|
+
return Object.hasOwn(OP_KINDS, op) ? OP_KINDS[op] : "write";
|
|
26
|
+
}
|
|
27
|
+
export function isKnownOp(op) {
|
|
28
|
+
return Object.hasOwn(OP_KINDS, op);
|
|
29
|
+
}
|
|
30
|
+
export function isMutation(kind) {
|
|
31
|
+
return kind === "write" || kind === "delete";
|
|
32
|
+
}
|
|
33
|
+
export function isCollectionOp(op) {
|
|
34
|
+
return getOpKind(op) === "collection";
|
|
35
|
+
}
|
|
36
|
+
export function isIndexOp(op) {
|
|
37
|
+
return getOpKind(op) === "index";
|
|
38
|
+
}
|
|
39
|
+
export function isMetaOp(op) {
|
|
40
|
+
return getOpKind(op) === "meta";
|
|
41
|
+
}
|
|
42
|
+
export function isControlOp(op) {
|
|
43
|
+
return getOpKind(op) === "control";
|
|
44
|
+
}
|
|
45
|
+
export function isNoQueryOp(op) {
|
|
46
|
+
const kind = getOpKind(op);
|
|
47
|
+
return kind === "collection" || kind === "meta";
|
|
48
|
+
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import { hasUnsafeSegment } from "./security.js";
|
|
1
2
|
function resolvePath(path) {
|
|
2
3
|
return Array.isArray(path) ? path : path.split(".");
|
|
3
4
|
}
|
|
4
5
|
function deleteNested(obj, path) {
|
|
5
6
|
const segs = resolvePath(path);
|
|
6
|
-
if (segs.length === 0)
|
|
7
|
+
if (segs.length === 0 || hasUnsafeSegment(segs))
|
|
7
8
|
return;
|
|
8
9
|
let cur = obj;
|
|
9
10
|
for (let i = 0; i < segs.length - 1; i++) {
|
|
10
11
|
if (cur == null || typeof cur !== "object")
|
|
11
12
|
return;
|
|
13
|
+
if (!Object.hasOwn(cur, segs[i]))
|
|
14
|
+
return;
|
|
12
15
|
cur = cur[segs[i]];
|
|
13
16
|
}
|
|
14
17
|
if (cur != null && typeof cur === "object") {
|
|
@@ -39,12 +42,14 @@ export function updateFindObject(obj, findOpts) {
|
|
|
39
42
|
const result = {};
|
|
40
43
|
for (const field of select) {
|
|
41
44
|
const segs = typeof field === "string" ? field.split(".") : field;
|
|
42
|
-
if (!segs.length)
|
|
45
|
+
if (!segs.length || hasUnsafeSegment(segs))
|
|
43
46
|
continue;
|
|
44
47
|
let src = obj;
|
|
45
48
|
let found = true;
|
|
46
49
|
for (let i = 0; i < segs.length; i++) {
|
|
47
|
-
if (src == null ||
|
|
50
|
+
if (src == null ||
|
|
51
|
+
typeof src !== "object" ||
|
|
52
|
+
!Object.hasOwn(src, segs[i])) {
|
|
48
53
|
found = false;
|
|
49
54
|
break;
|
|
50
55
|
}
|
|
@@ -54,7 +59,7 @@ export function updateFindObject(obj, findOpts) {
|
|
|
54
59
|
let dst = result;
|
|
55
60
|
for (let i = 0; i < segs.length - 1; i++) {
|
|
56
61
|
const s = segs[i];
|
|
57
|
-
if (!(s
|
|
62
|
+
if (!Object.hasOwn(dst, s) ||
|
|
58
63
|
typeof dst[s] !== "object" ||
|
|
59
64
|
Array.isArray(dst[s])) {
|
|
60
65
|
dst[s] = {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { QueryValidationError } from "../errors.js";
|
|
2
2
|
import { _deepMerge } from "./merge.js";
|
|
3
|
+
import { isUnsafeKey } from "./security.js";
|
|
3
4
|
/**
|
|
4
5
|
* Updates an object with new values.
|
|
5
6
|
* @param obj - The object to update.
|
|
@@ -17,6 +18,8 @@ export function updateObjectAdvanced(obj, field) {
|
|
|
17
18
|
let hasDollar = false;
|
|
18
19
|
let hasSubset = false;
|
|
19
20
|
for (const key in field) {
|
|
21
|
+
if (!Object.hasOwn(field, key) || isUnsafeKey(key))
|
|
22
|
+
continue;
|
|
20
23
|
if (key.charCodeAt(0) === 36) {
|
|
21
24
|
$fields[key.toLowerCase()] = field[key];
|
|
22
25
|
hasDollar = true;
|
|
@@ -34,7 +37,7 @@ export function updateObjectAdvanced(obj, field) {
|
|
|
34
37
|
updateObject(obj, subsetFields);
|
|
35
38
|
}
|
|
36
39
|
function deepMerge(obj, fields) {
|
|
37
|
-
if (!("$deepmerge"
|
|
40
|
+
if (!Object.hasOwn(fields, "$deepmerge"))
|
|
38
41
|
return obj;
|
|
39
42
|
if (Object.keys(obj).length === 0)
|
|
40
43
|
return fields.$deepmerge;
|
|
@@ -86,10 +89,16 @@ function mainUpdate(obj, fields) {
|
|
|
86
89
|
];
|
|
87
90
|
}
|
|
88
91
|
if (typeof item === "object" &&
|
|
92
|
+
item !== null &&
|
|
89
93
|
typeof updater === "object" &&
|
|
94
|
+
updater !== null &&
|
|
90
95
|
!Array.isArray(item) &&
|
|
91
96
|
!Array.isArray(updater)) {
|
|
92
|
-
Object.
|
|
97
|
+
for (const key of Object.keys(updater)) {
|
|
98
|
+
if (isUnsafeKey(key))
|
|
99
|
+
continue;
|
|
100
|
+
item[key] = updater[key];
|
|
101
|
+
}
|
|
93
102
|
return item;
|
|
94
103
|
}
|
|
95
104
|
return updater;
|
|
@@ -98,7 +107,7 @@ function mainUpdate(obj, fields) {
|
|
|
98
107
|
if (typeof item === "number" && typeof updater === "number") {
|
|
99
108
|
return item + updater;
|
|
100
109
|
}
|
|
101
|
-
if (!(key
|
|
110
|
+
if (!Object.hasOwn(deepObj, key))
|
|
102
111
|
return updater;
|
|
103
112
|
throw new QueryValidationError(`Cannot increment non-numeric value at key: ${key}`, {
|
|
104
113
|
op: "update",
|
|
@@ -109,7 +118,7 @@ function mainUpdate(obj, fields) {
|
|
|
109
118
|
if (typeof item === "number" && typeof updater === "number") {
|
|
110
119
|
return item - updater;
|
|
111
120
|
}
|
|
112
|
-
if (!(key
|
|
121
|
+
if (!Object.hasOwn(deepObj, key))
|
|
113
122
|
return -updater;
|
|
114
123
|
throw new QueryValidationError(`Cannot decrement non-numeric value at key: ${key}`, {
|
|
115
124
|
op: "update",
|
|
@@ -117,14 +126,16 @@ function mainUpdate(obj, fields) {
|
|
|
117
126
|
});
|
|
118
127
|
},
|
|
119
128
|
rename: (_, newKey, oldKey, deepObj) => {
|
|
120
|
-
if (oldKey
|
|
129
|
+
if (isUnsafeKey(oldKey) || isUnsafeKey(newKey))
|
|
130
|
+
return;
|
|
131
|
+
if (Object.hasOwn(deepObj, oldKey)) {
|
|
121
132
|
deepObj[newKey] = deepObj[oldKey];
|
|
122
133
|
delete deepObj[oldKey];
|
|
123
134
|
}
|
|
124
135
|
},
|
|
125
136
|
set: (_, value) => value,
|
|
126
137
|
unset: (_, __, key, deepObj) => {
|
|
127
|
-
if (key
|
|
138
|
+
if (Object.hasOwn(deepObj, key))
|
|
128
139
|
delete deepObj[key];
|
|
129
140
|
},
|
|
130
141
|
});
|
|
@@ -135,20 +146,24 @@ function mainUpdate(obj, fields) {
|
|
|
135
146
|
* @param newVal - An object containing new values to update in the target object.
|
|
136
147
|
*/
|
|
137
148
|
function updateObject(obj, newVal) {
|
|
138
|
-
for (const key in newVal)
|
|
139
|
-
if (Object.hasOwn(newVal, key))
|
|
140
|
-
|
|
149
|
+
for (const key in newVal) {
|
|
150
|
+
if (!Object.hasOwn(newVal, key) || isUnsafeKey(key))
|
|
151
|
+
continue;
|
|
152
|
+
obj[key] = newVal[key];
|
|
153
|
+
}
|
|
141
154
|
return obj;
|
|
142
155
|
}
|
|
143
156
|
function _for(fields, obj, opts) {
|
|
144
157
|
for (const [field, value] of Object.entries(fields)) {
|
|
145
158
|
const fieldRaw = field.slice(1);
|
|
146
|
-
const fieldFn = opts[fieldRaw];
|
|
159
|
+
const fieldFn = Object.hasOwn(opts, fieldRaw) ? opts[fieldRaw] : undefined;
|
|
147
160
|
if (!fieldFn)
|
|
148
161
|
continue;
|
|
149
162
|
for (const [key, val] of Object.entries(value)) {
|
|
163
|
+
if (isUnsafeKey(key))
|
|
164
|
+
continue;
|
|
150
165
|
if (typeof val === "object" && val !== null && !Array.isArray(val)) {
|
|
151
|
-
if (!obj[key])
|
|
166
|
+
if (!Object.hasOwn(obj, key) || !obj[key])
|
|
152
167
|
obj[key] = {};
|
|
153
168
|
deepUpdateCheck(val, obj[key], fieldFn);
|
|
154
169
|
}
|
|
@@ -162,8 +177,10 @@ function _for(fields, obj, opts) {
|
|
|
162
177
|
}
|
|
163
178
|
function deepUpdateCheck(valueObj, targetObj, fieldFn) {
|
|
164
179
|
for (const [k, v] of Object.entries(valueObj)) {
|
|
180
|
+
if (isUnsafeKey(k))
|
|
181
|
+
continue;
|
|
165
182
|
if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
166
|
-
if (!targetObj[k])
|
|
183
|
+
if (!Object.hasOwn(targetObj, k) || !targetObj[k])
|
|
167
184
|
targetObj[k] = {};
|
|
168
185
|
deepUpdateCheck(v, targetObj[k], fieldFn);
|
|
169
186
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "0.12.
|
|
1
|
+
export declare const version = "0.12.13";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "0.12.
|
|
1
|
+
export const version = "0.12.13";
|