@dbcube/query-builder 5.2.2 → 5.2.4

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/index.d.ts CHANGED
@@ -1,5 +1,54 @@
1
1
  import { QueryEngine } from '@dbcube/core';
2
2
 
3
+ export interface JoinCondition {
4
+ column1: string;
5
+ operator: string;
6
+ column2: string;
7
+ }
8
+ export interface Join {
9
+ type: "INNER" | "LEFT" | "RIGHT";
10
+ table: string;
11
+ on: JoinCondition;
12
+ }
13
+ export interface WhereCondition {
14
+ column: string;
15
+ operator: string;
16
+ value?: any;
17
+ type: "AND" | "OR";
18
+ isGroup: boolean;
19
+ }
20
+ export interface GroupedWhereCondition {
21
+ type: "AND" | "OR";
22
+ isGroup: true;
23
+ conditions: WhereCondition[];
24
+ }
25
+ export interface OrderBy {
26
+ column: string;
27
+ direction: "ASC" | "DESC";
28
+ }
29
+ export interface Aggregation {
30
+ type: "COUNT" | "SUM" | "AVG" | "MAX" | "MIN";
31
+ column: string;
32
+ alias: string;
33
+ }
34
+ export interface DML {
35
+ type: "select" | "insert" | "update" | "delete" | "columns";
36
+ database: string;
37
+ table: string;
38
+ columns: string[];
39
+ requestedFields?: string[];
40
+ computedFieldsNeeded?: any[];
41
+ distinct: boolean;
42
+ joins: Join[];
43
+ where: (WhereCondition | GroupedWhereCondition)[];
44
+ orderBy: OrderBy[];
45
+ groupBy: string[];
46
+ limit: number | null;
47
+ offset: number | null;
48
+ data: any[] | Record<string, any> | null;
49
+ aggregation: Aggregation | null;
50
+ having?: (WhereCondition | GroupedWhereCondition)[];
51
+ }
3
52
  export interface PaginatedResult<T = DatabaseRecord> {
4
53
  items: T[];
5
54
  page: number;
@@ -80,6 +129,24 @@ export declare class Database {
80
129
  * });
81
130
  */
82
131
  transaction<T>(callback: (trx: Database) => Promise<T>): Promise<T>;
132
+ /**
133
+ * Atomic batch: every write queued in the callback runs inside ONE
134
+ * transaction with a SINGLE engine round-trip (begin/commit included).
135
+ * The callback is synchronous — writes are collected, not awaited.
136
+ * Any failure rolls back everything.
137
+ *
138
+ * This is the fastest way to run a sequence of writes atomically.
139
+ * For transactions that need to READ intermediate results, use
140
+ * `transaction()` (interactive) instead. Triggers/computed fields do
141
+ * not run in batch mode.
142
+ *
143
+ * @example
144
+ * await db.batch(b => {
145
+ * b.table('accounts').where('id', '=', 1).decrement('balance', 200);
146
+ * b.table('accounts').where('id', '=', 2).increment('balance', 200);
147
+ * });
148
+ */
149
+ batch(builder: (b: Database) => void): Promise<MutationInfo[][]>;
83
150
  useComputes(): Promise<Database>;
84
151
  useTriggers(): Promise<Database>;
85
152
  connect(): Promise<void>;
@@ -143,6 +210,9 @@ export declare class Table<T extends DatabaseRecord = DatabaseRecord> {
143
210
  private relations;
144
211
  /** Builders de grupo mutan en sitio (ver clone()) */
145
212
  private _mutable;
213
+ /** En modo batch (db.batch) las escrituras se encolan aquí en vez de
214
+ * ejecutarse: toda la transacción viaja al engine en UN solo cruce. */
215
+ _batchSink: DML[] | null;
146
216
  private instance;
147
217
  constructor(instance: Database, databaseName: string, tableName: string, engine: QueryEngine, computedFields?: ComputedFieldConfig[], triggers?: TriggerConfig[], txId?: string | null);
148
218
  /**
package/dist/index.js CHANGED
@@ -120,6 +120,39 @@ var Database = class _Database {
120
120
  throw error;
121
121
  }
122
122
  }
123
+ /**
124
+ * Atomic batch: every write queued in the callback runs inside ONE
125
+ * transaction with a SINGLE engine round-trip (begin/commit included).
126
+ * The callback is synchronous — writes are collected, not awaited.
127
+ * Any failure rolls back everything.
128
+ *
129
+ * This is the fastest way to run a sequence of writes atomically.
130
+ * For transactions that need to READ intermediate results, use
131
+ * `transaction()` (interactive) instead. Triggers/computed fields do
132
+ * not run in batch mode.
133
+ *
134
+ * @example
135
+ * await db.batch(b => {
136
+ * b.table('accounts').where('id', '=', 1).decrement('balance', 200);
137
+ * b.table('accounts').where('id', '=', 2).increment('balance', 200);
138
+ * });
139
+ */
140
+ async batch(builder) {
141
+ const ops = [];
142
+ const collector = new _Database(this.name);
143
+ collector.engine = this.engine;
144
+ collector.computedFields = this.computedFields;
145
+ collector.triggers = this.triggers;
146
+ collector._collectInto = ops;
147
+ builder(collector);
148
+ if (ops.length === 0) return [];
149
+ const response = await this.engine.executeBatch(ops);
150
+ if (response.status !== 200) {
151
+ returnFormattedError(response.status, response.message);
152
+ throw new Error(String(response.message));
153
+ }
154
+ return response.data;
155
+ }
123
156
  async useComputes() {
124
157
  const newDatabase = new _Database(this.name);
125
158
  const arrayComputedFields = await ComputedFieldProcessor.getComputedFields(this.name);
@@ -185,7 +218,10 @@ var Database = class _Database {
185
218
  * const columns = await db.table('users').columns().get();
186
219
  */
187
220
  table(tableName) {
188
- return new Table(this, this.name, tableName, this.engine, this.computedFields, this.triggers, this.txId);
221
+ const t = new Table(this, this.name, tableName, this.engine, this.computedFields, this.triggers, this.txId);
222
+ const sink = this._collectInto;
223
+ if (sink) t._batchSink = sink;
224
+ return t;
189
225
  }
190
226
  setComputedFields(computedFields) {
191
227
  this.computedFields = computedFields;
@@ -205,13 +241,16 @@ var Table = class _Table {
205
241
  relations = [];
206
242
  /** Builders de grupo mutan en sitio (ver clone()) */
207
243
  _mutable = false;
244
+ /** En modo batch (db.batch) las escrituras se encolan aquí en vez de
245
+ * ejecutarse: toda la transacción viaja al engine en UN solo cruce. */
246
+ _batchSink = null;
208
247
  instance;
209
248
  constructor(instance, databaseName, tableName, engine, computedFields = [], triggers = [], txId = null) {
210
249
  this.engine = engine;
211
250
  this.instance = instance;
212
251
  this.computedFields = computedFields;
213
252
  this.triggers = triggers;
214
- this.trigger = new Trigger(instance, databaseName, triggers);
253
+ this.trigger = triggers.length > 0 ? new Trigger(instance, databaseName, triggers) : null;
215
254
  this.nextType = "AND";
216
255
  this.txId = txId;
217
256
  this.dml = {
@@ -838,6 +877,30 @@ var Table = class _Table {
838
877
  * console.log(user); // { id: 1, name: 'John' }
839
878
  */
840
879
  async find(value, column = "id") {
880
+ if (this.computedFields.length === 0 && this.relations.length === 0 && !this._mutable) {
881
+ const dml = {
882
+ type: "select",
883
+ database: this.dml.database,
884
+ table: this.dml.table,
885
+ columns: ["*"],
886
+ distinct: false,
887
+ joins: [],
888
+ where: [{ column, operator: "=", value, type: "AND", isGroup: false }],
889
+ orderBy: [],
890
+ groupBy: [],
891
+ limit: 1,
892
+ offset: null,
893
+ data: null,
894
+ aggregation: null,
895
+ having: []
896
+ };
897
+ const response = await this.engine.executeDml(dml, this.txId ?? void 0);
898
+ if (response.status != 200) {
899
+ returnFormattedError(response.status, response.message);
900
+ throw new Error(String(response.message));
901
+ }
902
+ return response.data?.[0] || null;
903
+ }
841
904
  const clone = this.clone().where(column, "=", value);
842
905
  clone.dml.type = "select";
843
906
  clone.dml.data = null;
@@ -864,13 +927,26 @@ var Table = class _Table {
864
927
  * console.log(newUsers); // [{ id: 3, name: 'Alice', age: 28 }, { id: 4, name: 'Bob', age: 32 }]
865
928
  */
866
929
  async insert(data) {
867
- const clone = this.clone();
868
930
  if (!Array.isArray(data)) {
869
931
  throw new Error("The insert method requires an array of objects with key-value pairs.");
870
932
  }
871
933
  if (!data.every((item) => typeof item === "object" && item !== null)) {
872
934
  throw new Error("The array must contain only valid objects.");
873
935
  }
936
+ if (this._batchSink) {
937
+ this._batchSink.push({ ...this.dml, type: "insert", data });
938
+ return void 0;
939
+ }
940
+ if (this.triggers.length === 0 && this.computedFields.length === 0) {
941
+ const dml = { ...this.dml, type: "insert", data };
942
+ const response = await this.engine.executeDml(dml, this.txId ?? void 0);
943
+ if (response.status != 200) {
944
+ returnFormattedError(response.status, response.message);
945
+ throw new Error(String(response.message));
946
+ }
947
+ return response.data ?? data;
948
+ }
949
+ const clone = this.clone();
874
950
  clone.dml.type = "insert";
875
951
  clone.dml.data = data;
876
952
  const result = await clone.getResponse(clone.dml, "Add");
@@ -889,13 +965,26 @@ var Table = class _Table {
889
965
  * console.log(result); // { affectedRows: 1 }
890
966
  */
891
967
  async update(data) {
892
- const clone = this.clone();
893
968
  if (typeof data !== "object" || Array.isArray(data)) {
894
969
  throw new Error("The update method requires an object with key-value pairs.");
895
970
  }
896
- if (clone.dml.where.length === 0) {
971
+ if (this.dml.where.length === 0) {
897
972
  throw new Error("You must specify at least one WHERE condition to perform an update.");
898
973
  }
974
+ if (this._batchSink) {
975
+ this._batchSink.push({ ...this.dml, type: "update", data });
976
+ return void 0;
977
+ }
978
+ if (this.triggers.length === 0 && this.computedFields.length === 0) {
979
+ const dml = { ...this.dml, type: "update", data };
980
+ const response = await this.engine.executeDml(dml, this.txId ?? void 0);
981
+ if (response.status != 200) {
982
+ returnFormattedError(response.status, response.message);
983
+ throw new Error(String(response.message));
984
+ }
985
+ return response.data;
986
+ }
987
+ const clone = this.clone();
899
988
  clone.dml.type = "update";
900
989
  clone.dml.data = data;
901
990
  return clone.getResponse(clone.dml, "Update");
@@ -910,10 +999,23 @@ var Table = class _Table {
910
999
  * console.log(result); // { affectedRows: 1 }
911
1000
  */
912
1001
  async delete() {
913
- const clone = this.clone();
914
- if (clone.dml.where.length === 0) {
1002
+ if (this.dml.where.length === 0) {
915
1003
  throw new Error("You must specify at least one WHERE condition to perform a delete.");
916
1004
  }
1005
+ if (this._batchSink) {
1006
+ this._batchSink.push({ ...this.dml, type: "delete", data: null });
1007
+ return void 0;
1008
+ }
1009
+ if (this.triggers.length === 0 && this.computedFields.length === 0) {
1010
+ const dml = { ...this.dml, type: "delete", data: null };
1011
+ const response = await this.engine.executeDml(dml, this.txId ?? void 0);
1012
+ if (response.status != 200) {
1013
+ returnFormattedError(response.status, response.message);
1014
+ throw new Error(String(response.message));
1015
+ }
1016
+ return response.data;
1017
+ }
1018
+ const clone = this.clone();
917
1019
  clone.dml.type = "delete";
918
1020
  const deleteData = await clone.getResponse(clone.dml, "Delete");
919
1021
  return deleteData;
@@ -1066,12 +1168,26 @@ var Table = class _Table {
1066
1168
  * await db.table('posts').where('id', '=', 1).increment('views', 1, { last_viewed_at: new Date().toISOString() });
1067
1169
  */
1068
1170
  async increment(column, amount = 1, extra = {}) {
1069
- const clone = this.clone();
1070
- if (clone.dml.where.length === 0) {
1171
+ if (this.dml.where.length === 0) {
1071
1172
  throw new Error("You must specify at least one WHERE condition to perform an increment.");
1072
1173
  }
1174
+ const data = { ...extra, [column]: { $inc: Number(amount) } };
1175
+ if (this._batchSink) {
1176
+ this._batchSink.push({ ...this.dml, type: "update", data });
1177
+ return void 0;
1178
+ }
1179
+ if (this.triggers.length === 0 && this.computedFields.length === 0) {
1180
+ const dml = { ...this.dml, type: "update", data };
1181
+ const response = await this.engine.executeDml(dml, this.txId ?? void 0);
1182
+ if (response.status != 200) {
1183
+ returnFormattedError(response.status, response.message);
1184
+ throw new Error(String(response.message));
1185
+ }
1186
+ return response.data;
1187
+ }
1188
+ const clone = this.clone();
1073
1189
  clone.dml.type = "update";
1074
- clone.dml.data = { ...extra, [column]: { $inc: Number(amount) } };
1190
+ clone.dml.data = data;
1075
1191
  return clone.getResponse();
1076
1192
  }
1077
1193
  /**
@@ -1271,8 +1387,8 @@ var Table = class _Table {
1271
1387
  }
1272
1388
  let arrayResult = [];
1273
1389
  if (type) {
1274
- const beffore = this.trigger.get("before" + type);
1275
- const after = this.trigger.get("after" + type);
1390
+ const beffore = this.trigger ? this.trigger.get("before" + type) : void 0;
1391
+ const after = this.trigger ? this.trigger.get("after" + type) : void 0;
1276
1392
  if (this.triggers.length > 0 && (beffore || after)) {
1277
1393
  const dataset = localDML.data;
1278
1394
  for (let index = 0; index < dataset.length; index++) {
@@ -1332,6 +1448,7 @@ var Table = class _Table {
1332
1448
  const cloned = Object.create(Object.getPrototypeOf(this));
1333
1449
  cloned.engine = this.engine;
1334
1450
  cloned.instance = this.instance;
1451
+ cloned._batchSink = this._batchSink;
1335
1452
  cloned.nextType = this.nextType;
1336
1453
  cloned.computedFields = this.computedFields;
1337
1454
  cloned.trigger = this.trigger;
@@ -1440,7 +1557,7 @@ ${color}${BOLD}${message}${RESET}
1440
1557
  (line) => line.includes(".js:") && !line.includes("node_modules")
1441
1558
  );
1442
1559
  if (relevantStackLine) {
1443
- const match = relevantStackLine.match(/\((.*):(\d+):(\d+)\)/) || relevantStackLine.match(/at (.*):(\d+):(\d+)/);
1560
+ const match = relevantStackLine.match(/\((.*):(\d+):(\d+)\)/) || relevantStackLine.match(/at (?:async )?(.*):(\d+):(\d+)/);
1444
1561
  if (match) {
1445
1562
  const [, filePath, lineStr, columnStr] = match;
1446
1563
  const lineNum = parseInt(lineStr, 10);