@inflector/optima 1.0.1 → 1.0.2
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 +323 -3
- package/dist/index.js +1155 -3
- package/package.json +3 -2
- package/dist/Qfluent.d.ts +0 -40
- package/dist/Qfluent.js +0 -64
- package/dist/database.d.ts +0 -32
- package/dist/database.js +0 -240
- package/dist/fluent.d.ts +0 -13
- package/dist/fluent.js +0 -29
- package/dist/schema.d.ts +0 -85
- package/dist/schema.js +0 -214
- package/dist/table.d.ts +0 -178
- package/dist/table.js +0 -617
package/dist/table.js
DELETED
|
@@ -1,617 +0,0 @@
|
|
|
1
|
-
import { SQLBuilder, } from "./schema";
|
|
2
|
-
import { createFluentBuilder } from "./fluent";
|
|
3
|
-
import { createQueryBuilder, createQueryBuilderOne, } from "./Qfluent";
|
|
4
|
-
// ---------------------------------------------------------
|
|
5
|
-
// 1. OPERATOR DEFINITIONS (Unchanged)
|
|
6
|
-
// ---------------------------------------------------------
|
|
7
|
-
export const OPS = [
|
|
8
|
-
"eq",
|
|
9
|
-
"ne",
|
|
10
|
-
"gt",
|
|
11
|
-
"gte",
|
|
12
|
-
"lt",
|
|
13
|
-
"lte",
|
|
14
|
-
"like",
|
|
15
|
-
"notLike",
|
|
16
|
-
"in",
|
|
17
|
-
"notIn",
|
|
18
|
-
"is",
|
|
19
|
-
"isNot",
|
|
20
|
-
"between",
|
|
21
|
-
"notBetween",
|
|
22
|
-
"startsWith",
|
|
23
|
-
"endsWith",
|
|
24
|
-
"contains",
|
|
25
|
-
"regexp",
|
|
26
|
-
"notRegexp",
|
|
27
|
-
];
|
|
28
|
-
class ConditionBuilderImpl {
|
|
29
|
-
constructor(node) {
|
|
30
|
-
this.node = node;
|
|
31
|
-
}
|
|
32
|
-
createOp(op, value) {
|
|
33
|
-
return new ConditionBuilderImpl({
|
|
34
|
-
type: "condition",
|
|
35
|
-
op,
|
|
36
|
-
value,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
eq(value) {
|
|
40
|
-
return this.createOp("eq", value);
|
|
41
|
-
}
|
|
42
|
-
ne(value) {
|
|
43
|
-
return this.createOp("ne", value);
|
|
44
|
-
}
|
|
45
|
-
gt(value) {
|
|
46
|
-
return this.createOp("gt", value);
|
|
47
|
-
}
|
|
48
|
-
gte(value) {
|
|
49
|
-
return this.createOp("gte", value);
|
|
50
|
-
}
|
|
51
|
-
lt(value) {
|
|
52
|
-
return this.createOp("lt", value);
|
|
53
|
-
}
|
|
54
|
-
lte(value) {
|
|
55
|
-
return this.createOp("lte", value);
|
|
56
|
-
}
|
|
57
|
-
in(value) {
|
|
58
|
-
return this.createOp("in", value);
|
|
59
|
-
}
|
|
60
|
-
notIn(value) {
|
|
61
|
-
return this.createOp("notIn", value);
|
|
62
|
-
}
|
|
63
|
-
between(value) {
|
|
64
|
-
return this.createOp("between", value);
|
|
65
|
-
}
|
|
66
|
-
notBetween(value) {
|
|
67
|
-
return this.createOp("notBetween", value);
|
|
68
|
-
}
|
|
69
|
-
is(value) {
|
|
70
|
-
return this.createOp("is", value);
|
|
71
|
-
}
|
|
72
|
-
isNot(value) {
|
|
73
|
-
return this.createOp("isNot", value);
|
|
74
|
-
}
|
|
75
|
-
like(value) {
|
|
76
|
-
return this.createOp("like", value);
|
|
77
|
-
}
|
|
78
|
-
notLike(value) {
|
|
79
|
-
return this.createOp("notLike", value);
|
|
80
|
-
}
|
|
81
|
-
startsWith(value) {
|
|
82
|
-
return this.createOp("startsWith", value);
|
|
83
|
-
}
|
|
84
|
-
endsWith(value) {
|
|
85
|
-
return this.createOp("endsWith", value);
|
|
86
|
-
}
|
|
87
|
-
contains(value) {
|
|
88
|
-
return this.createOp("contains", value);
|
|
89
|
-
}
|
|
90
|
-
regexp(value) {
|
|
91
|
-
return this.createOp("regexp", value);
|
|
92
|
-
}
|
|
93
|
-
notRegexp(value) {
|
|
94
|
-
return this.createOp("notRegexp", value);
|
|
95
|
-
}
|
|
96
|
-
and(other) {
|
|
97
|
-
return new ConditionBuilderImpl({
|
|
98
|
-
type: "and",
|
|
99
|
-
left: this.node,
|
|
100
|
-
right: other.__getNode(),
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
or(other) {
|
|
104
|
-
return new ConditionBuilderImpl({
|
|
105
|
-
type: "or",
|
|
106
|
-
left: this.node,
|
|
107
|
-
right: other.__getNode(),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
__getNode() {
|
|
111
|
-
return this.node;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
export function cond() {
|
|
115
|
-
return new ConditionBuilderImpl({ type: "condition" });
|
|
116
|
-
}
|
|
117
|
-
// Exports for standalone operators
|
|
118
|
-
export const eq = (value) => cond().eq(value);
|
|
119
|
-
export const ne = (value) => cond().ne(value);
|
|
120
|
-
export const gt = (value) => cond().gt(value);
|
|
121
|
-
export const gte = (value) => cond().gte(value);
|
|
122
|
-
export const lt = (value) => cond().lt(value);
|
|
123
|
-
export const lte = (value) => cond().lte(value);
|
|
124
|
-
export const inOp = (value) => cond().in(value);
|
|
125
|
-
export const notIn = (value) => cond().notIn(value);
|
|
126
|
-
export const between = (value) => cond().between(value);
|
|
127
|
-
export const notBetween = (value) => cond().notBetween(value);
|
|
128
|
-
export const is = (value) => cond().is(value);
|
|
129
|
-
export const isNot = (value) => cond().isNot(value);
|
|
130
|
-
export const like = (value) => cond().like(value);
|
|
131
|
-
export const notLike = (value) => cond().notLike(value);
|
|
132
|
-
export const startsWith = (value) => cond().startsWith(value);
|
|
133
|
-
export const endsWith = (value) => cond().endsWith(value);
|
|
134
|
-
export const contains = (value) => cond().contains(value);
|
|
135
|
-
export const regexp = (value) => cond().regexp(value);
|
|
136
|
-
export const notRegexp = (value) => cond().notRegexp(value);
|
|
137
|
-
// ---------------------------------------------------------
|
|
138
|
-
// 4. SQL GENERATION UTILITIES (Unchanged)
|
|
139
|
-
// ---------------------------------------------------------
|
|
140
|
-
const escape = (val) => {
|
|
141
|
-
if (val === null || val === undefined)
|
|
142
|
-
return "NULL";
|
|
143
|
-
if (typeof val === "string")
|
|
144
|
-
return `'${val.replace(/'/g, "''")}'`;
|
|
145
|
-
if (val instanceof Date)
|
|
146
|
-
return `'${val.toISOString()}'`;
|
|
147
|
-
if (typeof val === "boolean")
|
|
148
|
-
return val ? "TRUE" : "FALSE";
|
|
149
|
-
if (typeof val === "object")
|
|
150
|
-
return `'${JSON.stringify(val).replace(/'/g, "''")}'`;
|
|
151
|
-
return String(val);
|
|
152
|
-
};
|
|
153
|
-
const SQL_GENERATORS = {
|
|
154
|
-
eq: (c, v) => `${c} = ${escape(v)}`,
|
|
155
|
-
ne: (c, v) => `${c} != ${escape(v)}`,
|
|
156
|
-
gt: (c, v) => `${c} > ${escape(v)}`,
|
|
157
|
-
gte: (c, v) => `${c} >= ${escape(v)}`,
|
|
158
|
-
lt: (c, v) => `${c} < ${escape(v)}`,
|
|
159
|
-
lte: (c, v) => `${c} <= ${escape(v)}`,
|
|
160
|
-
in: (c, v) => `${c} IN (${v.map(escape).join(", ")})`,
|
|
161
|
-
notIn: (c, v) => `${c} NOT IN (${v.map(escape).join(", ")})`,
|
|
162
|
-
between: (c, v) => `${c} BETWEEN ${escape(v[0])} AND ${escape(v[1])}`,
|
|
163
|
-
notBetween: (c, v) => `${c} NOT BETWEEN ${escape(v[0])} AND ${escape(v[1])}`,
|
|
164
|
-
is: (c, v) => (v === null ? `${c} IS NULL` : `${c} IS ${escape(v)}`),
|
|
165
|
-
isNot: (c, v) => v === null ? `${c} IS NOT NULL` : `${c} IS NOT ${escape(v)}`,
|
|
166
|
-
like: (c, v) => `${c} LIKE ${escape(v)}`,
|
|
167
|
-
notLike: (c, v) => `${c} NOT LIKE ${escape(v)}`,
|
|
168
|
-
startsWith: (c, v) => `${c} LIKE ${escape(v + "%")}`,
|
|
169
|
-
endsWith: (c, v) => `${c} LIKE ${escape("%" + v)}`,
|
|
170
|
-
contains: (c, v) => `${c} LIKE ${escape("%" + v + "%")}`,
|
|
171
|
-
regexp: (c, v) => `regexp_matches(${c}, ${escape(v)})`,
|
|
172
|
-
notRegexp: (c, v) => `NOT regexp_matches(${c}, ${escape(v)})`,
|
|
173
|
-
};
|
|
174
|
-
export class OptimaTable {
|
|
175
|
-
constructor(name, Columns, Connection) {
|
|
176
|
-
this.listeners = new Set();
|
|
177
|
-
// --- SHARED: Where Clause Builder ---
|
|
178
|
-
this.BuildWhereClause = (where) => {
|
|
179
|
-
if (!where)
|
|
180
|
-
return "";
|
|
181
|
-
const conditions = [];
|
|
182
|
-
// Helper to generate SQL for specific operators
|
|
183
|
-
const nodeToSQL = (node, colName) => {
|
|
184
|
-
if (node.type === "condition" && node.op) {
|
|
185
|
-
return SQL_GENERATORS[node.op](colName, node.value);
|
|
186
|
-
}
|
|
187
|
-
else if (node.type === "and" && node.left && node.right) {
|
|
188
|
-
return `(${nodeToSQL(node.left, colName)} AND ${nodeToSQL(node.right, colName)})`;
|
|
189
|
-
}
|
|
190
|
-
else if (node.type === "or" && node.left && node.right) {
|
|
191
|
-
return `(${nodeToSQL(node.left, colName)} OR ${nodeToSQL(node.right, colName)})`;
|
|
192
|
-
}
|
|
193
|
-
return "";
|
|
194
|
-
};
|
|
195
|
-
// Recursive function to handle nested objects (Structs)
|
|
196
|
-
const processLevel = (currentWhere, pathPrefix) => {
|
|
197
|
-
for (const key of Object.keys(currentWhere)) {
|
|
198
|
-
const val = currentWhere[key];
|
|
199
|
-
// Build the dot-notation column name (e.g. "address.city")
|
|
200
|
-
const colName = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
201
|
-
// 1. Skip undefined values
|
|
202
|
-
if (val === undefined)
|
|
203
|
-
continue;
|
|
204
|
-
// 2. Check for ConditionBuilder (e.g., eq(5), is(null))
|
|
205
|
-
if (val &&
|
|
206
|
-
typeof val === "object" &&
|
|
207
|
-
"__getNode" in val &&
|
|
208
|
-
typeof val.__getNode === "function") {
|
|
209
|
-
const node = val.__getNode();
|
|
210
|
-
const sql = nodeToSQL(node, colName);
|
|
211
|
-
if (sql)
|
|
212
|
-
conditions.push(sql);
|
|
213
|
-
}
|
|
214
|
-
// 3. Check for Nested Object (Struct recursion)
|
|
215
|
-
// Must exclude Arrays and Dates which are treated as values
|
|
216
|
-
else if (val &&
|
|
217
|
-
typeof val === "object" &&
|
|
218
|
-
!Array.isArray(val) &&
|
|
219
|
-
!(val instanceof Date)) {
|
|
220
|
-
processLevel(val, colName);
|
|
221
|
-
}
|
|
222
|
-
// 4. Direct Value (Implicit Equality)
|
|
223
|
-
else {
|
|
224
|
-
conditions.push(SQL_GENERATORS.eq(colName, val));
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
processLevel(where, "");
|
|
229
|
-
if (conditions.length > 0) {
|
|
230
|
-
return ` WHERE ${conditions.join(" AND ")}`;
|
|
231
|
-
}
|
|
232
|
-
return "";
|
|
233
|
-
};
|
|
234
|
-
this.BuildSelect = (TableName, options) => {
|
|
235
|
-
const { limit, orderBy, offset, groupBy, where, extend } = options;
|
|
236
|
-
// 1. Base Columns
|
|
237
|
-
let selectColumns = `${TableName}.*`;
|
|
238
|
-
// 2. Extended Columns (DuckDB Correlated Subquery)
|
|
239
|
-
if (extend) {
|
|
240
|
-
const extensions = Array.isArray(extend) ? extend : [extend];
|
|
241
|
-
for (const extTable of extensions) {
|
|
242
|
-
const joinLogic = this.ResolveJoin(extTable);
|
|
243
|
-
if (joinLogic) {
|
|
244
|
-
selectColumns += `, (SELECT ${joinLogic.isMany ? "list(" : ""}${extTable.Name}${joinLogic.isMany ? ")" : ""} FROM ${extTable.Name} WHERE ${joinLogic.sql}) AS "$${extTable.Name}"`;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
let query = `SELECT ${selectColumns} FROM ${TableName}`;
|
|
249
|
-
query += this.BuildWhereClause(where);
|
|
250
|
-
if (groupBy && groupBy.length > 0)
|
|
251
|
-
query += ` GROUP BY ${groupBy.join(", ")}`;
|
|
252
|
-
if (orderBy) {
|
|
253
|
-
const [columns, direction] = orderBy;
|
|
254
|
-
const columnStr = Array.isArray(columns) ? columns.join(", ") : columns;
|
|
255
|
-
query += ` ORDER BY ${String(columnStr)} ${direction}`;
|
|
256
|
-
}
|
|
257
|
-
if (limit !== undefined && limit !== null)
|
|
258
|
-
query += ` LIMIT ${limit}`;
|
|
259
|
-
if (offset !== undefined && offset !== null)
|
|
260
|
-
query += ` OFFSET ${offset}`;
|
|
261
|
-
return { sql: query, args: [] };
|
|
262
|
-
};
|
|
263
|
-
this.BuildInsert = (TableName, Records, isReturning) => {
|
|
264
|
-
const columnKeys = Object.keys(this.Columns);
|
|
265
|
-
const columnsHeader = `(${columnKeys.join(", ")})`;
|
|
266
|
-
const valuesBlock = Records.map((r) => {
|
|
267
|
-
const orderedValues = columnKeys.map((key) => {
|
|
268
|
-
let v = r[key];
|
|
269
|
-
if (v == null && this.Columns[key].config.default) {
|
|
270
|
-
v = this.Columns[key].config.default();
|
|
271
|
-
}
|
|
272
|
-
return escape(v);
|
|
273
|
-
});
|
|
274
|
-
return `(${orderedValues.join(",")})`;
|
|
275
|
-
}).join(",\n");
|
|
276
|
-
const sql = `INSERT INTO ${TableName} ${columnsHeader} VALUES \n${valuesBlock}${isReturning ? "\nRETURNING *" : ""};`;
|
|
277
|
-
return { sql: sql };
|
|
278
|
-
};
|
|
279
|
-
this.BuildUpdate = (TableName, changes, options) => {
|
|
280
|
-
const { where, returning } = options;
|
|
281
|
-
const setClauses = [];
|
|
282
|
-
for (const [key, value] of Object.entries(changes)) {
|
|
283
|
-
if (value !== undefined) {
|
|
284
|
-
setClauses.push(`${key} = ${escape(value)}`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
if (setClauses.length === 0) {
|
|
288
|
-
throw new Error("No fields provided to update.");
|
|
289
|
-
}
|
|
290
|
-
let query = `UPDATE ${TableName} SET ${setClauses.join(", ")}`;
|
|
291
|
-
query += this.BuildWhereClause(where);
|
|
292
|
-
if (returning) {
|
|
293
|
-
query += " RETURNING *";
|
|
294
|
-
}
|
|
295
|
-
return { sql: query, args: [] };
|
|
296
|
-
};
|
|
297
|
-
this.BuildDelete = (TableName, options) => {
|
|
298
|
-
const { where, returning } = options;
|
|
299
|
-
let query = `DELETE FROM ${TableName}`;
|
|
300
|
-
query += this.BuildWhereClause(where);
|
|
301
|
-
if (returning) {
|
|
302
|
-
query += " RETURNING *";
|
|
303
|
-
}
|
|
304
|
-
return { sql: query, args: [] };
|
|
305
|
-
};
|
|
306
|
-
// Helper to handle the deep recursion
|
|
307
|
-
this.parseRecursive = (value) => {
|
|
308
|
-
// 1. Handle Null/Undefined
|
|
309
|
-
if (value === null || value === undefined) {
|
|
310
|
-
return value;
|
|
311
|
-
}
|
|
312
|
-
// 2. Handle Dates (return as is, don't try to recurse properties)
|
|
313
|
-
if (value instanceof Date) {
|
|
314
|
-
return value;
|
|
315
|
-
}
|
|
316
|
-
// 3. Handle Arrays (recurse over every item)
|
|
317
|
-
if (Array.isArray(value)) {
|
|
318
|
-
return value.map((item) => this.parseRecursive(item));
|
|
319
|
-
}
|
|
320
|
-
// 4. Handle Objects (DuckDB structs, maps, or generic objects)
|
|
321
|
-
if (typeof value === "object") {
|
|
322
|
-
// DuckDB specific: If it has an 'entries' property, unwrap it and recurse
|
|
323
|
-
// @ts-ignore
|
|
324
|
-
if (value.entries !== undefined) {
|
|
325
|
-
// @ts-ignore
|
|
326
|
-
return this.parseRecursive(value.entries);
|
|
327
|
-
}
|
|
328
|
-
// DuckDB specific: If it has an 'items' property (lists), unwrap it and recurse
|
|
329
|
-
// @ts-ignore
|
|
330
|
-
if (value.items !== undefined) {
|
|
331
|
-
// @ts-ignore
|
|
332
|
-
return this.parseRecursive(value.items);
|
|
333
|
-
}
|
|
334
|
-
// Standard Object: recurse over all keys
|
|
335
|
-
const parsedObj = {};
|
|
336
|
-
for (const [k, v] of Object.entries(value)) {
|
|
337
|
-
parsedObj[k] = this.parseRecursive(v);
|
|
338
|
-
}
|
|
339
|
-
return parsedObj;
|
|
340
|
-
}
|
|
341
|
-
// 5. Primitives (string, number, boolean)
|
|
342
|
-
return value;
|
|
343
|
-
};
|
|
344
|
-
this.FormatOut = (data) => {
|
|
345
|
-
return data.map((row) => {
|
|
346
|
-
const formatted = {};
|
|
347
|
-
for (const [key, value] of Object.entries(row)) {
|
|
348
|
-
if (key.startsWith("$") && value === null) {
|
|
349
|
-
formatted[key] = [];
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
// 2. Recursively parse everything else
|
|
353
|
-
formatted[key] = this.parseRecursive(value);
|
|
354
|
-
}
|
|
355
|
-
return formatted;
|
|
356
|
-
});
|
|
357
|
-
};
|
|
358
|
-
this.Name = name;
|
|
359
|
-
const filteredCols = { ...Columns };
|
|
360
|
-
if ("__tableName" in filteredCols) {
|
|
361
|
-
delete filteredCols["__tableName"];
|
|
362
|
-
}
|
|
363
|
-
this.Columns = filteredCols;
|
|
364
|
-
this.Connection = Connection;
|
|
365
|
-
}
|
|
366
|
-
notifyChange(change) {
|
|
367
|
-
this.listeners.forEach((listener) => listener(change));
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Subscribe to changes to the table.
|
|
371
|
-
* The callback receives the latest change as its argument.
|
|
372
|
-
* Returns an unsubscribe function.
|
|
373
|
-
*/
|
|
374
|
-
Subscribe(fn) {
|
|
375
|
-
this.listeners.add(fn);
|
|
376
|
-
return () => {
|
|
377
|
-
this.listeners.delete(fn);
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
async InitTable() {
|
|
381
|
-
const SQL = SQLBuilder.BuildTable(this.Name, this.Columns);
|
|
382
|
-
await this.Connection.run(SQL);
|
|
383
|
-
}
|
|
384
|
-
static async create(name, Columns, Connection) {
|
|
385
|
-
const table = new OptimaTable(name, Columns, Connection);
|
|
386
|
-
await table.InitTable();
|
|
387
|
-
return table;
|
|
388
|
-
}
|
|
389
|
-
// ---------------------------------------------------------
|
|
390
|
-
// UPDATED: Get() now uses the Fluent Factory
|
|
391
|
-
// ---------------------------------------------------------
|
|
392
|
-
Get() {
|
|
393
|
-
return createQueryBuilder(this);
|
|
394
|
-
}
|
|
395
|
-
// ---------------------------------------------------------
|
|
396
|
-
// UPDATED: GetOne() now uses the Fluent Factory
|
|
397
|
-
// ---------------------------------------------------------
|
|
398
|
-
GetOne() {
|
|
399
|
-
return createQueryBuilderOne(this);
|
|
400
|
-
}
|
|
401
|
-
Exist() {
|
|
402
|
-
return createFluentBuilder(async (data) => {
|
|
403
|
-
const whereClause = this.BuildWhereClause(data.where);
|
|
404
|
-
const sql = `SELECT 1 FROM ${this.Name}${whereClause} LIMIT 1`;
|
|
405
|
-
const Result = await (await this.Connection.run(sql)).getRowObjects();
|
|
406
|
-
return Result.length > 0;
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Add a single record.
|
|
411
|
-
* Returns: TSchema[] (containing the inserted record if returning is true)
|
|
412
|
-
*/
|
|
413
|
-
Add(record) {
|
|
414
|
-
return createFluentBuilder(async (data) => {
|
|
415
|
-
this.Validate(record);
|
|
416
|
-
record = this.Transform(record);
|
|
417
|
-
const { sql } = this.BuildInsert(this.Name, [record], data.returning);
|
|
418
|
-
const Result = await (await this.Connection.run(sql)).getRowObjects();
|
|
419
|
-
const Res = this.FormatOut(Result);
|
|
420
|
-
if (Res.length != 0) {
|
|
421
|
-
if (data.returning) {
|
|
422
|
-
this.notifyChange({ event: "Add", data: Res, time: new Date() });
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
this.notifyChange({
|
|
426
|
-
event: "Add",
|
|
427
|
-
data: await this.GetOne().where(record),
|
|
428
|
-
time: new Date(),
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return Res;
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Add multiple records.
|
|
437
|
-
* Returns: TSchema[]
|
|
438
|
-
*/
|
|
439
|
-
AddMany(records) {
|
|
440
|
-
return createFluentBuilder(async (data) => {
|
|
441
|
-
records.forEach((v) => this.Validate(v));
|
|
442
|
-
const transformedRecords = records.map((v) => this.Transform(v));
|
|
443
|
-
const { sql } = this.BuildInsert(this.Name, transformedRecords, data.returning);
|
|
444
|
-
const Result = await (await this.Connection.run(sql)).getRowObjects();
|
|
445
|
-
const Res = this.FormatOut(Result);
|
|
446
|
-
if (Res.length != 0) {
|
|
447
|
-
if (data.returning) {
|
|
448
|
-
this.notifyChange({ event: "AddMany", data: Res, time: new Date() });
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
const Added = transformedRecords;
|
|
452
|
-
// Use Promise.all to retrieve the added records
|
|
453
|
-
const addedPromises = Added.map(async (record) => await this.GetOne().where(record));
|
|
454
|
-
const addedRecords = await Promise.all(addedPromises);
|
|
455
|
-
this.notifyChange({
|
|
456
|
-
event: "Add",
|
|
457
|
-
data: addedRecords,
|
|
458
|
-
time: new Date(),
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return Res;
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Update records.
|
|
467
|
-
* Returns: TSchema[]
|
|
468
|
-
*/
|
|
469
|
-
Update(record) {
|
|
470
|
-
return createFluentBuilder(async (data) => {
|
|
471
|
-
this.Validate(record);
|
|
472
|
-
record = this.Transform(record);
|
|
473
|
-
const PotentialyUpdated = await this.Get().where(record);
|
|
474
|
-
if (PotentialyUpdated.length == 0)
|
|
475
|
-
return [];
|
|
476
|
-
else {
|
|
477
|
-
const { sql } = this.BuildUpdate(this.Name, record, data);
|
|
478
|
-
const Result = await (await this.Connection.run(sql)).getRowObjects();
|
|
479
|
-
if (Result.length != 0) {
|
|
480
|
-
if (data.returning) {
|
|
481
|
-
this.notifyChange({ event: "AddMany", data: Result, time: new Date() });
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
const Updated = PotentialyUpdated;
|
|
485
|
-
const UpdatedPromises = Updated.map(async (record) => await this.GetOne().where(record));
|
|
486
|
-
const UpdatedRecords = await Promise.all(UpdatedPromises);
|
|
487
|
-
this.notifyChange({
|
|
488
|
-
event: "Add",
|
|
489
|
-
data: UpdatedRecords,
|
|
490
|
-
time: new Date(),
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
return (data.returning ? this.FormatOut(Result) : Result);
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Delete records.
|
|
500
|
-
* Returns: TSchema | undefined (The code logic returns index [0], implies single delete or returning first)
|
|
501
|
-
*/
|
|
502
|
-
Delete() {
|
|
503
|
-
return createFluentBuilder(async (data) => {
|
|
504
|
-
// Find what would be potentially deleted using the where clause
|
|
505
|
-
const PotentiallyDeleted = await this.Get().where(data.where);
|
|
506
|
-
if (PotentiallyDeleted.length == 0)
|
|
507
|
-
return undefined;
|
|
508
|
-
const { sql } = this.BuildDelete(this.Name, { ...data });
|
|
509
|
-
const Result = await (await this.Connection.run(sql)).getRowObjects();
|
|
510
|
-
if (Result.length != 0) {
|
|
511
|
-
if (data.returning) {
|
|
512
|
-
this.notifyChange({ event: "DeleteMany", data: Result, time: new Date() });
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
const DeletedRecords = PotentiallyDeleted;
|
|
516
|
-
this.notifyChange({
|
|
517
|
-
event: "Delete",
|
|
518
|
-
data: DeletedRecords,
|
|
519
|
-
time: new Date(),
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
const formatted = this.FormatOut(Result);
|
|
524
|
-
return formatted[0];
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
Count() {
|
|
528
|
-
return createFluentBuilder(async (data) => {
|
|
529
|
-
const whereClause = this.BuildWhereClause(data.where);
|
|
530
|
-
const sql = `SELECT COUNT(*) as count FROM ${this.Name}${whereClause}`;
|
|
531
|
-
const result = await (await this.Connection.run(sql)).getRowObjects();
|
|
532
|
-
// @ts-ignore
|
|
533
|
-
return Number(result[0].count);
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Auto-resolves relationships based on schema 'reference'
|
|
538
|
-
*/
|
|
539
|
-
ResolveJoin(targetTable) {
|
|
540
|
-
const myName = this.Name;
|
|
541
|
-
const targetName = targetTable.Name;
|
|
542
|
-
const getRef = (builder) => {
|
|
543
|
-
// @ts-ignore
|
|
544
|
-
const refConf = builder.config.reference;
|
|
545
|
-
if (typeof refConf === "string")
|
|
546
|
-
return { ref: refConf, isMany: false };
|
|
547
|
-
if (refConf && typeof refConf === "object")
|
|
548
|
-
return refConf;
|
|
549
|
-
return null;
|
|
550
|
-
};
|
|
551
|
-
// // 1. They reference Me
|
|
552
|
-
// for (const [colName, builder] of Object.entries(targetTable.Columns)) {
|
|
553
|
-
// const config = getRef(builder);
|
|
554
|
-
// if (config && config.ref) {
|
|
555
|
-
// const [refTable, refCol] = config.ref.split(".");
|
|
556
|
-
// if (refTable === myName) {
|
|
557
|
-
// // if (config.isMany) {
|
|
558
|
-
// // return `list_contains(${targetName}.${colName}, ${myName}.${refCol})`;
|
|
559
|
-
// // }
|
|
560
|
-
// return `${targetName}.${colName} = ${myName}.${refCol}`;
|
|
561
|
-
// }
|
|
562
|
-
// }
|
|
563
|
-
// }
|
|
564
|
-
// 2. I reference Them
|
|
565
|
-
for (const [colName, builder] of Object.entries(this.Columns)) {
|
|
566
|
-
const config = getRef(builder);
|
|
567
|
-
if (config && config.ref) {
|
|
568
|
-
const [refTable, refCol] = config.ref.split(".");
|
|
569
|
-
if (refTable === targetName) {
|
|
570
|
-
// if (config.isMany) {
|
|
571
|
-
// return `list_contains(${myName}.${colName}, ${targetName}.${refCol})`;
|
|
572
|
-
// }
|
|
573
|
-
return {
|
|
574
|
-
sql: `${myName}.${colName} = ${targetName}.${refCol}`,
|
|
575
|
-
isMany: config.isMany,
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
console.warn(`Could not resolve relationship between ${myName} and ${targetName}`);
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
// -- Validator --
|
|
584
|
-
Validate(data) {
|
|
585
|
-
const Cols = Object.fromEntries(
|
|
586
|
-
//@ts-ignore
|
|
587
|
-
Object.entries(this.Columns).map(([col, val]) => [col, val.config]));
|
|
588
|
-
for (const [field, config] of Object.entries(Cols)) {
|
|
589
|
-
// For partial updates, skip validation if key is missing
|
|
590
|
-
if (data[field] === undefined)
|
|
591
|
-
continue;
|
|
592
|
-
if ("validate" in config && typeof config.validate === "function") {
|
|
593
|
-
const fn = config.validate;
|
|
594
|
-
if (!fn(data[field])) {
|
|
595
|
-
throw new Error(`Validation failed for field '${field}' : The value '${JSON.stringify(data[field])}' doesn't pass the validate function`);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
// -- Transformer --
|
|
601
|
-
Transform(data) {
|
|
602
|
-
const Cols = Object.fromEntries(
|
|
603
|
-
//@ts-ignore
|
|
604
|
-
Object.entries(this.Columns).map(([col, val]) => [col, val.config]));
|
|
605
|
-
const transformed = { ...data };
|
|
606
|
-
for (const [field, config] of Object.entries(Cols)) {
|
|
607
|
-
// For partial updates, skip transform if key is missing
|
|
608
|
-
if (transformed[field] === undefined)
|
|
609
|
-
continue;
|
|
610
|
-
if ("transform" in config && typeof config.transform === "function") {
|
|
611
|
-
const fn = config.transform;
|
|
612
|
-
transformed[field] = fn(transformed[field]);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
return transformed;
|
|
616
|
-
}
|
|
617
|
-
}
|