@dbcube/query-builder 5.1.5 → 5.1.6

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/bun.lockb CHANGED
Binary file
package/dist/index.cjs CHANGED
@@ -38,6 +38,7 @@ module.exports = __toCommonJS(index_exports);
38
38
 
39
39
  // src/lib/Database.ts
40
40
  var import_fs = __toESM(require("fs"));
41
+ var import_path2 = __toESM(require("path"));
41
42
  var import_core2 = require("@dbcube/core");
42
43
 
43
44
  // src/lib/Trigger.ts
@@ -91,6 +92,7 @@ var Database = class _Database {
91
92
  engine;
92
93
  computedFields;
93
94
  triggers;
95
+ txId = null;
94
96
  constructor(name) {
95
97
  this.name = name;
96
98
  const engine = new import_core2.QueryEngine(name);
@@ -98,6 +100,54 @@ var Database = class _Database {
98
100
  this.computedFields = [];
99
101
  this.triggers = [];
100
102
  }
103
+ /**
104
+ * Executes raw SQL (MySQL/PostgreSQL/SQLite) or a raw command document (MongoDB)
105
+ * with bound parameters. The escape hatch for anything the builder doesn't cover.
106
+ *
107
+ * @example
108
+ * const rows = await db.raw('SELECT * FROM users WHERE age > ?', [25]);
109
+ * await db.raw('CREATE INDEX idx_users_email ON users(email)');
110
+ */
111
+ async raw(query, params = []) {
112
+ const response = await this.engine.rawQuery(query, params, this.txId ?? void 0);
113
+ if (response.status != 200) {
114
+ returnFormattedError(response.status, response.message);
115
+ throw new Error(response.message);
116
+ }
117
+ return response.data;
118
+ }
119
+ /**
120
+ * Runs a callback inside a database transaction. Everything executed through
121
+ * the received connection is committed atomically; any thrown error rolls
122
+ * the whole transaction back.
123
+ *
124
+ * Requires daemon mode (enabled by default with an up-to-date query-engine).
125
+ *
126
+ * @example
127
+ * await db.transaction(async (trx) => {
128
+ * await trx.table('accounts').where('id', '=', 1).update({ balance: 50 });
129
+ * await trx.table('accounts').where('id', '=', 2).update({ balance: 150 });
130
+ * });
131
+ */
132
+ async transaction(callback) {
133
+ const txId = await this.engine.beginTransaction();
134
+ const trx = new _Database(this.name);
135
+ trx.engine = this.engine;
136
+ trx.computedFields = this.computedFields;
137
+ trx.triggers = this.triggers;
138
+ trx.txId = txId;
139
+ try {
140
+ const result = await callback(trx);
141
+ await this.engine.commitTransaction(txId);
142
+ return result;
143
+ } catch (error) {
144
+ try {
145
+ await this.engine.rollbackTransaction(txId);
146
+ } catch {
147
+ }
148
+ throw error;
149
+ }
150
+ }
101
151
  async useComputes() {
102
152
  const newDatabase = new _Database(this.name);
103
153
  const arrayComputedFields = await import_core2.ComputedFieldProcessor.getComputedFields(this.name);
@@ -164,7 +214,7 @@ var Database = class _Database {
164
214
  * const columns = await db.table('users').columns().get();
165
215
  */
166
216
  table(tableName) {
167
- return new Table(this, this.name, tableName, this.engine, this.computedFields, this.triggers);
217
+ return new Table(this, this.name, tableName, this.engine, this.computedFields, this.triggers, this.txId);
168
218
  }
169
219
  setComputedFields(computedFields) {
170
220
  this.computedFields = computedFields;
@@ -180,12 +230,15 @@ var Table = class _Table {
180
230
  computedFields = [];
181
231
  trigger;
182
232
  triggers;
183
- constructor(instance, databaseName, tableName, engine = null, computedFields = [], triggers = []) {
233
+ txId = null;
234
+ relations = [];
235
+ constructor(instance, databaseName, tableName, engine = null, computedFields = [], triggers = [], txId = null) {
184
236
  this.engine = engine;
185
237
  this.computedFields = computedFields;
186
238
  this.triggers = triggers;
187
239
  this.trigger = new Trigger(instance, databaseName, triggers);
188
240
  this.nextType = "AND";
241
+ this.txId = txId;
189
242
  this.dml = {
190
243
  type: "select",
191
244
  database: databaseName,
@@ -199,7 +252,8 @@ var Table = class _Table {
199
252
  limit: null,
200
253
  offset: null,
201
254
  data: null,
202
- aggregation: null
255
+ aggregation: null,
256
+ having: []
203
257
  };
204
258
  }
205
259
  /**
@@ -761,6 +815,9 @@ var Table = class _Table {
761
815
  clone.dml.data = null;
762
816
  clone.dml.aggregation = null;
763
817
  const result = await clone.getResponse();
818
+ if (this.relations.length > 0 && Array.isArray(result)) {
819
+ return await this.attachRelations(result);
820
+ }
764
821
  return result;
765
822
  } catch (error) {
766
823
  throw error;
@@ -882,6 +939,310 @@ var Table = class _Table {
882
939
  const deleteData = await clone.getResponse(clone.dml, "Delete");
883
940
  return deleteData;
884
941
  }
942
+ /**
943
+ * Adds a WHERE NOT IN condition to the query.
944
+ *
945
+ * @example
946
+ * const users = await db.table('users').whereNotIn('status', ['banned', 'deleted']).get();
947
+ */
948
+ whereNotIn(column, values) {
949
+ const clone = this.clone();
950
+ if (Array.isArray(values) && values.length > 0) {
951
+ clone.dml.where.push({
952
+ column,
953
+ operator: "NOT IN",
954
+ value: values,
955
+ type: clone.nextType,
956
+ isGroup: false
957
+ });
958
+ clone.nextType = "AND";
959
+ }
960
+ return clone;
961
+ }
962
+ /**
963
+ * Sets an explicit OFFSET for the query (alternative to page()).
964
+ *
965
+ * @example
966
+ * const rows = await db.table('logs').orderBy('id', 'ASC').limit(50).offset(100).get();
967
+ */
968
+ offset(number) {
969
+ const clone = this.clone();
970
+ clone.dml.offset = Number(number);
971
+ return clone;
972
+ }
973
+ /**
974
+ * Appends raw expressions to the SELECT list (aggregates, functions, aliases).
975
+ * Combine with groupBy() and having() for per-group metrics.
976
+ *
977
+ * @example
978
+ * const stats = await db.table('orders')
979
+ * .select(['user_id'])
980
+ * .selectRaw(['COUNT(*) AS order_count', 'SUM(amount) AS total'])
981
+ * .groupBy('user_id')
982
+ * .having('order_count', '>', 5)
983
+ * .get();
984
+ */
985
+ selectRaw(expressions) {
986
+ const clone = this.clone();
987
+ if (clone.dml.columns.length === 1 && clone.dml.columns[0] === "*") {
988
+ clone.dml.columns = [...expressions];
989
+ } else {
990
+ clone.dml.columns = [...clone.dml.columns, ...expressions];
991
+ }
992
+ return clone;
993
+ }
994
+ /**
995
+ * Adds a HAVING condition (filters grouped results).
996
+ *
997
+ * @example
998
+ * .groupBy('user_id').having('COUNT(*)', '>', 5)
999
+ */
1000
+ having(column, operator, value) {
1001
+ const clone = this.clone();
1002
+ clone.dml.having = clone.dml.having ?? [];
1003
+ clone.dml.having.push({
1004
+ column,
1005
+ operator,
1006
+ value,
1007
+ type: "AND",
1008
+ isGroup: false
1009
+ });
1010
+ return clone;
1011
+ }
1012
+ /**
1013
+ * Checks if at least one row matches the current conditions.
1014
+ * Cheaper than first(): selects a constant with LIMIT 1.
1015
+ *
1016
+ * @example
1017
+ * const taken = await db.table('users').where('email', '=', email).exists();
1018
+ */
1019
+ async exists() {
1020
+ const clone = this.clone();
1021
+ clone.dml.type = "select";
1022
+ clone.dml.columns = ["1 AS dbcube_exists"];
1023
+ clone.dml.data = null;
1024
+ clone.dml.aggregation = null;
1025
+ clone.dml.limit = 1;
1026
+ clone.dml.offset = null;
1027
+ const result = await clone.getResponse();
1028
+ return Array.isArray(result) && result.length > 0;
1029
+ }
1030
+ /**
1031
+ * Fetches one page of results plus pagination metadata in a single call.
1032
+ * Runs the page query and the total count with the same conditions.
1033
+ *
1034
+ * @example
1035
+ * const { items, total, totalPages, hasNext } = await db.table('products')
1036
+ * .where('published', '=', true)
1037
+ * .orderBy('id', 'ASC')
1038
+ * .paginate(2, 25);
1039
+ */
1040
+ async paginate(page = 1, perPage = 20) {
1041
+ const pageNum = Math.max(1, Number(page));
1042
+ const size = Math.max(1, Number(perPage));
1043
+ const itemsQuery = this.limit(size).page(pageNum);
1044
+ const [items, total] = await Promise.all([
1045
+ itemsQuery.get(),
1046
+ this.count()
1047
+ ]);
1048
+ const totalNum = Number(total);
1049
+ const totalPages = Math.ceil(totalNum / size);
1050
+ return {
1051
+ items,
1052
+ page: pageNum,
1053
+ perPage: size,
1054
+ total: totalNum,
1055
+ totalPages,
1056
+ hasNext: pageNum < totalPages,
1057
+ hasPrev: pageNum > 1
1058
+ };
1059
+ }
1060
+ /**
1061
+ * Processes all matching rows in batches of `size`, keeping memory flat.
1062
+ * Return `false` from the callback to stop early.
1063
+ *
1064
+ * @example
1065
+ * await db.table('logs').orderBy('id', 'ASC').chunk(500, async (rows) => {
1066
+ * await processBatch(rows);
1067
+ * });
1068
+ */
1069
+ async chunk(size, callback) {
1070
+ const batchSize = Math.max(1, Number(size));
1071
+ let page = 1;
1072
+ while (true) {
1073
+ const rows = await this.limit(batchSize).page(page).get();
1074
+ if (rows.length === 0) break;
1075
+ const result = await callback(rows, page);
1076
+ if (result === false) break;
1077
+ if (rows.length < batchSize) break;
1078
+ page++;
1079
+ }
1080
+ }
1081
+ /**
1082
+ * Atomically increments a numeric column: `SET col = col + amount`.
1083
+ * Requires at least one WHERE condition. Extra fields can be updated in the same statement.
1084
+ *
1085
+ * @example
1086
+ * await db.table('products').where('id', '=', 5).increment('stock', 3);
1087
+ * await db.table('posts').where('id', '=', 1).increment('views', 1, { last_viewed_at: new Date().toISOString() });
1088
+ */
1089
+ async increment(column, amount = 1, extra = {}) {
1090
+ const clone = this.clone();
1091
+ if (clone.dml.where.length === 0) {
1092
+ throw new Error("You must specify at least one WHERE condition to perform an increment.");
1093
+ }
1094
+ clone.dml.type = "update";
1095
+ clone.dml.data = { ...extra, [column]: { $inc: Number(amount) } };
1096
+ return clone.getResponse();
1097
+ }
1098
+ /**
1099
+ * Atomically decrements a numeric column: `SET col = col - amount`.
1100
+ *
1101
+ * @example
1102
+ * await db.table('products').where('id', '=', 5).decrement('stock', 1);
1103
+ */
1104
+ async decrement(column, amount = 1, extra = {}) {
1105
+ return this.increment(column, -Math.abs(Number(amount)), extra);
1106
+ }
1107
+ /**
1108
+ * Deletes ALL rows from the table. The only write operation allowed
1109
+ * without a WHERE — the destructive intent is explicit in the name.
1110
+ *
1111
+ * @example
1112
+ * await db.table('temp_imports').truncate();
1113
+ */
1114
+ async truncate() {
1115
+ const clone = this.clone();
1116
+ clone.dml.type = "delete";
1117
+ clone.dml.where = [];
1118
+ return clone.getResponse();
1119
+ }
1120
+ /**
1121
+ * Inserts rows, updating them instead when a conflict occurs on the given keys.
1122
+ * MySQL → ON DUPLICATE KEY UPDATE · PostgreSQL/SQLite → ON CONFLICT ... DO UPDATE.
1123
+ *
1124
+ * @param data Rows to insert.
1125
+ * @param conflictKeys Column(s) with the UNIQUE/PK constraint that triggers the update.
1126
+ * @param updateColumns Columns to overwrite on conflict (defaults to every non-conflict column).
1127
+ *
1128
+ * @example
1129
+ * await db.table('settings').upsert(
1130
+ * [{ key: 'theme', value: 'dark' }],
1131
+ * ['key']
1132
+ * );
1133
+ */
1134
+ async upsert(data, conflictKeys, updateColumns) {
1135
+ if (!Array.isArray(data) || data.length === 0) {
1136
+ throw new Error("The upsert method requires a non-empty array of objects.");
1137
+ }
1138
+ if (!Array.isArray(conflictKeys) || conflictKeys.length === 0) {
1139
+ throw new Error("The upsert method requires at least one conflict key column.");
1140
+ }
1141
+ const motor = this.engine?.getConfig?.()?.type ?? "mysql";
1142
+ if (motor === "mongodb") {
1143
+ throw new Error("upsert() is not supported for MongoDB yet. Use update() + insert() or raw commands.");
1144
+ }
1145
+ const table = this.dml.table;
1146
+ const columns = Object.keys(data[0]);
1147
+ const targets = updateColumns ?? columns.filter((c) => !conflictKeys.includes(c));
1148
+ const quote = (id) => motor === "mysql" ? `\`${id}\`` : `"${id}"`;
1149
+ const params = [];
1150
+ const placeholder = () => motor === "postgres" || motor === "postgresql" ? `$${params.length}` : "?";
1151
+ const rowsSql = data.map((row) => {
1152
+ const cells = columns.map((col) => {
1153
+ params.push(row[col] === void 0 ? null : row[col]);
1154
+ return placeholder();
1155
+ });
1156
+ return `(${cells.join(", ")})`;
1157
+ }).join(", ");
1158
+ const colsSql = columns.map(quote).join(", ");
1159
+ let sql;
1160
+ if (motor === "mysql") {
1161
+ const updates = targets.map((c) => `${quote(c)} = VALUES(${quote(c)})`).join(", ");
1162
+ sql = `INSERT INTO ${quote(table)} (${colsSql}) VALUES ${rowsSql} ON DUPLICATE KEY UPDATE ${updates}`;
1163
+ } else {
1164
+ const conflict = conflictKeys.map(quote).join(", ");
1165
+ const updates = targets.map((c) => `${quote(c)} = excluded.${quote(c)}`).join(", ");
1166
+ sql = `INSERT INTO ${quote(table)} (${colsSql}) VALUES ${rowsSql} ON CONFLICT (${conflict}) DO UPDATE SET ${updates}`;
1167
+ }
1168
+ const response = await this.engine.rawQuery(sql, params, this.txId ?? void 0);
1169
+ if (response.status != 200) {
1170
+ returnFormattedError(response.status, response.message);
1171
+ throw new Error(response.message);
1172
+ }
1173
+ return response.data;
1174
+ }
1175
+ /**
1176
+ * Declares a relation to eager-load with the results of get().
1177
+ * Relations are resolved from `foreign` definitions in your .cube files,
1178
+ * or explicitly via options. Loads each relation with ONE batched query
1179
+ * (whereIn) — no N+1.
1180
+ *
1181
+ * @example
1182
+ * // hasMany: orders.user_id → users.id (auto-detected from orders.table.cube)
1183
+ * const users = await db.table('users').with('orders').get();
1184
+ * // users[0].orders === [{...}, {...}]
1185
+ *
1186
+ * // belongsTo: posts.author_id → users.id, attached as a single object
1187
+ * const posts = await db.table('posts')
1188
+ * .with('author', { table: 'users', foreignKey: 'author_id', type: 'one' })
1189
+ * .get();
1190
+ */
1191
+ with(relation, options = {}) {
1192
+ const clone = this.clone();
1193
+ clone.relations.push({ name: relation, options });
1194
+ return clone;
1195
+ }
1196
+ async attachRelations(rows) {
1197
+ if (rows.length === 0 || this.relations.length === 0) return rows;
1198
+ for (const { name, options } of this.relations) {
1199
+ const relatedTable = options.table ?? name;
1200
+ const cubeMeta = loadCubeRelations();
1201
+ let type = options.type;
1202
+ let foreignKey = options.foreignKey;
1203
+ let localKey = options.localKey ?? "id";
1204
+ if (!foreignKey || !type) {
1205
+ const childFks = cubeMeta[relatedTable]?.foreigns ?? [];
1206
+ const ownFks = cubeMeta[this.dml.table]?.foreigns ?? [];
1207
+ const childToParent = childFks.find((fk) => fk.table === this.dml.table);
1208
+ const parentToChild = ownFks.find((fk) => fk.table === relatedTable);
1209
+ if (childToParent) {
1210
+ type = type ?? "many";
1211
+ foreignKey = foreignKey ?? childToParent.column_name;
1212
+ localKey = options.localKey ?? childToParent.column;
1213
+ } else if (parentToChild) {
1214
+ type = type ?? "one";
1215
+ foreignKey = foreignKey ?? parentToChild.column_name;
1216
+ localKey = options.localKey ?? parentToChild.column;
1217
+ } else {
1218
+ type = type ?? "many";
1219
+ foreignKey = foreignKey ?? `${singularize(this.dml.table)}_id`;
1220
+ }
1221
+ }
1222
+ const relatedQuery = new _Table(this, this.dml.database, relatedTable, this.engine, this.computedFields, this.triggers, this.txId);
1223
+ if (type === "one") {
1224
+ const fkValues = [...new Set(rows.map((r) => r[foreignKey]).filter((v) => v !== null && v !== void 0))];
1225
+ const related = fkValues.length > 0 ? await relatedQuery.whereIn(localKey, fkValues).get() : [];
1226
+ const index = new Map(related.map((r) => [r[localKey], r]));
1227
+ for (const row of rows) {
1228
+ row[name] = index.get(row[foreignKey]) ?? null;
1229
+ }
1230
+ } else {
1231
+ const ids = [...new Set(rows.map((r) => r[localKey]).filter((v) => v !== null && v !== void 0))];
1232
+ const related = ids.length > 0 ? await relatedQuery.whereIn(foreignKey, ids).get() : [];
1233
+ const groups = /* @__PURE__ */ new Map();
1234
+ for (const rel of related) {
1235
+ const key = rel[foreignKey];
1236
+ if (!groups.has(key)) groups.set(key, []);
1237
+ groups.get(key).push(rel);
1238
+ }
1239
+ for (const row of rows) {
1240
+ row[name] = groups.get(row[localKey]) ?? [];
1241
+ }
1242
+ }
1243
+ }
1244
+ return rows;
1245
+ }
885
1246
  async getResponse(dml = null, type = null) {
886
1247
  const localDML = dml ? dml : this.dml;
887
1248
  const computedFieldsNeeded = [];
@@ -911,12 +1272,7 @@ var Table = class _Table {
911
1272
  const newDML = { ...localDML, data: [data] };
912
1273
  if (beffore) {
913
1274
  const interceptor = await this.trigger.execute("before" + type, data);
914
- const response = await this.engine.run("query_engine", [
915
- "--action",
916
- "execute",
917
- "--dml",
918
- JSON.stringify(newDML)
919
- ]);
1275
+ const response = await this.engine.executeDml(newDML, this.txId ?? void 0);
920
1276
  if (response.status != 200) {
921
1277
  interceptor.discard();
922
1278
  returnFormattedError(response.status, response.message);
@@ -925,12 +1281,7 @@ var Table = class _Table {
925
1281
  arrayResult = response.data;
926
1282
  }
927
1283
  if (after) {
928
- const response = await this.engine.run("query_engine", [
929
- "--action",
930
- "execute",
931
- "--dml",
932
- JSON.stringify(newDML)
933
- ]);
1284
+ const response = await this.engine.executeDml(newDML, this.txId ?? void 0);
934
1285
  if (response.status != 200) {
935
1286
  returnFormattedError(response.status, response.message);
936
1287
  }
@@ -939,24 +1290,14 @@ var Table = class _Table {
939
1290
  }
940
1291
  }
941
1292
  } else {
942
- const response = await this.engine.run("query_engine", [
943
- "--action",
944
- "execute",
945
- "--dml",
946
- JSON.stringify(localDML)
947
- ]);
1293
+ const response = await this.engine.executeDml(localDML, this.txId ?? void 0);
948
1294
  if (response.status != 200) {
949
1295
  returnFormattedError(response.status, response.message);
950
1296
  }
951
1297
  arrayResult = response.data;
952
1298
  }
953
1299
  } else {
954
- const response = await this.engine.run("query_engine", [
955
- "--action",
956
- "execute",
957
- "--dml",
958
- JSON.stringify(localDML)
959
- ]);
1300
+ const response = await this.engine.executeDml(localDML, this.txId ?? void 0);
960
1301
  if (response.status != 200) {
961
1302
  returnFormattedError(response.status, response.message);
962
1303
  }
@@ -980,6 +1321,8 @@ var Table = class _Table {
980
1321
  cloned.computedFields = this.computedFields;
981
1322
  cloned.trigger = this.trigger;
982
1323
  cloned.triggers = this.triggers;
1324
+ cloned.txId = this.txId;
1325
+ cloned.relations = [...this.relations];
983
1326
  cloned.dml = {
984
1327
  ...this.dml,
985
1328
  columns: [...this.dml.columns],
@@ -987,6 +1330,7 @@ var Table = class _Table {
987
1330
  where: [...this.dml.where],
988
1331
  orderBy: [...this.dml.orderBy],
989
1332
  groupBy: [...this.dml.groupBy],
1333
+ having: [...this.dml.having ?? []],
990
1334
  // Clonar propiedades que faltaban para evitar mutación compartida
991
1335
  data: this.dml.data ? Array.isArray(this.dml.data) ? [...this.dml.data] : { ...this.dml.data } : null,
992
1336
  aggregation: this.dml.aggregation ? { ...this.dml.aggregation } : null
@@ -994,6 +1338,58 @@ var Table = class _Table {
994
1338
  return cloned;
995
1339
  }
996
1340
  };
1341
+ var cubeRelationsCache = null;
1342
+ function loadCubeRelations() {
1343
+ if (cubeRelationsCache) return cubeRelationsCache;
1344
+ const result = {};
1345
+ const scanDir = (dir) => {
1346
+ let entries;
1347
+ try {
1348
+ entries = import_fs.default.readdirSync(dir, { withFileTypes: true });
1349
+ } catch {
1350
+ return;
1351
+ }
1352
+ for (const entry of entries) {
1353
+ const full = import_path2.default.join(dir, entry.name);
1354
+ if (entry.isDirectory()) {
1355
+ if (entry.name !== "node_modules" && entry.name !== "triggers" && entry.name !== "logs") {
1356
+ scanDir(full);
1357
+ }
1358
+ } else if (entry.name.endsWith(".table.cube")) {
1359
+ try {
1360
+ const content = import_fs.default.readFileSync(full, "utf8");
1361
+ const nameMatch = content.match(/@meta\s*\(\s*\{[\s\S]*?name\s*:\s*"([^"]+)"/);
1362
+ const tableName = nameMatch ? nameMatch[1] : import_path2.default.basename(entry.name, ".table.cube");
1363
+ const foreigns = [];
1364
+ const colRegex = /(\w+)\s*:\s*\{(?:[^{}]|\{[^{}]*\})*?foreign\s*:\s*\{([^}]*)\}/g;
1365
+ let m;
1366
+ while ((m = colRegex.exec(content)) !== null) {
1367
+ const fkTable = m[2].match(/table\s*:\s*"([^"]+)"/);
1368
+ const fkColumn = m[2].match(/column\s*:\s*"([^"]+)"/);
1369
+ if (fkTable) {
1370
+ foreigns.push({
1371
+ column_name: m[1],
1372
+ table: fkTable[1],
1373
+ column: fkColumn ? fkColumn[1] : "id"
1374
+ });
1375
+ }
1376
+ }
1377
+ result[tableName] = { foreigns };
1378
+ } catch {
1379
+ }
1380
+ }
1381
+ }
1382
+ };
1383
+ scanDir(import_path2.default.join(process.cwd(), "dbcube"));
1384
+ cubeRelationsCache = result;
1385
+ return result;
1386
+ }
1387
+ function singularize(word) {
1388
+ if (word.endsWith("ies")) return word.slice(0, -3) + "y";
1389
+ if (word.endsWith("ses")) return word.slice(0, -2);
1390
+ if (word.endsWith("s") && !word.endsWith("ss")) return word.slice(0, -1);
1391
+ return word;
1392
+ }
997
1393
  function returnFormattedError(status, message) {
998
1394
  const RESET = "\x1B[0m";
999
1395
  const RED = "\x1B[31m";