@wxn0brp/db-core 0.12.12 → 0.12.14

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.
@@ -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
- return path.replace(/\/+/g, "/");
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;
@@ -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
- try {
37
- const result = await backend.findOne({
38
- ...config,
39
- });
40
- if (result !== null && result !== undefined) {
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
- try {
57
- const result = await backend.updateOne({
58
- ...config,
59
- });
60
- if (result)
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
- try {
76
- const result = await backend.removeOne({
77
- ...config,
78
- });
79
- if (result)
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
  }
@@ -1,11 +1,11 @@
1
1
  import { VEE } from "@wxn0brp/event-emitter";
2
- import { AdapterCloseError, AdapterInitError, ErrorCodes, isValtheraError, QueryValidationError, TransactionError, wrapAdapterError, } from "../errors.js";
2
+ import * as errors from "../errors.js";
3
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
6
  import { rewriteOp } from "../helpers/plugin.js";
7
- import { applyAddDefaults, applyBulkAddDefaults, applyCountDefaults, applyFindDefaults, applyFindOneDefaults, applyRemoveDefaults, applyToggleOneDefaults, applyUpdateDefaults, applyUpdateOneOrAddDefaults, } from "../helpers/queryDefaults.js";
8
- import { getOpKind } from "../utils/opKind.js";
7
+ import * as queryDefaults from "../helpers/queryDefaults.js";
8
+ import { getOpKind, isKnownOp } from "../utils/opKind.js";
9
9
  import { version } from "../version.js";
10
10
  import { Transaction } from "./transaction.js";
11
11
  /**
@@ -52,9 +52,9 @@ export class ValtheraClass {
52
52
  return;
53
53
  const self = this;
54
54
  const failInit = (err) => {
55
- if (isValtheraError(err))
55
+ if (errors.isValtheraError(err))
56
56
  throw err;
57
- throw new AdapterInitError(err, {
57
+ throw new errors.AdapterInitError(err, {
58
58
  adapter: self.adapter?.version,
59
59
  });
60
60
  };
@@ -101,9 +101,9 @@ export class ValtheraClass {
101
101
  await self.adapter.close(...args);
102
102
  }
103
103
  catch (err) {
104
- if (isValtheraError(err))
104
+ if (errors.isValtheraError(err))
105
105
  throw err;
106
- throw new AdapterCloseError(err, {
106
+ throw new errors.AdapterCloseError(err, {
107
107
  adapter: self.adapter?.version,
108
108
  });
109
109
  }
@@ -114,7 +114,7 @@ export class ValtheraClass {
114
114
  await this.init();
115
115
  const collection = typeof query === "string" ? query : query?.collection;
116
116
  if (!collection && name !== "getCollections")
117
- throw new QueryValidationError(`Missing "collection" for operation "${String(name)}"`, {
117
+ throw new errors.QueryValidationError(`Missing "collection" for operation "${String(name)}"`, {
118
118
  op: name,
119
119
  });
120
120
  if (txHandle && query && typeof query === "object")
@@ -132,13 +132,17 @@ export class ValtheraClass {
132
132
  return plugins[idx++].execute(ctx);
133
133
  if (txHandle && ctx.query && typeof ctx.query === "object")
134
134
  ctx.query.transaction = txHandle;
135
+ if (!isKnownOp(ctx.op))
136
+ throw new errors.QueryValidationError(`Unknown operation "${ctx.op}"`, {
137
+ op: ctx.op,
138
+ });
135
139
  try {
136
140
  if (txHandle)
137
141
  return await self.adapter[ctx.op](ctx.query, txHandle);
138
142
  return await self.executor.addOp(self.adapter[ctx.op].bind(self.adapter), ctx.query, typeof ctx.query === "string" ? ctx.query : ctx.query.collection);
139
143
  }
140
144
  catch (err) {
141
- throw wrapAdapterError(err, {
145
+ throw errors.wrapAdapterError(err, {
142
146
  op: ctx.op,
143
147
  collection: typeof ctx.query === "string" ? ctx.query : ctx.query?.collection,
144
148
  adapter: self.adapter?.version,
@@ -189,67 +193,67 @@ export class ValtheraClass {
189
193
  * Add data to a database.
190
194
  */
191
195
  add(query) {
192
- applyAddDefaults(query);
196
+ queryDefaults.applyAddDefaults(query);
193
197
  return this.execute("add", query);
194
198
  }
195
199
  bulkAdd(query) {
196
- applyBulkAddDefaults(query);
200
+ queryDefaults.applyBulkAddDefaults(query);
197
201
  return this.execute("bulkAdd", query);
198
202
  }
199
203
  /**
200
204
  * Find data in a database.
201
205
  */
202
206
  find(query) {
203
- applyFindDefaults(query);
207
+ queryDefaults.applyFindDefaults(query);
204
208
  return this.execute("find", query);
205
209
  }
206
210
  /**
207
211
  * Find one data entry in a database.
208
212
  */
209
213
  findOne(query) {
210
- applyFindOneDefaults(query);
214
+ queryDefaults.applyFindOneDefaults(query);
211
215
  return this.execute("findOne", query);
212
216
  }
213
217
  /**
214
218
  * Update data in a database.
215
219
  */
216
220
  update(query) {
217
- applyUpdateDefaults(query);
221
+ queryDefaults.applyUpdateDefaults(query);
218
222
  return this.execute("update", query);
219
223
  }
220
224
  /**
221
225
  * Update one data entry in a database.
222
226
  */
223
227
  updateOne(query) {
224
- applyUpdateDefaults(query);
228
+ queryDefaults.applyUpdateDefaults(query);
225
229
  return this.execute("updateOne", query);
226
230
  }
227
231
  /**
228
232
  * Remove data from a database.
229
233
  */
230
234
  remove(query) {
231
- applyRemoveDefaults(query);
235
+ queryDefaults.applyRemoveDefaults(query);
232
236
  return this.execute("remove", query);
233
237
  }
234
238
  /**
235
239
  * Remove one data entry from a database.
236
240
  */
237
241
  removeOne(query) {
238
- applyRemoveDefaults(query);
242
+ queryDefaults.applyRemoveDefaults(query);
239
243
  return this.execute("removeOne", query);
240
244
  }
241
245
  /**
242
246
  * Asynchronously updates one entry in a database or adds a new one if it doesn't exist.
243
247
  */
244
248
  updateOneOrAdd(query) {
245
- applyUpdateOneOrAddDefaults(query);
249
+ queryDefaults.applyUpdateOneOrAddDefaults(query);
246
250
  return this.execute("updateOneOrAdd", query);
247
251
  }
248
252
  /**
249
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.
250
254
  */
251
255
  toggleOne(query) {
252
- applyToggleOneDefaults(query);
256
+ queryDefaults.applyToggleOneDefaults(query);
253
257
  return this.execute("toggleOne", query);
254
258
  }
255
259
  /**
@@ -288,7 +292,7 @@ export class ValtheraClass {
288
292
  });
289
293
  }
290
294
  count(query) {
291
- applyCountDefaults(query);
295
+ queryDefaults.applyCountDefaults(query);
292
296
  return this.execute("count", query);
293
297
  }
294
298
  /**
@@ -333,7 +337,7 @@ export class ValtheraClass {
333
337
  handle = await this.adapter.beginTransaction(genId(), collections);
334
338
  }
335
339
  catch (err) {
336
- throw wrapAdapterError(err, {
340
+ throw errors.wrapAdapterError(err, {
337
341
  op: "beginTransaction",
338
342
  adapter: this.adapter?.version,
339
343
  });
@@ -349,8 +353,8 @@ export class ValtheraClass {
349
353
  await tx.rollback();
350
354
  }
351
355
  catch (rollbackErr) {
352
- throw new TransactionError("Transaction failed and rollback failed", {
353
- code: ErrorCodes.TRANSACTION_ROLLBACK_FAILED,
356
+ throw new errors.TransactionError("Transaction failed and rollback failed", {
357
+ code: errors.ErrorCodes.TRANSACTION_ROLLBACK_FAILED,
354
358
  cause: new AggregateError([
355
359
  err,
356
360
  rollbackErr,
@@ -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 => (obj[k] = dk[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
  }
@@ -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 in target) {
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) {
@@ -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
- else {
15
- dst[k] ||= {};
16
- dst = dst[k];
17
- src = src[k];
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)
@@ -5,7 +5,15 @@ import { UpdaterArg } from "./updater.js";
5
5
  export type Arg<T = any> = {
6
6
  [K in keyof T]?: any;
7
7
  } & Record<string, any>;
8
+ /**
9
+ * Function-based search.
10
+ * @deprecated Use operator objects or a plugin instead.
11
+ */
8
12
  export type SearchFunc<T = any> = (data: T, context: VContext) => boolean;
13
+ /**
14
+ * Function-based updater.
15
+ * @deprecated Use operator objects instead.
16
+ */
9
17
  export type UpdaterFunc<T = any> = (data: T, context: VContext) => Data | void;
10
18
  export type Search<T = any, AllowFn extends boolean = true> = AllowFn extends true ? SearchOptions<T> | SearchFunc<T> : SearchOptions<T>;
11
19
  export type Updater<T = any, AllowFn extends boolean = true> = AllowFn extends true ? UpdaterArg<T> | UpdaterFunc<T> : UpdaterArg<T>;
@@ -3,5 +3,6 @@ import { VContext } from "./types.js";
3
3
  export interface UpdateOneOrAdd<T> {
4
4
  add_arg?: Arg<T>;
5
5
  id_gen?: boolean;
6
+ /** @deprecated */
6
7
  context?: VContext;
7
8
  }
@@ -10,6 +10,8 @@ export declare namespace ValtheraTypes {
10
10
  type Data = import("./data.js").Data;
11
11
  type SearchOptions = import("./searchOpts.js").SearchOptions;
12
12
  type Id = import("./Id.js").Id;
13
+ /** @deprecated */
13
14
  type Context = import("./types.js").VContext;
15
+ /** @deprecated */
14
16
  type VContext = import("./types.js").VContext;
15
17
  }
@@ -19,6 +19,7 @@ export interface VQuery<T = Data, AllowFn extends boolean = true> {
19
19
  /** logic path, dir or file, depends on context */
20
20
  collection?: string;
21
21
  search?: Search<T, AllowFn>;
22
+ /** @deprecated */
22
23
  context?: VContext;
23
24
  dbFindOpts?: DbFindOpts<T>;
24
25
  findOpts?: FindOpts<T>;
@@ -49,20 +50,24 @@ export declare namespace VQueryT {
49
50
  search?: Search<T, AllowFn>;
50
51
  findOpts?: FindOpts<T>;
51
52
  dbFindOpts?: DbFindOpts<T>;
53
+ /** @deprecated */
52
54
  context?: VContext;
53
55
  };
54
56
  type FindOne<T = Data, AllowFn extends boolean = true> = QueryBase<T> & {
55
57
  search: Search<T, AllowFn>;
56
58
  findOpts?: FindOpts<T>;
59
+ /** @deprecated */
57
60
  context?: VContext;
58
61
  };
59
62
  type Update<T = Data, AllowFn extends boolean = true> = QueryBase<T> & {
60
63
  search: Search<T, AllowFn>;
61
64
  updater: Updater<T, AllowFn>;
65
+ /** @deprecated */
62
66
  context?: VContext;
63
67
  };
64
68
  type Remove<T = Data, AllowFn extends boolean = true> = QueryBase<T> & {
65
69
  search: Search<T, AllowFn>;
70
+ /** @deprecated */
66
71
  context?: VContext;
67
72
  };
68
73
  type UpdateOneOrAdd<T = Data, AllowFn extends boolean = true> = QueryBase<T> & {
@@ -70,11 +75,13 @@ export declare namespace VQueryT {
70
75
  updater: Updater<T, AllowFn>;
71
76
  add_arg?: Arg<T>;
72
77
  id_gen?: boolean;
78
+ /** @deprecated */
73
79
  context?: VContext;
74
80
  };
75
81
  type ToggleOne<T = Data, AllowFn extends boolean = true> = QueryBase<T> & {
76
82
  search: Search<T, AllowFn>;
77
83
  data: Arg<T>;
84
+ /** @deprecated */
78
85
  context?: VContext;
79
86
  };
80
87
  interface UpdateOneOrAddResult<T> {
@@ -72,10 +72,20 @@ export type ArrayOperators<T = any> = {
72
72
  /** [1, 2, 3] has size 3 */
73
73
  $size?: NestedValue<T, number>;
74
74
  };
75
+ /**
76
+ * Raw regex pattern string.
77
+ * @deprecated Pass a `RegExp` instance instead.
78
+ */
79
+ export interface RawRegexPattern extends String {
80
+ }
75
81
  /** String Operators with nested support */
76
82
  export type StringOperators<T = any> = {
77
- /** "John" matches /oh/ */
78
- $regex?: NestedValue<T, RegExp | string, string>;
83
+ /**
84
+ * "John" matches /oh/
85
+ *
86
+ * Prefer a `RegExp` instance; raw strings ({@link RawRegexPattern}) are deprecated.
87
+ */
88
+ $regex?: NestedValue<T, RegExp | RawRegexPattern, string>;
79
89
  /** "John" starts with "Jo" */
80
90
  $startsWith?: NestedValue<T, string, string>;
81
91
  /** "John" ends with "hn" */
@@ -1,3 +1,6 @@
1
+ /**
2
+ * @deprecated Function-based queries are deprecated, so this type will be removed together with them.
3
+ */
1
4
  export interface VContext {
2
5
  [key: string]: any;
3
6
  }
@@ -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[groupKeys[0]] ?? "");
85
+ let key = String(getOwnValue(data, groupKeys[0]) ?? "");
82
86
  for (let i = 1; i < groupKeys.length; i++) {
83
- key += "|" + String(data[groupKeys[i]] ?? "");
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
- result[k] = sample[k];
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][srcKey];
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][srcField];
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][srcField];
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][srcField];
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][srcField];
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][distinct];
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[field], b[field]) * dir;
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[sortBy], b[sortBy]) * dir);
223
+ datas.sort((a, b) => compareSafe(getOwnValue(a, sortBy), getOwnValue(b, sortBy)) * dir);
208
224
  }
209
225
  }
210
226
  if (offset > 0 || limit !== -1) {
@@ -0,0 +1 @@
1
+ export declare function warnDeprecated(code: string, message: string): void;
@@ -0,0 +1,15 @@
1
+ const warned = new Set();
2
+ export function warnDeprecated(code, message) {
3
+ if (warned.has(code))
4
+ return;
5
+ warned.add(code);
6
+ const proc = globalThis.process;
7
+ if (typeof proc?.emitWarning === "function") {
8
+ proc.emitWarning(message, {
9
+ type: "DeprecationWarning",
10
+ code,
11
+ });
12
+ return;
13
+ }
14
+ console.warn(message);
15
+ }
@@ -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[key] !== undefined) {
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,8 @@
1
1
  import { QueryValidationError } from "../errors.js";
2
+ import { warnDeprecated } from "./deprecation.js";
2
3
  import { hasFields } from "./hasFields.js";
3
4
  import { compareIds } from "./id.js";
5
+ import { isUnsafeKey } from "./security.js";
4
6
  /**
5
7
  * Checks if an object meets the criteria specified in the fields with operators.
6
8
  */
@@ -16,6 +18,8 @@ export function hasFieldsAdvanced(obj, fields) {
16
18
  const subsetFields = {};
17
19
  let hasSubset = false;
18
20
  for (const key in fields) {
21
+ if (!Object.hasOwn(fields, key) || isUnsafeKey(key))
22
+ continue;
19
23
  if (key.charCodeAt(0) === 36) {
20
24
  $fields[key.toLowerCase()] = fields[key];
21
25
  }
@@ -31,17 +35,17 @@ export function hasFieldsAdvanced(obj, fields) {
31
35
  if (Object.keys($fields).length === 0)
32
36
  return true;
33
37
  // Subset check
34
- if ("$subset" in $fields && !hasFields(obj, $fields["$subset"]))
38
+ if (Object.hasOwn($fields, "$subset") && !hasFields(obj, $fields["$subset"]))
35
39
  return false;
36
40
  // Advanced operators
37
41
  if (!mainCheck(obj, $fields))
38
42
  return false;
39
43
  if (!checkNot(obj, $fields))
40
44
  return false;
41
- if ("$and" in $fields) {
45
+ if (Object.hasOwn($fields, "$and")) {
42
46
  return $fields["$and"].every((subFields) => hasFieldsAdvanced(obj, subFields));
43
47
  }
44
- if ("$or" in $fields) {
48
+ if (Object.hasOwn($fields, "$or")) {
45
49
  return $fields["$or"].some((subFields) => hasFieldsAdvanced(obj, subFields));
46
50
  }
47
51
  return true;
@@ -57,15 +61,17 @@ function mainCheck(obj, fields) {
57
61
  nin: (data, value) => !value.includes(data),
58
62
  type: (data, value) => typeof data === value,
59
63
  exists: (targetObj, shouldExist, key) => {
60
- if (shouldExist && !(key in targetObj))
64
+ if (shouldExist && !Object.hasOwn(targetObj, key))
61
65
  return false;
62
- if (!shouldExist && key in targetObj)
66
+ if (!shouldExist && Object.hasOwn(targetObj, key))
63
67
  return false;
64
68
  return true;
65
69
  },
66
70
  regex: (data, regexData) => {
67
71
  if (typeof data !== "string")
68
72
  return false;
73
+ if (typeof regexData === "string")
74
+ warnDeprecated("VALTHERA_REGEX_STRING_PATTERN", "$regex with a string pattern is deprecated; pass a RegExp instance instead.");
69
75
  const regex = typeof regexData === "string" ? new RegExp(regexData) : regexData;
70
76
  return regex.test(data);
71
77
  },
@@ -93,7 +99,7 @@ function mainCheck(obj, fields) {
93
99
  });
94
100
  }
95
101
  function checkNot(obj, fields) {
96
- if ("$not" in fields) {
102
+ if (Object.hasOwn(fields, "$not")) {
97
103
  return !hasFieldsAdvanced(obj, fields["$not"]);
98
104
  }
99
105
  return true;
@@ -101,11 +107,13 @@ function checkNot(obj, fields) {
101
107
  function _for(fields, obj, opts) {
102
108
  for (const [field, value] of Object.entries(fields)) {
103
109
  const fieldRaw = field.slice(1);
104
- const fieldFn = opts[fieldRaw];
110
+ const fieldFn = Object.hasOwn(opts, fieldRaw) ? opts[fieldRaw] : undefined;
105
111
  if (!fieldFn)
106
112
  continue;
107
113
  for (const [key, val] of Object.entries(value)) {
108
- const targetValue = obj?.[key];
114
+ if (isUnsafeKey(key))
115
+ continue;
116
+ const targetValue = obj != null && Object.hasOwn(obj, key) ? obj[key] : undefined;
109
117
  if (typeof val === "object" &&
110
118
  val !== null &&
111
119
  !Array.isArray(val) &&
@@ -131,7 +139,9 @@ function deepCheck(valueObj, targetObj, fieldFn, fieldRaw) {
131
139
  if (typeof targetObj !== "object" || targetObj === null)
132
140
  return false;
133
141
  for (const [k, v] of Object.entries(valueObj)) {
134
- const targetValue = targetObj[k];
142
+ if (isUnsafeKey(k))
143
+ continue;
144
+ const targetValue = Object.hasOwn(targetObj, k) ? targetObj[k] : undefined;
135
145
  if (typeof v === "object" &&
136
146
  v !== null &&
137
147
  !Array.isArray(v) &&
@@ -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) {
@@ -1,5 +1,6 @@
1
1
  export type PluginOpKind = "read" | "write" | "delete" | "collection" | "index" | "meta" | "control";
2
2
  export declare function getOpKind(op: string): PluginOpKind;
3
+ export declare function isKnownOp(op: string): boolean;
3
4
  export declare function isMutation(kind: PluginOpKind): kind is "delete" | "write";
4
5
  export declare function isCollectionOp(op: string): boolean;
5
6
  export declare function isIndexOp(op: string): boolean;
@@ -22,7 +22,10 @@ const OP_KINDS = {
22
22
  rollbackTransaction: "control",
23
23
  };
24
24
  export function getOpKind(op) {
25
- return OP_KINDS[op] ?? "write";
25
+ return Object.hasOwn(OP_KINDS, op) ? OP_KINDS[op] : "write";
26
+ }
27
+ export function isKnownOp(op) {
28
+ return Object.hasOwn(OP_KINDS, op);
26
29
  }
27
30
  export function isMutation(kind) {
28
31
  return kind === "write" || kind === "delete";
@@ -1,3 +1,4 @@
1
+ import { warnDeprecated } from "./deprecation.js";
1
2
  import { hasFieldsAdvanced } from "./hasFieldsAdvanced.js";
2
3
  import { updateFindObject } from "./updateFindObject.js";
3
4
  import { updateObjectAdvanced } from "./updateObject.js";
@@ -10,8 +11,11 @@ export function matchObj(config, obj) {
10
11
  const { search, context } = config;
11
12
  if (search === undefined || search === null)
12
13
  return true;
13
- if (typeof search === "function" && search(obj, context))
14
- return true;
14
+ if (typeof search === "function") {
15
+ warnDeprecated("VALTHERA_FUNCTION_SEARCH", "Function-based search is deprecated; use operator objects or a plugin.");
16
+ if (search(obj, context))
17
+ return true;
18
+ }
15
19
  if (typeof search === "object" &&
16
20
  !Array.isArray(search) &&
17
21
  hasFieldsAdvanced(obj, search))
@@ -28,6 +32,7 @@ export function updateObj(config, obj) {
28
32
  return obj;
29
33
  }
30
34
  if (typeof updater === "function") {
35
+ warnDeprecated("VALTHERA_FUNCTION_UPDATER", "Function-based updater is deprecated; use operator objects.");
31
36
  const updateObjValue = updater(obj, context);
32
37
  if (updateObjValue)
33
38
  return updateObjValue;
@@ -0,0 +1,2 @@
1
+ export declare function isUnsafeKey(key: string): boolean;
2
+ export declare function hasUnsafeSegment(path: string[]): boolean;
@@ -0,0 +1,11 @@
1
+ const UNSAFE_KEYS = new Set([
2
+ "__proto__",
3
+ "constructor",
4
+ "prototype",
5
+ ]);
6
+ export function isUnsafeKey(key) {
7
+ return UNSAFE_KEYS.has(key);
8
+ }
9
+ export function hasUnsafeSegment(path) {
10
+ return path.some(segment => isUnsafeKey(segment));
11
+ }
@@ -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 || typeof src !== "object" || !(segs[i] in src)) {
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 in dst) ||
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" in fields))
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.assign(item, updater);
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 in deepObj))
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 in deepObj))
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 in deepObj) {
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 in deepObj)
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
- obj[key] = newVal[key];
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.12";
1
+ export declare const version = "0.12.14";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = "0.12.12";
1
+ export const version = "0.12.14";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wxn0brp/db-core",
3
- "version": "0.12.12",
3
+ "version": "0.12.14",
4
4
  "types": "dist/index.d.ts",
5
5
  "main": "dist/index.js",
6
6
  "author": "wxn0brP",