@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/index.js
CHANGED
|
@@ -1,3 +1,1155 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
Array: () => Array2,
|
|
23
|
+
BigInt: () => BigInt,
|
|
24
|
+
Boolean: () => Boolean,
|
|
25
|
+
Bytes: () => Bytes,
|
|
26
|
+
Color: () => Color,
|
|
27
|
+
DateType: () => DateType,
|
|
28
|
+
Email: () => Email,
|
|
29
|
+
Enum: () => Enum,
|
|
30
|
+
Float: () => Float,
|
|
31
|
+
GeoArea: () => GeoArea,
|
|
32
|
+
GeoPoint: () => GeoPoint,
|
|
33
|
+
Int: () => Int,
|
|
34
|
+
Json: () => Json,
|
|
35
|
+
OptimaDB: () => OptimaDB,
|
|
36
|
+
Password: () => Password,
|
|
37
|
+
SQLBuilder: () => SQLBuilder,
|
|
38
|
+
Slug: () => Slug,
|
|
39
|
+
Table: () => Table,
|
|
40
|
+
Text: () => Text,
|
|
41
|
+
Timestamp: () => Timestamp,
|
|
42
|
+
Uuid: () => Uuid,
|
|
43
|
+
between: () => between,
|
|
44
|
+
cond: () => cond,
|
|
45
|
+
contains: () => contains,
|
|
46
|
+
endsWith: () => endsWith,
|
|
47
|
+
eq: () => eq,
|
|
48
|
+
gt: () => gt,
|
|
49
|
+
gte: () => gte,
|
|
50
|
+
inOp: () => inOp,
|
|
51
|
+
is: () => is,
|
|
52
|
+
isNot: () => isNot,
|
|
53
|
+
like: () => like,
|
|
54
|
+
lt: () => lt,
|
|
55
|
+
lte: () => lte,
|
|
56
|
+
ne: () => ne,
|
|
57
|
+
notBetween: () => notBetween,
|
|
58
|
+
notIn: () => notIn,
|
|
59
|
+
notLike: () => notLike,
|
|
60
|
+
notRegexp: () => notRegexp,
|
|
61
|
+
refHelpers: () => refHelpers,
|
|
62
|
+
regexp: () => regexp,
|
|
63
|
+
startsWith: () => startsWith
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// src/database.ts
|
|
67
|
+
import { DuckDBInstance } from "@duckdb/node-api";
|
|
68
|
+
|
|
69
|
+
// src/schema.ts
|
|
70
|
+
var schema_exports = {};
|
|
71
|
+
__export(schema_exports, {
|
|
72
|
+
Array: () => Array2,
|
|
73
|
+
BigInt: () => BigInt,
|
|
74
|
+
Boolean: () => Boolean,
|
|
75
|
+
Bytes: () => Bytes,
|
|
76
|
+
Color: () => Color,
|
|
77
|
+
DateType: () => DateType,
|
|
78
|
+
Email: () => Email,
|
|
79
|
+
Enum: () => Enum,
|
|
80
|
+
Float: () => Float,
|
|
81
|
+
GeoArea: () => GeoArea,
|
|
82
|
+
GeoPoint: () => GeoPoint,
|
|
83
|
+
Int: () => Int,
|
|
84
|
+
Json: () => Json,
|
|
85
|
+
Password: () => Password,
|
|
86
|
+
SQLBuilder: () => SQLBuilder,
|
|
87
|
+
Slug: () => Slug,
|
|
88
|
+
Table: () => Table,
|
|
89
|
+
Text: () => Text,
|
|
90
|
+
Timestamp: () => Timestamp,
|
|
91
|
+
Uuid: () => Uuid,
|
|
92
|
+
refHelpers: () => refHelpers
|
|
93
|
+
});
|
|
94
|
+
__reExport(schema_exports, zod_star);
|
|
95
|
+
import * as z from "zod";
|
|
96
|
+
import * as zod_star from "zod";
|
|
97
|
+
var refHelpers = {
|
|
98
|
+
one: (ref) => ref,
|
|
99
|
+
many: (ref) => ref
|
|
100
|
+
};
|
|
101
|
+
var ColumnImpl = class _ColumnImpl {
|
|
102
|
+
constructor(config = {}) {
|
|
103
|
+
this.config = {};
|
|
104
|
+
this.config = config;
|
|
105
|
+
}
|
|
106
|
+
next(key, value = true) {
|
|
107
|
+
return new _ColumnImpl({ ...this.config, [key]: value });
|
|
108
|
+
}
|
|
109
|
+
SQlType(val) {
|
|
110
|
+
return this.next("SQlType", val);
|
|
111
|
+
}
|
|
112
|
+
primaryKey() {
|
|
113
|
+
return this.next("primaryKey");
|
|
114
|
+
}
|
|
115
|
+
notnull() {
|
|
116
|
+
return this.next("notnull");
|
|
117
|
+
}
|
|
118
|
+
unique() {
|
|
119
|
+
return this.next("unique");
|
|
120
|
+
}
|
|
121
|
+
default(val) {
|
|
122
|
+
return this.next("default", val);
|
|
123
|
+
}
|
|
124
|
+
defaultNow() {
|
|
125
|
+
return this.next("defaultNow");
|
|
126
|
+
}
|
|
127
|
+
reference(c) {
|
|
128
|
+
const result = c(refHelpers);
|
|
129
|
+
const isManyRef = result?.[/* @__PURE__ */ Symbol.for("__refType")] === "many" || typeof result === "object" && result !== null && "__refType" in result;
|
|
130
|
+
const actualRef = result;
|
|
131
|
+
let refName = "";
|
|
132
|
+
if (actualRef?.__parent && actualRef?.__fieldName) {
|
|
133
|
+
refName = `${actualRef.__parent}.${actualRef.__fieldName}`;
|
|
134
|
+
} else {
|
|
135
|
+
const code = c.toString();
|
|
136
|
+
const match = code.match(/(?:one|many)\s*\(\s*([^)]+)\s*\)/);
|
|
137
|
+
if (match && match[1]) {
|
|
138
|
+
const refPart = match[1].trim();
|
|
139
|
+
if (refPart.includes(".")) {
|
|
140
|
+
refName = refPart;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const codeStr = c.toString();
|
|
145
|
+
const isMany = codeStr.includes("many(");
|
|
146
|
+
const nextConfig = { ...this.config, reference: { ref: refName, isMany } };
|
|
147
|
+
return new _ColumnImpl(nextConfig);
|
|
148
|
+
}
|
|
149
|
+
validate(fn) {
|
|
150
|
+
return this.next("validate", fn);
|
|
151
|
+
}
|
|
152
|
+
transform(fn) {
|
|
153
|
+
return this.next("transform", fn);
|
|
154
|
+
}
|
|
155
|
+
deprecated() {
|
|
156
|
+
return this.next("deprecated");
|
|
157
|
+
}
|
|
158
|
+
STRUCTType(val) {
|
|
159
|
+
return this.next("STRUCTType", val);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
var Column = (conf) => {
|
|
163
|
+
return new ColumnImpl(conf);
|
|
164
|
+
};
|
|
165
|
+
var Int = () => Column().SQlType("INTEGER");
|
|
166
|
+
var BigInt = () => Column().SQlType("BIGINT");
|
|
167
|
+
var Float = () => Column().SQlType("FLOAT");
|
|
168
|
+
var Boolean = () => Column().SQlType(
|
|
169
|
+
"BOOLEAN"
|
|
170
|
+
);
|
|
171
|
+
var Text = () => Column().SQlType("VARCHAR");
|
|
172
|
+
var Uuid = () => Column().SQlType("VARCHAR");
|
|
173
|
+
var DateType = () => Column().SQlType("DATE");
|
|
174
|
+
var Timestamp = () => Column().SQlType("TIMESTAMP");
|
|
175
|
+
var Enum = (vals) => {
|
|
176
|
+
const isString = typeof vals[0] === "string";
|
|
177
|
+
return Column().validate((v) => vals.includes(v)).SQlType(isString ? "VARCHAR" : "INTEGER");
|
|
178
|
+
};
|
|
179
|
+
var zodToDuckDBType = (zodType) => {
|
|
180
|
+
const def = zodType?._def;
|
|
181
|
+
if (def && (def.typeName === "ZodOptional" || def.typeName === "ZodNullable")) {
|
|
182
|
+
return zodToDuckDBType(def.innerType);
|
|
183
|
+
}
|
|
184
|
+
if (def && def.typeName === "ZodDefault") {
|
|
185
|
+
return zodToDuckDBType(def.innerType);
|
|
186
|
+
}
|
|
187
|
+
if (zodType instanceof z.ZodString) return "VARCHAR";
|
|
188
|
+
if (zodType instanceof z.ZodNumber) return "INTEGER";
|
|
189
|
+
if (zodType instanceof z.ZodBoolean) return "BOOLEAN";
|
|
190
|
+
if (zodType instanceof z.ZodDate) return "TIMESTAMP";
|
|
191
|
+
if (zodType instanceof z.ZodBigInt) return "BIGINT";
|
|
192
|
+
if ("element" in (def ?? {})) {
|
|
193
|
+
const innerType = zodToDuckDBType(def.element);
|
|
194
|
+
return `${innerType}[]`;
|
|
195
|
+
}
|
|
196
|
+
if ("shape" in (def ?? {})) {
|
|
197
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
198
|
+
const structParts = Object.entries(shape).map(([key, value]) => {
|
|
199
|
+
const fieldType = zodToDuckDBType(value);
|
|
200
|
+
return `${key} ${fieldType}`;
|
|
201
|
+
});
|
|
202
|
+
return `STRUCT(${structParts.join(", ")})`;
|
|
203
|
+
}
|
|
204
|
+
return "VARCHAR";
|
|
205
|
+
};
|
|
206
|
+
var Json = (obj) => {
|
|
207
|
+
const zodObj = z.object(obj);
|
|
208
|
+
const duckDBStructString = zodToDuckDBType(zodObj);
|
|
209
|
+
const col = Column().SQlType("STRUCT");
|
|
210
|
+
return col.STRUCTType(duckDBStructString);
|
|
211
|
+
};
|
|
212
|
+
var Array2 = (schema) => {
|
|
213
|
+
const innerType = zodToDuckDBType(schema);
|
|
214
|
+
const col = Column().SQlType("LIST");
|
|
215
|
+
return col.STRUCTType(`${innerType}[]`);
|
|
216
|
+
};
|
|
217
|
+
var Email = () => Column().SQlType("VARCHAR").validate((v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v));
|
|
218
|
+
var Slug = () => Column().SQlType("VARCHAR").validate((v) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(v));
|
|
219
|
+
var Color = () => Column().SQlType("VARCHAR").validate((v) => /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(v));
|
|
220
|
+
var Bytes = () => Column().SQlType("BLOB");
|
|
221
|
+
var Password = () => Column().SQlType(
|
|
222
|
+
"VARCHAR"
|
|
223
|
+
);
|
|
224
|
+
var GeoPoint = () => Column().SQlType("STRUCT");
|
|
225
|
+
var GeoArea = () => Column().SQlType("STRUCT");
|
|
226
|
+
var Table = (name, fields) => {
|
|
227
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
228
|
+
Object.assign(value, { __parent: name, __fieldName: key });
|
|
229
|
+
}
|
|
230
|
+
return { ...fields, __tableName: name };
|
|
231
|
+
};
|
|
232
|
+
var GetFieldConfig = (f) => {
|
|
233
|
+
return f.config;
|
|
234
|
+
};
|
|
235
|
+
var SQLBuilder = class {
|
|
236
|
+
static BuildField(name, f) {
|
|
237
|
+
const FieldConfig = GetFieldConfig(f);
|
|
238
|
+
let sql = `${name} ${FieldConfig.SQlType || "TEXT"}`;
|
|
239
|
+
if ((FieldConfig.SQlType == "STRUCT" || FieldConfig.SQlType == "LIST") && FieldConfig.STRUCTType) {
|
|
240
|
+
sql = `${name} ${FieldConfig.STRUCTType}`;
|
|
241
|
+
}
|
|
242
|
+
if (FieldConfig.primaryKey) sql += " PRIMARY KEY";
|
|
243
|
+
if (FieldConfig.notnull) sql += " NOT NULL";
|
|
244
|
+
if (FieldConfig.unique) sql += " UNIQUE";
|
|
245
|
+
if (FieldConfig.defaultNow) sql += ` DEFAULT CURRENT_TIMESTAMP`;
|
|
246
|
+
if (FieldConfig.SQlType !== "STRUCT" && FieldConfig.SQlType !== "LIST" && FieldConfig.default !== false && FieldConfig.default !== void 0) {
|
|
247
|
+
if (typeof FieldConfig.default !== "function") {
|
|
248
|
+
sql += ` DEFAULT ${typeof FieldConfig.default === "string" ? `'${FieldConfig.default}'` : JSON.stringify(FieldConfig.default)}`;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if ((FieldConfig.SQlType == "STRUCT" || FieldConfig.SQlType == "LIST") && FieldConfig.default !== false && FieldConfig.default !== void 0) {
|
|
252
|
+
sql += ` DEFAULT ${JSON.stringify(FieldConfig.default).split('"').join("'")}`;
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
sql: sql.trim(),
|
|
256
|
+
// @ts-ignore
|
|
257
|
+
ref: FieldConfig.reference
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
static BuildTable(name, t) {
|
|
261
|
+
const fieldDefs = Object.entries(t).filter(([key]) => key !== "__tableName").map(([fieldName, builder]) => {
|
|
262
|
+
const Result = this.BuildField(fieldName, builder);
|
|
263
|
+
return Result.sql;
|
|
264
|
+
});
|
|
265
|
+
return `CREATE TABLE IF NOT EXISTS ${name} (
|
|
266
|
+
${fieldDefs.join(
|
|
267
|
+
",\n "
|
|
268
|
+
)}
|
|
269
|
+
);`;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// src/fluent.ts
|
|
274
|
+
function createFluentBuilder(onBuild) {
|
|
275
|
+
const data = {};
|
|
276
|
+
const handler = {
|
|
277
|
+
get(target, prop) {
|
|
278
|
+
if (prop === "then") {
|
|
279
|
+
return (resolve, reject) => {
|
|
280
|
+
const result = onBuild ? onBuild(target) : target;
|
|
281
|
+
Promise.resolve(result).then(resolve, reject);
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
return (...args) => {
|
|
285
|
+
if (args.length === 0) {
|
|
286
|
+
target[prop] = true;
|
|
287
|
+
} else if (args.length === 1) {
|
|
288
|
+
target[prop] = args[0];
|
|
289
|
+
} else {
|
|
290
|
+
target[prop] = args;
|
|
291
|
+
}
|
|
292
|
+
return new Proxy(target, handler);
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
return new Proxy(data, handler);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/Qfluent.ts
|
|
300
|
+
var createProxyHandler = (table, config, isSingleResult) => {
|
|
301
|
+
return {
|
|
302
|
+
get(target, prop) {
|
|
303
|
+
if (prop === "then") {
|
|
304
|
+
return async (resolve, reject) => {
|
|
305
|
+
const tableAny = table;
|
|
306
|
+
try {
|
|
307
|
+
const { sql } = tableAny.BuildSelect(tableAny["Name"], config);
|
|
308
|
+
const rawResult = await (await tableAny.Connection.run(sql)).getRowObjects();
|
|
309
|
+
const formatted = tableAny.FormatOut(rawResult);
|
|
310
|
+
if (isSingleResult) {
|
|
311
|
+
const val = formatted.length > 0 ? formatted[0] : void 0;
|
|
312
|
+
resolve(val);
|
|
313
|
+
} else {
|
|
314
|
+
resolve(formatted);
|
|
315
|
+
}
|
|
316
|
+
} catch (e) {
|
|
317
|
+
reject(e);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
if (prop === "extend") {
|
|
322
|
+
return (extTable) => {
|
|
323
|
+
config.extend = config.extend || [];
|
|
324
|
+
if (!config.extend.includes(extTable)) {
|
|
325
|
+
config.extend.push(extTable);
|
|
326
|
+
}
|
|
327
|
+
return new Proxy(
|
|
328
|
+
target,
|
|
329
|
+
createProxyHandler(table, config, isSingleResult)
|
|
330
|
+
);
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return (...args) => {
|
|
334
|
+
if (args.length === 1) {
|
|
335
|
+
config[prop] = args[0];
|
|
336
|
+
} else if (args.length > 1) {
|
|
337
|
+
config[prop] = args;
|
|
338
|
+
} else {
|
|
339
|
+
config[prop] = true;
|
|
340
|
+
}
|
|
341
|
+
return new Proxy(
|
|
342
|
+
target,
|
|
343
|
+
createProxyHandler(table, config, isSingleResult)
|
|
344
|
+
);
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
function createQueryBuilder(table, initialConfig = {}) {
|
|
350
|
+
const dummyState = {};
|
|
351
|
+
return new Proxy(
|
|
352
|
+
dummyState,
|
|
353
|
+
createProxyHandler(table, initialConfig, false)
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
function createQueryBuilderOne(table, initialConfig = {}) {
|
|
357
|
+
const config = { ...initialConfig, limit: 1 };
|
|
358
|
+
const dummyState = {};
|
|
359
|
+
return new Proxy(
|
|
360
|
+
dummyState,
|
|
361
|
+
createProxyHandler(table, config, true)
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/table.ts
|
|
366
|
+
var ConditionBuilderImpl = class _ConditionBuilderImpl {
|
|
367
|
+
constructor(node) {
|
|
368
|
+
this.node = node;
|
|
369
|
+
}
|
|
370
|
+
createOp(op, value) {
|
|
371
|
+
return new _ConditionBuilderImpl({
|
|
372
|
+
type: "condition",
|
|
373
|
+
op,
|
|
374
|
+
value
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
eq(value) {
|
|
378
|
+
return this.createOp("eq", value);
|
|
379
|
+
}
|
|
380
|
+
ne(value) {
|
|
381
|
+
return this.createOp("ne", value);
|
|
382
|
+
}
|
|
383
|
+
gt(value) {
|
|
384
|
+
return this.createOp("gt", value);
|
|
385
|
+
}
|
|
386
|
+
gte(value) {
|
|
387
|
+
return this.createOp("gte", value);
|
|
388
|
+
}
|
|
389
|
+
lt(value) {
|
|
390
|
+
return this.createOp("lt", value);
|
|
391
|
+
}
|
|
392
|
+
lte(value) {
|
|
393
|
+
return this.createOp("lte", value);
|
|
394
|
+
}
|
|
395
|
+
in(value) {
|
|
396
|
+
return this.createOp("in", value);
|
|
397
|
+
}
|
|
398
|
+
notIn(value) {
|
|
399
|
+
return this.createOp("notIn", value);
|
|
400
|
+
}
|
|
401
|
+
between(value) {
|
|
402
|
+
return this.createOp("between", value);
|
|
403
|
+
}
|
|
404
|
+
notBetween(value) {
|
|
405
|
+
return this.createOp("notBetween", value);
|
|
406
|
+
}
|
|
407
|
+
is(value) {
|
|
408
|
+
return this.createOp("is", value);
|
|
409
|
+
}
|
|
410
|
+
isNot(value) {
|
|
411
|
+
return this.createOp("isNot", value);
|
|
412
|
+
}
|
|
413
|
+
like(value) {
|
|
414
|
+
return this.createOp("like", value);
|
|
415
|
+
}
|
|
416
|
+
notLike(value) {
|
|
417
|
+
return this.createOp("notLike", value);
|
|
418
|
+
}
|
|
419
|
+
startsWith(value) {
|
|
420
|
+
return this.createOp("startsWith", value);
|
|
421
|
+
}
|
|
422
|
+
endsWith(value) {
|
|
423
|
+
return this.createOp("endsWith", value);
|
|
424
|
+
}
|
|
425
|
+
contains(value) {
|
|
426
|
+
return this.createOp("contains", value);
|
|
427
|
+
}
|
|
428
|
+
regexp(value) {
|
|
429
|
+
return this.createOp("regexp", value);
|
|
430
|
+
}
|
|
431
|
+
notRegexp(value) {
|
|
432
|
+
return this.createOp("notRegexp", value);
|
|
433
|
+
}
|
|
434
|
+
and(other) {
|
|
435
|
+
return new _ConditionBuilderImpl({
|
|
436
|
+
type: "and",
|
|
437
|
+
left: this.node,
|
|
438
|
+
right: other.__getNode()
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
or(other) {
|
|
442
|
+
return new _ConditionBuilderImpl({
|
|
443
|
+
type: "or",
|
|
444
|
+
left: this.node,
|
|
445
|
+
right: other.__getNode()
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
__getNode() {
|
|
449
|
+
return this.node;
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
function cond() {
|
|
453
|
+
return new ConditionBuilderImpl({ type: "condition" });
|
|
454
|
+
}
|
|
455
|
+
var eq = (value) => cond().eq(value);
|
|
456
|
+
var ne = (value) => cond().ne(value);
|
|
457
|
+
var gt = (value) => cond().gt(value);
|
|
458
|
+
var gte = (value) => cond().gte(value);
|
|
459
|
+
var lt = (value) => cond().lt(value);
|
|
460
|
+
var lte = (value) => cond().lte(value);
|
|
461
|
+
var inOp = (value) => cond().in(value);
|
|
462
|
+
var notIn = (value) => cond().notIn(value);
|
|
463
|
+
var between = (value) => cond().between(value);
|
|
464
|
+
var notBetween = (value) => cond().notBetween(value);
|
|
465
|
+
var is = (value) => cond().is(value);
|
|
466
|
+
var isNot = (value) => cond().isNot(value);
|
|
467
|
+
var like = (value) => cond().like(value);
|
|
468
|
+
var notLike = (value) => cond().notLike(value);
|
|
469
|
+
var startsWith = (value) => cond().startsWith(value);
|
|
470
|
+
var endsWith = (value) => cond().endsWith(value);
|
|
471
|
+
var contains = (value) => cond().contains(value);
|
|
472
|
+
var regexp = (value) => cond().regexp(value);
|
|
473
|
+
var notRegexp = (value) => cond().notRegexp(value);
|
|
474
|
+
var escape = (val) => {
|
|
475
|
+
if (val === null || val === void 0) return "NULL";
|
|
476
|
+
if (typeof val === "string") return `'${val.replace(/'/g, "''")}'`;
|
|
477
|
+
if (val instanceof Date) return `'${val.toISOString()}'`;
|
|
478
|
+
if (typeof val === "boolean") return val ? "TRUE" : "FALSE";
|
|
479
|
+
if (typeof val === "object")
|
|
480
|
+
return `'${JSON.stringify(val).replace(/'/g, "''")}'`;
|
|
481
|
+
return String(val);
|
|
482
|
+
};
|
|
483
|
+
var SQL_GENERATORS = {
|
|
484
|
+
eq: (c, v) => `${c} = ${escape(v)}`,
|
|
485
|
+
ne: (c, v) => `${c} != ${escape(v)}`,
|
|
486
|
+
gt: (c, v) => `${c} > ${escape(v)}`,
|
|
487
|
+
gte: (c, v) => `${c} >= ${escape(v)}`,
|
|
488
|
+
lt: (c, v) => `${c} < ${escape(v)}`,
|
|
489
|
+
lte: (c, v) => `${c} <= ${escape(v)}`,
|
|
490
|
+
in: (c, v) => `${c} IN (${v.map(escape).join(", ")})`,
|
|
491
|
+
notIn: (c, v) => `${c} NOT IN (${v.map(escape).join(", ")})`,
|
|
492
|
+
between: (c, v) => `${c} BETWEEN ${escape(v[0])} AND ${escape(v[1])}`,
|
|
493
|
+
notBetween: (c, v) => `${c} NOT BETWEEN ${escape(v[0])} AND ${escape(v[1])}`,
|
|
494
|
+
is: (c, v) => v === null ? `${c} IS NULL` : `${c} IS ${escape(v)}`,
|
|
495
|
+
isNot: (c, v) => v === null ? `${c} IS NOT NULL` : `${c} IS NOT ${escape(v)}`,
|
|
496
|
+
like: (c, v) => `${c} LIKE ${escape(v)}`,
|
|
497
|
+
notLike: (c, v) => `${c} NOT LIKE ${escape(v)}`,
|
|
498
|
+
startsWith: (c, v) => `${c} LIKE ${escape(v + "%")}`,
|
|
499
|
+
endsWith: (c, v) => `${c} LIKE ${escape("%" + v)}`,
|
|
500
|
+
contains: (c, v) => `${c} LIKE ${escape("%" + v + "%")}`,
|
|
501
|
+
regexp: (c, v) => `regexp_matches(${c}, ${escape(v)})`,
|
|
502
|
+
notRegexp: (c, v) => `NOT regexp_matches(${c}, ${escape(v)})`
|
|
503
|
+
};
|
|
504
|
+
var OptimaTable = class _OptimaTable {
|
|
505
|
+
constructor(name, Columns, Connection) {
|
|
506
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
507
|
+
// --- SHARED: Where Clause Builder ---
|
|
508
|
+
this.BuildWhereClause = (where) => {
|
|
509
|
+
if (!where) return "";
|
|
510
|
+
const conditions = [];
|
|
511
|
+
const nodeToSQL = (node, colName) => {
|
|
512
|
+
if (node.type === "condition" && node.op) {
|
|
513
|
+
return SQL_GENERATORS[node.op](colName, node.value);
|
|
514
|
+
} else if (node.type === "and" && node.left && node.right) {
|
|
515
|
+
return `(${nodeToSQL(node.left, colName)} AND ${nodeToSQL(
|
|
516
|
+
node.right,
|
|
517
|
+
colName
|
|
518
|
+
)})`;
|
|
519
|
+
} else if (node.type === "or" && node.left && node.right) {
|
|
520
|
+
return `(${nodeToSQL(node.left, colName)} OR ${nodeToSQL(
|
|
521
|
+
node.right,
|
|
522
|
+
colName
|
|
523
|
+
)})`;
|
|
524
|
+
}
|
|
525
|
+
return "";
|
|
526
|
+
};
|
|
527
|
+
const processLevel = (currentWhere, pathPrefix) => {
|
|
528
|
+
for (const key of Object.keys(currentWhere)) {
|
|
529
|
+
const val = currentWhere[key];
|
|
530
|
+
const colName = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
531
|
+
if (val === void 0) continue;
|
|
532
|
+
if (val && typeof val === "object" && "__getNode" in val && typeof val.__getNode === "function") {
|
|
533
|
+
const node = val.__getNode();
|
|
534
|
+
const sql = nodeToSQL(node, colName);
|
|
535
|
+
if (sql) conditions.push(sql);
|
|
536
|
+
} else if (val && typeof val === "object" && !Array.isArray(val) && !(val instanceof Date)) {
|
|
537
|
+
processLevel(val, colName);
|
|
538
|
+
} else {
|
|
539
|
+
conditions.push(SQL_GENERATORS.eq(colName, val));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
processLevel(where, "");
|
|
544
|
+
if (conditions.length > 0) {
|
|
545
|
+
return ` WHERE ${conditions.join(" AND ")}`;
|
|
546
|
+
}
|
|
547
|
+
return "";
|
|
548
|
+
};
|
|
549
|
+
this.BuildSelect = (TableName, options) => {
|
|
550
|
+
const { limit, orderBy, offset, groupBy, where, extend } = options;
|
|
551
|
+
let selectColumns = `${TableName}.*`;
|
|
552
|
+
if (extend) {
|
|
553
|
+
const extensions = Array.isArray(extend) ? extend : [extend];
|
|
554
|
+
for (const extTable of extensions) {
|
|
555
|
+
const joinLogic = this.ResolveJoin(extTable);
|
|
556
|
+
if (joinLogic) {
|
|
557
|
+
selectColumns += `, (SELECT ${joinLogic.isMany ? "list(" : ""}${extTable.Name}${joinLogic.isMany ? ")" : ""} FROM ${extTable.Name} WHERE ${joinLogic.sql}) AS "$${extTable.Name}"`;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
let query = `SELECT ${selectColumns} FROM ${TableName}`;
|
|
562
|
+
query += this.BuildWhereClause(where);
|
|
563
|
+
if (groupBy && groupBy.length > 0)
|
|
564
|
+
query += ` GROUP BY ${groupBy.join(", ")}`;
|
|
565
|
+
if (orderBy) {
|
|
566
|
+
const [columns, direction] = orderBy;
|
|
567
|
+
const columnStr = Array.isArray(columns) ? columns.join(", ") : columns;
|
|
568
|
+
query += ` ORDER BY ${String(columnStr)} ${direction}`;
|
|
569
|
+
}
|
|
570
|
+
if (limit !== void 0 && limit !== null) query += ` LIMIT ${limit}`;
|
|
571
|
+
if (offset !== void 0 && offset !== null) query += ` OFFSET ${offset}`;
|
|
572
|
+
return { sql: query, args: [] };
|
|
573
|
+
};
|
|
574
|
+
this.BuildInsert = (TableName, Records, isReturning) => {
|
|
575
|
+
const columnKeys = Object.keys(this.Columns);
|
|
576
|
+
const columnsHeader = `(${columnKeys.join(", ")})`;
|
|
577
|
+
const valuesBlock = Records.map((r) => {
|
|
578
|
+
const orderedValues = columnKeys.map((key) => {
|
|
579
|
+
let v = r[key];
|
|
580
|
+
if (v == null && this.Columns[key].config.default) {
|
|
581
|
+
v = this.Columns[key].config.default();
|
|
582
|
+
}
|
|
583
|
+
return escape(v);
|
|
584
|
+
});
|
|
585
|
+
return `(${orderedValues.join(",")})`;
|
|
586
|
+
}).join(",\n");
|
|
587
|
+
const sql = `INSERT INTO ${TableName} ${columnsHeader} VALUES
|
|
588
|
+
${valuesBlock}${isReturning ? "\nRETURNING *" : ""};`;
|
|
589
|
+
return { sql };
|
|
590
|
+
};
|
|
591
|
+
this.BuildUpdate = (TableName, changes, options) => {
|
|
592
|
+
const { where, returning } = options;
|
|
593
|
+
const setClauses = [];
|
|
594
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
595
|
+
if (value !== void 0) {
|
|
596
|
+
setClauses.push(`${key} = ${escape(value)}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (setClauses.length === 0) {
|
|
600
|
+
throw new Error("No fields provided to update.");
|
|
601
|
+
}
|
|
602
|
+
let query = `UPDATE ${TableName} SET ${setClauses.join(", ")}`;
|
|
603
|
+
query += this.BuildWhereClause(where);
|
|
604
|
+
if (returning) {
|
|
605
|
+
query += " RETURNING *";
|
|
606
|
+
}
|
|
607
|
+
return { sql: query, args: [] };
|
|
608
|
+
};
|
|
609
|
+
this.BuildDelete = (TableName, options) => {
|
|
610
|
+
const { where, returning } = options;
|
|
611
|
+
let query = `DELETE FROM ${TableName}`;
|
|
612
|
+
query += this.BuildWhereClause(where);
|
|
613
|
+
if (returning) {
|
|
614
|
+
query += " RETURNING *";
|
|
615
|
+
}
|
|
616
|
+
return { sql: query, args: [] };
|
|
617
|
+
};
|
|
618
|
+
// Helper to handle the deep recursion
|
|
619
|
+
this.parseRecursive = (value) => {
|
|
620
|
+
if (value === null || value === void 0) {
|
|
621
|
+
return value;
|
|
622
|
+
}
|
|
623
|
+
if (value instanceof Date) {
|
|
624
|
+
return value;
|
|
625
|
+
}
|
|
626
|
+
if (Array.isArray(value)) {
|
|
627
|
+
return value.map((item) => this.parseRecursive(item));
|
|
628
|
+
}
|
|
629
|
+
if (typeof value === "object") {
|
|
630
|
+
if (value.entries !== void 0) {
|
|
631
|
+
return this.parseRecursive(value.entries);
|
|
632
|
+
}
|
|
633
|
+
if (value.items !== void 0) {
|
|
634
|
+
return this.parseRecursive(value.items);
|
|
635
|
+
}
|
|
636
|
+
const parsedObj = {};
|
|
637
|
+
for (const [k, v] of Object.entries(value)) {
|
|
638
|
+
parsedObj[k] = this.parseRecursive(v);
|
|
639
|
+
}
|
|
640
|
+
return parsedObj;
|
|
641
|
+
}
|
|
642
|
+
return value;
|
|
643
|
+
};
|
|
644
|
+
this.FormatOut = (data) => {
|
|
645
|
+
return data.map((row) => {
|
|
646
|
+
const formatted = {};
|
|
647
|
+
for (const [key, value] of Object.entries(row)) {
|
|
648
|
+
if (key.startsWith("$") && value === null) {
|
|
649
|
+
formatted[key] = [];
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
formatted[key] = this.parseRecursive(value);
|
|
653
|
+
}
|
|
654
|
+
return formatted;
|
|
655
|
+
});
|
|
656
|
+
};
|
|
657
|
+
this.Name = name;
|
|
658
|
+
const filteredCols = { ...Columns };
|
|
659
|
+
if ("__tableName" in filteredCols) {
|
|
660
|
+
delete filteredCols["__tableName"];
|
|
661
|
+
}
|
|
662
|
+
this.Columns = filteredCols;
|
|
663
|
+
this.Connection = Connection;
|
|
664
|
+
}
|
|
665
|
+
notifyChange(change) {
|
|
666
|
+
this.listeners.forEach((listener) => listener(change));
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Subscribe to changes to the table.
|
|
670
|
+
* The callback receives the latest change as its argument.
|
|
671
|
+
* Returns an unsubscribe function.
|
|
672
|
+
*/
|
|
673
|
+
Subscribe(fn) {
|
|
674
|
+
this.listeners.add(fn);
|
|
675
|
+
return () => {
|
|
676
|
+
this.listeners.delete(fn);
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
async InitTable() {
|
|
680
|
+
const SQL = SQLBuilder.BuildTable(this.Name, this.Columns);
|
|
681
|
+
await this.Connection.run(SQL);
|
|
682
|
+
}
|
|
683
|
+
static async create(name, Columns, Connection) {
|
|
684
|
+
const table = new _OptimaTable(name, Columns, Connection);
|
|
685
|
+
await table.InitTable();
|
|
686
|
+
return table;
|
|
687
|
+
}
|
|
688
|
+
// ---------------------------------------------------------
|
|
689
|
+
// UPDATED: Get() now uses the Fluent Factory
|
|
690
|
+
// ---------------------------------------------------------
|
|
691
|
+
Get() {
|
|
692
|
+
return createQueryBuilder(this);
|
|
693
|
+
}
|
|
694
|
+
// ---------------------------------------------------------
|
|
695
|
+
// UPDATED: GetOne() now uses the Fluent Factory
|
|
696
|
+
// ---------------------------------------------------------
|
|
697
|
+
GetOne() {
|
|
698
|
+
return createQueryBuilderOne(this);
|
|
699
|
+
}
|
|
700
|
+
Exist() {
|
|
701
|
+
return createFluentBuilder(async (data) => {
|
|
702
|
+
const whereClause = this.BuildWhereClause(data.where);
|
|
703
|
+
const sql = `SELECT 1 FROM ${this.Name}${whereClause} LIMIT 1`;
|
|
704
|
+
const Result = await (await this.Connection.run(sql)).getRowObjects();
|
|
705
|
+
return Result.length > 0;
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Add a single record.
|
|
710
|
+
* Returns: TSchema[] (containing the inserted record if returning is true)
|
|
711
|
+
*/
|
|
712
|
+
Add(record) {
|
|
713
|
+
return createFluentBuilder(async (data) => {
|
|
714
|
+
this.Validate(record);
|
|
715
|
+
record = this.Transform(record);
|
|
716
|
+
const { sql } = this.BuildInsert(this.Name, [record], data.returning);
|
|
717
|
+
const Result = await (await this.Connection.run(sql)).getRowObjects();
|
|
718
|
+
const Res = this.FormatOut(Result);
|
|
719
|
+
if (Res.length != 0) {
|
|
720
|
+
if (data.returning) {
|
|
721
|
+
this.notifyChange({ event: "Add", data: Res, time: /* @__PURE__ */ new Date() });
|
|
722
|
+
} else {
|
|
723
|
+
this.notifyChange({
|
|
724
|
+
event: "Add",
|
|
725
|
+
data: await this.GetOne().where(record),
|
|
726
|
+
time: /* @__PURE__ */ new Date()
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return Res;
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Add multiple records.
|
|
735
|
+
* Returns: TSchema[]
|
|
736
|
+
*/
|
|
737
|
+
AddMany(records) {
|
|
738
|
+
return createFluentBuilder(async (data) => {
|
|
739
|
+
records.forEach((v) => this.Validate(v));
|
|
740
|
+
const transformedRecords = records.map((v) => this.Transform(v));
|
|
741
|
+
const { sql } = this.BuildInsert(
|
|
742
|
+
this.Name,
|
|
743
|
+
transformedRecords,
|
|
744
|
+
data.returning
|
|
745
|
+
);
|
|
746
|
+
const Result = await (await this.Connection.run(sql)).getRowObjects();
|
|
747
|
+
const Res = this.FormatOut(Result);
|
|
748
|
+
if (Res.length != 0) {
|
|
749
|
+
if (data.returning) {
|
|
750
|
+
this.notifyChange({ event: "AddMany", data: Res, time: /* @__PURE__ */ new Date() });
|
|
751
|
+
} else {
|
|
752
|
+
const Added = transformedRecords;
|
|
753
|
+
const addedPromises = Added.map(
|
|
754
|
+
async (record) => await this.GetOne().where(record)
|
|
755
|
+
);
|
|
756
|
+
const addedRecords = await Promise.all(addedPromises);
|
|
757
|
+
this.notifyChange({
|
|
758
|
+
event: "Add",
|
|
759
|
+
data: addedRecords,
|
|
760
|
+
time: /* @__PURE__ */ new Date()
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return Res;
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Update records.
|
|
769
|
+
* Returns: TSchema[]
|
|
770
|
+
*/
|
|
771
|
+
Update(record) {
|
|
772
|
+
return createFluentBuilder(async (data) => {
|
|
773
|
+
this.Validate(record);
|
|
774
|
+
record = this.Transform(record);
|
|
775
|
+
const PotentialyUpdated = await this.Get().where(record);
|
|
776
|
+
if (PotentialyUpdated.length == 0) return [];
|
|
777
|
+
else {
|
|
778
|
+
const { sql } = this.BuildUpdate(this.Name, record, data);
|
|
779
|
+
const Result = await (await this.Connection.run(sql)).getRowObjects();
|
|
780
|
+
if (Result.length != 0) {
|
|
781
|
+
if (data.returning) {
|
|
782
|
+
this.notifyChange({ event: "AddMany", data: Result, time: /* @__PURE__ */ new Date() });
|
|
783
|
+
} else {
|
|
784
|
+
const Updated = PotentialyUpdated;
|
|
785
|
+
const UpdatedPromises = Updated.map(
|
|
786
|
+
async (record2) => await this.GetOne().where(record2)
|
|
787
|
+
);
|
|
788
|
+
const UpdatedRecords = await Promise.all(UpdatedPromises);
|
|
789
|
+
this.notifyChange({
|
|
790
|
+
event: "Add",
|
|
791
|
+
data: UpdatedRecords,
|
|
792
|
+
time: /* @__PURE__ */ new Date()
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return data.returning ? this.FormatOut(Result) : Result;
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Delete records.
|
|
802
|
+
* Returns: TSchema | undefined (The code logic returns index [0], implies single delete or returning first)
|
|
803
|
+
*/
|
|
804
|
+
Delete() {
|
|
805
|
+
return createFluentBuilder(async (data) => {
|
|
806
|
+
const PotentiallyDeleted = await this.Get().where(data.where);
|
|
807
|
+
if (PotentiallyDeleted.length == 0) return void 0;
|
|
808
|
+
const { sql } = this.BuildDelete(this.Name, { ...data });
|
|
809
|
+
const Result = await (await this.Connection.run(sql)).getRowObjects();
|
|
810
|
+
if (Result.length != 0) {
|
|
811
|
+
if (data.returning) {
|
|
812
|
+
this.notifyChange({ event: "DeleteMany", data: Result, time: /* @__PURE__ */ new Date() });
|
|
813
|
+
} else {
|
|
814
|
+
const DeletedRecords = PotentiallyDeleted;
|
|
815
|
+
this.notifyChange({
|
|
816
|
+
event: "Delete",
|
|
817
|
+
data: DeletedRecords,
|
|
818
|
+
time: /* @__PURE__ */ new Date()
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
const formatted = this.FormatOut(Result);
|
|
823
|
+
return formatted[0];
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
Count() {
|
|
827
|
+
return createFluentBuilder(
|
|
828
|
+
async (data) => {
|
|
829
|
+
const whereClause = this.BuildWhereClause(data.where);
|
|
830
|
+
const sql = `SELECT COUNT(*) as count FROM ${this.Name}${whereClause}`;
|
|
831
|
+
const result = await (await this.Connection.run(sql)).getRowObjects();
|
|
832
|
+
return Number(result[0].count);
|
|
833
|
+
}
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Auto-resolves relationships based on schema 'reference'
|
|
838
|
+
*/
|
|
839
|
+
ResolveJoin(targetTable) {
|
|
840
|
+
const myName = this.Name;
|
|
841
|
+
const targetName = targetTable.Name;
|
|
842
|
+
const getRef = (builder) => {
|
|
843
|
+
const refConf = builder.config.reference;
|
|
844
|
+
if (typeof refConf === "string") return { ref: refConf, isMany: false };
|
|
845
|
+
if (refConf && typeof refConf === "object") return refConf;
|
|
846
|
+
return null;
|
|
847
|
+
};
|
|
848
|
+
for (const [colName, builder] of Object.entries(this.Columns)) {
|
|
849
|
+
const config = getRef(builder);
|
|
850
|
+
if (config && config.ref) {
|
|
851
|
+
const [refTable, refCol] = config.ref.split(".");
|
|
852
|
+
if (refTable === targetName) {
|
|
853
|
+
return {
|
|
854
|
+
sql: `${myName}.${colName} = ${targetName}.${refCol}`,
|
|
855
|
+
isMany: config.isMany
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
console.warn(
|
|
861
|
+
`Could not resolve relationship between ${myName} and ${targetName}`
|
|
862
|
+
);
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
// -- Validator --
|
|
866
|
+
Validate(data) {
|
|
867
|
+
const Cols = Object.fromEntries(
|
|
868
|
+
//@ts-ignore
|
|
869
|
+
Object.entries(this.Columns).map(([col, val]) => [col, val.config])
|
|
870
|
+
);
|
|
871
|
+
for (const [field, config] of Object.entries(Cols)) {
|
|
872
|
+
if (data[field] === void 0) continue;
|
|
873
|
+
if ("validate" in config && typeof config.validate === "function") {
|
|
874
|
+
const fn = config.validate;
|
|
875
|
+
if (!fn(data[field])) {
|
|
876
|
+
throw new Error(
|
|
877
|
+
`Validation failed for field '${field}' : The value '${JSON.stringify(
|
|
878
|
+
data[field]
|
|
879
|
+
)}' doesn't pass the validate function`
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
// -- Transformer --
|
|
886
|
+
Transform(data) {
|
|
887
|
+
const Cols = Object.fromEntries(
|
|
888
|
+
//@ts-ignore
|
|
889
|
+
Object.entries(this.Columns).map(([col, val]) => [col, val.config])
|
|
890
|
+
);
|
|
891
|
+
const transformed = { ...data };
|
|
892
|
+
for (const [field, config] of Object.entries(Cols)) {
|
|
893
|
+
if (transformed[field] === void 0) continue;
|
|
894
|
+
if ("transform" in config && typeof config.transform === "function") {
|
|
895
|
+
const fn = config.transform;
|
|
896
|
+
transformed[field] = fn(transformed[field]);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return transformed;
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// src/database.ts
|
|
904
|
+
var OptimaDB = class _OptimaDB {
|
|
905
|
+
constructor(instance, connection, tables) {
|
|
906
|
+
this.instance = instance;
|
|
907
|
+
this.connection = connection;
|
|
908
|
+
this.Tables = tables;
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Initializes the database, connects, detects schema changes,
|
|
912
|
+
* runs migrations, and returns the DB instance.
|
|
913
|
+
*/
|
|
914
|
+
static async Open(name = ":memory:", schema, options) {
|
|
915
|
+
const instance = await DuckDBInstance.create(name);
|
|
916
|
+
const connection = await instance.connect();
|
|
917
|
+
if (options?.MemoryLimit) {
|
|
918
|
+
await connection.run(`SET memory_limit = '${options.MemoryLimit}';`);
|
|
919
|
+
}
|
|
920
|
+
if (options?.ThreadCount) {
|
|
921
|
+
await connection.run(`SET threads = ${options.ThreadCount};`);
|
|
922
|
+
}
|
|
923
|
+
const existingTables = await (await connection.run(`SELECT * from duckdb_tables`)).getRowObjects();
|
|
924
|
+
const cleanSchema = SchemaMigrator.transformSchema(schema);
|
|
925
|
+
const migrationSQL = SchemaMigrator.generateSQL(existingTables, cleanSchema);
|
|
926
|
+
if (migrationSQL.length > 0) {
|
|
927
|
+
console.log(`[OptimaDB] Applying ${migrationSQL.length} migrations...`);
|
|
928
|
+
await connection.run("BEGIN TRANSACTION;");
|
|
929
|
+
try {
|
|
930
|
+
for (const sql of migrationSQL) {
|
|
931
|
+
await connection.run(sql);
|
|
932
|
+
}
|
|
933
|
+
await connection.run("COMMIT;");
|
|
934
|
+
} catch (err) {
|
|
935
|
+
await connection.run("ROLLBACK;");
|
|
936
|
+
console.error("[OptimaDB] Migration failed, rolling back.", err);
|
|
937
|
+
throw err;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const tables = {};
|
|
941
|
+
for (const key of Object.keys(schema)) {
|
|
942
|
+
tables[key] = await OptimaTable.create(
|
|
943
|
+
key,
|
|
944
|
+
schema[key],
|
|
945
|
+
connection
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
return new _OptimaDB(instance, connection, tables);
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Executes a raw SQL query and returns rows as objects.
|
|
952
|
+
*/
|
|
953
|
+
async execute(sql) {
|
|
954
|
+
return (await this.connection.run(sql)).getRowObjects();
|
|
955
|
+
}
|
|
956
|
+
async getTables() {
|
|
957
|
+
const result = await this.execute("PRAGMA show_tables;");
|
|
958
|
+
return result.map((t) => t.name);
|
|
959
|
+
}
|
|
960
|
+
async getDiskSize() {
|
|
961
|
+
const result = await this.execute("PRAGMA database_size;");
|
|
962
|
+
return result[0];
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Runs a function within a transaction.
|
|
966
|
+
* Re-throws error after rollback so caller handles logic failure.
|
|
967
|
+
*/
|
|
968
|
+
async transaction(fn) {
|
|
969
|
+
await this.connection.run("BEGIN TRANSACTION;");
|
|
970
|
+
try {
|
|
971
|
+
const result = await fn();
|
|
972
|
+
await this.connection.run("COMMIT;");
|
|
973
|
+
return result;
|
|
974
|
+
} catch (e) {
|
|
975
|
+
await this.connection.run("ROLLBACK;");
|
|
976
|
+
throw e;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
var SchemaMigrator = class {
|
|
981
|
+
/**
|
|
982
|
+
* Transforms the complex Module/ColumnImpl schema into a simple config object.
|
|
983
|
+
*/
|
|
984
|
+
static transformSchema(schemaObject) {
|
|
985
|
+
const cleanSchema = {};
|
|
986
|
+
for (const [tableName, tableDef] of Object.entries(schemaObject)) {
|
|
987
|
+
if (typeof tableDef !== "object" || tableDef === null) continue;
|
|
988
|
+
const realTableName = tableDef.__tableName || tableName;
|
|
989
|
+
cleanSchema[realTableName] = {};
|
|
990
|
+
for (const [colName, colImpl] of Object.entries(tableDef)) {
|
|
991
|
+
if (colName.startsWith("__")) continue;
|
|
992
|
+
if (colImpl?.config) {
|
|
993
|
+
const config = { ...colImpl.config };
|
|
994
|
+
if (config.SQlType === "STRUCT" && config.STRUCTType) {
|
|
995
|
+
config.SQlType = config.STRUCTType;
|
|
996
|
+
}
|
|
997
|
+
cleanSchema[realTableName][colName] = config;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return cleanSchema;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Generates SQL statements to migrate DB from Current -> Desired state.
|
|
1005
|
+
*/
|
|
1006
|
+
static generateSQL(existingTablesRaw, desiredSchema) {
|
|
1007
|
+
const currentSchema = this.parseDuckDBTableSQL(existingTablesRaw);
|
|
1008
|
+
const steps = [];
|
|
1009
|
+
for (const [tableName, fields] of Object.entries(desiredSchema)) {
|
|
1010
|
+
if (!currentSchema[tableName]) {
|
|
1011
|
+
const fieldDefs = Object.entries(fields).map(([colName, config]) => {
|
|
1012
|
+
let def = `"${colName}" ${config.SQlType}`;
|
|
1013
|
+
if (config.primaryKey) def += " PRIMARY KEY";
|
|
1014
|
+
if (config.notnull) def += " NOT NULL";
|
|
1015
|
+
return def;
|
|
1016
|
+
});
|
|
1017
|
+
steps.push(`CREATE TABLE ${tableName} (${fieldDefs.join(", ")});`);
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
const currentTableCols = currentSchema[tableName];
|
|
1021
|
+
for (const [colName, config] of Object.entries(fields)) {
|
|
1022
|
+
if (!currentTableCols[colName]) {
|
|
1023
|
+
let def = `${config.SQlType}`;
|
|
1024
|
+
if (config.notnull) {
|
|
1025
|
+
def += ` DEFAULT ${this.getDefaultValueForType(config.SQlType)}`;
|
|
1026
|
+
}
|
|
1027
|
+
steps.push(`ALTER TABLE ${tableName} ADD COLUMN "${colName}" ${def};`);
|
|
1028
|
+
if (config.notnull) {
|
|
1029
|
+
steps.push(`ALTER TABLE ${tableName} ALTER "${colName}" SET NOT NULL;`);
|
|
1030
|
+
}
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
const currentCol = currentTableCols[colName];
|
|
1034
|
+
const normCurrentType = currentCol.type.replace(/\s+/g, "").toUpperCase();
|
|
1035
|
+
const normNewType = config.SQlType.replace(/\s+/g, "").toUpperCase();
|
|
1036
|
+
if (normCurrentType !== normNewType && !normNewType.startsWith("STRUCT")) {
|
|
1037
|
+
steps.push(`ALTER TABLE ${tableName} ALTER "${colName}" TYPE ${config.SQlType};`);
|
|
1038
|
+
}
|
|
1039
|
+
const desiredNotNull = !!config.notnull;
|
|
1040
|
+
if (desiredNotNull !== currentCol.isNotNull) {
|
|
1041
|
+
if (desiredNotNull) {
|
|
1042
|
+
steps.push(`ALTER TABLE ${tableName} ALTER "${colName}" SET NOT NULL;`);
|
|
1043
|
+
} else {
|
|
1044
|
+
steps.push(`ALTER TABLE ${tableName} ALTER "${colName}" DROP NOT NULL;`);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return steps;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Parses raw "CREATE TABLE" SQL from DuckDB into a usable map.
|
|
1053
|
+
*/
|
|
1054
|
+
static parseDuckDBTableSQL(existingTables) {
|
|
1055
|
+
const schemaMap = {};
|
|
1056
|
+
existingTables.forEach((table) => {
|
|
1057
|
+
const match = table.sql.match(/CREATE TABLE "?\w+"?\s*\((.*)\);/s);
|
|
1058
|
+
if (!match) return;
|
|
1059
|
+
const columnBody = match[1];
|
|
1060
|
+
const columns = {};
|
|
1061
|
+
let depth = 0;
|
|
1062
|
+
let currentChunk = "";
|
|
1063
|
+
for (let i = 0; i < columnBody.length; i++) {
|
|
1064
|
+
const char = columnBody[i];
|
|
1065
|
+
if (char === "(") depth++;
|
|
1066
|
+
if (char === ")") depth--;
|
|
1067
|
+
if (char === "," && depth === 0) {
|
|
1068
|
+
this.processColumnLine(currentChunk, columns);
|
|
1069
|
+
currentChunk = "";
|
|
1070
|
+
} else {
|
|
1071
|
+
currentChunk += char;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (currentChunk.trim()) this.processColumnLine(currentChunk, columns);
|
|
1075
|
+
schemaMap[table.table_name] = columns;
|
|
1076
|
+
});
|
|
1077
|
+
return schemaMap;
|
|
1078
|
+
}
|
|
1079
|
+
static processColumnLine(line, columnMap) {
|
|
1080
|
+
const parts = line.trim().match(/^("?[\w]+"?)\s+(.*)$/);
|
|
1081
|
+
if (!parts) return;
|
|
1082
|
+
const name = parts[1]?.replace(/"/g, "");
|
|
1083
|
+
const definition = parts[2];
|
|
1084
|
+
const upDef = definition?.toUpperCase();
|
|
1085
|
+
let type = definition?.split(" ")[0];
|
|
1086
|
+
if (upDef?.startsWith("STRUCT")) {
|
|
1087
|
+
type = definition;
|
|
1088
|
+
}
|
|
1089
|
+
if (name !== void 0) {
|
|
1090
|
+
columnMap[name] = {
|
|
1091
|
+
type,
|
|
1092
|
+
isNotNull: upDef?.includes("NOT NULL") ?? false,
|
|
1093
|
+
isPrimaryKey: upDef?.includes("PRIMARY KEY") ?? false
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Returns a safe default value for a given SQL type.
|
|
1099
|
+
* Required when adding NOT NULL columns to existing tables.
|
|
1100
|
+
*/
|
|
1101
|
+
static getDefaultValueForType(type) {
|
|
1102
|
+
const t = type.toUpperCase();
|
|
1103
|
+
if (t.includes("INT") || t.includes("FLOAT") || t.includes("DECIMAL") || t.includes("DOUBLE")) return "0";
|
|
1104
|
+
if (t.includes("BOOL")) return "FALSE";
|
|
1105
|
+
if (t.includes("STRUCT") || t.includes("MAP") || t.includes("LIST")) return "NULL";
|
|
1106
|
+
return "''";
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
// src/index.ts
|
|
1111
|
+
__reExport(index_exports, schema_exports);
|
|
1112
|
+
export {
|
|
1113
|
+
Array2 as Array,
|
|
1114
|
+
BigInt,
|
|
1115
|
+
Boolean,
|
|
1116
|
+
Bytes,
|
|
1117
|
+
Color,
|
|
1118
|
+
DateType,
|
|
1119
|
+
Email,
|
|
1120
|
+
Enum,
|
|
1121
|
+
Float,
|
|
1122
|
+
GeoArea,
|
|
1123
|
+
GeoPoint,
|
|
1124
|
+
Int,
|
|
1125
|
+
Json,
|
|
1126
|
+
OptimaDB,
|
|
1127
|
+
Password,
|
|
1128
|
+
SQLBuilder,
|
|
1129
|
+
Slug,
|
|
1130
|
+
Table,
|
|
1131
|
+
Text,
|
|
1132
|
+
Timestamp,
|
|
1133
|
+
Uuid,
|
|
1134
|
+
between,
|
|
1135
|
+
cond,
|
|
1136
|
+
contains,
|
|
1137
|
+
endsWith,
|
|
1138
|
+
eq,
|
|
1139
|
+
gt,
|
|
1140
|
+
gte,
|
|
1141
|
+
inOp,
|
|
1142
|
+
is,
|
|
1143
|
+
isNot,
|
|
1144
|
+
like,
|
|
1145
|
+
lt,
|
|
1146
|
+
lte,
|
|
1147
|
+
ne,
|
|
1148
|
+
notBetween,
|
|
1149
|
+
notIn,
|
|
1150
|
+
notLike,
|
|
1151
|
+
notRegexp,
|
|
1152
|
+
refHelpers,
|
|
1153
|
+
regexp,
|
|
1154
|
+
startsWith
|
|
1155
|
+
};
|