@wxn0brp/vql 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,12 @@
1
1
  import { checkRequestPermission } from "../permissions/index.js";
2
2
  import { parseSelect } from "./utils.js";
3
+ import { LowAdapter } from "../helpers/lowAdapter.js";
3
4
  export async function executeQuery(cpu, query, user) {
4
5
  if (!query.db || !cpu.dbInstances[query.db])
5
6
  return { err: true, msg: `Invalid query - db "${query.db || "undefined"}" not found`, c: 400 };
6
7
  const db = cpu.dbInstances[query.db];
8
+ if (db instanceof LowAdapter)
9
+ return await db.resolver(query, user);
7
10
  const operation = Object.keys(query.d)[0];
8
11
  if (!cpu.config.noCheckPermissions && !await checkRequestPermission(cpu.config, cpu.permValidFn, user, query)) {
9
12
  return { err: true, msg: "Permission denied", c: 403 };
@@ -51,6 +54,10 @@ export async function executeQuery(cpu, query, user) {
51
54
  opts.id_gen = params.id_gen;
52
55
  return db.updateOneOrAdd(params.collection, params.search, params.updater, opts);
53
56
  }
57
+ else if (operation === "toggleOne") {
58
+ const params = query.d[operation];
59
+ return db.toggleOne(params.collection, params.search, params.data);
60
+ }
54
61
  else if (operation === "removeCollection") {
55
62
  const params = query.d[operation];
56
63
  return db.removeCollection(params.collection);
@@ -0,0 +1 @@
1
+ export declare function parseArgs(input: string): Record<string, any>;
@@ -0,0 +1,102 @@
1
+ export function parseArgs(input) {
2
+ const result = {};
3
+ const tokens = [];
4
+ let current = "";
5
+ let inQuotes = false;
6
+ let escape = false;
7
+ let objTree = 0;
8
+ for (let i = 0; i < input.length; i++) {
9
+ const char = input[i];
10
+ if (escape) {
11
+ current += char;
12
+ escape = false;
13
+ }
14
+ else if (char === "\\") {
15
+ escape = true;
16
+ }
17
+ else if (!inQuotes && (char === "{" || char === "[")) {
18
+ objTree++;
19
+ current += char;
20
+ }
21
+ else if (!inQuotes && (char === "}" || char === "]")) {
22
+ objTree--;
23
+ current += char;
24
+ if (objTree === 0) {
25
+ tokens.push(current);
26
+ current = "";
27
+ }
28
+ }
29
+ else if (!objTree && (char === "'" || char === '"' || char === "`")) {
30
+ if (inQuotes === char) {
31
+ inQuotes = false;
32
+ tokens.push(`"` + current + `"`);
33
+ current = "";
34
+ }
35
+ else if (typeof inQuotes === "boolean") {
36
+ inQuotes = char;
37
+ }
38
+ else {
39
+ current += char;
40
+ }
41
+ }
42
+ else if (!inQuotes && (char === " " || char === "=" || char === "<" || char === ">")) {
43
+ if (current !== "") {
44
+ if (char === "<" || char === ">") {
45
+ let type = char === ">" ? "gt" : "lt";
46
+ if (i < input.length - 1 && input[i + 1] === "=") {
47
+ type += "e";
48
+ i++;
49
+ }
50
+ const split = current.split(".");
51
+ if (split.length > 1) {
52
+ const original = split.shift();
53
+ const operation = "$" + type;
54
+ split.unshift(operation);
55
+ split.unshift(original);
56
+ current = split.join(".");
57
+ }
58
+ else {
59
+ current = "$" + type + "." + current;
60
+ }
61
+ }
62
+ tokens.push(current);
63
+ current = "";
64
+ }
65
+ }
66
+ else {
67
+ current += char;
68
+ }
69
+ }
70
+ if (current !== "")
71
+ tokens.push(current);
72
+ for (let i = 0; i < tokens.length; i += 2) {
73
+ const key = tokens[i];
74
+ let value = tokens[i + 1] ?? true;
75
+ if (typeof value === "string") {
76
+ const trimmed = value.trim();
77
+ if (trimmed === "") {
78
+ value = true;
79
+ }
80
+ else if (/^".*"$/.test(trimmed)) {
81
+ value = trimmed.slice(1, -1);
82
+ }
83
+ else if (trimmed.toLowerCase() === "true") {
84
+ value = true;
85
+ }
86
+ else if (trimmed.toLowerCase() === "false") {
87
+ value = false;
88
+ }
89
+ else if (!isNaN(Number(trimmed))) {
90
+ value = Number(trimmed);
91
+ }
92
+ else if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
93
+ try {
94
+ value = JSON.parse(trimmed);
95
+ }
96
+ catch { }
97
+ }
98
+ }
99
+ result[key] = value;
100
+ }
101
+ return result;
102
+ }
@@ -0,0 +1,2 @@
1
+ import { VQL_Query } from "../../types/vql.js";
2
+ export declare function buildVQL(db: string, op: string, collection: string, query: Record<string, any>): VQL_Query;
@@ -0,0 +1,44 @@
1
+ import { convertSearchObjToSearchArray } from "./utils.js";
2
+ export function buildVQL(db, op, collection, query) {
3
+ return "relations" in query ?
4
+ buildRelation(db, collection, query) :
5
+ buildQuery(db, op, collection, query);
6
+ }
7
+ function buildRelation(db, collection, query) {
8
+ const relations = {};
9
+ for (const key in query.relations) {
10
+ const value = query.relations[key];
11
+ relations[key] = {
12
+ path: [value.db || db, value.c || key],
13
+ ...value
14
+ };
15
+ delete relations[key].db;
16
+ delete relations[key].c;
17
+ }
18
+ if ("select" in query)
19
+ query.select = convertSearchObjToSearchArray(query.select);
20
+ return {
21
+ r: {
22
+ path: [db, collection],
23
+ ...query,
24
+ relations,
25
+ }
26
+ };
27
+ }
28
+ function buildQuery(db, op, collection, query) {
29
+ if (query.fields && !query.select) {
30
+ query.select = query.fields;
31
+ delete query.fields;
32
+ }
33
+ if ("select" in query)
34
+ query.select = [...new Set(convertSearchObjToSearchArray(query.select).map(k => k[0]).flat())];
35
+ return {
36
+ db,
37
+ d: {
38
+ [op]: {
39
+ collection,
40
+ ...query,
41
+ }
42
+ }
43
+ };
44
+ }
@@ -1,4 +1,6 @@
1
- import { convertSearchObjToSearchArray, extractMeta } from "./utils.js";
1
+ import { parseArgs } from "./args.js";
2
+ import { buildVQL } from "./build.js";
3
+ import { extractMeta } from "./utils.js";
2
4
  const aliases = {
3
5
  s: "search",
4
6
  f: "fields",
@@ -8,152 +10,6 @@ const aliases = {
8
10
  e: "select",
9
11
  u: "updater",
10
12
  };
11
- function parseArgs(input) {
12
- const result = {};
13
- const tokens = [];
14
- let current = "";
15
- let inQuotes = false;
16
- let escape = false;
17
- let objTree = 0;
18
- for (let i = 0; i < input.length; i++) {
19
- const char = input[i];
20
- if (escape) {
21
- current += char;
22
- escape = false;
23
- }
24
- else if (char === "\\") {
25
- escape = true;
26
- }
27
- else if (!inQuotes && (char === "{" || char === "[")) {
28
- objTree++;
29
- current += char;
30
- }
31
- else if (!inQuotes && (char === "}" || char === "]")) {
32
- objTree--;
33
- current += char;
34
- if (objTree === 0) {
35
- tokens.push(current);
36
- current = "";
37
- }
38
- }
39
- else if (!objTree && (char === "'" || char === '"' || char === "`")) {
40
- if (inQuotes === char) {
41
- inQuotes = false;
42
- tokens.push(`"` + current + `"`);
43
- current = "";
44
- }
45
- else if (typeof inQuotes === "boolean") {
46
- inQuotes = char;
47
- }
48
- else {
49
- current += char;
50
- }
51
- }
52
- else if (!inQuotes && (char === " " || char === "=" || char === "<" || char === ">")) {
53
- if (current !== "") {
54
- if (char === "<" || char === ">") {
55
- let type = char === ">" ? "gt" : "lt";
56
- if (i < input.length - 1 && input[i + 1] === "=") {
57
- type += "e";
58
- i++;
59
- }
60
- const split = current.split(".");
61
- if (split.length > 1) {
62
- const original = split.shift();
63
- const operation = "$" + type;
64
- split.unshift(operation);
65
- split.unshift(original);
66
- current = split.join(".");
67
- }
68
- else {
69
- current = "$" + type + "." + current;
70
- }
71
- }
72
- tokens.push(current);
73
- current = "";
74
- }
75
- }
76
- else {
77
- current += char;
78
- }
79
- }
80
- if (current !== "") {
81
- tokens.push(current);
82
- }
83
- for (let i = 0; i < tokens.length; i += 2) {
84
- const key = tokens[i];
85
- let value = tokens[i + 1] ?? true;
86
- if (typeof value === "string") {
87
- const trimmed = value.trim();
88
- if (trimmed === "") {
89
- value = true;
90
- }
91
- else if (/^".*"$/.test(trimmed)) {
92
- value = trimmed.slice(1, -1);
93
- }
94
- else if (trimmed.toLowerCase() === "true") {
95
- value = true;
96
- }
97
- else if (trimmed.toLowerCase() === "false") {
98
- value = false;
99
- }
100
- else if (!isNaN(Number(trimmed))) {
101
- value = Number(trimmed);
102
- }
103
- else if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
104
- try {
105
- value = JSON.parse(trimmed);
106
- }
107
- catch { }
108
- }
109
- }
110
- result[key] = value;
111
- }
112
- return result;
113
- }
114
- function buildVQL(db, op, collection, query) {
115
- const hasRelations = "relations" in query;
116
- if (hasRelations) {
117
- const relations = {};
118
- for (const key in query.relations) {
119
- const value = query.relations[key];
120
- relations[key] = {
121
- path: [value.db || db, value.c || key],
122
- ...value
123
- };
124
- delete relations[key].db;
125
- delete relations[key].c;
126
- }
127
- if ("select" in query) {
128
- query.select = convertSearchObjToSearchArray(query.select);
129
- }
130
- return {
131
- r: {
132
- path: [db, collection],
133
- ...query,
134
- relations,
135
- }
136
- };
137
- }
138
- else {
139
- if (query.fields && !query.select) {
140
- query.select = query.fields;
141
- delete query.fields;
142
- }
143
- if ("select" in query) {
144
- query.select = [...new Set(convertSearchObjToSearchArray(query.select).map(k => k[0]).flat())];
145
- }
146
- return {
147
- db,
148
- d: {
149
- [op]: {
150
- collection,
151
- ...query,
152
- }
153
- }
154
- };
155
- }
156
- }
157
13
  export function parseVQLS(query) {
158
14
  const { db, op, collection, body } = extractMeta(query);
159
15
  const parsed = parseArgs(body);
@@ -167,7 +23,9 @@ export function parseVQLS(query) {
167
23
  const key = keys[i];
168
24
  if (i < keys.length - 1) {
169
25
  if (!(key in obj)) {
170
- obj[key] = {};
26
+ const nextKey = keys[i + 1];
27
+ const isArrayIndex = /^\d+$/.test(nextKey);
28
+ obj[key] = isArrayIndex ? [] : {};
171
29
  }
172
30
  obj = obj[key];
173
31
  }
@@ -2,13 +2,15 @@ const opPrefix = {
2
2
  "-": "remove",
3
3
  "+": "add",
4
4
  "~": "update",
5
+ "?": "updateOneOrAdd",
6
+ "^": "toggleOne"
5
7
  };
6
8
  const operations = [
7
9
  "add",
8
10
  "find", "findOne",
9
11
  "update", "updateOne",
10
12
  "remove", "removeOne",
11
- "updateOneOrAdd",
13
+ "updateOneOrAdd", "toggleOne",
12
14
  "ensureCollection", "issetCollection",
13
15
  ];
14
16
  /**
@@ -55,7 +57,7 @@ export function extractMeta(input) {
55
57
  let collection = "";
56
58
  let body = "";
57
59
  // Determine operation and collection based on special characters
58
- if (["!", "-", "+", "~"].some(c => split[0].includes(c)) || // Check if operation is indicated by special character
60
+ if (["!", "-", "+", "~", "?", "^"].some(c => split[0].includes(c)) || // Check if operation is indicated by special character
59
61
  [".", ":", "{"].some(c => split[1].includes(c)) // Check if query body is indicated by special character
60
62
  ) {
61
63
  let temp = split.shift();
@@ -93,7 +95,7 @@ export function convertSearchObjToSearchArray(obj, parentKeys = []) {
93
95
  if (!value) {
94
96
  return acc;
95
97
  }
96
- else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
98
+ else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
97
99
  return [...acc, ...convertSearchObjToSearchArray(value, currentPath)];
98
100
  }
99
101
  else {
@@ -1,3 +1,4 @@
1
+ import { VQL_Query_CRUD_Keys } from "../types/vql.js";
1
2
  import { ValtheraCompatible } from "@wxn0brp/db-core";
2
3
  type ResolverFn<TArgs extends any[] = any[], TReturn = any> = (...args: TArgs) => Promise<TReturn>;
3
4
  export interface ValtheraResolverMeta {
@@ -18,47 +19,42 @@ export interface ValtheraResolver {
18
19
  find?: ResolverFn<[
19
20
  collection: string,
20
21
  search: any,
21
- context?: any,
22
22
  options?: any,
23
- findOpts?: any
23
+ findOpts?: any,
24
+ context?: any
24
25
  ], any[]>;
25
26
  findOne?: ResolverFn<[
26
27
  collection: string,
27
28
  search: any,
28
- context?: any,
29
- findOpts?: any
29
+ findOpts?: any,
30
+ context?: any
30
31
  ], any | null>;
31
32
  update?: ResolverFn<[collection: string, search: any, updater: any, context?: any], boolean>;
32
33
  updateOne?: ResolverFn<[collection: string, search: any, updater: any, context?: any], boolean>;
33
- updateOneOrAdd?: ResolverFn<[
34
- collection: string,
35
- search: any,
36
- updater: any,
37
- add_arg?: any,
38
- context?: any,
39
- id_gen?: boolean
40
- ], boolean>;
34
+ updateOneOrAdd?: ResolverFn<[collection: string, search: any, updater: any, opts?: any], boolean>;
35
+ toggleOne?: ResolverFn<[collection: string, search: any, data?: any, context?: any], boolean>;
41
36
  remove?: ResolverFn<[collection: string, search: any, context?: any], boolean>;
42
37
  removeOne?: ResolverFn<[collection: string, search: any, context?: any], boolean>;
43
38
  removeCollection?: ResolverFn<[collection: string], boolean>;
44
39
  }
40
+ export type Operation = Exclude<VQL_Query_CRUD_Keys, "f">;
45
41
  export declare function createValtheraAdapter(resolver: ValtheraResolver, extendedFind?: boolean): ValtheraCompatible;
46
- export type Operation = "add" | "find" | "findOne" | "update" | "updateOne" | "updateOneOrAdd" | "remove" | "removeOne" | "removeCollection";
47
42
  export declare class AdapterBuilder {
48
43
  private catchCb;
49
44
  private handlers;
50
45
  private collections;
51
46
  constructor(catchCb?: (e: any, op: string, args: any[]) => void);
52
47
  register(op: Operation, collection: string, fn: Function): this;
53
- add(collection: string, fn: ResolverFn<[data: any, id_gen?: boolean], any>): this;
54
- find(collection: string, fn: ResolverFn<[search: any, context?: any, options?: any, findOpts?: any], any[]>): this;
55
- findOne(collection: string, fn: ResolverFn<[search: any, context?: any, findOpts?: any], any | null>): this;
56
- update(collection: string, fn: ResolverFn<[collection: string, search: any, updater: any], boolean>): this;
57
- updateOne(collection: string, fn: ResolverFn<[search: any, updater: any], boolean>): this;
58
- updateOneOrAdd(collection: string, fn: ResolverFn<[search: any, updater: any, add_arg?: any, context?: any, id_gen?: boolean], boolean>): this;
59
- remove(collection: string, fn: ResolverFn<[search: any], boolean>): this;
60
- removeOne(collection: string, fn: ResolverFn<[search: any], boolean>): this;
61
- removeCollection(collection: string, fn: ResolverFn<[], boolean>): this;
48
+ add(collection: string, fn: ValtheraResolver["add"]): this;
49
+ find(collection: string, fn: ValtheraResolver["find"]): this;
50
+ findOne(collection: string, fn: ValtheraResolver["findOne"]): this;
51
+ update(collection: string, fn: ValtheraResolver["update"]): this;
52
+ updateOne(collection: string, fn: ValtheraResolver["updateOne"]): this;
53
+ updateOneOrAdd(collection: string, fn: ValtheraResolver["updateOneOrAdd"]): this;
54
+ toggleOne(collection: string, fn: ValtheraResolver["toggleOne"]): this;
55
+ remove(collection: string, fn: ValtheraResolver["remove"]): this;
56
+ removeOne(collection: string, fn: ValtheraResolver["removeOne"]): this;
57
+ removeCollection(collection: string, fn: ValtheraResolver["removeCollection"]): this;
62
58
  getAdapter(extendedFind?: boolean): ValtheraCompatible;
63
59
  }
64
60
  export {};
@@ -4,7 +4,8 @@ const list = [
4
4
  "ensureCollection", "issetCollection", "getCollections", "removeCollection",
5
5
  "add",
6
6
  "find", "findOne",
7
- "update", "updateOne", "updateOneOrAdd",
7
+ "update", "updateOne",
8
+ "updateOneOrAdd", "toggleOne",
8
9
  "remove", "removeOne"
9
10
  ];
10
11
  export function createValtheraAdapter(resolver, extendedFind = false) {
@@ -26,8 +27,8 @@ export function createValtheraAdapter(resolver, extendedFind = false) {
26
27
  let data = await safe(resolver.find)(col, search, options, findOpts, context);
27
28
  if (options?.reverse)
28
29
  data.reverse();
29
- if (options?.max !== -1 && data.length > options?.max)
30
- data = data.slice(0, options?.max);
30
+ if (options?.limit !== -1 && data.length > options?.limit)
31
+ data = data.slice(0, options?.limit);
31
32
  data = data.map(d => updateFindObject(d, findOpts || {}));
32
33
  return data;
33
34
  };
@@ -70,6 +71,9 @@ export class AdapterBuilder {
70
71
  updateOneOrAdd(collection, fn) {
71
72
  return this.register("updateOneOrAdd", collection, fn);
72
73
  }
74
+ toggleOne(collection, fn) {
75
+ return this.register("toggleOne", collection, fn);
76
+ }
73
77
  remove(collection, fn) {
74
78
  return this.register("remove", collection, fn);
75
79
  }
@@ -103,9 +107,8 @@ export class AdapterBuilder {
103
107
  return true;
104
108
  },
105
109
  }, extendedFind);
106
- for (const name of ["add", "find", "findOne", "update", "updateOne", "updateOneOrAdd", "remove", "removeOne", "removeCollection"]) {
110
+ for (const name of list.slice(4))
107
111
  adapter[name] = (...args) => resolve(name, ...args);
108
- }
109
112
  return adapter;
110
113
  }
111
114
  }
@@ -0,0 +1,6 @@
1
+ import { VQL_Query_CRUD } from "../types/vql.js";
2
+ export type LowResolver = (query: VQL_Query_CRUD, user: any) => Promise<any> | any;
3
+ export declare class LowAdapter {
4
+ resolver: LowResolver;
5
+ constructor(resolver: LowResolver);
6
+ }
@@ -0,0 +1,6 @@
1
+ export class LowAdapter {
2
+ resolver;
3
+ constructor(resolver) {
4
+ this.resolver = resolver;
5
+ }
6
+ }
@@ -22,6 +22,7 @@ function replaceVariables(obj, variables) {
22
22
  export function replaceVars(query, user) {
23
23
  query.var = {
24
24
  _me: user?.id || user?._id || user,
25
+ _user: user,
25
26
  _now: Date.now(),
26
27
  _nowShort: Math.floor(Date.now() / 1000),
27
28
  __now: Date.now().toString(),
@@ -131,6 +131,15 @@ function validD(query) {
131
131
  return emptyErr("'updateOneOrAdd' operation 'id_gen' property must be a boolean.");
132
132
  return true;
133
133
  }
134
+ if (key === "toggleOne") {
135
+ const op = d.toggleOne;
136
+ if (!isObj(op.search, false))
137
+ return emptyErr("'toggleOne' operation requires a 'search' object.");
138
+ if ("data" in op && !isObj(op.data, false))
139
+ return emptyErr("'toggleOne' operation 'data' property must be an object.");
140
+ return true;
141
+ }
142
+ const n = key;
134
143
  return emptyErr(`Unknown or invalid CRUD operation: '${key}'`);
135
144
  }
136
145
  export function validateVql(query) {
@@ -34,12 +34,19 @@ export async function extractPaths(config, query) {
34
34
  permPaths.paths.push({ filed: extractPathsFromData(qo.search), p: PermCRUD.READ });
35
35
  permPaths.paths.push({ filed: extractPathsFromData(qo.updater), p: PermCRUD.UPDATE });
36
36
  break;
37
+ case "toggleOne":
38
+ permPaths.paths.push({ c: PermCRUD.DELETE });
39
+ permPaths.paths.push({ c: PermCRUD.CREATE });
40
+ break;
37
41
  case "ensureCollection":
38
42
  case "getCollections":
39
43
  case "issetCollection":
40
44
  case "removeCollection":
41
45
  permPaths.paths.push({ c: PermCRUD.COLLECTION });
42
46
  break;
47
+ default:
48
+ const n = operation;
49
+ break;
43
50
  }
44
51
  permPaths.paths = (await Promise.all(permPaths.paths.map(async (path) => {
45
52
  if (!path.filed)
@@ -39,6 +39,11 @@ export interface VQL_OP_UpdateOneOrAdd<T = any> {
39
39
  add_arg?: Arg<T>;
40
40
  id_gen?: boolean;
41
41
  }
42
+ export interface VQL_OP_ToggleOne<T = any> {
43
+ collection: string;
44
+ search: Search<T>;
45
+ data?: Arg<T>;
46
+ }
42
47
  export interface VQL_OP_CollectionOperation {
43
48
  collection: string;
44
49
  }
@@ -61,6 +66,8 @@ export type VQL_Query_CRUD_Data<T = any> = {
61
66
  removeOne: VQL_OP_Remove<T>;
62
67
  } | {
63
68
  updateOneOrAdd: VQL_OP_UpdateOneOrAdd<T>;
69
+ } | {
70
+ toggleOne: VQL_OP_ToggleOne<T>;
64
71
  } | {
65
72
  removeCollection: VQL_OP_CollectionOperation;
66
73
  } | {
@@ -70,6 +77,7 @@ export type VQL_Query_CRUD_Data<T = any> = {
70
77
  } | {
71
78
  getCollections: {};
72
79
  };
80
+ export type VQL_Query_CRUD_Keys = VQL_Query_CRUD_Data extends infer U ? U extends Record<string, unknown> ? keyof U : never : never;
73
81
  export interface VQL_Query_CRUD<T = any> {
74
82
  db: string;
75
83
  d: VQL_Query_CRUD_Data<T>;
package/dist/vql.d.ts CHANGED
@@ -9,8 +9,14 @@ export interface VContext {
9
9
  export type KeysMatching<T, V, C = V> = {
10
10
  [K in keyof T]-?: T[K] extends C ? K : never;
11
11
  }[keyof T];
12
- export type PartialOfType<T, V, C = V> = Partial<Record<KeysMatching<T, V, C>, V>>;
13
- export type PartialPickMatching<T, V, C = V> = Partial<Pick<T, KeysMatching<T, V, C>>>;
12
+ /** Helper type for nested path values with type filtering */
13
+ export type NestedValue<T, V, C = V> = {
14
+ [K in keyof T as T[K] extends C ? K : T[K] extends object ? K : never]?: T[K] extends C ? V : T[K] extends object ? NestedValue<T[K], V, C> : never;
15
+ };
16
+ /** Helper type for nested path structure */
17
+ export type DeepPartial<T> = {
18
+ [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
19
+ };
14
20
  /** Logical Operators */
15
21
  export type LogicalOperators<T = any> = {
16
22
  /**
@@ -29,66 +35,110 @@ export type LogicalOperators<T = any> = {
29
35
  */
30
36
  $not?: SearchOptions<T>;
31
37
  };
32
- /** Comparison Operators */
38
+ /** Comparison Operators with nested support */
33
39
  export type ComparisonOperators<T = any> = {
34
- $gt?: PartialOfType<T, number>;
35
- $lt?: PartialOfType<T, number>;
36
- $gte?: PartialOfType<T, number>;
37
- $lte?: PartialOfType<T, number>;
38
- $between?: PartialOfType<T, [
40
+ /** 5 > 4 */
41
+ $gt?: NestedValue<T, number, number>;
42
+ /** 5 < 4 */
43
+ $lt?: NestedValue<T, number, number>;
44
+ /** 5 >= 4 */
45
+ $gte?: NestedValue<T, number, number>;
46
+ /** 5 <= 4 */
47
+ $lte?: NestedValue<T, number, number>;
48
+ /** 5 between [min, max] */
49
+ $between?: NestedValue<T, [
39
50
  number,
40
51
  number
41
52
  ], number>;
42
- $in?: Partial<Record<keyof T, T[keyof T][]>>;
43
- $nin?: Partial<Record<keyof T, T[keyof T][]>>;
53
+ /** 2 in [1, 2, 3] */
54
+ $in?: DeepPartial<T> & {
55
+ [K in keyof T]?: T[K] extends any[] ? T[K] : T[K][];
56
+ };
57
+ /** 5 not in [1, 2, 3] */
58
+ $nin?: DeepPartial<T> & {
59
+ [K in keyof T]?: T[K] extends any[] ? T[K] : T[K][];
60
+ };
61
+ /** id > 4 */
62
+ $idGt?: NestedValue<T, string | number, string | number>;
63
+ /** id < 4 */
64
+ $idLt?: NestedValue<T, string | number, string | number>;
65
+ /** id >= 4 */
66
+ $idGte?: NestedValue<T, string | number, string | number>;
67
+ /** id <= 4 */
68
+ $idLte?: NestedValue<T, string | number, string | number>;
44
69
  };
45
- /** Type and Existence Operators */
70
+ /** Type and Existence Operators with nested support */
46
71
  export type TypeAndExistenceOperators<T = any> = {
47
- $exists?: PartialOfType<T, boolean, any>;
48
- $type?: PartialOfType<T, string>;
72
+ /** "name" in { name: "John" } */
73
+ $exists?: NestedValue<T, boolean>;
74
+ /** "name" == "string" in { name: "John" } */
75
+ $type?: NestedValue<T, string>;
49
76
  };
50
- /** Array Operators */
77
+ /** Array Operators with nested support */
51
78
  export type ArrayOperators<T = any> = {
52
- $arrinc?: PartialPickMatching<T, any[]>;
53
- $arrincall?: PartialPickMatching<T, any[]>;
54
- $size?: PartialOfType<T, number>;
79
+ /** [1, 2, 3] includes 2 */
80
+ $arrinc?: DeepPartial<T>;
81
+ /** [1, 2, 3] array includes all elements e.g. [1, 2] */
82
+ $arrincall?: DeepPartial<T>;
83
+ /** [1, 2, 3] has size 3 */
84
+ $size?: NestedValue<T, number>;
55
85
  };
56
- /** String Operators */
86
+ /** String Operators with nested support */
57
87
  export type StringOperators<T = any> = {
58
- $regex?: PartialOfType<T, RegExp | string, string>;
59
- $startsWith?: PartialOfType<T, string>;
60
- $endsWith?: PartialOfType<T, string>;
88
+ /** "John" matches /oh/ */
89
+ $regex?: NestedValue<T, RegExp | string, string>;
90
+ /** "John" starts with "Jo" */
91
+ $startsWith?: NestedValue<T, string, string>;
92
+ /** "John" ends with "hn" */
93
+ $endsWith?: NestedValue<T, string, string>;
61
94
  };
62
- /** Other Operators */
95
+ /** Other Operators with nested support */
63
96
  export type OtherOperators<T = any> = {
64
- $subset?: Partial<Record<keyof T, T[keyof T]>>;
97
+ /** { $type: "name" } matches { $type: "name" } literally - Ignore $ operators */
98
+ $subset?: DeepPartial<T>;
65
99
  };
66
100
  /** Predefined Search Operators */
67
101
  export type PredefinedSearchOperators<T = any> = LogicalOperators<T> & ComparisonOperators<T> & TypeAndExistenceOperators<T> & ArrayOperators<T> & StringOperators<T> & OtherOperators<T>;
68
102
  /**
69
103
  * SearchOptions can be either a function or an object with predefined operators.
70
104
  */
71
- export type SearchOptions<T = any> = PredefinedSearchOperators<T> & Arg<T>;
105
+ export type SearchOptions<T = any> = PredefinedSearchOperators<T> & DeepPartial<T> & Record<string, any>;
72
106
  /** Arrays */
73
107
  export type ArrayUpdater<T = any> = {
74
- $push?: PartialOfType<T, any>;
75
- /** Pushes items into an array and removes duplicates */
76
- $pushset?: PartialOfType<T, any>;
77
- $pull?: PartialOfType<T, any>;
78
- $pullall?: PartialOfType<T, any>;
108
+ /** [1,2] -> $push 3 -> [1,2,3] */
109
+ $push?: NestedValue<T, any>;
110
+ /** [1,2] -> $pushset 2,3 -> [1,2,3] */
111
+ $pushset?: NestedValue<T, any>;
112
+ /** [1,2,3] -> $pull 2 -> [1,3] */
113
+ $pull?: NestedValue<T, any>;
114
+ /** [1,2,2,3] -> $pullall [2] -> [1,3] */
115
+ $pullall?: NestedValue<T, any>;
79
116
  };
80
117
  /** Objects */
81
118
  export type ObjectUpdater<T = any> = {
82
- $merge?: PartialOfType<T, any[]>;
119
+ /** { a: 1 } -> $merge { b: 2 } -> { a: 1, b: 2 } */
120
+ $merge?: NestedValue<T, any>;
121
+ /** { a: { x: 1 } } -> $deepMerge { a: { y: 2 } } -> { a: { x: 1, y: 2 } } */
122
+ $deepMerge?: NestedValue<T, any>;
83
123
  };
84
124
  /** Values */
85
125
  export type ValueUpdater<T = any> = {
86
- $inc?: PartialOfType<T, number>;
87
- $dec?: PartialOfType<T, number>;
88
- $unset?: PartialOfType<T, any>;
89
- $rename?: PartialOfType<T, any>;
126
+ /** { count: 1 } -> $inc 2 -> { count: 3 } */
127
+ $inc?: NestedValue<T, number>;
128
+ /** { count: 5 } -> $dec 2 -> { count: 3 } */
129
+ $dec?: NestedValue<T, number>;
130
+ /** { name: "John" } -> $unset "name" -> {} */
131
+ $unset?: NestedValue<T, any>;
132
+ /** { oldName: "value" } -> $rename "oldName" to "newName" -> { newName: "value" } */
133
+ $rename?: NestedValue<T, any>;
134
+ /**
135
+ * {} -> $set { name: "John" } -> { name: "John" }
136
+ *
137
+ * Note: same as { name: value }
138
+ */
139
+ $set?: NestedValue<T, any>;
90
140
  };
91
- export type UpdaterArg<T = any> = ArrayUpdater<T> & ObjectUpdater<T> & ValueUpdater<T> & Arg<T>;
141
+ export type UpdaterArg<T = any> = ArrayUpdater<T> & ObjectUpdater<T> & ValueUpdater<T> & DeepPartial<T> & Record<string, any>;
92
142
  export type Arg<T = any> = {
93
143
  [K in keyof T]?: any;
94
144
  } & Record<string, any>;
@@ -98,7 +148,7 @@ export type Search<T = any> = SearchOptions<T> | SearchFunc<T>;
98
148
  export type Updater<T = any> = UpdaterArg<T> | UpdaterArg<T>[] | UpdaterFunc<T>;
99
149
  export interface DbFindOpts<T = any> {
100
150
  reverse?: boolean;
101
- max?: number;
151
+ limit?: number;
102
152
  offset?: number;
103
153
  sortBy?: KeysMatching<T, any>;
104
154
  sortAsc?: boolean;
@@ -119,31 +169,35 @@ declare class CollectionManager<D = Data> {
119
169
  /**
120
170
  * Find data in a database.
121
171
  */
122
- find<T = Data>(search?: Search<T & D>, options?: DbFindOpts<T & Data>, findOpts?: FindOpts<T & Data>, context?: VContext): Promise<T[]>;
172
+ find<T = Data>(search?: Search<T & D>, options?: DbFindOpts<T & D>, findOpts?: FindOpts<T & D>, context?: VContext): Promise<T[]>;
123
173
  /**
124
174
  * Find one data entry in a database.
125
175
  */
126
- findOne<T = Data>(search?: Search<T & Data>, findOpts?: FindOpts<T & Data>, context?: VContext): Promise<T>;
176
+ findOne<T = Data>(search?: Search<T & D>, findOpts?: FindOpts<T & D>, context?: VContext): Promise<T>;
127
177
  /**
128
178
  * Update data in a database.
129
179
  */
130
- update<T = Data>(search: Search<T & Data>, updater: Updater<T & Data>, context?: VContext): Promise<boolean>;
180
+ update<T = Data>(search: Search<T & D>, updater: Updater<T & D>, context?: VContext): Promise<boolean>;
131
181
  /**
132
182
  * Update one data entry in a database.
133
183
  */
134
- updateOne<T = Data>(search: Search<T & Data>, updater: Updater<T & Data>, context?: VContext): Promise<boolean>;
184
+ updateOne<T = Data>(search: Search<T & D>, updater: Updater<T & D>, context?: VContext): Promise<boolean>;
135
185
  /**
136
186
  * Remove data from a database.
137
187
  */
138
- remove<T = Data>(search: Search<T & Data>, context?: VContext): Promise<boolean>;
188
+ remove<T = Data>(search: Search<T & D>, context?: VContext): Promise<boolean>;
139
189
  /**
140
190
  * Remove one data entry from a database.
141
191
  */
142
- removeOne<T = Data>(search: Search<T & Data>, context?: VContext): Promise<boolean>;
192
+ removeOne<T = Data>(search: Search<T & D>, context?: VContext): Promise<boolean>;
143
193
  /**
144
194
  * Asynchronously updates one entry in a database or adds a new one if it doesn't exist.
145
195
  */
146
- updateOneOrAdd<T = Data>(search: Search<T & Data>, updater: Updater<T & Data>, { add_arg, context, id_gen }: UpdateOneOrAdd<T & Data>): Promise<boolean>;
196
+ updateOneOrAdd<T = Data>(search: Search<T & D>, updater: Updater<T & D>, { add_arg, context, id_gen }: UpdateOneOrAdd<T & D>): Promise<boolean>;
197
+ /**
198
+ * Asynchronously removes one entry in a database or adds a new one if it doesn't exist. Usage e.g. for toggling a flag.
199
+ */
200
+ toggleOne<T = Data>(search: Search<T & D>, data?: Arg<T & D>, context?: VContext): Promise<boolean>;
147
201
  }
148
202
  export interface ValtheraCompatible {
149
203
  c(collection: string): CollectionManager;
@@ -159,6 +213,7 @@ export interface ValtheraCompatible {
159
213
  removeOne<T = Data>(collection: string, search: Search<T>, context?: VContext): Promise<boolean>;
160
214
  removeCollection(collection: string): Promise<boolean>;
161
215
  updateOneOrAdd<T = Data>(collection: string, search: Search<T>, updater: Updater<T>, opts?: UpdateOneOrAdd<T>): Promise<boolean>;
216
+ toggleOne<T = Data>(collection: string, search: Search<T>, data?: Arg<T>, context?: VContext): Promise<boolean>;
162
217
  }
163
218
  export interface UpdateOneOrAdd<T> {
164
219
  add_arg?: Arg<T>;
@@ -231,6 +286,11 @@ export interface VQL_OP_UpdateOneOrAdd<T = any> {
231
286
  add_arg?: Arg<T>;
232
287
  id_gen?: boolean;
233
288
  }
289
+ export interface VQL_OP_ToggleOne<T = any> {
290
+ collection: string;
291
+ search: Search<T>;
292
+ data?: Arg<T>;
293
+ }
234
294
  export interface VQL_OP_CollectionOperation {
235
295
  collection: string;
236
296
  }
@@ -253,6 +313,8 @@ export type VQL_Query_CRUD_Data<T = any> = {
253
313
  removeOne: VQL_OP_Remove<T>;
254
314
  } | {
255
315
  updateOneOrAdd: VQL_OP_UpdateOneOrAdd<T>;
316
+ } | {
317
+ toggleOne: VQL_OP_ToggleOne<T>;
256
318
  } | {
257
319
  removeCollection: VQL_OP_CollectionOperation;
258
320
  } | {
@@ -262,6 +324,7 @@ export type VQL_Query_CRUD_Data<T = any> = {
262
324
  } | {
263
325
  getCollections: {};
264
326
  };
327
+ export type VQL_Query_CRUD_Keys = VQL_Query_CRUD_Data extends infer U ? U extends Record<string, unknown> ? keyof U : never : never;
265
328
  export interface VQL_Query_CRUD<T = any> {
266
329
  db: string;
267
330
  d: VQL_Query_CRUD_Data<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wxn0brp/vql",
3
- "version": "0.8.3",
3
+ "version": "0.9.0",
4
4
  "main": "dist/index.js",
5
5
  "author": "wxn0brP",
6
6
  "license": "MIT",
@@ -27,9 +27,9 @@
27
27
  "query-language"
28
28
  ],
29
29
  "peerDependencies": {
30
- "@wxn0brp/db-core": ">=0.2.5",
31
- "@wxn0brp/falcon-frame": ">=0.5.1",
32
- "@wxn0brp/gate-warden": ">=0.4.1"
30
+ "@wxn0brp/db-core": ">=0.3.0",
31
+ "@wxn0brp/falcon-frame": ">=0.5.3",
32
+ "@wxn0brp/gate-warden": ">=0.5.0"
33
33
  },
34
34
  "peerDependenciesMeta": {
35
35
  "@wxn0brp/falcon-frame": {
@@ -43,17 +43,18 @@
43
43
  }
44
44
  },
45
45
  "devDependencies": {
46
+ "@types/bun": "*",
46
47
  "@types/node": "*",
47
- "@wxn0brp/db": "^0.40.1",
48
- "@wxn0brp/db-core": "^0.2.5",
49
- "@wxn0brp/falcon-frame": "^0.5.1",
50
- "@wxn0brp/gate-warden": "^0.4.1",
51
- "esbuild": "^0.25.11",
48
+ "@wxn0brp/db": "^0.41.0",
49
+ "@wxn0brp/db-core": "^0.3.0",
50
+ "@wxn0brp/falcon-frame": "^0.5.3",
51
+ "@wxn0brp/gate-warden": "^0.5.0",
52
+ "esbuild": "*",
52
53
  "tsc-alias": "*",
53
54
  "typescript": "*"
54
55
  },
55
56
  "dependencies": {
56
- "@wxn0brp/lucerna-log": "^0.2.1"
57
+ "@wxn0brp/lucerna-log": "^0.2.2"
57
58
  },
58
59
  "exports": {
59
60
  ".": {