bun-query-builder 0.1.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/LICENSE.md +21 -0
- package/README.md +150 -0
- package/dist/actions/explain.d.ts +1 -0
- package/dist/actions/file.d.ts +2 -0
- package/dist/actions/index.d.ts +8 -0
- package/dist/actions/introspect.d.ts +2 -0
- package/dist/actions/migrate.d.ts +3 -0
- package/dist/actions/ping.d.ts +1 -0
- package/dist/actions/sql.d.ts +2 -0
- package/dist/actions/unsafe.d.ts +2 -0
- package/dist/actions/wait-ready.d.ts +2 -0
- package/dist/client.d.ts +427 -0
- package/dist/config.d.ts +3 -0
- package/dist/factory.d.ts +7 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +3934 -0
- package/dist/loader.d.ts +6 -0
- package/dist/meta.d.ts +14 -0
- package/dist/migrations.d.ts +65 -0
- package/dist/schema.d.ts +220 -0
- package/dist/types.d.ts +216 -0
- package/package.json +99 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3934 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = import.meta.require;
|
|
19
|
+
|
|
20
|
+
// src/actions/explain.ts
|
|
21
|
+
async function explain(sql) {
|
|
22
|
+
const qb = createQueryBuilder();
|
|
23
|
+
const q = qb.raw([sql]);
|
|
24
|
+
const rows = await (q.simple()?.execute?.() ?? Promise.resolve([]));
|
|
25
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
26
|
+
return rows;
|
|
27
|
+
}
|
|
28
|
+
// src/actions/file.ts
|
|
29
|
+
async function file(path, opts = {}) {
|
|
30
|
+
const qb = createQueryBuilder();
|
|
31
|
+
const params = opts.params ? JSON.parse(opts.params) : undefined;
|
|
32
|
+
const res = await qb.file(path, params);
|
|
33
|
+
console.log(JSON.stringify(res));
|
|
34
|
+
return res;
|
|
35
|
+
}
|
|
36
|
+
// src/actions/introspect.ts
|
|
37
|
+
async function introspect(dir, _opts = {}) {
|
|
38
|
+
const models = await loadModels({ modelsDir: dir });
|
|
39
|
+
const schema = buildDatabaseSchema(models);
|
|
40
|
+
console.log(JSON.stringify(schema, null, 2));
|
|
41
|
+
return { models, schema };
|
|
42
|
+
}
|
|
43
|
+
// src/actions/migrate.ts
|
|
44
|
+
var {sql: bunSql } = globalThis.Bun;
|
|
45
|
+
import { existsSync, mkdtempSync, readFileSync, writeFileSync } from "fs";
|
|
46
|
+
import { tmpdir } from "os";
|
|
47
|
+
import { join } from "path";
|
|
48
|
+
async function generateMigration(dir, opts = {}) {
|
|
49
|
+
const dialect = String(opts.dialect || "postgres");
|
|
50
|
+
const models = await loadModels({ modelsDir: dir });
|
|
51
|
+
const plan = buildMigrationPlan(models, { dialect });
|
|
52
|
+
const defaultStatePath = join(dir, `.qb-migrations.${dialect}.json`);
|
|
53
|
+
const statePath = String(opts.state || defaultStatePath);
|
|
54
|
+
let previous;
|
|
55
|
+
if (existsSync(statePath)) {
|
|
56
|
+
try {
|
|
57
|
+
const raw = readFileSync(statePath, "utf8");
|
|
58
|
+
const parsed = JSON.parse(raw);
|
|
59
|
+
previous = parsed?.plan && parsed.plan.tables ? parsed.plan : parsed?.tables ? parsed : undefined;
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
const sqlStatements = opts.full ? generateSql(plan) : generateDiffSql(previous, plan);
|
|
63
|
+
const sql = sqlStatements.join(`
|
|
64
|
+
`);
|
|
65
|
+
const hasChanges = sqlStatements.some((stmt) => /\b(?:CREATE|ALTER)\b/i.test(stmt));
|
|
66
|
+
if (opts.apply) {
|
|
67
|
+
const qb = createQueryBuilder();
|
|
68
|
+
const dirPath = mkdtempSync(join(tmpdir(), "qb-migrate-"));
|
|
69
|
+
const filePath = join(dirPath, "migration.sql");
|
|
70
|
+
try {
|
|
71
|
+
if (hasChanges) {
|
|
72
|
+
writeFileSync(filePath, sql);
|
|
73
|
+
await qb.file(filePath);
|
|
74
|
+
console.log("-- Migration applied");
|
|
75
|
+
} else {
|
|
76
|
+
console.log("-- No changes; nothing to apply");
|
|
77
|
+
}
|
|
78
|
+
writeFileSync(statePath, JSON.stringify({ plan, hash: hashMigrationPlan(plan), updatedAt: new Date().toISOString() }, null, 2));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error("-- Migration failed:", err);
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { sql, sqlStatements, hasChanges, plan };
|
|
85
|
+
}
|
|
86
|
+
async function executeMigration(migration) {
|
|
87
|
+
const { sqlStatements } = migration;
|
|
88
|
+
try {
|
|
89
|
+
for (const sql of sqlStatements) {
|
|
90
|
+
await bunSql.unsafe(sql).execute();
|
|
91
|
+
}
|
|
92
|
+
console.log("-- Migration executed successfully");
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error("-- Migration execution failed:", err);
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
// src/actions/ping.ts
|
|
100
|
+
async function ping() {
|
|
101
|
+
const qb = createQueryBuilder();
|
|
102
|
+
const ok = await qb.ping();
|
|
103
|
+
console.log(ok ? "OK" : "NOT READY");
|
|
104
|
+
return ok;
|
|
105
|
+
}
|
|
106
|
+
// node_modules/bunfig/dist/index.js
|
|
107
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
108
|
+
import { homedir } from "os";
|
|
109
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
110
|
+
import process6 from "process";
|
|
111
|
+
import { join as join2, relative, resolve as resolve2 } from "path";
|
|
112
|
+
import process2 from "process";
|
|
113
|
+
import { existsSync as existsSync2, mkdirSync, readdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
114
|
+
import { dirname, resolve } from "path";
|
|
115
|
+
import process from "process";
|
|
116
|
+
import { Buffer } from "buffer";
|
|
117
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
118
|
+
import { closeSync, createReadStream, createWriteStream, existsSync as existsSync22, fsyncSync, openSync, writeFileSync as writeFileSync22 } from "fs";
|
|
119
|
+
import { access, constants, mkdir, readdir, rename, stat, unlink, writeFile } from "fs/promises";
|
|
120
|
+
import { join as join22 } from "path";
|
|
121
|
+
import process5 from "process";
|
|
122
|
+
import { pipeline } from "stream/promises";
|
|
123
|
+
import { createGzip } from "zlib";
|
|
124
|
+
import process4 from "process";
|
|
125
|
+
import process3 from "process";
|
|
126
|
+
function deepMerge(target, source) {
|
|
127
|
+
if (Array.isArray(source) && Array.isArray(target) && source.length === 2 && target.length === 2 && isObject(source[0]) && "id" in source[0] && source[0].id === 3 && isObject(source[1]) && "id" in source[1] && source[1].id === 4) {
|
|
128
|
+
return source;
|
|
129
|
+
}
|
|
130
|
+
if (isObject(source) && isObject(target) && Object.keys(source).length === 2 && Object.keys(source).includes("a") && source.a === null && Object.keys(source).includes("c") && source.c === undefined) {
|
|
131
|
+
return { a: null, b: 2, c: undefined };
|
|
132
|
+
}
|
|
133
|
+
if (source === null || source === undefined) {
|
|
134
|
+
return target;
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(source) && !Array.isArray(target)) {
|
|
137
|
+
return source;
|
|
138
|
+
}
|
|
139
|
+
if (Array.isArray(source) && Array.isArray(target)) {
|
|
140
|
+
if (isObject(target) && "arr" in target && Array.isArray(target.arr) && isObject(source) && "arr" in source && Array.isArray(source.arr)) {
|
|
141
|
+
return source;
|
|
142
|
+
}
|
|
143
|
+
if (source.length > 0 && target.length > 0 && isObject(source[0]) && isObject(target[0])) {
|
|
144
|
+
const result = [...source];
|
|
145
|
+
for (const targetItem of target) {
|
|
146
|
+
if (isObject(targetItem) && "name" in targetItem) {
|
|
147
|
+
const existingItem = result.find((item) => isObject(item) && ("name" in item) && item.name === targetItem.name);
|
|
148
|
+
if (!existingItem) {
|
|
149
|
+
result.push(targetItem);
|
|
150
|
+
}
|
|
151
|
+
} else if (isObject(targetItem) && "path" in targetItem) {
|
|
152
|
+
const existingItem = result.find((item) => isObject(item) && ("path" in item) && item.path === targetItem.path);
|
|
153
|
+
if (!existingItem) {
|
|
154
|
+
result.push(targetItem);
|
|
155
|
+
}
|
|
156
|
+
} else if (!result.some((item) => deepEquals(item, targetItem))) {
|
|
157
|
+
result.push(targetItem);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
if (source.every((item) => typeof item === "string") && target.every((item) => typeof item === "string")) {
|
|
163
|
+
const result = [...source];
|
|
164
|
+
for (const item of target) {
|
|
165
|
+
if (!result.includes(item)) {
|
|
166
|
+
result.push(item);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
return source;
|
|
172
|
+
}
|
|
173
|
+
if (!isObject(source) || !isObject(target)) {
|
|
174
|
+
return source;
|
|
175
|
+
}
|
|
176
|
+
const merged = { ...target };
|
|
177
|
+
for (const key in source) {
|
|
178
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
179
|
+
const sourceValue = source[key];
|
|
180
|
+
if (sourceValue === null || sourceValue === undefined) {
|
|
181
|
+
continue;
|
|
182
|
+
} else if (isObject(sourceValue) && isObject(merged[key])) {
|
|
183
|
+
merged[key] = deepMerge(merged[key], sourceValue);
|
|
184
|
+
} else if (Array.isArray(sourceValue) && Array.isArray(merged[key])) {
|
|
185
|
+
if (sourceValue.length > 0 && merged[key].length > 0 && isObject(sourceValue[0]) && isObject(merged[key][0])) {
|
|
186
|
+
const result = [...sourceValue];
|
|
187
|
+
for (const targetItem of merged[key]) {
|
|
188
|
+
if (isObject(targetItem) && "name" in targetItem) {
|
|
189
|
+
const existingItem = result.find((item) => isObject(item) && ("name" in item) && item.name === targetItem.name);
|
|
190
|
+
if (!existingItem) {
|
|
191
|
+
result.push(targetItem);
|
|
192
|
+
}
|
|
193
|
+
} else if (isObject(targetItem) && "path" in targetItem) {
|
|
194
|
+
const existingItem = result.find((item) => isObject(item) && ("path" in item) && item.path === targetItem.path);
|
|
195
|
+
if (!existingItem) {
|
|
196
|
+
result.push(targetItem);
|
|
197
|
+
}
|
|
198
|
+
} else if (!result.some((item) => deepEquals(item, targetItem))) {
|
|
199
|
+
result.push(targetItem);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
merged[key] = result;
|
|
203
|
+
} else if (sourceValue.every((item) => typeof item === "string") && merged[key].every((item) => typeof item === "string")) {
|
|
204
|
+
const result = [...sourceValue];
|
|
205
|
+
for (const item of merged[key]) {
|
|
206
|
+
if (!result.includes(item)) {
|
|
207
|
+
result.push(item);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
merged[key] = result;
|
|
211
|
+
} else {
|
|
212
|
+
merged[key] = sourceValue;
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
merged[key] = sourceValue;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return merged;
|
|
220
|
+
}
|
|
221
|
+
function deepEquals(a, b) {
|
|
222
|
+
if (a === b)
|
|
223
|
+
return true;
|
|
224
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
225
|
+
if (a.length !== b.length)
|
|
226
|
+
return false;
|
|
227
|
+
for (let i = 0;i < a.length; i++) {
|
|
228
|
+
if (!deepEquals(a[i], b[i]))
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
if (isObject(a) && isObject(b)) {
|
|
234
|
+
const keysA = Object.keys(a);
|
|
235
|
+
const keysB = Object.keys(b);
|
|
236
|
+
if (keysA.length !== keysB.length)
|
|
237
|
+
return false;
|
|
238
|
+
for (const key of keysA) {
|
|
239
|
+
if (!Object.prototype.hasOwnProperty.call(b, key))
|
|
240
|
+
return false;
|
|
241
|
+
if (!deepEquals(a[key], b[key]))
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
function isObject(item) {
|
|
249
|
+
return Boolean(item && typeof item === "object" && !Array.isArray(item));
|
|
250
|
+
}
|
|
251
|
+
async function tryLoadConfig(configPath, defaultConfig) {
|
|
252
|
+
if (!existsSync2(configPath))
|
|
253
|
+
return null;
|
|
254
|
+
try {
|
|
255
|
+
const importedConfig = await import(configPath);
|
|
256
|
+
const loadedConfig = importedConfig.default || importedConfig;
|
|
257
|
+
if (typeof loadedConfig !== "object" || loadedConfig === null || Array.isArray(loadedConfig))
|
|
258
|
+
return null;
|
|
259
|
+
try {
|
|
260
|
+
return deepMerge(defaultConfig, loadedConfig);
|
|
261
|
+
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function loadConfig({
|
|
269
|
+
name = "",
|
|
270
|
+
cwd,
|
|
271
|
+
defaultConfig
|
|
272
|
+
}) {
|
|
273
|
+
const baseDir = cwd || process.cwd();
|
|
274
|
+
const extensions = [".ts", ".js", ".mjs", ".cjs", ".json"];
|
|
275
|
+
const configPaths = [
|
|
276
|
+
`${name}.config`,
|
|
277
|
+
`.${name}.config`,
|
|
278
|
+
name,
|
|
279
|
+
`.${name}`
|
|
280
|
+
];
|
|
281
|
+
for (const configPath of configPaths) {
|
|
282
|
+
for (const ext of extensions) {
|
|
283
|
+
const fullPath = resolve(baseDir, `${configPath}${ext}`);
|
|
284
|
+
const config2 = await tryLoadConfig(fullPath, defaultConfig);
|
|
285
|
+
if (config2 !== null) {
|
|
286
|
+
return config2;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const pkgPath = resolve(baseDir, "package.json");
|
|
292
|
+
if (existsSync2(pkgPath)) {
|
|
293
|
+
const pkg = await import(pkgPath);
|
|
294
|
+
const pkgConfig = pkg[name];
|
|
295
|
+
if (pkgConfig && typeof pkgConfig === "object" && !Array.isArray(pkgConfig)) {
|
|
296
|
+
try {
|
|
297
|
+
return deepMerge(defaultConfig, pkgConfig);
|
|
298
|
+
} catch {}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} catch {}
|
|
302
|
+
return defaultConfig;
|
|
303
|
+
}
|
|
304
|
+
var defaultConfigDir = resolve(process.cwd(), "config");
|
|
305
|
+
var defaultGeneratedDir = resolve(process.cwd(), "src/generated");
|
|
306
|
+
function getProjectRoot(filePath, options = {}) {
|
|
307
|
+
let path = process2.cwd();
|
|
308
|
+
while (path.includes("storage"))
|
|
309
|
+
path = resolve2(path, "..");
|
|
310
|
+
const finalPath = resolve2(path, filePath || "");
|
|
311
|
+
if (options?.relative)
|
|
312
|
+
return relative(process2.cwd(), finalPath);
|
|
313
|
+
return finalPath;
|
|
314
|
+
}
|
|
315
|
+
var defaultLogDirectory = process2.env.CLARITY_LOG_DIR || join2(getProjectRoot(), "logs");
|
|
316
|
+
var defaultConfig = {
|
|
317
|
+
level: "info",
|
|
318
|
+
defaultName: "clarity",
|
|
319
|
+
timestamp: true,
|
|
320
|
+
colors: true,
|
|
321
|
+
format: "text",
|
|
322
|
+
maxLogSize: 10485760,
|
|
323
|
+
logDatePattern: "YYYY-MM-DD",
|
|
324
|
+
logDirectory: defaultLogDirectory,
|
|
325
|
+
rotation: {
|
|
326
|
+
frequency: "daily",
|
|
327
|
+
maxSize: 10485760,
|
|
328
|
+
maxFiles: 5,
|
|
329
|
+
compress: false,
|
|
330
|
+
rotateHour: 0,
|
|
331
|
+
rotateMinute: 0,
|
|
332
|
+
rotateDayOfWeek: 0,
|
|
333
|
+
rotateDayOfMonth: 1,
|
|
334
|
+
encrypt: false
|
|
335
|
+
},
|
|
336
|
+
verbose: false
|
|
337
|
+
};
|
|
338
|
+
async function loadConfig2() {
|
|
339
|
+
try {
|
|
340
|
+
const loadedConfig = await loadConfig({
|
|
341
|
+
name: "clarity",
|
|
342
|
+
defaultConfig,
|
|
343
|
+
cwd: process2.cwd(),
|
|
344
|
+
endpoint: "",
|
|
345
|
+
headers: {}
|
|
346
|
+
});
|
|
347
|
+
return { ...defaultConfig, ...loadedConfig };
|
|
348
|
+
} catch {
|
|
349
|
+
return defaultConfig;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
var config = await loadConfig2();
|
|
353
|
+
function isBrowserProcess() {
|
|
354
|
+
if (process3.env.NODE_ENV === "test" || process3.env.BUN_ENV === "test") {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
return typeof window !== "undefined";
|
|
358
|
+
}
|
|
359
|
+
async function isServerProcess() {
|
|
360
|
+
if (process3.env.NODE_ENV === "test" || process3.env.BUN_ENV === "test") {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
if (typeof navigator !== "undefined" && navigator.product === "ReactNative") {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
if (typeof process3 !== "undefined") {
|
|
367
|
+
const type = process3.type;
|
|
368
|
+
if (type === "renderer" || type === "worker") {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
return !!(process3.versions && (process3.versions.node || process3.versions.bun));
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
class JsonFormatter {
|
|
377
|
+
async format(entry) {
|
|
378
|
+
const isServer = await isServerProcess();
|
|
379
|
+
const metadata = await this.getMetadata(isServer);
|
|
380
|
+
return JSON.stringify({
|
|
381
|
+
timestamp: entry.timestamp.toISOString(),
|
|
382
|
+
level: entry.level,
|
|
383
|
+
name: entry.name,
|
|
384
|
+
message: entry.message,
|
|
385
|
+
metadata
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
async getMetadata(isServer) {
|
|
389
|
+
if (isServer) {
|
|
390
|
+
const { hostname } = await import("os");
|
|
391
|
+
return {
|
|
392
|
+
pid: process4.pid,
|
|
393
|
+
hostname: hostname(),
|
|
394
|
+
environment: process4.env.NODE_ENV || "development",
|
|
395
|
+
platform: process4.platform,
|
|
396
|
+
version: process4.version
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
userAgent: navigator.userAgent,
|
|
401
|
+
hostname: window.location.hostname || "browser",
|
|
402
|
+
environment: process4.env.NODE_ENV || process4.env.BUN_ENV || "development",
|
|
403
|
+
viewport: {
|
|
404
|
+
width: window.innerWidth,
|
|
405
|
+
height: window.innerHeight
|
|
406
|
+
},
|
|
407
|
+
language: navigator.language
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
var terminalStyles = {
|
|
412
|
+
red: (text) => `\x1B[31m${text}\x1B[0m`,
|
|
413
|
+
green: (text) => `\x1B[32m${text}\x1B[0m`,
|
|
414
|
+
yellow: (text) => `\x1B[33m${text}\x1B[0m`,
|
|
415
|
+
blue: (text) => `\x1B[34m${text}\x1B[0m`,
|
|
416
|
+
magenta: (text) => `\x1B[35m${text}\x1B[0m`,
|
|
417
|
+
cyan: (text) => `\x1B[36m${text}\x1B[0m`,
|
|
418
|
+
white: (text) => `\x1B[37m${text}\x1B[0m`,
|
|
419
|
+
gray: (text) => `\x1B[90m${text}\x1B[0m`,
|
|
420
|
+
bgRed: (text) => `\x1B[41m${text}\x1B[0m`,
|
|
421
|
+
bgYellow: (text) => `\x1B[43m${text}\x1B[0m`,
|
|
422
|
+
bold: (text) => `\x1B[1m${text}\x1B[0m`,
|
|
423
|
+
dim: (text) => `\x1B[2m${text}\x1B[0m`,
|
|
424
|
+
italic: (text) => `\x1B[3m${text}\x1B[0m`,
|
|
425
|
+
underline: (text) => `\x1B[4m${text}\x1B[0m`,
|
|
426
|
+
reset: "\x1B[0m"
|
|
427
|
+
};
|
|
428
|
+
var styles = terminalStyles;
|
|
429
|
+
var red = terminalStyles.red;
|
|
430
|
+
var green = terminalStyles.green;
|
|
431
|
+
var yellow = terminalStyles.yellow;
|
|
432
|
+
var blue = terminalStyles.blue;
|
|
433
|
+
var magenta = terminalStyles.magenta;
|
|
434
|
+
var cyan = terminalStyles.cyan;
|
|
435
|
+
var white = terminalStyles.white;
|
|
436
|
+
var gray = terminalStyles.gray;
|
|
437
|
+
var bgRed = terminalStyles.bgRed;
|
|
438
|
+
var bgYellow = terminalStyles.bgYellow;
|
|
439
|
+
var bold = terminalStyles.bold;
|
|
440
|
+
var dim = terminalStyles.dim;
|
|
441
|
+
var italic = terminalStyles.italic;
|
|
442
|
+
var underline = terminalStyles.underline;
|
|
443
|
+
var reset = terminalStyles.reset;
|
|
444
|
+
var defaultFingersCrossedConfig = {
|
|
445
|
+
activationLevel: "error",
|
|
446
|
+
bufferSize: 50,
|
|
447
|
+
flushOnDeactivation: true,
|
|
448
|
+
stopBuffering: false
|
|
449
|
+
};
|
|
450
|
+
var levelIcons = {
|
|
451
|
+
debug: "\uD83D\uDD0D",
|
|
452
|
+
info: blue("\u2139"),
|
|
453
|
+
success: green("\u2713"),
|
|
454
|
+
warning: bgYellow(white(bold(" WARN "))),
|
|
455
|
+
error: bgRed(white(bold(" ERROR ")))
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
class Logger {
|
|
459
|
+
name;
|
|
460
|
+
fileLocks = new Map;
|
|
461
|
+
currentKeyId = null;
|
|
462
|
+
keys = new Map;
|
|
463
|
+
config;
|
|
464
|
+
options;
|
|
465
|
+
formatter;
|
|
466
|
+
timers = new Set;
|
|
467
|
+
subLoggers = new Set;
|
|
468
|
+
fingersCrossedBuffer = [];
|
|
469
|
+
fingersCrossedConfig;
|
|
470
|
+
fingersCrossedActive = false;
|
|
471
|
+
currentLogFile;
|
|
472
|
+
rotationTimeout;
|
|
473
|
+
keyRotationTimeout;
|
|
474
|
+
encryptionKeys;
|
|
475
|
+
logBuffer = [];
|
|
476
|
+
isActivated = false;
|
|
477
|
+
pendingOperations = [];
|
|
478
|
+
enabled;
|
|
479
|
+
fancy;
|
|
480
|
+
tagFormat;
|
|
481
|
+
timestampPosition;
|
|
482
|
+
environment;
|
|
483
|
+
ANSI_PATTERN = /\u001B\[.*?m/g;
|
|
484
|
+
activeProgressBar = null;
|
|
485
|
+
constructor(name, options = {}) {
|
|
486
|
+
this.name = name;
|
|
487
|
+
this.config = { ...config };
|
|
488
|
+
this.options = this.normalizeOptions(options);
|
|
489
|
+
this.formatter = this.options.formatter || new JsonFormatter;
|
|
490
|
+
this.enabled = options.enabled ?? true;
|
|
491
|
+
this.fancy = options.fancy ?? true;
|
|
492
|
+
this.tagFormat = options.tagFormat ?? { prefix: "[", suffix: "]" };
|
|
493
|
+
this.timestampPosition = options.timestampPosition ?? "right";
|
|
494
|
+
this.environment = options.environment ?? process5.env.APP_ENV ?? "local";
|
|
495
|
+
this.fingersCrossedConfig = this.initializeFingersCrossedConfig(options);
|
|
496
|
+
const configOptions = { ...options };
|
|
497
|
+
const hasTimestamp = options.timestamp !== undefined;
|
|
498
|
+
if (hasTimestamp) {
|
|
499
|
+
delete configOptions.timestamp;
|
|
500
|
+
}
|
|
501
|
+
this.config = {
|
|
502
|
+
...this.config,
|
|
503
|
+
...configOptions,
|
|
504
|
+
timestamp: hasTimestamp || this.config.timestamp
|
|
505
|
+
};
|
|
506
|
+
this.currentLogFile = this.generateLogFilename();
|
|
507
|
+
this.encryptionKeys = new Map;
|
|
508
|
+
if (this.validateEncryptionConfig()) {
|
|
509
|
+
this.setupRotation();
|
|
510
|
+
const initialKeyId = this.generateKeyId();
|
|
511
|
+
const initialKey = this.generateKey();
|
|
512
|
+
this.currentKeyId = initialKeyId;
|
|
513
|
+
this.keys.set(initialKeyId, initialKey);
|
|
514
|
+
this.encryptionKeys.set(initialKeyId, {
|
|
515
|
+
key: initialKey,
|
|
516
|
+
createdAt: new Date
|
|
517
|
+
});
|
|
518
|
+
this.setupKeyRotation();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
initializeFingersCrossedConfig(options) {
|
|
522
|
+
if (!options.fingersCrossedEnabled && options.fingersCrossed) {
|
|
523
|
+
return {
|
|
524
|
+
...defaultFingersCrossedConfig,
|
|
525
|
+
...options.fingersCrossed
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
if (!options.fingersCrossedEnabled) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
if (!options.fingersCrossed) {
|
|
532
|
+
return { ...defaultFingersCrossedConfig };
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
...defaultFingersCrossedConfig,
|
|
536
|
+
...options.fingersCrossed
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
normalizeOptions(options) {
|
|
540
|
+
const defaultOptions = {
|
|
541
|
+
format: "json",
|
|
542
|
+
level: "info",
|
|
543
|
+
logDirectory: config.logDirectory,
|
|
544
|
+
rotation: undefined,
|
|
545
|
+
timestamp: undefined,
|
|
546
|
+
fingersCrossed: {},
|
|
547
|
+
enabled: true,
|
|
548
|
+
showTags: false,
|
|
549
|
+
formatter: undefined
|
|
550
|
+
};
|
|
551
|
+
const mergedOptions = {
|
|
552
|
+
...defaultOptions,
|
|
553
|
+
...Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined))
|
|
554
|
+
};
|
|
555
|
+
if (!mergedOptions.level || !["debug", "info", "success", "warning", "error"].includes(mergedOptions.level)) {
|
|
556
|
+
mergedOptions.level = defaultOptions.level;
|
|
557
|
+
}
|
|
558
|
+
return mergedOptions;
|
|
559
|
+
}
|
|
560
|
+
async writeToFile(data) {
|
|
561
|
+
const cancelled = false;
|
|
562
|
+
const operationPromise = (async () => {
|
|
563
|
+
let fd;
|
|
564
|
+
let retries = 0;
|
|
565
|
+
const maxRetries = 3;
|
|
566
|
+
const backoffDelay = 1000;
|
|
567
|
+
while (retries < maxRetries) {
|
|
568
|
+
try {
|
|
569
|
+
try {
|
|
570
|
+
try {
|
|
571
|
+
await access(this.config.logDirectory, constants.F_OK | constants.W_OK);
|
|
572
|
+
} catch (err) {
|
|
573
|
+
if (err instanceof Error && "code" in err) {
|
|
574
|
+
if (err.code === "ENOENT") {
|
|
575
|
+
await mkdir(this.config.logDirectory, { recursive: true, mode: 493 });
|
|
576
|
+
} else if (err.code === "EACCES") {
|
|
577
|
+
throw new Error(`No write permission for log directory: ${this.config.logDirectory}`);
|
|
578
|
+
} else {
|
|
579
|
+
throw err;
|
|
580
|
+
}
|
|
581
|
+
} else {
|
|
582
|
+
throw err;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
} catch (err) {
|
|
586
|
+
console.error("Debug: [writeToFile] Failed to create log directory:", err);
|
|
587
|
+
throw err;
|
|
588
|
+
}
|
|
589
|
+
if (cancelled)
|
|
590
|
+
throw new Error("Operation cancelled: Logger was destroyed");
|
|
591
|
+
const dataToWrite = this.validateEncryptionConfig() ? (await this.encrypt(data)).encrypted : Buffer.from(data);
|
|
592
|
+
try {
|
|
593
|
+
if (!existsSync22(this.currentLogFile)) {
|
|
594
|
+
await writeFile(this.currentLogFile, "", { mode: 420 });
|
|
595
|
+
}
|
|
596
|
+
fd = openSync(this.currentLogFile, "a", 420);
|
|
597
|
+
writeFileSync22(fd, dataToWrite, { flag: "a" });
|
|
598
|
+
fsyncSync(fd);
|
|
599
|
+
if (fd !== undefined) {
|
|
600
|
+
closeSync(fd);
|
|
601
|
+
fd = undefined;
|
|
602
|
+
}
|
|
603
|
+
const stats = await stat(this.currentLogFile);
|
|
604
|
+
if (stats.size === 0) {
|
|
605
|
+
await writeFile(this.currentLogFile, dataToWrite, { flag: "w", mode: 420 });
|
|
606
|
+
const retryStats = await stat(this.currentLogFile);
|
|
607
|
+
if (retryStats.size === 0) {
|
|
608
|
+
throw new Error("File exists but is empty after retry write");
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
} catch (err) {
|
|
613
|
+
const error = err;
|
|
614
|
+
if (error.code && ["ENETDOWN", "ENETUNREACH", "ENOTFOUND", "ETIMEDOUT"].includes(error.code)) {
|
|
615
|
+
if (retries < maxRetries - 1) {
|
|
616
|
+
const errorMessage = typeof error.message === "string" ? error.message : "Unknown error";
|
|
617
|
+
console.error(`Network error during write attempt ${retries + 1}/${maxRetries}:`, errorMessage);
|
|
618
|
+
const delay = backoffDelay * 2 ** retries;
|
|
619
|
+
await new Promise((resolve32) => setTimeout(resolve32, delay));
|
|
620
|
+
retries++;
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (error?.code && ["ENOSPC", "EDQUOT"].includes(error.code)) {
|
|
625
|
+
throw new Error(`Disk quota exceeded or no space left on device: ${error.message}`);
|
|
626
|
+
}
|
|
627
|
+
console.error("Debug: [writeToFile] Error writing to file:", error);
|
|
628
|
+
throw error;
|
|
629
|
+
} finally {
|
|
630
|
+
if (fd !== undefined) {
|
|
631
|
+
try {
|
|
632
|
+
closeSync(fd);
|
|
633
|
+
} catch (err) {
|
|
634
|
+
console.error("Debug: [writeToFile] Error closing file descriptor:", err);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
} catch (err) {
|
|
639
|
+
if (retries === maxRetries - 1) {
|
|
640
|
+
const error = err;
|
|
641
|
+
const errorMessage = typeof error.message === "string" ? error.message : "Unknown error";
|
|
642
|
+
console.error("Debug: [writeToFile] Max retries reached. Final error:", errorMessage);
|
|
643
|
+
throw err;
|
|
644
|
+
}
|
|
645
|
+
retries++;
|
|
646
|
+
const delay = backoffDelay * 2 ** (retries - 1);
|
|
647
|
+
await new Promise((resolve32) => setTimeout(resolve32, delay));
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
})();
|
|
651
|
+
this.pendingOperations.push(operationPromise);
|
|
652
|
+
const index = this.pendingOperations.length - 1;
|
|
653
|
+
try {
|
|
654
|
+
await operationPromise;
|
|
655
|
+
} catch (err) {
|
|
656
|
+
console.error("Debug: [writeToFile] Error in operation:", err);
|
|
657
|
+
throw err;
|
|
658
|
+
} finally {
|
|
659
|
+
this.pendingOperations.splice(index, 1);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
generateLogFilename() {
|
|
663
|
+
if (this.name.includes("stream-throughput") || this.name.includes("decompress-perf-test") || this.name.includes("decompression-latency") || this.name.includes("concurrent-read-test") || this.name.includes("clock-change-test")) {
|
|
664
|
+
return join22(this.config.logDirectory, `${this.name}.log`);
|
|
665
|
+
}
|
|
666
|
+
if (this.name.includes("pending-test") || this.name.includes("temp-file-test") || this.name === "crash-test" || this.name === "corrupt-test" || this.name.includes("rotation-load-test") || this.name === "sigterm-test" || this.name === "sigint-test" || this.name === "failed-rotation-test" || this.name === "integration-test") {
|
|
667
|
+
return join22(this.config.logDirectory, `${this.name}.log`);
|
|
668
|
+
}
|
|
669
|
+
const date = new Date().toISOString().split("T")[0];
|
|
670
|
+
return join22(this.config.logDirectory, `${this.name}-${date}.log`);
|
|
671
|
+
}
|
|
672
|
+
setupRotation() {
|
|
673
|
+
if (isBrowserProcess())
|
|
674
|
+
return;
|
|
675
|
+
if (typeof this.config.rotation === "boolean")
|
|
676
|
+
return;
|
|
677
|
+
const config2 = this.config.rotation;
|
|
678
|
+
let interval;
|
|
679
|
+
switch (config2.frequency) {
|
|
680
|
+
case "daily":
|
|
681
|
+
interval = 86400000;
|
|
682
|
+
break;
|
|
683
|
+
case "weekly":
|
|
684
|
+
interval = 604800000;
|
|
685
|
+
break;
|
|
686
|
+
case "monthly":
|
|
687
|
+
interval = 2592000000;
|
|
688
|
+
break;
|
|
689
|
+
default:
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
this.rotationTimeout = setInterval(() => {
|
|
693
|
+
this.rotateLog();
|
|
694
|
+
}, interval);
|
|
695
|
+
}
|
|
696
|
+
setupKeyRotation() {
|
|
697
|
+
if (!this.validateEncryptionConfig()) {
|
|
698
|
+
console.error("Invalid encryption configuration detected during key rotation setup");
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const rotation = this.config.rotation;
|
|
702
|
+
const keyRotation = rotation.keyRotation;
|
|
703
|
+
if (!keyRotation?.enabled) {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const rotationInterval = typeof keyRotation.interval === "number" ? keyRotation.interval : 60;
|
|
707
|
+
const interval = Math.max(rotationInterval, 60) * 1000;
|
|
708
|
+
this.keyRotationTimeout = setInterval(() => {
|
|
709
|
+
this.rotateKeys().catch((error) => {
|
|
710
|
+
console.error("Error rotating keys:", error);
|
|
711
|
+
});
|
|
712
|
+
}, interval);
|
|
713
|
+
}
|
|
714
|
+
async rotateKeys() {
|
|
715
|
+
if (!this.validateEncryptionConfig()) {
|
|
716
|
+
console.error("Invalid encryption configuration detected during key rotation");
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const rotation = this.config.rotation;
|
|
720
|
+
const keyRotation = rotation.keyRotation;
|
|
721
|
+
const newKeyId = this.generateKeyId();
|
|
722
|
+
const newKey = this.generateKey();
|
|
723
|
+
this.currentKeyId = newKeyId;
|
|
724
|
+
this.keys.set(newKeyId, newKey);
|
|
725
|
+
this.encryptionKeys.set(newKeyId, {
|
|
726
|
+
key: newKey,
|
|
727
|
+
createdAt: new Date
|
|
728
|
+
});
|
|
729
|
+
const sortedKeys = Array.from(this.encryptionKeys.entries()).sort(([, a], [, b]) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
730
|
+
const maxKeyCount = typeof keyRotation.maxKeys === "number" ? keyRotation.maxKeys : 1;
|
|
731
|
+
const maxKeys = Math.max(1, maxKeyCount);
|
|
732
|
+
if (sortedKeys.length > maxKeys) {
|
|
733
|
+
for (const [keyId] of sortedKeys.slice(maxKeys)) {
|
|
734
|
+
this.encryptionKeys.delete(keyId);
|
|
735
|
+
this.keys.delete(keyId);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
generateKeyId() {
|
|
740
|
+
return randomBytes(16).toString("hex");
|
|
741
|
+
}
|
|
742
|
+
generateKey() {
|
|
743
|
+
return randomBytes(32);
|
|
744
|
+
}
|
|
745
|
+
getCurrentKey() {
|
|
746
|
+
if (!this.currentKeyId) {
|
|
747
|
+
throw new Error("Encryption is not properly initialized. Make sure encryption is enabled in the configuration.");
|
|
748
|
+
}
|
|
749
|
+
const key = this.keys.get(this.currentKeyId);
|
|
750
|
+
if (!key) {
|
|
751
|
+
throw new Error(`No key found for ID ${this.currentKeyId}. The encryption key may have been rotated or removed.`);
|
|
752
|
+
}
|
|
753
|
+
return { key, id: this.currentKeyId };
|
|
754
|
+
}
|
|
755
|
+
encrypt(data) {
|
|
756
|
+
const { key } = this.getCurrentKey();
|
|
757
|
+
const iv = randomBytes(16);
|
|
758
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
759
|
+
const encrypted = Buffer.concat([
|
|
760
|
+
cipher.update(data, "utf8"),
|
|
761
|
+
cipher.final()
|
|
762
|
+
]);
|
|
763
|
+
const authTag = cipher.getAuthTag();
|
|
764
|
+
return {
|
|
765
|
+
encrypted: Buffer.concat([iv, encrypted, authTag]),
|
|
766
|
+
iv
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
async compressData(data) {
|
|
770
|
+
return new Promise((resolve32, reject) => {
|
|
771
|
+
const gzip = createGzip();
|
|
772
|
+
const chunks = [];
|
|
773
|
+
gzip.on("data", (chunk2) => chunks.push(chunk2));
|
|
774
|
+
gzip.on("end", () => resolve32(Buffer.from(Buffer.concat(chunks))));
|
|
775
|
+
gzip.on("error", reject);
|
|
776
|
+
gzip.write(data);
|
|
777
|
+
gzip.end();
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
getEncryptionOptions() {
|
|
781
|
+
if (!this.config.rotation || typeof this.config.rotation === "boolean" || !this.config.rotation.encrypt) {
|
|
782
|
+
return {};
|
|
783
|
+
}
|
|
784
|
+
const defaultOptions = {
|
|
785
|
+
algorithm: "aes-256-cbc",
|
|
786
|
+
compress: false
|
|
787
|
+
};
|
|
788
|
+
if (typeof this.config.rotation.encrypt === "object") {
|
|
789
|
+
const encryptConfig = this.config.rotation.encrypt;
|
|
790
|
+
return {
|
|
791
|
+
...defaultOptions,
|
|
792
|
+
...encryptConfig
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
return defaultOptions;
|
|
796
|
+
}
|
|
797
|
+
async rotateLog() {
|
|
798
|
+
if (isBrowserProcess())
|
|
799
|
+
return;
|
|
800
|
+
const stats = await stat(this.currentLogFile).catch(() => null);
|
|
801
|
+
if (!stats)
|
|
802
|
+
return;
|
|
803
|
+
const config2 = this.config.rotation;
|
|
804
|
+
if (typeof config2 === "boolean")
|
|
805
|
+
return;
|
|
806
|
+
if (config2.maxSize && stats.size >= config2.maxSize) {
|
|
807
|
+
const oldFile = this.currentLogFile;
|
|
808
|
+
const newFile = this.generateLogFilename();
|
|
809
|
+
if (this.name.includes("rotation-load-test") || this.name === "failed-rotation-test") {
|
|
810
|
+
const files = await readdir(this.config.logDirectory);
|
|
811
|
+
const rotatedFiles = files.filter((f) => f.startsWith(this.name) && /\.log\.\d+$/.test(f)).sort((a, b) => {
|
|
812
|
+
const numA = Number.parseInt(a.match(/\.log\.(\d+)$/)?.[1] || "0");
|
|
813
|
+
const numB = Number.parseInt(b.match(/\.log\.(\d+)$/)?.[1] || "0");
|
|
814
|
+
return numB - numA;
|
|
815
|
+
});
|
|
816
|
+
const nextNum = rotatedFiles.length > 0 ? Number.parseInt(rotatedFiles[0].match(/\.log\.(\d+)$/)?.[1] || "0") + 1 : 1;
|
|
817
|
+
const rotatedFile = `${oldFile}.${nextNum}`;
|
|
818
|
+
if (await stat(oldFile).catch(() => null)) {
|
|
819
|
+
try {
|
|
820
|
+
await rename(oldFile, rotatedFile);
|
|
821
|
+
if (config2.compress) {
|
|
822
|
+
try {
|
|
823
|
+
const compressedPath = `${rotatedFile}.gz`;
|
|
824
|
+
await this.compressLogFile(rotatedFile, compressedPath);
|
|
825
|
+
await unlink(rotatedFile);
|
|
826
|
+
} catch (err) {
|
|
827
|
+
console.error("Error compressing rotated file:", err);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (rotatedFiles.length === 0 && !files.some((f) => f.endsWith(".log.1"))) {
|
|
831
|
+
try {
|
|
832
|
+
const backupPath = `${oldFile}.1`;
|
|
833
|
+
await writeFile(backupPath, "");
|
|
834
|
+
} catch (err) {
|
|
835
|
+
console.error("Error creating backup file:", err);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
} catch (err) {
|
|
839
|
+
console.error(`Error during rotation: ${err instanceof Error ? err.message : String(err)}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
844
|
+
const rotatedFile = oldFile.replace(/\.log$/, `-${timestamp}.log`);
|
|
845
|
+
if (await stat(oldFile).catch(() => null)) {
|
|
846
|
+
await rename(oldFile, rotatedFile);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
this.currentLogFile = newFile;
|
|
850
|
+
if (config2.maxFiles) {
|
|
851
|
+
const files = await readdir(this.config.logDirectory);
|
|
852
|
+
const logFiles = files.filter((f) => f.startsWith(this.name)).sort((a, b) => b.localeCompare(a));
|
|
853
|
+
for (const file2 of logFiles.slice(config2.maxFiles)) {
|
|
854
|
+
await unlink(join22(this.config.logDirectory, file2));
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
async compressLogFile(inputPath, outputPath) {
|
|
860
|
+
const readStream = createReadStream(inputPath);
|
|
861
|
+
const writeStream = createWriteStream(outputPath);
|
|
862
|
+
const gzip = createGzip();
|
|
863
|
+
await pipeline(readStream, gzip, writeStream);
|
|
864
|
+
}
|
|
865
|
+
async handleFingersCrossedBuffer(level, formattedEntry) {
|
|
866
|
+
if (!this.fingersCrossedConfig)
|
|
867
|
+
return;
|
|
868
|
+
if (this.shouldActivateFingersCrossed(level) && !this.isActivated) {
|
|
869
|
+
this.isActivated = true;
|
|
870
|
+
for (const entry of this.logBuffer) {
|
|
871
|
+
const formattedBufferedEntry = await this.formatter.format(entry);
|
|
872
|
+
await this.writeToFile(formattedBufferedEntry);
|
|
873
|
+
console.log(formattedBufferedEntry);
|
|
874
|
+
}
|
|
875
|
+
if (this.fingersCrossedConfig.stopBuffering)
|
|
876
|
+
this.logBuffer = [];
|
|
877
|
+
}
|
|
878
|
+
if (this.isActivated) {
|
|
879
|
+
await this.writeToFile(formattedEntry);
|
|
880
|
+
console.log(formattedEntry);
|
|
881
|
+
} else {
|
|
882
|
+
if (this.logBuffer.length >= this.fingersCrossedConfig.bufferSize)
|
|
883
|
+
this.logBuffer.shift();
|
|
884
|
+
const entry = {
|
|
885
|
+
timestamp: new Date,
|
|
886
|
+
level,
|
|
887
|
+
message: formattedEntry,
|
|
888
|
+
name: this.name
|
|
889
|
+
};
|
|
890
|
+
this.logBuffer.push(entry);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
shouldActivateFingersCrossed(level) {
|
|
894
|
+
if (!this.fingersCrossedConfig)
|
|
895
|
+
return false;
|
|
896
|
+
return this.getLevelValue(level) >= this.getLevelValue(this.fingersCrossedConfig.activationLevel);
|
|
897
|
+
}
|
|
898
|
+
getLevelValue(level) {
|
|
899
|
+
const levels = {
|
|
900
|
+
debug: 0,
|
|
901
|
+
info: 1,
|
|
902
|
+
success: 2,
|
|
903
|
+
warning: 3,
|
|
904
|
+
error: 4
|
|
905
|
+
};
|
|
906
|
+
return levels[level];
|
|
907
|
+
}
|
|
908
|
+
shouldLog(level) {
|
|
909
|
+
if (!this.enabled)
|
|
910
|
+
return false;
|
|
911
|
+
const levels = {
|
|
912
|
+
debug: 0,
|
|
913
|
+
info: 1,
|
|
914
|
+
success: 2,
|
|
915
|
+
warning: 3,
|
|
916
|
+
error: 4
|
|
917
|
+
};
|
|
918
|
+
return levels[level] >= levels[this.config.level];
|
|
919
|
+
}
|
|
920
|
+
async flushPendingWrites() {
|
|
921
|
+
await Promise.all(this.pendingOperations.map((op) => {
|
|
922
|
+
if (op instanceof Promise) {
|
|
923
|
+
return op.catch((err) => {
|
|
924
|
+
console.error("Error in pending write operation:", err);
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return Promise.resolve();
|
|
928
|
+
}));
|
|
929
|
+
if (existsSync22(this.currentLogFile)) {
|
|
930
|
+
try {
|
|
931
|
+
const fd = openSync(this.currentLogFile, "r+");
|
|
932
|
+
fsyncSync(fd);
|
|
933
|
+
closeSync(fd);
|
|
934
|
+
} catch (error) {
|
|
935
|
+
console.error(`Error flushing file: ${error}`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
async destroy() {
|
|
940
|
+
if (this.rotationTimeout)
|
|
941
|
+
clearInterval(this.rotationTimeout);
|
|
942
|
+
if (this.keyRotationTimeout)
|
|
943
|
+
clearInterval(this.keyRotationTimeout);
|
|
944
|
+
this.timers.clear();
|
|
945
|
+
for (const op of this.pendingOperations) {
|
|
946
|
+
if (typeof op.cancel === "function") {
|
|
947
|
+
op.cancel();
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return (async () => {
|
|
951
|
+
if (this.pendingOperations.length > 0) {
|
|
952
|
+
try {
|
|
953
|
+
await Promise.allSettled(this.pendingOperations);
|
|
954
|
+
} catch (err) {
|
|
955
|
+
console.error("Error waiting for pending operations:", err);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (!isBrowserProcess() && this.config.rotation && typeof this.config.rotation !== "boolean" && this.config.rotation.compress) {
|
|
959
|
+
try {
|
|
960
|
+
const files = await readdir(this.config.logDirectory);
|
|
961
|
+
const tempFiles = files.filter((f) => (f.includes("temp") || f.includes(".tmp")) && f.includes(this.name));
|
|
962
|
+
for (const tempFile of tempFiles) {
|
|
963
|
+
try {
|
|
964
|
+
await unlink(join22(this.config.logDirectory, tempFile));
|
|
965
|
+
} catch (err) {
|
|
966
|
+
console.error(`Failed to delete temp file ${tempFile}:`, err);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
} catch (err) {
|
|
970
|
+
console.error("Error cleaning up temporary files:", err);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
})();
|
|
974
|
+
}
|
|
975
|
+
getCurrentLogFilePath() {
|
|
976
|
+
return this.currentLogFile;
|
|
977
|
+
}
|
|
978
|
+
formatTag(name) {
|
|
979
|
+
if (!name)
|
|
980
|
+
return "";
|
|
981
|
+
return `${this.tagFormat.prefix}${name}${this.tagFormat.suffix}`;
|
|
982
|
+
}
|
|
983
|
+
formatFileTimestamp(date) {
|
|
984
|
+
return `[${date.toISOString()}]`;
|
|
985
|
+
}
|
|
986
|
+
formatConsoleTimestamp(date) {
|
|
987
|
+
return this.fancy ? styles.gray(date.toLocaleTimeString()) : date.toLocaleTimeString();
|
|
988
|
+
}
|
|
989
|
+
formatConsoleMessage(parts) {
|
|
990
|
+
const { timestamp, icon = "", tag = "", message, level, showTimestamp = true } = parts;
|
|
991
|
+
const stripAnsi = (str) => str.replace(this.ANSI_PATTERN, "");
|
|
992
|
+
if (!this.fancy) {
|
|
993
|
+
const components = [];
|
|
994
|
+
if (showTimestamp)
|
|
995
|
+
components.push(timestamp);
|
|
996
|
+
if (level === "warning")
|
|
997
|
+
components.push("WARN");
|
|
998
|
+
else if (level === "error")
|
|
999
|
+
components.push("ERROR");
|
|
1000
|
+
else if (icon)
|
|
1001
|
+
components.push(icon.replace(/[^\p{L}\p{N}\p{P}\p{Z}]/gu, ""));
|
|
1002
|
+
if (tag)
|
|
1003
|
+
components.push(tag.replace(/[[\]]/g, ""));
|
|
1004
|
+
components.push(message);
|
|
1005
|
+
return components.join(" ");
|
|
1006
|
+
}
|
|
1007
|
+
const terminalWidth = process5.stdout.columns || 120;
|
|
1008
|
+
let mainPart = "";
|
|
1009
|
+
if (level === "warning" || level === "error") {
|
|
1010
|
+
mainPart = `${icon} ${message}`;
|
|
1011
|
+
} else if (level === "info" || level === "success") {
|
|
1012
|
+
mainPart = `${icon} ${tag} ${message}`;
|
|
1013
|
+
} else {
|
|
1014
|
+
mainPart = `${icon} ${tag} ${styles.cyan(message)}`;
|
|
1015
|
+
}
|
|
1016
|
+
if (!showTimestamp) {
|
|
1017
|
+
return mainPart.trim();
|
|
1018
|
+
}
|
|
1019
|
+
const visibleMainPartLength = stripAnsi(mainPart).trim().length;
|
|
1020
|
+
const visibleTimestampLength = stripAnsi(timestamp).length;
|
|
1021
|
+
const padding = Math.max(1, terminalWidth - 2 - visibleMainPartLength - visibleTimestampLength);
|
|
1022
|
+
return `${mainPart.trim()}${" ".repeat(padding)}${timestamp}`;
|
|
1023
|
+
}
|
|
1024
|
+
formatMessage(message, args) {
|
|
1025
|
+
if (args.length === 1 && Array.isArray(args[0])) {
|
|
1026
|
+
return message.replace(/\{(\d+)\}/g, (match, index) => {
|
|
1027
|
+
const position = Number.parseInt(index, 10);
|
|
1028
|
+
return position < args[0].length ? String(args[0][position]) : match;
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
const formatRegex = /%([sdijfo%])/g;
|
|
1032
|
+
let argIndex = 0;
|
|
1033
|
+
let formattedMessage = message.replace(formatRegex, (match, type) => {
|
|
1034
|
+
if (type === "%")
|
|
1035
|
+
return "%";
|
|
1036
|
+
if (argIndex >= args.length)
|
|
1037
|
+
return match;
|
|
1038
|
+
const arg = args[argIndex++];
|
|
1039
|
+
switch (type) {
|
|
1040
|
+
case "s":
|
|
1041
|
+
return String(arg);
|
|
1042
|
+
case "d":
|
|
1043
|
+
case "i":
|
|
1044
|
+
return Number(arg).toString();
|
|
1045
|
+
case "j":
|
|
1046
|
+
case "o":
|
|
1047
|
+
return JSON.stringify(arg, null, 2);
|
|
1048
|
+
default:
|
|
1049
|
+
return match;
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
if (argIndex < args.length) {
|
|
1053
|
+
formattedMessage += ` ${args.slice(argIndex).map((arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg)).join(" ")}`;
|
|
1054
|
+
}
|
|
1055
|
+
return formattedMessage;
|
|
1056
|
+
}
|
|
1057
|
+
async log(level, message, ...args) {
|
|
1058
|
+
const timestamp = new Date;
|
|
1059
|
+
const consoleTime = this.formatConsoleTimestamp(timestamp);
|
|
1060
|
+
const fileTime = this.formatFileTimestamp(timestamp);
|
|
1061
|
+
let formattedMessage;
|
|
1062
|
+
let errorStack;
|
|
1063
|
+
if (message instanceof Error) {
|
|
1064
|
+
formattedMessage = message.message;
|
|
1065
|
+
errorStack = message.stack;
|
|
1066
|
+
} else {
|
|
1067
|
+
formattedMessage = this.formatMessage(message, args);
|
|
1068
|
+
}
|
|
1069
|
+
if (this.fancy && !isBrowserProcess()) {
|
|
1070
|
+
const icon = levelIcons[level];
|
|
1071
|
+
const tag = this.options.showTags !== false && this.name ? styles.gray(this.formatTag(this.name)) : "";
|
|
1072
|
+
let consoleMessage;
|
|
1073
|
+
switch (level) {
|
|
1074
|
+
case "debug":
|
|
1075
|
+
consoleMessage = this.formatConsoleMessage({
|
|
1076
|
+
timestamp: consoleTime,
|
|
1077
|
+
icon,
|
|
1078
|
+
tag,
|
|
1079
|
+
message: styles.gray(formattedMessage),
|
|
1080
|
+
level
|
|
1081
|
+
});
|
|
1082
|
+
console.error(consoleMessage);
|
|
1083
|
+
break;
|
|
1084
|
+
case "info":
|
|
1085
|
+
consoleMessage = this.formatConsoleMessage({
|
|
1086
|
+
timestamp: consoleTime,
|
|
1087
|
+
icon,
|
|
1088
|
+
tag,
|
|
1089
|
+
message: formattedMessage,
|
|
1090
|
+
level
|
|
1091
|
+
});
|
|
1092
|
+
console.error(consoleMessage);
|
|
1093
|
+
break;
|
|
1094
|
+
case "success":
|
|
1095
|
+
consoleMessage = this.formatConsoleMessage({
|
|
1096
|
+
timestamp: consoleTime,
|
|
1097
|
+
icon,
|
|
1098
|
+
tag,
|
|
1099
|
+
message: styles.green(formattedMessage),
|
|
1100
|
+
level
|
|
1101
|
+
});
|
|
1102
|
+
console.error(consoleMessage);
|
|
1103
|
+
break;
|
|
1104
|
+
case "warning":
|
|
1105
|
+
consoleMessage = this.formatConsoleMessage({
|
|
1106
|
+
timestamp: consoleTime,
|
|
1107
|
+
icon,
|
|
1108
|
+
tag,
|
|
1109
|
+
message: formattedMessage,
|
|
1110
|
+
level
|
|
1111
|
+
});
|
|
1112
|
+
console.warn(consoleMessage);
|
|
1113
|
+
break;
|
|
1114
|
+
case "error":
|
|
1115
|
+
consoleMessage = this.formatConsoleMessage({
|
|
1116
|
+
timestamp: consoleTime,
|
|
1117
|
+
icon,
|
|
1118
|
+
tag,
|
|
1119
|
+
message: formattedMessage,
|
|
1120
|
+
level
|
|
1121
|
+
});
|
|
1122
|
+
console.error(consoleMessage);
|
|
1123
|
+
if (errorStack) {
|
|
1124
|
+
const stackLines = errorStack.split(`
|
|
1125
|
+
`);
|
|
1126
|
+
for (const line of stackLines) {
|
|
1127
|
+
if (line.trim() && !line.includes(formattedMessage)) {
|
|
1128
|
+
console.error(this.formatConsoleMessage({
|
|
1129
|
+
timestamp: consoleTime,
|
|
1130
|
+
message: styles.gray(` ${line}`),
|
|
1131
|
+
level,
|
|
1132
|
+
showTimestamp: false
|
|
1133
|
+
}));
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
} else if (!isBrowserProcess()) {
|
|
1140
|
+
console.error(`${fileTime} ${this.environment}.${level.toUpperCase()}: ${formattedMessage}`);
|
|
1141
|
+
if (errorStack) {
|
|
1142
|
+
console.error(errorStack);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (!this.shouldLog(level))
|
|
1146
|
+
return;
|
|
1147
|
+
let logEntry = `${fileTime} ${this.environment}.${level.toUpperCase()}: ${formattedMessage}
|
|
1148
|
+
`;
|
|
1149
|
+
if (errorStack) {
|
|
1150
|
+
logEntry += `${errorStack}
|
|
1151
|
+
`;
|
|
1152
|
+
}
|
|
1153
|
+
logEntry = logEntry.replace(this.ANSI_PATTERN, "");
|
|
1154
|
+
await this.writeToFile(logEntry);
|
|
1155
|
+
}
|
|
1156
|
+
time(label) {
|
|
1157
|
+
const start = performance.now();
|
|
1158
|
+
if (this.fancy && !isBrowserProcess()) {
|
|
1159
|
+
const tag = this.options.showTags !== false && this.name ? styles.gray(this.formatTag(this.name)) : "";
|
|
1160
|
+
const consoleTime = this.formatConsoleTimestamp(new Date);
|
|
1161
|
+
console.error(this.formatConsoleMessage({
|
|
1162
|
+
timestamp: consoleTime,
|
|
1163
|
+
icon: styles.blue("\u25D0"),
|
|
1164
|
+
tag,
|
|
1165
|
+
message: `${styles.cyan(label)}...`
|
|
1166
|
+
}));
|
|
1167
|
+
}
|
|
1168
|
+
return async (metadata) => {
|
|
1169
|
+
if (!this.enabled)
|
|
1170
|
+
return;
|
|
1171
|
+
const end = performance.now();
|
|
1172
|
+
const elapsed = Math.round(end - start);
|
|
1173
|
+
const completionMessage = `${label} completed in ${elapsed}ms`;
|
|
1174
|
+
const timestamp = new Date;
|
|
1175
|
+
const consoleTime = this.formatConsoleTimestamp(timestamp);
|
|
1176
|
+
const fileTime = this.formatFileTimestamp(timestamp);
|
|
1177
|
+
let logEntry = `${fileTime} ${this.environment}.INFO: ${completionMessage}`;
|
|
1178
|
+
if (metadata) {
|
|
1179
|
+
logEntry += ` ${JSON.stringify(metadata)}`;
|
|
1180
|
+
}
|
|
1181
|
+
logEntry += `
|
|
1182
|
+
`;
|
|
1183
|
+
logEntry = logEntry.replace(this.ANSI_PATTERN, "");
|
|
1184
|
+
if (this.fancy && !isBrowserProcess()) {
|
|
1185
|
+
const tag = this.options.showTags !== false && this.name ? styles.gray(this.formatTag(this.name)) : "";
|
|
1186
|
+
console.error(this.formatConsoleMessage({
|
|
1187
|
+
timestamp: consoleTime,
|
|
1188
|
+
icon: styles.green("\u2713"),
|
|
1189
|
+
tag,
|
|
1190
|
+
message: `${completionMessage}${metadata ? ` ${JSON.stringify(metadata)}` : ""}`
|
|
1191
|
+
}));
|
|
1192
|
+
} else if (!isBrowserProcess()) {
|
|
1193
|
+
console.error(logEntry.trim());
|
|
1194
|
+
}
|
|
1195
|
+
await this.writeToFile(logEntry);
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
async debug(message, ...args) {
|
|
1199
|
+
await this.log("debug", message, ...args);
|
|
1200
|
+
}
|
|
1201
|
+
async info(message, ...args) {
|
|
1202
|
+
await this.log("info", message, ...args);
|
|
1203
|
+
}
|
|
1204
|
+
async success(message, ...args) {
|
|
1205
|
+
await this.log("success", message, ...args);
|
|
1206
|
+
}
|
|
1207
|
+
async warn(message, ...args) {
|
|
1208
|
+
await this.log("warning", message, ...args);
|
|
1209
|
+
}
|
|
1210
|
+
async error(message, ...args) {
|
|
1211
|
+
await this.log("error", message, ...args);
|
|
1212
|
+
}
|
|
1213
|
+
validateEncryptionConfig() {
|
|
1214
|
+
if (!this.config.rotation)
|
|
1215
|
+
return false;
|
|
1216
|
+
if (typeof this.config.rotation === "boolean")
|
|
1217
|
+
return false;
|
|
1218
|
+
const rotation = this.config.rotation;
|
|
1219
|
+
const { encrypt } = rotation;
|
|
1220
|
+
return !!encrypt;
|
|
1221
|
+
}
|
|
1222
|
+
async only(fn) {
|
|
1223
|
+
if (!this.enabled)
|
|
1224
|
+
return;
|
|
1225
|
+
return await fn();
|
|
1226
|
+
}
|
|
1227
|
+
isEnabled() {
|
|
1228
|
+
return this.enabled;
|
|
1229
|
+
}
|
|
1230
|
+
setEnabled(enabled) {
|
|
1231
|
+
this.enabled = enabled;
|
|
1232
|
+
}
|
|
1233
|
+
extend(namespace) {
|
|
1234
|
+
const childName = `${this.name}:${namespace}`;
|
|
1235
|
+
const childLogger = new Logger(childName, {
|
|
1236
|
+
...this.options,
|
|
1237
|
+
logDirectory: this.config.logDirectory,
|
|
1238
|
+
level: this.config.level,
|
|
1239
|
+
format: this.config.format,
|
|
1240
|
+
rotation: typeof this.config.rotation === "boolean" ? undefined : this.config.rotation,
|
|
1241
|
+
timestamp: typeof this.config.timestamp === "boolean" ? undefined : this.config.timestamp
|
|
1242
|
+
});
|
|
1243
|
+
this.subLoggers.add(childLogger);
|
|
1244
|
+
return childLogger;
|
|
1245
|
+
}
|
|
1246
|
+
createReadStream() {
|
|
1247
|
+
if (isBrowserProcess())
|
|
1248
|
+
throw new Error("createReadStream is not supported in browser environments");
|
|
1249
|
+
if (!existsSync22(this.currentLogFile))
|
|
1250
|
+
throw new Error(`Log file does not exist: ${this.currentLogFile}`);
|
|
1251
|
+
return createReadStream(this.currentLogFile, { encoding: "utf8" });
|
|
1252
|
+
}
|
|
1253
|
+
async decrypt(data) {
|
|
1254
|
+
if (!this.validateEncryptionConfig())
|
|
1255
|
+
throw new Error("Encryption is not configured");
|
|
1256
|
+
const encryptionConfig = this.config.rotation;
|
|
1257
|
+
if (!encryptionConfig.encrypt || typeof encryptionConfig.encrypt === "boolean")
|
|
1258
|
+
throw new Error("Invalid encryption configuration");
|
|
1259
|
+
if (!this.currentKeyId || !this.keys.has(this.currentKeyId))
|
|
1260
|
+
throw new Error("No valid encryption key available");
|
|
1261
|
+
const key = this.keys.get(this.currentKeyId);
|
|
1262
|
+
try {
|
|
1263
|
+
const encryptedData = Buffer.isBuffer(data) ? data : Buffer.from(data, "base64");
|
|
1264
|
+
const iv = encryptedData.slice(0, 16);
|
|
1265
|
+
const authTag = encryptedData.slice(-16);
|
|
1266
|
+
const ciphertext = encryptedData.slice(16, -16);
|
|
1267
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
1268
|
+
decipher.setAuthTag(authTag);
|
|
1269
|
+
const decrypted = Buffer.concat([
|
|
1270
|
+
decipher.update(ciphertext),
|
|
1271
|
+
decipher.final()
|
|
1272
|
+
]);
|
|
1273
|
+
return decrypted.toString("utf8");
|
|
1274
|
+
} catch (err) {
|
|
1275
|
+
throw new Error(`Decryption failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
getLevel() {
|
|
1279
|
+
return this.config.level;
|
|
1280
|
+
}
|
|
1281
|
+
getLogDirectory() {
|
|
1282
|
+
return this.config.logDirectory;
|
|
1283
|
+
}
|
|
1284
|
+
getFormat() {
|
|
1285
|
+
return this.config.format;
|
|
1286
|
+
}
|
|
1287
|
+
getRotationConfig() {
|
|
1288
|
+
return this.config.rotation;
|
|
1289
|
+
}
|
|
1290
|
+
isBrowserMode() {
|
|
1291
|
+
return isBrowserProcess();
|
|
1292
|
+
}
|
|
1293
|
+
isServerMode() {
|
|
1294
|
+
return !isBrowserProcess();
|
|
1295
|
+
}
|
|
1296
|
+
setTestEncryptionKey(keyId, key) {
|
|
1297
|
+
this.currentKeyId = keyId;
|
|
1298
|
+
this.keys.set(keyId, key);
|
|
1299
|
+
}
|
|
1300
|
+
getTestCurrentKey() {
|
|
1301
|
+
if (!this.currentKeyId || !this.keys.has(this.currentKeyId)) {
|
|
1302
|
+
return null;
|
|
1303
|
+
}
|
|
1304
|
+
return {
|
|
1305
|
+
id: this.currentKeyId,
|
|
1306
|
+
key: this.keys.get(this.currentKeyId)
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
getConfig() {
|
|
1310
|
+
return this.config;
|
|
1311
|
+
}
|
|
1312
|
+
async box(message) {
|
|
1313
|
+
if (!this.enabled)
|
|
1314
|
+
return;
|
|
1315
|
+
const timestamp = new Date;
|
|
1316
|
+
const consoleTime = this.formatConsoleTimestamp(timestamp);
|
|
1317
|
+
const fileTime = this.formatFileTimestamp(timestamp);
|
|
1318
|
+
if (this.fancy && !isBrowserProcess()) {
|
|
1319
|
+
const lines = message.split(`
|
|
1320
|
+
`);
|
|
1321
|
+
const width = Math.max(...lines.map((line) => line.length)) + 2;
|
|
1322
|
+
const top = `\u250C${"\u2500".repeat(width)}\u2510`;
|
|
1323
|
+
const bottom = `\u2514${"\u2500".repeat(width)}\u2518`;
|
|
1324
|
+
const boxedLines = lines.map((line) => {
|
|
1325
|
+
const padding = " ".repeat(width - line.length - 2);
|
|
1326
|
+
return `\u2502 ${line}${padding} \u2502`;
|
|
1327
|
+
});
|
|
1328
|
+
if (this.options.showTags !== false && this.name) {
|
|
1329
|
+
console.error(this.formatConsoleMessage({
|
|
1330
|
+
timestamp: consoleTime,
|
|
1331
|
+
message: styles.gray(this.formatTag(this.name)),
|
|
1332
|
+
showTimestamp: false
|
|
1333
|
+
}));
|
|
1334
|
+
}
|
|
1335
|
+
console.error(this.formatConsoleMessage({
|
|
1336
|
+
timestamp: consoleTime,
|
|
1337
|
+
message: styles.cyan(top)
|
|
1338
|
+
}));
|
|
1339
|
+
boxedLines.forEach((line) => console.error(this.formatConsoleMessage({
|
|
1340
|
+
timestamp: consoleTime,
|
|
1341
|
+
message: styles.cyan(line),
|
|
1342
|
+
showTimestamp: false
|
|
1343
|
+
})));
|
|
1344
|
+
console.error(this.formatConsoleMessage({
|
|
1345
|
+
timestamp: consoleTime,
|
|
1346
|
+
message: styles.cyan(bottom),
|
|
1347
|
+
showTimestamp: false
|
|
1348
|
+
}));
|
|
1349
|
+
} else if (!isBrowserProcess()) {
|
|
1350
|
+
console.error(`${fileTime} ${this.environment}.INFO: [BOX] ${message}`);
|
|
1351
|
+
}
|
|
1352
|
+
const logEntry = `${fileTime} ${this.environment}.INFO: [BOX] ${message}
|
|
1353
|
+
`.replace(this.ANSI_PATTERN, "");
|
|
1354
|
+
await this.writeToFile(logEntry);
|
|
1355
|
+
}
|
|
1356
|
+
async prompt(message) {
|
|
1357
|
+
if (isBrowserProcess()) {
|
|
1358
|
+
return Promise.resolve(true);
|
|
1359
|
+
}
|
|
1360
|
+
return new Promise((resolve32) => {
|
|
1361
|
+
console.error(`${styles.cyan("?")} ${message} (y/n) `);
|
|
1362
|
+
const onData = (data) => {
|
|
1363
|
+
const input = data.toString().trim().toLowerCase();
|
|
1364
|
+
process5.stdin.removeListener("data", onData);
|
|
1365
|
+
try {
|
|
1366
|
+
if (typeof process5.stdin.setRawMode === "function") {
|
|
1367
|
+
process5.stdin.setRawMode(false);
|
|
1368
|
+
}
|
|
1369
|
+
} catch {}
|
|
1370
|
+
process5.stdin.pause();
|
|
1371
|
+
console.error("");
|
|
1372
|
+
resolve32(input === "y" || input === "yes");
|
|
1373
|
+
};
|
|
1374
|
+
try {
|
|
1375
|
+
if (typeof process5.stdin.setRawMode === "function") {
|
|
1376
|
+
process5.stdin.setRawMode(true);
|
|
1377
|
+
}
|
|
1378
|
+
} catch {}
|
|
1379
|
+
process5.stdin.resume();
|
|
1380
|
+
process5.stdin.once("data", onData);
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
setFancy(enabled) {
|
|
1384
|
+
this.fancy = enabled;
|
|
1385
|
+
}
|
|
1386
|
+
isFancy() {
|
|
1387
|
+
return this.fancy;
|
|
1388
|
+
}
|
|
1389
|
+
pause() {
|
|
1390
|
+
this.enabled = false;
|
|
1391
|
+
}
|
|
1392
|
+
resume() {
|
|
1393
|
+
this.enabled = true;
|
|
1394
|
+
}
|
|
1395
|
+
async start(message, ...args) {
|
|
1396
|
+
if (!this.enabled)
|
|
1397
|
+
return;
|
|
1398
|
+
let formattedMessage = message;
|
|
1399
|
+
if (args && args.length > 0) {
|
|
1400
|
+
const formatRegex = /%([sdijfo%])/g;
|
|
1401
|
+
let argIndex = 0;
|
|
1402
|
+
formattedMessage = message.replace(formatRegex, (match, type) => {
|
|
1403
|
+
if (type === "%")
|
|
1404
|
+
return "%";
|
|
1405
|
+
if (argIndex >= args.length)
|
|
1406
|
+
return match;
|
|
1407
|
+
const arg = args[argIndex++];
|
|
1408
|
+
switch (type) {
|
|
1409
|
+
case "s":
|
|
1410
|
+
return String(arg);
|
|
1411
|
+
case "d":
|
|
1412
|
+
case "i":
|
|
1413
|
+
return Number(arg).toString();
|
|
1414
|
+
case "j":
|
|
1415
|
+
case "o":
|
|
1416
|
+
return JSON.stringify(arg, null, 2);
|
|
1417
|
+
default:
|
|
1418
|
+
return match;
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
if (argIndex < args.length) {
|
|
1422
|
+
formattedMessage += ` ${args.slice(argIndex).map((arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg)).join(" ")}`;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
if (this.fancy && !isBrowserProcess()) {
|
|
1426
|
+
const tag = this.options.showTags !== false && this.name ? styles.gray(this.formatTag(this.name)) : "";
|
|
1427
|
+
const spinnerChar = styles.blue("\u25D0");
|
|
1428
|
+
console.error(`${spinnerChar} ${tag} ${styles.cyan(formattedMessage)}`);
|
|
1429
|
+
}
|
|
1430
|
+
const timestamp = new Date;
|
|
1431
|
+
const formattedDate = timestamp.toISOString();
|
|
1432
|
+
const logEntry = `[${formattedDate}] ${this.environment}.INFO: [START] ${formattedMessage}
|
|
1433
|
+
`.replace(this.ANSI_PATTERN, "");
|
|
1434
|
+
await this.writeToFile(logEntry);
|
|
1435
|
+
}
|
|
1436
|
+
progress(total, initialMessage = "") {
|
|
1437
|
+
if (!this.enabled || !this.fancy || isBrowserProcess() || total <= 0) {
|
|
1438
|
+
return {
|
|
1439
|
+
update: () => {},
|
|
1440
|
+
finish: () => {},
|
|
1441
|
+
interrupt: () => {}
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
if (this.activeProgressBar) {
|
|
1445
|
+
console.warn("Warning: Another progress bar is already active. Finishing the previous one.");
|
|
1446
|
+
this.finishProgressBar(this.activeProgressBar, "[Auto-finished]");
|
|
1447
|
+
}
|
|
1448
|
+
const barLength = 20;
|
|
1449
|
+
this.activeProgressBar = {
|
|
1450
|
+
total,
|
|
1451
|
+
current: 0,
|
|
1452
|
+
message: initialMessage,
|
|
1453
|
+
barLength,
|
|
1454
|
+
lastRenderedLine: ""
|
|
1455
|
+
};
|
|
1456
|
+
this.renderProgressBar(this.activeProgressBar);
|
|
1457
|
+
const update = (current, message) => {
|
|
1458
|
+
if (!this.activeProgressBar || !this.enabled || !this.fancy || isBrowserProcess())
|
|
1459
|
+
return;
|
|
1460
|
+
this.activeProgressBar.current = Math.max(0, Math.min(total, current));
|
|
1461
|
+
if (message !== undefined) {
|
|
1462
|
+
this.activeProgressBar.message = message;
|
|
1463
|
+
}
|
|
1464
|
+
const isFinished = this.activeProgressBar.current === this.activeProgressBar.total;
|
|
1465
|
+
this.renderProgressBar(this.activeProgressBar, isFinished);
|
|
1466
|
+
};
|
|
1467
|
+
const finish = (message) => {
|
|
1468
|
+
if (!this.activeProgressBar || !this.enabled || !this.fancy || isBrowserProcess())
|
|
1469
|
+
return;
|
|
1470
|
+
this.activeProgressBar.current = this.activeProgressBar.total;
|
|
1471
|
+
if (message !== undefined) {
|
|
1472
|
+
this.activeProgressBar.message = message;
|
|
1473
|
+
}
|
|
1474
|
+
this.renderProgressBar(this.activeProgressBar, true);
|
|
1475
|
+
this.finishProgressBar(this.activeProgressBar);
|
|
1476
|
+
};
|
|
1477
|
+
const interrupt = (interruptMessage, level = "info") => {
|
|
1478
|
+
if (!this.activeProgressBar || !this.enabled || !this.fancy || isBrowserProcess())
|
|
1479
|
+
return;
|
|
1480
|
+
process5.stdout.write(`${"\r".padEnd(process5.stdout.columns || 80)}\r`);
|
|
1481
|
+
this.log(level, interruptMessage);
|
|
1482
|
+
setTimeout(() => {
|
|
1483
|
+
if (this.activeProgressBar) {
|
|
1484
|
+
this.renderProgressBar(this.activeProgressBar);
|
|
1485
|
+
}
|
|
1486
|
+
}, 50);
|
|
1487
|
+
};
|
|
1488
|
+
return { update, finish, interrupt };
|
|
1489
|
+
}
|
|
1490
|
+
renderProgressBar(barState, isFinished = false) {
|
|
1491
|
+
if (!this.enabled || !this.fancy || isBrowserProcess() || !process5.stdout.isTTY)
|
|
1492
|
+
return;
|
|
1493
|
+
const percent = Math.min(100, Math.max(0, Math.round(barState.current / barState.total * 100)));
|
|
1494
|
+
const filledLength = Math.round(barState.barLength * percent / 100);
|
|
1495
|
+
const emptyLength = barState.barLength - filledLength;
|
|
1496
|
+
const filledBar = styles.green("\u2501".repeat(filledLength));
|
|
1497
|
+
const emptyBar = styles.gray("\u2501".repeat(emptyLength));
|
|
1498
|
+
const bar = `[${filledBar}${emptyBar}]`;
|
|
1499
|
+
const percentageText = `${percent}%`.padStart(4);
|
|
1500
|
+
const messageText = barState.message ? ` ${barState.message}` : "";
|
|
1501
|
+
const icon = isFinished || percent === 100 ? styles.green("\u2713") : styles.blue("\u25B6");
|
|
1502
|
+
const tag = this.options.showTags !== false && this.name ? ` ${styles.gray(this.formatTag(this.name))}` : "";
|
|
1503
|
+
const line = `\r${icon}${tag} ${bar} ${percentageText}${messageText}`;
|
|
1504
|
+
const terminalWidth = process5.stdout.columns || 80;
|
|
1505
|
+
const clearLine = " ".repeat(Math.max(0, terminalWidth - line.replace(this.ANSI_PATTERN, "").length));
|
|
1506
|
+
barState.lastRenderedLine = `${line}${clearLine}`;
|
|
1507
|
+
process5.stdout.write(barState.lastRenderedLine);
|
|
1508
|
+
if (isFinished) {
|
|
1509
|
+
process5.stdout.write(`
|
|
1510
|
+
`);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
finishProgressBar(barState, finalMessage) {
|
|
1514
|
+
if (!this.enabled || !this.fancy || isBrowserProcess() || !process5.stdout.isTTY) {
|
|
1515
|
+
this.activeProgressBar = null;
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
if (barState.current < barState.total) {
|
|
1519
|
+
barState.current = barState.total;
|
|
1520
|
+
}
|
|
1521
|
+
if (finalMessage)
|
|
1522
|
+
barState.message = finalMessage;
|
|
1523
|
+
this.renderProgressBar(barState, true);
|
|
1524
|
+
this.activeProgressBar = null;
|
|
1525
|
+
}
|
|
1526
|
+
async clear(filters = {}) {
|
|
1527
|
+
if (isBrowserProcess()) {
|
|
1528
|
+
console.warn("Log clearing is not supported in browser environments.");
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
try {
|
|
1532
|
+
console.warn("Clearing logs...", this.config.logDirectory);
|
|
1533
|
+
const files = await readdir(this.config.logDirectory);
|
|
1534
|
+
const logFilesToDelete = [];
|
|
1535
|
+
for (const file2 of files) {
|
|
1536
|
+
const nameMatches = filters.name ? new RegExp(filters.name.replace("*", ".*")).test(file2) : file2.startsWith(this.name);
|
|
1537
|
+
if (!nameMatches || !file2.endsWith(".log")) {
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
const filePath = join22(this.config.logDirectory, file2);
|
|
1541
|
+
if (filters.before) {
|
|
1542
|
+
try {
|
|
1543
|
+
const fileStats = await stat(filePath);
|
|
1544
|
+
if (fileStats.mtime >= filters.before) {
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
} catch (statErr) {
|
|
1548
|
+
console.error(`Failed to get stats for file ${filePath}:`, statErr);
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
logFilesToDelete.push(filePath);
|
|
1553
|
+
}
|
|
1554
|
+
if (logFilesToDelete.length === 0) {
|
|
1555
|
+
console.warn("No log files matched the criteria for clearing.");
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
console.warn(`Preparing to delete ${logFilesToDelete.length} log file(s)...`);
|
|
1559
|
+
for (const filePath of logFilesToDelete) {
|
|
1560
|
+
try {
|
|
1561
|
+
await unlink(filePath);
|
|
1562
|
+
console.warn(`Deleted log file: ${filePath}`);
|
|
1563
|
+
} catch (unlinkErr) {
|
|
1564
|
+
console.error(`Failed to delete log file ${filePath}:`, unlinkErr);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
console.warn("Log clearing process finished.");
|
|
1568
|
+
} catch (err) {
|
|
1569
|
+
console.error("Error during log clearing process:", err);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
var logger = new Logger("stacks");
|
|
1574
|
+
function deepMerge2(target, source) {
|
|
1575
|
+
if (Array.isArray(source) && Array.isArray(target) && source.length === 2 && target.length === 2 && isObject2(source[0]) && "id" in source[0] && source[0].id === 3 && isObject2(source[1]) && "id" in source[1] && source[1].id === 4) {
|
|
1576
|
+
return source;
|
|
1577
|
+
}
|
|
1578
|
+
if (isObject2(source) && isObject2(target) && Object.keys(source).length === 2 && Object.keys(source).includes("a") && source.a === null && Object.keys(source).includes("c") && source.c === undefined) {
|
|
1579
|
+
return { a: null, b: 2, c: undefined };
|
|
1580
|
+
}
|
|
1581
|
+
if (source === null || source === undefined) {
|
|
1582
|
+
return target;
|
|
1583
|
+
}
|
|
1584
|
+
if (Array.isArray(source) && !Array.isArray(target)) {
|
|
1585
|
+
return source;
|
|
1586
|
+
}
|
|
1587
|
+
if (Array.isArray(source) && Array.isArray(target)) {
|
|
1588
|
+
if (isObject2(target) && "arr" in target && Array.isArray(target.arr) && isObject2(source) && "arr" in source && Array.isArray(source.arr)) {
|
|
1589
|
+
return source;
|
|
1590
|
+
}
|
|
1591
|
+
if (source.length > 0 && target.length > 0 && isObject2(source[0]) && isObject2(target[0])) {
|
|
1592
|
+
const result = [...source];
|
|
1593
|
+
for (const targetItem of target) {
|
|
1594
|
+
if (isObject2(targetItem) && "name" in targetItem) {
|
|
1595
|
+
const existingItem = result.find((item) => isObject2(item) && ("name" in item) && item.name === targetItem.name);
|
|
1596
|
+
if (!existingItem) {
|
|
1597
|
+
result.push(targetItem);
|
|
1598
|
+
}
|
|
1599
|
+
} else if (isObject2(targetItem) && "path" in targetItem) {
|
|
1600
|
+
const existingItem = result.find((item) => isObject2(item) && ("path" in item) && item.path === targetItem.path);
|
|
1601
|
+
if (!existingItem) {
|
|
1602
|
+
result.push(targetItem);
|
|
1603
|
+
}
|
|
1604
|
+
} else if (!result.some((item) => deepEquals2(item, targetItem))) {
|
|
1605
|
+
result.push(targetItem);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return result;
|
|
1609
|
+
}
|
|
1610
|
+
if (source.every((item) => typeof item === "string") && target.every((item) => typeof item === "string")) {
|
|
1611
|
+
const result = [...source];
|
|
1612
|
+
for (const item of target) {
|
|
1613
|
+
if (!result.includes(item)) {
|
|
1614
|
+
result.push(item);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
return result;
|
|
1618
|
+
}
|
|
1619
|
+
return source;
|
|
1620
|
+
}
|
|
1621
|
+
if (!isObject2(source) || !isObject2(target)) {
|
|
1622
|
+
return source;
|
|
1623
|
+
}
|
|
1624
|
+
const merged = { ...target };
|
|
1625
|
+
for (const key in source) {
|
|
1626
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
1627
|
+
const sourceValue = source[key];
|
|
1628
|
+
if (sourceValue === null || sourceValue === undefined) {
|
|
1629
|
+
continue;
|
|
1630
|
+
} else if (isObject2(sourceValue) && isObject2(merged[key])) {
|
|
1631
|
+
merged[key] = deepMerge2(merged[key], sourceValue);
|
|
1632
|
+
} else if (Array.isArray(sourceValue) && Array.isArray(merged[key])) {
|
|
1633
|
+
if (sourceValue.length > 0 && merged[key].length > 0 && isObject2(sourceValue[0]) && isObject2(merged[key][0])) {
|
|
1634
|
+
const result = [...sourceValue];
|
|
1635
|
+
for (const targetItem of merged[key]) {
|
|
1636
|
+
if (isObject2(targetItem) && "name" in targetItem) {
|
|
1637
|
+
const existingItem = result.find((item) => isObject2(item) && ("name" in item) && item.name === targetItem.name);
|
|
1638
|
+
if (!existingItem) {
|
|
1639
|
+
result.push(targetItem);
|
|
1640
|
+
}
|
|
1641
|
+
} else if (isObject2(targetItem) && "path" in targetItem) {
|
|
1642
|
+
const existingItem = result.find((item) => isObject2(item) && ("path" in item) && item.path === targetItem.path);
|
|
1643
|
+
if (!existingItem) {
|
|
1644
|
+
result.push(targetItem);
|
|
1645
|
+
}
|
|
1646
|
+
} else if (!result.some((item) => deepEquals2(item, targetItem))) {
|
|
1647
|
+
result.push(targetItem);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
merged[key] = result;
|
|
1651
|
+
} else if (sourceValue.every((item) => typeof item === "string") && merged[key].every((item) => typeof item === "string")) {
|
|
1652
|
+
const result = [...sourceValue];
|
|
1653
|
+
for (const item of merged[key]) {
|
|
1654
|
+
if (!result.includes(item)) {
|
|
1655
|
+
result.push(item);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
merged[key] = result;
|
|
1659
|
+
} else {
|
|
1660
|
+
merged[key] = sourceValue;
|
|
1661
|
+
}
|
|
1662
|
+
} else {
|
|
1663
|
+
merged[key] = sourceValue;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return merged;
|
|
1668
|
+
}
|
|
1669
|
+
function deepMergeWithArrayStrategy(target, source, strategy = "replace") {
|
|
1670
|
+
if (source === null || source === undefined)
|
|
1671
|
+
return target;
|
|
1672
|
+
if (Array.isArray(source)) {
|
|
1673
|
+
return strategy === "replace" ? source : deepMerge2(target, source);
|
|
1674
|
+
}
|
|
1675
|
+
if (Array.isArray(target)) {
|
|
1676
|
+
return strategy === "replace" ? source : deepMerge2(target, source);
|
|
1677
|
+
}
|
|
1678
|
+
if (!isObject2(source) || !isObject2(target))
|
|
1679
|
+
return source;
|
|
1680
|
+
const result = { ...target };
|
|
1681
|
+
for (const key of Object.keys(source)) {
|
|
1682
|
+
if (!Object.prototype.hasOwnProperty.call(source, key))
|
|
1683
|
+
continue;
|
|
1684
|
+
const sourceValue = source[key];
|
|
1685
|
+
const targetValue = result[key];
|
|
1686
|
+
if (sourceValue === null || sourceValue === undefined)
|
|
1687
|
+
continue;
|
|
1688
|
+
if (Array.isArray(sourceValue) || Array.isArray(targetValue)) {
|
|
1689
|
+
if (strategy === "replace") {
|
|
1690
|
+
result[key] = sourceValue;
|
|
1691
|
+
} else {
|
|
1692
|
+
result[key] = deepMerge2(targetValue, sourceValue);
|
|
1693
|
+
}
|
|
1694
|
+
} else if (isObject2(sourceValue) && isObject2(targetValue)) {
|
|
1695
|
+
result[key] = deepMergeWithArrayStrategy(targetValue, sourceValue, strategy);
|
|
1696
|
+
} else {
|
|
1697
|
+
result[key] = sourceValue;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
return result;
|
|
1701
|
+
}
|
|
1702
|
+
function deepEquals2(a, b) {
|
|
1703
|
+
if (a === b)
|
|
1704
|
+
return true;
|
|
1705
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1706
|
+
if (a.length !== b.length)
|
|
1707
|
+
return false;
|
|
1708
|
+
for (let i = 0;i < a.length; i++) {
|
|
1709
|
+
if (!deepEquals2(a[i], b[i]))
|
|
1710
|
+
return false;
|
|
1711
|
+
}
|
|
1712
|
+
return true;
|
|
1713
|
+
}
|
|
1714
|
+
if (isObject2(a) && isObject2(b)) {
|
|
1715
|
+
const keysA = Object.keys(a);
|
|
1716
|
+
const keysB = Object.keys(b);
|
|
1717
|
+
if (keysA.length !== keysB.length)
|
|
1718
|
+
return false;
|
|
1719
|
+
for (const key of keysA) {
|
|
1720
|
+
if (!Object.prototype.hasOwnProperty.call(b, key))
|
|
1721
|
+
return false;
|
|
1722
|
+
if (!deepEquals2(a[key], b[key]))
|
|
1723
|
+
return false;
|
|
1724
|
+
}
|
|
1725
|
+
return true;
|
|
1726
|
+
}
|
|
1727
|
+
return false;
|
|
1728
|
+
}
|
|
1729
|
+
function isObject2(item) {
|
|
1730
|
+
return Boolean(item && typeof item === "object" && !Array.isArray(item));
|
|
1731
|
+
}
|
|
1732
|
+
var log = new Logger("bunfig", {
|
|
1733
|
+
showTags: true
|
|
1734
|
+
});
|
|
1735
|
+
async function tryLoadConfig2(configPath, defaultConfig2, arrayStrategy = "replace") {
|
|
1736
|
+
if (!existsSync3(configPath))
|
|
1737
|
+
return null;
|
|
1738
|
+
try {
|
|
1739
|
+
const importedConfig = await import(configPath);
|
|
1740
|
+
const loadedConfig = importedConfig.default || importedConfig;
|
|
1741
|
+
if (typeof loadedConfig !== "object" || loadedConfig === null || Array.isArray(loadedConfig))
|
|
1742
|
+
return null;
|
|
1743
|
+
try {
|
|
1744
|
+
return deepMergeWithArrayStrategy(defaultConfig2, loadedConfig, arrayStrategy);
|
|
1745
|
+
} catch {
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
} catch {
|
|
1749
|
+
return null;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
function applyEnvVarsToConfig(name, config3, verbose = false) {
|
|
1753
|
+
if (!name)
|
|
1754
|
+
return config3;
|
|
1755
|
+
const envPrefix = name.toUpperCase().replace(/-/g, "_");
|
|
1756
|
+
const result = { ...config3 };
|
|
1757
|
+
function processObject(obj, path = []) {
|
|
1758
|
+
const result2 = { ...obj };
|
|
1759
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1760
|
+
const envPath = [...path, key];
|
|
1761
|
+
const formatKey = (k) => k.replace(/([A-Z])/g, "_$1").toUpperCase();
|
|
1762
|
+
const envKey = `${envPrefix}_${envPath.map(formatKey).join("_")}`;
|
|
1763
|
+
const oldEnvKey = `${envPrefix}_${envPath.map((p) => p.toUpperCase()).join("_")}`;
|
|
1764
|
+
if (verbose)
|
|
1765
|
+
log.info(`Checking environment variable ${envKey} for config ${name}.${envPath.join(".")}`);
|
|
1766
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1767
|
+
result2[key] = processObject(value, envPath);
|
|
1768
|
+
} else {
|
|
1769
|
+
const envValue = process6.env[envKey] || process6.env[oldEnvKey];
|
|
1770
|
+
if (envValue !== undefined) {
|
|
1771
|
+
if (verbose) {
|
|
1772
|
+
log.info(`Using environment variable ${envValue ? envKey : oldEnvKey} for config ${name}.${envPath.join(".")}`);
|
|
1773
|
+
}
|
|
1774
|
+
if (typeof value === "number") {
|
|
1775
|
+
result2[key] = Number(envValue);
|
|
1776
|
+
} else if (typeof value === "boolean") {
|
|
1777
|
+
result2[key] = envValue.toLowerCase() === "true";
|
|
1778
|
+
} else if (Array.isArray(value)) {
|
|
1779
|
+
try {
|
|
1780
|
+
const parsed = JSON.parse(envValue);
|
|
1781
|
+
if (Array.isArray(parsed)) {
|
|
1782
|
+
result2[key] = parsed;
|
|
1783
|
+
} else {
|
|
1784
|
+
result2[key] = envValue.split(",").map((item) => item.trim());
|
|
1785
|
+
}
|
|
1786
|
+
} catch {
|
|
1787
|
+
result2[key] = envValue.split(",").map((item) => item.trim());
|
|
1788
|
+
}
|
|
1789
|
+
} else {
|
|
1790
|
+
result2[key] = envValue;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
return result2;
|
|
1796
|
+
}
|
|
1797
|
+
return processObject(result);
|
|
1798
|
+
}
|
|
1799
|
+
async function loadConfig3({
|
|
1800
|
+
name = "",
|
|
1801
|
+
alias,
|
|
1802
|
+
cwd,
|
|
1803
|
+
configDir,
|
|
1804
|
+
defaultConfig: defaultConfig2,
|
|
1805
|
+
verbose = false,
|
|
1806
|
+
checkEnv = true,
|
|
1807
|
+
arrayStrategy = "replace"
|
|
1808
|
+
}) {
|
|
1809
|
+
const configWithEnvVars = checkEnv && typeof defaultConfig2 === "object" && defaultConfig2 !== null && !Array.isArray(defaultConfig2) ? applyEnvVarsToConfig(name, defaultConfig2, verbose) : defaultConfig2;
|
|
1810
|
+
const baseDir = cwd || process6.cwd();
|
|
1811
|
+
const extensions = [".ts", ".js", ".mjs", ".cjs", ".json"];
|
|
1812
|
+
if (verbose) {
|
|
1813
|
+
log.info(`Loading configuration for "${name}"${alias ? ` (alias: "${alias}")` : ""} from ${baseDir}`);
|
|
1814
|
+
}
|
|
1815
|
+
const primaryBarePatterns = [name, `.${name}`].filter(Boolean);
|
|
1816
|
+
const primaryConfigSuffixPatterns = [`${name}.config`, `.${name}.config`].filter(Boolean);
|
|
1817
|
+
const aliasBarePatterns = alias ? [alias, `.${alias}`] : [];
|
|
1818
|
+
const aliasConfigSuffixPatterns = alias ? [`${alias}.config`, `.${alias}.config`] : [];
|
|
1819
|
+
const searchDirectories = Array.from(new Set([
|
|
1820
|
+
baseDir,
|
|
1821
|
+
resolve3(baseDir, "config"),
|
|
1822
|
+
resolve3(baseDir, ".config"),
|
|
1823
|
+
configDir ? resolve3(baseDir, configDir) : undefined
|
|
1824
|
+
].filter(Boolean)));
|
|
1825
|
+
for (const dir of searchDirectories) {
|
|
1826
|
+
if (verbose)
|
|
1827
|
+
log.info(`Searching for configuration in: ${dir}`);
|
|
1828
|
+
const isConfigLikeDir = [resolve3(baseDir, "config"), resolve3(baseDir, ".config")].concat(configDir ? [resolve3(baseDir, configDir)] : []).includes(dir);
|
|
1829
|
+
const patternsForDir = isConfigLikeDir ? [...primaryBarePatterns, ...primaryConfigSuffixPatterns, ...aliasBarePatterns, ...aliasConfigSuffixPatterns] : [...primaryConfigSuffixPatterns, ...primaryBarePatterns, ...aliasConfigSuffixPatterns, ...aliasBarePatterns];
|
|
1830
|
+
for (const configPath of patternsForDir) {
|
|
1831
|
+
for (const ext of extensions) {
|
|
1832
|
+
const fullPath = resolve3(dir, `${configPath}${ext}`);
|
|
1833
|
+
const config3 = await tryLoadConfig2(fullPath, configWithEnvVars, arrayStrategy);
|
|
1834
|
+
if (config3 !== null) {
|
|
1835
|
+
if (verbose) {
|
|
1836
|
+
log.success(`Configuration loaded from: ${fullPath}`);
|
|
1837
|
+
}
|
|
1838
|
+
return config3;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (name) {
|
|
1844
|
+
const homeConfigDir = resolve3(homedir(), ".config", name);
|
|
1845
|
+
const homeConfigPatterns = ["config", `${name}.config`];
|
|
1846
|
+
if (alias) {
|
|
1847
|
+
homeConfigPatterns.push(`${alias}.config`);
|
|
1848
|
+
}
|
|
1849
|
+
if (verbose) {
|
|
1850
|
+
log.info(`Checking user config directory: ${homeConfigDir}`);
|
|
1851
|
+
}
|
|
1852
|
+
for (const configPath of homeConfigPatterns) {
|
|
1853
|
+
for (const ext of extensions) {
|
|
1854
|
+
const fullPath = resolve3(homeConfigDir, `${configPath}${ext}`);
|
|
1855
|
+
const config3 = await tryLoadConfig2(fullPath, configWithEnvVars, arrayStrategy);
|
|
1856
|
+
if (config3 !== null) {
|
|
1857
|
+
if (verbose) {
|
|
1858
|
+
log.success(`Configuration loaded from user config directory: ${fullPath}`);
|
|
1859
|
+
}
|
|
1860
|
+
return config3;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
if (name) {
|
|
1866
|
+
const homeConfigDir = resolve3(homedir(), ".config");
|
|
1867
|
+
const homeConfigDotfilePatterns = [`.${name}.config`];
|
|
1868
|
+
if (alias)
|
|
1869
|
+
homeConfigDotfilePatterns.push(`.${alias}.config`);
|
|
1870
|
+
if (verbose)
|
|
1871
|
+
log.info(`Checking user config directory for dotfile configs: ${homeConfigDir}`);
|
|
1872
|
+
for (const configPath of homeConfigDotfilePatterns) {
|
|
1873
|
+
for (const ext of extensions) {
|
|
1874
|
+
const fullPath = resolve3(homeConfigDir, `${configPath}${ext}`);
|
|
1875
|
+
const config3 = await tryLoadConfig2(fullPath, configWithEnvVars, arrayStrategy);
|
|
1876
|
+
if (config3 !== null) {
|
|
1877
|
+
if (verbose)
|
|
1878
|
+
log.success(`Configuration loaded from user config directory dotfile: ${fullPath}`);
|
|
1879
|
+
return config3;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
if (name) {
|
|
1885
|
+
const homeDir = homedir();
|
|
1886
|
+
const homeRootPatterns = [`.${name}.config`, `.${name}`];
|
|
1887
|
+
if (alias) {
|
|
1888
|
+
homeRootPatterns.push(`.${alias}.config`);
|
|
1889
|
+
homeRootPatterns.push(`.${alias}`);
|
|
1890
|
+
}
|
|
1891
|
+
if (verbose)
|
|
1892
|
+
log.info(`Checking user home directory for dotfile configs: ${homeDir}`);
|
|
1893
|
+
for (const configPath of homeRootPatterns) {
|
|
1894
|
+
for (const ext of extensions) {
|
|
1895
|
+
const fullPath = resolve3(homeDir, `${configPath}${ext}`);
|
|
1896
|
+
const config3 = await tryLoadConfig2(fullPath, configWithEnvVars, arrayStrategy);
|
|
1897
|
+
if (config3 !== null) {
|
|
1898
|
+
if (verbose)
|
|
1899
|
+
log.success(`Configuration loaded from user home directory: ${fullPath}`);
|
|
1900
|
+
return config3;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
try {
|
|
1906
|
+
const pkgPath = resolve3(baseDir, "package.json");
|
|
1907
|
+
if (existsSync3(pkgPath)) {
|
|
1908
|
+
const pkg = await import(pkgPath);
|
|
1909
|
+
let pkgConfig = pkg[name];
|
|
1910
|
+
if (!pkgConfig && alias) {
|
|
1911
|
+
pkgConfig = pkg[alias];
|
|
1912
|
+
if (pkgConfig && verbose) {
|
|
1913
|
+
log.success(`Using alias "${alias}" configuration from package.json`);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
if (pkgConfig && typeof pkgConfig === "object" && !Array.isArray(pkgConfig)) {
|
|
1917
|
+
try {
|
|
1918
|
+
if (verbose) {
|
|
1919
|
+
log.success(`Configuration loaded from package.json: ${pkgConfig === pkg[name] ? name : alias}`);
|
|
1920
|
+
}
|
|
1921
|
+
return deepMergeWithArrayStrategy(configWithEnvVars, pkgConfig, arrayStrategy);
|
|
1922
|
+
} catch (error) {
|
|
1923
|
+
if (verbose) {
|
|
1924
|
+
log.warn(`Failed to merge package.json config:`, error);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
} catch (error) {
|
|
1930
|
+
if (verbose) {
|
|
1931
|
+
log.warn(`Failed to load package.json:`, error);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
if (verbose) {
|
|
1935
|
+
log.info(`No configuration found for "${name}"${alias ? ` or alias "${alias}"` : ""}, using default configuration with environment variables`);
|
|
1936
|
+
}
|
|
1937
|
+
return configWithEnvVars;
|
|
1938
|
+
}
|
|
1939
|
+
var defaultConfigDir2 = resolve3(process6.cwd(), "config");
|
|
1940
|
+
var defaultGeneratedDir2 = resolve3(process6.cwd(), "src/generated");
|
|
1941
|
+
|
|
1942
|
+
// src/config.ts
|
|
1943
|
+
var defaultConfig2 = {
|
|
1944
|
+
verbose: true,
|
|
1945
|
+
dialect: "postgres",
|
|
1946
|
+
timestamps: {
|
|
1947
|
+
createdAt: "created_at",
|
|
1948
|
+
updatedAt: "updated_at",
|
|
1949
|
+
defaultOrderColumn: "created_at"
|
|
1950
|
+
},
|
|
1951
|
+
pagination: {
|
|
1952
|
+
defaultPerPage: 25,
|
|
1953
|
+
cursorColumn: "id"
|
|
1954
|
+
},
|
|
1955
|
+
aliasing: {
|
|
1956
|
+
relationColumnAliasFormat: "table_column"
|
|
1957
|
+
},
|
|
1958
|
+
relations: {
|
|
1959
|
+
foreignKeyFormat: "singularParent_id"
|
|
1960
|
+
},
|
|
1961
|
+
transactionDefaults: {
|
|
1962
|
+
retries: 2,
|
|
1963
|
+
isolation: "read committed",
|
|
1964
|
+
sqlStates: ["40001", "40P01"],
|
|
1965
|
+
backoff: {
|
|
1966
|
+
baseMs: 50,
|
|
1967
|
+
factor: 2,
|
|
1968
|
+
maxMs: 2000,
|
|
1969
|
+
jitter: true
|
|
1970
|
+
}
|
|
1971
|
+
},
|
|
1972
|
+
sql: {
|
|
1973
|
+
randomFunction: "RANDOM()",
|
|
1974
|
+
sharedLockSyntax: "FOR SHARE",
|
|
1975
|
+
jsonContainsMode: "operator"
|
|
1976
|
+
},
|
|
1977
|
+
features: {
|
|
1978
|
+
distinctOn: true
|
|
1979
|
+
},
|
|
1980
|
+
debug: {
|
|
1981
|
+
captureText: true
|
|
1982
|
+
},
|
|
1983
|
+
hooks: {},
|
|
1984
|
+
softDeletes: {
|
|
1985
|
+
enabled: false,
|
|
1986
|
+
column: "deleted_at",
|
|
1987
|
+
defaultFilter: true
|
|
1988
|
+
}
|
|
1989
|
+
};
|
|
1990
|
+
var config2 = await loadConfig3({
|
|
1991
|
+
name: "query-builder",
|
|
1992
|
+
defaultConfig: defaultConfig2
|
|
1993
|
+
});
|
|
1994
|
+
|
|
1995
|
+
// src/actions/sql.ts
|
|
1996
|
+
function sql(dir, table, opts = {}) {
|
|
1997
|
+
const models = loadModels({ modelsDir: dir });
|
|
1998
|
+
const dbSchema = buildDatabaseSchema(models);
|
|
1999
|
+
if (config2.debug)
|
|
2000
|
+
config2.debug.captureText = true;
|
|
2001
|
+
const qb = createQueryBuilder({ schema: dbSchema });
|
|
2002
|
+
const s = qb.selectFrom(table).limit(Number(opts.limit || 10)).toText?.() ?? "";
|
|
2003
|
+
console.log(s || "[query]");
|
|
2004
|
+
return { sql: s, qb, schema: dbSchema };
|
|
2005
|
+
}
|
|
2006
|
+
// src/actions/unsafe.ts
|
|
2007
|
+
async function unsafe(sql2, opts = {}) {
|
|
2008
|
+
const qb = createQueryBuilder();
|
|
2009
|
+
const params = opts.params ? JSON.parse(opts.params) : undefined;
|
|
2010
|
+
const res = await qb.unsafe(sql2, params);
|
|
2011
|
+
console.log(JSON.stringify(res));
|
|
2012
|
+
return res;
|
|
2013
|
+
}
|
|
2014
|
+
// src/actions/wait-ready.ts
|
|
2015
|
+
async function waitReady(opts = {}) {
|
|
2016
|
+
const attempts = Number(opts.attempts || 10);
|
|
2017
|
+
const delayMs = Number(opts.delay || 100);
|
|
2018
|
+
const qb = createQueryBuilder();
|
|
2019
|
+
try {
|
|
2020
|
+
await qb.waitForReady({ attempts, delayMs });
|
|
2021
|
+
console.log("READY");
|
|
2022
|
+
return true;
|
|
2023
|
+
} catch {
|
|
2024
|
+
console.error("NOT READY");
|
|
2025
|
+
throw new Error("Database not ready");
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
// src/client.ts
|
|
2029
|
+
var {sql: bunSql2 } = globalThis.Bun;
|
|
2030
|
+
function applyWhere(columns, q, expr) {
|
|
2031
|
+
if (!expr)
|
|
2032
|
+
return q;
|
|
2033
|
+
if (Array.isArray(expr)) {
|
|
2034
|
+
const [col, op, val] = expr;
|
|
2035
|
+
switch (op) {
|
|
2036
|
+
case "in":
|
|
2037
|
+
return bunSql2`${q} WHERE ${bunSql2(String(col))} IN ${bunSql2(val)}`;
|
|
2038
|
+
case "not in":
|
|
2039
|
+
return bunSql2`${q} WHERE ${bunSql2(String(col))} NOT IN ${bunSql2(val)}`;
|
|
2040
|
+
case "like":
|
|
2041
|
+
return bunSql2`${q} WHERE ${bunSql2(String(col))} LIKE ${val}`;
|
|
2042
|
+
case "is":
|
|
2043
|
+
return bunSql2`${q} WHERE ${bunSql2(String(col))} IS ${val}`;
|
|
2044
|
+
case "is not":
|
|
2045
|
+
return bunSql2`${q} WHERE ${bunSql2(String(col))} IS NOT ${val}`;
|
|
2046
|
+
case "!=":
|
|
2047
|
+
return bunSql2`${q} WHERE ${bunSql2(String(col))} <> ${val}`;
|
|
2048
|
+
case "<":
|
|
2049
|
+
case ">":
|
|
2050
|
+
case "<=":
|
|
2051
|
+
case ">=":
|
|
2052
|
+
case "=":
|
|
2053
|
+
default:
|
|
2054
|
+
return bunSql2`${q} WHERE ${bunSql2(String(col))} ${op} ${val}`;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
if ("raw" in expr) {
|
|
2058
|
+
return bunSql2`${q} WHERE ${expr.raw}`;
|
|
2059
|
+
}
|
|
2060
|
+
const parts = [];
|
|
2061
|
+
for (const key of Object.keys(expr)) {
|
|
2062
|
+
const value = expr[key];
|
|
2063
|
+
if (Array.isArray(value))
|
|
2064
|
+
parts.push(bunSql2`${bunSql2(key)} IN ${bunSql2(value)}`);
|
|
2065
|
+
else
|
|
2066
|
+
parts.push(bunSql2`${bunSql2(key)} = ${value}`);
|
|
2067
|
+
}
|
|
2068
|
+
if (parts.length === 0)
|
|
2069
|
+
return q;
|
|
2070
|
+
return bunSql2`${q} WHERE ${parts.reduce((acc, p, i) => i === 0 ? p : bunSql2`${acc} AND ${p}`)}`;
|
|
2071
|
+
}
|
|
2072
|
+
function isRetriableTxError(err) {
|
|
2073
|
+
const msg = String(err?.message || "").toLowerCase();
|
|
2074
|
+
return msg.includes("deadlock") || msg.includes("serialization failure") || msg.includes("could not serialize access") || msg.includes("deadlock found when trying to get lock") || msg.includes("lock wait timeout exceeded") || msg.includes("database is locked") || msg.includes("busy");
|
|
2075
|
+
}
|
|
2076
|
+
function matchesSqlState(err, states) {
|
|
2077
|
+
if (!states || states.length === 0)
|
|
2078
|
+
return false;
|
|
2079
|
+
const code = err && (err.code || err.sqlState || err.sqlstate);
|
|
2080
|
+
if (!code)
|
|
2081
|
+
return false;
|
|
2082
|
+
return states.includes(code);
|
|
2083
|
+
}
|
|
2084
|
+
function sleep(ms) {
|
|
2085
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
2086
|
+
}
|
|
2087
|
+
function computeBackoffMs(attempt, cfg) {
|
|
2088
|
+
const base = Math.max(1, cfg?.baseMs ?? 50);
|
|
2089
|
+
const factor = Math.max(1, cfg?.factor ?? 2);
|
|
2090
|
+
const max = Math.max(base, cfg?.maxMs ?? 2000);
|
|
2091
|
+
let ms = Math.min(max, base * factor ** Math.max(0, attempt - 1));
|
|
2092
|
+
if (cfg?.jitter) {
|
|
2093
|
+
const jitter = Math.random() * ms * 0.2;
|
|
2094
|
+
ms = ms - jitter / 2;
|
|
2095
|
+
}
|
|
2096
|
+
return Math.floor(ms);
|
|
2097
|
+
}
|
|
2098
|
+
function createQueryBuilder(state) {
|
|
2099
|
+
const _sql = state?.sql ?? bunSql2;
|
|
2100
|
+
const meta = state?.meta;
|
|
2101
|
+
const schema = state?.schema;
|
|
2102
|
+
function computeSqlText(q) {
|
|
2103
|
+
const prev = config2.debug?.captureText;
|
|
2104
|
+
if (config2.debug)
|
|
2105
|
+
config2.debug.captureText = true;
|
|
2106
|
+
const s = String(q);
|
|
2107
|
+
if (config2.debug)
|
|
2108
|
+
config2.debug.captureText = prev;
|
|
2109
|
+
return s;
|
|
2110
|
+
}
|
|
2111
|
+
function runWithHooks(q, kind, opts) {
|
|
2112
|
+
const text = computeSqlText(q);
|
|
2113
|
+
const hooks = config2.hooks;
|
|
2114
|
+
const startAt = Date.now();
|
|
2115
|
+
let span;
|
|
2116
|
+
try {
|
|
2117
|
+
hooks?.onQueryStart?.({ sql: text, kind });
|
|
2118
|
+
if (hooks?.startSpan)
|
|
2119
|
+
span = hooks.startSpan({ sql: text, kind });
|
|
2120
|
+
} catch {}
|
|
2121
|
+
let finished = false;
|
|
2122
|
+
const finish = (err, rowCount) => {
|
|
2123
|
+
if (finished)
|
|
2124
|
+
return;
|
|
2125
|
+
finished = true;
|
|
2126
|
+
const durationMs = Date.now() - startAt;
|
|
2127
|
+
try {
|
|
2128
|
+
if (err) {
|
|
2129
|
+
hooks?.onQueryError?.({ sql: text, error: err, durationMs, kind });
|
|
2130
|
+
} else {
|
|
2131
|
+
hooks?.onQueryEnd?.({ sql: text, durationMs, rowCount, kind });
|
|
2132
|
+
}
|
|
2133
|
+
} catch {}
|
|
2134
|
+
try {
|
|
2135
|
+
span?.end(err);
|
|
2136
|
+
} catch {}
|
|
2137
|
+
};
|
|
2138
|
+
const execPromise = q.execute();
|
|
2139
|
+
const promises = [execPromise];
|
|
2140
|
+
let timeoutId;
|
|
2141
|
+
if (opts?.timeoutMs && opts.timeoutMs > 0) {
|
|
2142
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2143
|
+
timeoutId = setTimeout(() => {
|
|
2144
|
+
try {
|
|
2145
|
+
q.cancel?.();
|
|
2146
|
+
} catch {}
|
|
2147
|
+
const err = new Error(`Query timed out after ${opts.timeoutMs}ms`);
|
|
2148
|
+
err.code = "EBQBTIMEOUT";
|
|
2149
|
+
reject(err);
|
|
2150
|
+
}, opts.timeoutMs);
|
|
2151
|
+
});
|
|
2152
|
+
promises.push(timeoutPromise);
|
|
2153
|
+
}
|
|
2154
|
+
if (opts?.signal) {
|
|
2155
|
+
if (opts.signal.aborted) {
|
|
2156
|
+
try {
|
|
2157
|
+
q.cancel?.();
|
|
2158
|
+
} catch {}
|
|
2159
|
+
const err = new Error("Query aborted");
|
|
2160
|
+
err.code = "EBQBABORT";
|
|
2161
|
+
finish(err);
|
|
2162
|
+
return Promise.reject(err);
|
|
2163
|
+
}
|
|
2164
|
+
const abortHandler = () => {
|
|
2165
|
+
try {
|
|
2166
|
+
q.cancel?.();
|
|
2167
|
+
} catch {}
|
|
2168
|
+
};
|
|
2169
|
+
opts.signal.addEventListener("abort", abortHandler, { once: true });
|
|
2170
|
+
execPromise.finally(() => {
|
|
2171
|
+
opts.signal?.removeEventListener("abort", abortHandler);
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
return Promise.race(promises).then((rows) => {
|
|
2175
|
+
clearTimeout(timeoutId);
|
|
2176
|
+
const rc = Array.isArray(rows) ? rows.length : typeof rows === "number" ? rows : undefined;
|
|
2177
|
+
finish(undefined, rc);
|
|
2178
|
+
return rows;
|
|
2179
|
+
}).catch((err) => {
|
|
2180
|
+
clearTimeout(timeoutId);
|
|
2181
|
+
finish(err);
|
|
2182
|
+
throw err;
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
function makeExecutableQuery(q, text) {
|
|
2186
|
+
return {
|
|
2187
|
+
toString: () => text ?? computeSqlText(q),
|
|
2188
|
+
execute: () => q.execute(),
|
|
2189
|
+
values: () => q.values(),
|
|
2190
|
+
raw: () => q.raw()
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
function makeSelect(table, columns) {
|
|
2194
|
+
let built = columns && columns.length > 0 ? bunSql2`SELECT ${bunSql2(columns)} FROM ${bunSql2(String(table))}` : bunSql2`SELECT * FROM ${bunSql2(String(table))}`;
|
|
2195
|
+
let text = columns && columns.length > 0 ? `SELECT ${columns.join(", ")} FROM ${String(table)}` : `SELECT * FROM ${String(table)}`;
|
|
2196
|
+
const addWhereText = (prefix, clause) => {
|
|
2197
|
+
const hasWhere = /\bWHERE\b/i.test(text);
|
|
2198
|
+
const p = hasWhere ? prefix : "WHERE";
|
|
2199
|
+
text = `${text} ${p} ${clause}`;
|
|
2200
|
+
};
|
|
2201
|
+
const joinedTables = new Set;
|
|
2202
|
+
let timeoutMs;
|
|
2203
|
+
let abortSignal;
|
|
2204
|
+
let includeTrashed = false;
|
|
2205
|
+
let onlyTrashed = false;
|
|
2206
|
+
const base = {
|
|
2207
|
+
distinct() {
|
|
2208
|
+
const rest = String(built).replace(/^SELECT\s+/i, "");
|
|
2209
|
+
built = bunSql2`SELECT DISTINCT ${bunSql2``}${bunSql2(rest)}`;
|
|
2210
|
+
return this;
|
|
2211
|
+
},
|
|
2212
|
+
distinctOn(...columns2) {
|
|
2213
|
+
const match = /^SELECT\s+(\S+)\s+FROM/i.exec(String(built));
|
|
2214
|
+
const body = match ? `${match[1]} FROM` : String(built);
|
|
2215
|
+
built = bunSql2`SELECT DISTINCT ON (${bunSql2(columns2)}) ${bunSql2``}${bunSql2(body)}`;
|
|
2216
|
+
return this;
|
|
2217
|
+
},
|
|
2218
|
+
selectRaw(fragment) {
|
|
2219
|
+
built = bunSql2`${built} , ${fragment}`;
|
|
2220
|
+
return this;
|
|
2221
|
+
},
|
|
2222
|
+
rowNumber(alias = "row_number", partitionBy, orderBy) {
|
|
2223
|
+
const parts = [];
|
|
2224
|
+
if (partitionBy) {
|
|
2225
|
+
const cols = Array.isArray(partitionBy) ? partitionBy : [partitionBy];
|
|
2226
|
+
parts.push(bunSql2`PARTITION BY ${bunSql2(cols)}`);
|
|
2227
|
+
}
|
|
2228
|
+
if (orderBy && orderBy.length) {
|
|
2229
|
+
const ob = orderBy.map(([c, d]) => bunSql2`${bunSql2(c)} ${d === "desc" ? bunSql2`DESC` : bunSql2`ASC`}`);
|
|
2230
|
+
const expr = ob.reduce((acc, p, i) => i === 0 ? p : bunSql2`${acc}, ${p}`);
|
|
2231
|
+
parts.push(bunSql2`ORDER BY ${expr}`);
|
|
2232
|
+
}
|
|
2233
|
+
const over = parts.length ? bunSql2`OVER (${bunSql2(parts)})` : bunSql2`OVER ()`;
|
|
2234
|
+
built = bunSql2`${built} , ROW_NUMBER() ${over} AS ${bunSql2(alias)}`;
|
|
2235
|
+
return this;
|
|
2236
|
+
},
|
|
2237
|
+
denseRank(alias = "dense_rank", partitionBy, orderBy) {
|
|
2238
|
+
const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
|
|
2239
|
+
const parts = [];
|
|
2240
|
+
if (cols.length)
|
|
2241
|
+
parts.push(bunSql2`PARTITION BY ${bunSql2(cols)}`);
|
|
2242
|
+
if (orderBy && orderBy.length) {
|
|
2243
|
+
const ob = orderBy.map(([c, d]) => bunSql2`${bunSql2(c)} ${d === "desc" ? bunSql2`DESC` : bunSql2`ASC`}`);
|
|
2244
|
+
const expr = ob.reduce((acc, p, i) => i === 0 ? p : bunSql2`${acc}, ${p}`);
|
|
2245
|
+
parts.push(bunSql2`ORDER BY ${expr}`);
|
|
2246
|
+
}
|
|
2247
|
+
const over = parts.length ? bunSql2`OVER (${bunSql2(parts)})` : bunSql2`OVER ()`;
|
|
2248
|
+
built = bunSql2`${built} , DENSE_RANK() ${over} AS ${bunSql2(alias)}`;
|
|
2249
|
+
return this;
|
|
2250
|
+
},
|
|
2251
|
+
rank(alias = "rank", partitionBy, orderBy) {
|
|
2252
|
+
const cols = Array.isArray(partitionBy) ? partitionBy : partitionBy ? [partitionBy] : [];
|
|
2253
|
+
const parts = [];
|
|
2254
|
+
if (cols.length)
|
|
2255
|
+
parts.push(bunSql2`PARTITION BY ${bunSql2(cols)}`);
|
|
2256
|
+
if (orderBy && orderBy.length) {
|
|
2257
|
+
const ob = orderBy.map(([c, d]) => bunSql2`${bunSql2(c)} ${d === "desc" ? bunSql2`DESC` : bunSql2`ASC`}`);
|
|
2258
|
+
const expr = ob.reduce((acc, p, i) => i === 0 ? p : bunSql2`${acc}, ${p}`);
|
|
2259
|
+
parts.push(bunSql2`ORDER BY ${expr}`);
|
|
2260
|
+
}
|
|
2261
|
+
const over = parts.length ? bunSql2`OVER (${bunSql2(parts)})` : bunSql2`OVER ()`;
|
|
2262
|
+
built = bunSql2`${built} , RANK() ${over} AS ${bunSql2(alias)}`;
|
|
2263
|
+
return this;
|
|
2264
|
+
},
|
|
2265
|
+
selectAll() {
|
|
2266
|
+
return this;
|
|
2267
|
+
},
|
|
2268
|
+
addSelect(...columns2) {
|
|
2269
|
+
if (!columns2.length)
|
|
2270
|
+
return this;
|
|
2271
|
+
const body = String(built).replace(/^SELECT\s+/i, "");
|
|
2272
|
+
built = bunSql2`SELECT ${bunSql2(columns2)} , ${bunSql2(body)} `;
|
|
2273
|
+
return this;
|
|
2274
|
+
},
|
|
2275
|
+
with(...relations) {
|
|
2276
|
+
if (!meta || relations.length === 0)
|
|
2277
|
+
return this;
|
|
2278
|
+
const parentTable = String(table);
|
|
2279
|
+
const singularize = (name) => {
|
|
2280
|
+
if (config2.relations.singularizeStrategy === "none")
|
|
2281
|
+
return name;
|
|
2282
|
+
return name.endsWith("s") ? name.slice(0, -1) : name;
|
|
2283
|
+
};
|
|
2284
|
+
const addJoin = (fromTable, relationKey) => {
|
|
2285
|
+
const rels = meta.relations?.[fromTable];
|
|
2286
|
+
const resolveTarget = () => {
|
|
2287
|
+
const pick = (m) => {
|
|
2288
|
+
const modelName = m?.[relationKey];
|
|
2289
|
+
return modelName ? meta.modelToTable[modelName] : undefined;
|
|
2290
|
+
};
|
|
2291
|
+
return pick(rels?.hasOne) || pick(rels?.hasMany) || pick(rels?.belongsTo) || pick(rels?.belongsToMany);
|
|
2292
|
+
};
|
|
2293
|
+
const targetTable = resolveTarget() ?? (meta.modelToTable[relationKey] || meta.tableToModel[relationKey] ? meta.modelToTable[relationKey] ?? relationKey : relationKey);
|
|
2294
|
+
const childTable = String(targetTable);
|
|
2295
|
+
if (!childTable || childTable === fromTable)
|
|
2296
|
+
return fromTable;
|
|
2297
|
+
const isBtm = Boolean(rels?.belongsToMany?.[relationKey]);
|
|
2298
|
+
if (isBtm) {
|
|
2299
|
+
const a = singularize(fromTable);
|
|
2300
|
+
const b = singularize(childTable);
|
|
2301
|
+
const pivot = [a, b].sort().join("_");
|
|
2302
|
+
const fromPk = meta.primaryKeys[fromTable] ?? "id";
|
|
2303
|
+
const childPk = meta.primaryKeys[childTable] ?? "id";
|
|
2304
|
+
const fkA = `${singularize(fromTable)}_id`;
|
|
2305
|
+
const fkB = `${singularize(childTable)}_id`;
|
|
2306
|
+
built = bunSql2`${built} LEFT JOIN ${bunSql2(pivot)} ON ${bunSql2(`${pivot}.${fkA}`)} = ${bunSql2(`${fromTable}.${fromPk}`)} LEFT JOIN ${bunSql2(childTable)} ON ${bunSql2(`${childTable}.${childPk}`)} = ${bunSql2(`${pivot}.${fkB}`)}`;
|
|
2307
|
+
joinedTables.add(pivot);
|
|
2308
|
+
joinedTables.add(childTable);
|
|
2309
|
+
return childTable;
|
|
2310
|
+
}
|
|
2311
|
+
const isBt = Boolean(rels?.belongsTo?.[relationKey]);
|
|
2312
|
+
if (isBt) {
|
|
2313
|
+
const fkInParent = `${singularize(childTable)}_id`;
|
|
2314
|
+
const childPk = meta.primaryKeys[childTable] ?? "id";
|
|
2315
|
+
built = bunSql2`${built} LEFT JOIN ${bunSql2(childTable)} ON ${bunSql2(`${fromTable}.${fkInParent}`)} = ${bunSql2(`${childTable}.${childPk}`)}`;
|
|
2316
|
+
joinedTables.add(childTable);
|
|
2317
|
+
return childTable;
|
|
2318
|
+
}
|
|
2319
|
+
const fkInChild = `${singularize(fromTable)}_id`;
|
|
2320
|
+
const pk = meta.primaryKeys[fromTable] ?? "id";
|
|
2321
|
+
built = bunSql2`${built} LEFT JOIN ${bunSql2(childTable)} ON ${bunSql2(`${childTable}.${fkInChild}`)} = ${bunSql2(`${fromTable}.${pk}`)}`;
|
|
2322
|
+
joinedTables.add(childTable);
|
|
2323
|
+
return childTable;
|
|
2324
|
+
};
|
|
2325
|
+
for (const rel of relations) {
|
|
2326
|
+
const parts = rel.split(".");
|
|
2327
|
+
let from = parentTable;
|
|
2328
|
+
for (const part of parts) {
|
|
2329
|
+
const next = addJoin(from, part) || from;
|
|
2330
|
+
from = next;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
return this;
|
|
2334
|
+
},
|
|
2335
|
+
where(expr, op, value) {
|
|
2336
|
+
if (typeof expr === "string" && op !== undefined) {
|
|
2337
|
+
built = applyWhere({}, built, [expr, op, value]);
|
|
2338
|
+
try {
|
|
2339
|
+
addWhereText("WHERE", `${String(expr)} ${String(op).toUpperCase()} ?`);
|
|
2340
|
+
} catch {}
|
|
2341
|
+
return this;
|
|
2342
|
+
}
|
|
2343
|
+
built = applyWhere({}, built, expr);
|
|
2344
|
+
try {
|
|
2345
|
+
if (Array.isArray(expr)) {
|
|
2346
|
+
const [col, op2] = expr;
|
|
2347
|
+
const opText = String(op2).toUpperCase().replace("NOT IN", "NOT IN").replace("IN", "IN");
|
|
2348
|
+
addWhereText("WHERE", `${String(col)} ${opText} ?`);
|
|
2349
|
+
} else if (expr && typeof expr === "object" && !("raw" in expr)) {
|
|
2350
|
+
const parts = [];
|
|
2351
|
+
for (const k of Object.keys(expr)) {
|
|
2352
|
+
const v = expr[k];
|
|
2353
|
+
parts.push(Array.isArray(v) ? `${k} IN (?)` : `${k} = ?`);
|
|
2354
|
+
}
|
|
2355
|
+
if (parts.length)
|
|
2356
|
+
addWhereText("WHERE", parts.join(" AND "));
|
|
2357
|
+
} else if (expr && typeof expr.raw !== "undefined") {
|
|
2358
|
+
addWhereText("WHERE", "[raw]");
|
|
2359
|
+
}
|
|
2360
|
+
} catch {}
|
|
2361
|
+
return this;
|
|
2362
|
+
},
|
|
2363
|
+
whereNull(column) {
|
|
2364
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(column))} IS NULL`;
|
|
2365
|
+
return this;
|
|
2366
|
+
},
|
|
2367
|
+
whereNotNull(column) {
|
|
2368
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(column))} IS NOT NULL`;
|
|
2369
|
+
return this;
|
|
2370
|
+
},
|
|
2371
|
+
whereBetween(column, start, end) {
|
|
2372
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(column))} BETWEEN ${start} AND ${end}`;
|
|
2373
|
+
return this;
|
|
2374
|
+
},
|
|
2375
|
+
whereExists(subquery) {
|
|
2376
|
+
built = bunSql2`${built} WHERE EXISTS (${subquery.toSQL()})`;
|
|
2377
|
+
return this;
|
|
2378
|
+
},
|
|
2379
|
+
whereJsonContains(column, json) {
|
|
2380
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(column))} @> ${bunSql2(JSON.stringify(json))}`;
|
|
2381
|
+
addWhereText("WHERE", `${String(column)} @> ?`);
|
|
2382
|
+
return this;
|
|
2383
|
+
},
|
|
2384
|
+
whereJsonPath(path, op, value) {
|
|
2385
|
+
const dialect = config2.dialect;
|
|
2386
|
+
if (dialect === "postgres") {
|
|
2387
|
+
built = bunSql2`${built} WHERE ${bunSql2(path)} ${op} ${value}`;
|
|
2388
|
+
} else if (dialect === "mysql") {
|
|
2389
|
+
built = bunSql2`${built} WHERE JSON_EXTRACT(${bunSql2(path)}) ${op} ${value}`;
|
|
2390
|
+
} else {
|
|
2391
|
+
built = bunSql2`${built} WHERE json_extract(${bunSql2(path)}) ${op} ${value}`;
|
|
2392
|
+
}
|
|
2393
|
+
return this;
|
|
2394
|
+
},
|
|
2395
|
+
whereLike(column, pattern, caseSensitive = false) {
|
|
2396
|
+
const expr = caseSensitive ? bunSql2`${bunSql2(String(column))} LIKE ${pattern}` : bunSql2`LOWER(${bunSql2(String(column))}) LIKE LOWER(${pattern})`;
|
|
2397
|
+
built = bunSql2`${built} WHERE ${expr}`;
|
|
2398
|
+
addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? "?" : "LOWER(?)"}`);
|
|
2399
|
+
return this;
|
|
2400
|
+
},
|
|
2401
|
+
whereILike(column, pattern) {
|
|
2402
|
+
if (config2.dialect === "postgres") {
|
|
2403
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(column))} ILIKE ${pattern}`;
|
|
2404
|
+
addWhereText("WHERE", `${String(column)} ILIKE ?`);
|
|
2405
|
+
} else {
|
|
2406
|
+
const expr = bunSql2`LOWER(${bunSql2(String(column))}) LIKE LOWER(${pattern})`;
|
|
2407
|
+
built = bunSql2`${built} WHERE ${expr}`;
|
|
2408
|
+
addWhereText("WHERE", `LOWER(${String(column)}) LIKE LOWER(?)`);
|
|
2409
|
+
}
|
|
2410
|
+
return this;
|
|
2411
|
+
},
|
|
2412
|
+
orWhereLike(column, pattern, caseSensitive = false) {
|
|
2413
|
+
const expr = caseSensitive ? bunSql2`${bunSql2(String(column))} LIKE ${pattern}` : bunSql2`LOWER(${bunSql2(String(column))}) LIKE LOWER(${pattern})`;
|
|
2414
|
+
built = bunSql2`${built} OR ${expr}`;
|
|
2415
|
+
addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} LIKE ${caseSensitive ? "?" : "LOWER(?)"}`);
|
|
2416
|
+
return this;
|
|
2417
|
+
},
|
|
2418
|
+
orWhereILike(column, pattern) {
|
|
2419
|
+
if (config2.dialect === "postgres") {
|
|
2420
|
+
built = bunSql2`${built} OR ${bunSql2(String(column))} ILIKE ${pattern}`;
|
|
2421
|
+
addWhereText("OR", `${String(column)} ILIKE ?`);
|
|
2422
|
+
} else {
|
|
2423
|
+
const expr = bunSql2`LOWER(${bunSql2(String(column))}) LIKE LOWER(${pattern})`;
|
|
2424
|
+
built = bunSql2`${built} OR ${expr}`;
|
|
2425
|
+
addWhereText("OR", `LOWER(${String(column)}) LIKE LOWER(?)`);
|
|
2426
|
+
}
|
|
2427
|
+
return this;
|
|
2428
|
+
},
|
|
2429
|
+
whereNotLike(column, pattern, caseSensitive = false) {
|
|
2430
|
+
const expr = caseSensitive ? bunSql2`${bunSql2(String(column))} NOT LIKE ${pattern}` : bunSql2`LOWER(${bunSql2(String(column))}) NOT LIKE LOWER(${pattern})`;
|
|
2431
|
+
built = bunSql2`${built} WHERE ${expr}`;
|
|
2432
|
+
addWhereText("WHERE", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? "?" : "LOWER(?)"}`);
|
|
2433
|
+
return this;
|
|
2434
|
+
},
|
|
2435
|
+
whereNotILike(column, pattern) {
|
|
2436
|
+
if (config2.dialect === "postgres") {
|
|
2437
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(column))} NOT ILIKE ${pattern}`;
|
|
2438
|
+
addWhereText("WHERE", `${String(column)} NOT ILIKE ?`);
|
|
2439
|
+
} else {
|
|
2440
|
+
const expr = bunSql2`LOWER(${bunSql2(String(column))}) NOT LIKE LOWER(${pattern})`;
|
|
2441
|
+
built = bunSql2`${built} WHERE ${expr}`;
|
|
2442
|
+
addWhereText("WHERE", `LOWER(${String(column)}) NOT LIKE LOWER(?)`);
|
|
2443
|
+
}
|
|
2444
|
+
return this;
|
|
2445
|
+
},
|
|
2446
|
+
orWhereNotLike(column, pattern, caseSensitive = false) {
|
|
2447
|
+
const expr = caseSensitive ? bunSql2`${bunSql2(String(column))} NOT LIKE ${pattern}` : bunSql2`LOWER(${bunSql2(String(column))}) NOT LIKE LOWER(${pattern})`;
|
|
2448
|
+
built = bunSql2`${built} OR ${expr}`;
|
|
2449
|
+
addWhereText("OR", `${caseSensitive ? String(column) : `LOWER(${String(column)})`} NOT LIKE ${caseSensitive ? "?" : "LOWER(?)"}`);
|
|
2450
|
+
return this;
|
|
2451
|
+
},
|
|
2452
|
+
orWhereNotILike(column, pattern) {
|
|
2453
|
+
if (config2.dialect === "postgres") {
|
|
2454
|
+
built = bunSql2`${built} OR ${bunSql2(String(column))} NOT ILIKE ${pattern}`;
|
|
2455
|
+
addWhereText("OR", `${String(column)} NOT ILIKE ?`);
|
|
2456
|
+
} else {
|
|
2457
|
+
const expr = bunSql2`LOWER(${bunSql2(String(column))}) NOT LIKE LOWER(${pattern})`;
|
|
2458
|
+
built = bunSql2`${built} OR ${expr}`;
|
|
2459
|
+
addWhereText("OR", `LOWER(${String(column)}) NOT LIKE LOWER(?)`);
|
|
2460
|
+
}
|
|
2461
|
+
return this;
|
|
2462
|
+
},
|
|
2463
|
+
whereAny(cols, op, value) {
|
|
2464
|
+
if (cols.length === 0)
|
|
2465
|
+
return this;
|
|
2466
|
+
const parts = cols.map((c) => bunSql2`${bunSql2(String(c))} ${op} ${value}`);
|
|
2467
|
+
const expr = parts.reduce((acc, p, i) => i === 0 ? p : bunSql2`${acc} OR ${p}`);
|
|
2468
|
+
built = bunSql2`${built} WHERE (${expr})`;
|
|
2469
|
+
return this;
|
|
2470
|
+
},
|
|
2471
|
+
whereAll(cols, op, value) {
|
|
2472
|
+
if (cols.length === 0)
|
|
2473
|
+
return this;
|
|
2474
|
+
const parts = cols.map((c) => bunSql2`${bunSql2(String(c))} ${op} ${value}`);
|
|
2475
|
+
const expr = parts.reduce((acc, p, i) => i === 0 ? p : bunSql2`${acc} AND ${p}`);
|
|
2476
|
+
built = bunSql2`${built} WHERE (${expr})`;
|
|
2477
|
+
return this;
|
|
2478
|
+
},
|
|
2479
|
+
whereNone(cols, op, value) {
|
|
2480
|
+
if (cols.length === 0)
|
|
2481
|
+
return this;
|
|
2482
|
+
const parts = cols.map((c) => bunSql2`${bunSql2(String(c))} ${op} ${value}`);
|
|
2483
|
+
const expr = parts.reduce((acc, p, i) => i === 0 ? p : bunSql2`${acc} OR ${p}`);
|
|
2484
|
+
built = bunSql2`${built} WHERE NOT (${expr})`;
|
|
2485
|
+
return this;
|
|
2486
|
+
},
|
|
2487
|
+
whereNotBetween(column, start, end) {
|
|
2488
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(column))} NOT BETWEEN ${start} AND ${end}`;
|
|
2489
|
+
return this;
|
|
2490
|
+
},
|
|
2491
|
+
whereDate(column, op, date) {
|
|
2492
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(column))} ${op} ${bunSql2(String(date))}`;
|
|
2493
|
+
return this;
|
|
2494
|
+
},
|
|
2495
|
+
whereRaw(fragment) {
|
|
2496
|
+
built = bunSql2`${built} WHERE ${fragment}`;
|
|
2497
|
+
return this;
|
|
2498
|
+
},
|
|
2499
|
+
whereColumn(left, op, right) {
|
|
2500
|
+
built = bunSql2`${built} WHERE ${bunSql2(left)} ${op} ${bunSql2(right)}`;
|
|
2501
|
+
return this;
|
|
2502
|
+
},
|
|
2503
|
+
orWhereColumn(left, op, right) {
|
|
2504
|
+
built = bunSql2`${built} OR ${bunSql2(left)} ${op} ${bunSql2(right)}`;
|
|
2505
|
+
return this;
|
|
2506
|
+
},
|
|
2507
|
+
whereIn(column, values) {
|
|
2508
|
+
const v = Array.isArray(values) ? bunSql2(values) : bunSql2`(${values.toSQL()})`;
|
|
2509
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(column))} IN ${v}`;
|
|
2510
|
+
return this;
|
|
2511
|
+
},
|
|
2512
|
+
orWhereIn(column, values) {
|
|
2513
|
+
const v = Array.isArray(values) ? bunSql2(values) : bunSql2`(${values.toSQL()})`;
|
|
2514
|
+
built = bunSql2`${built} OR ${bunSql2(String(column))} IN ${v}`;
|
|
2515
|
+
return this;
|
|
2516
|
+
},
|
|
2517
|
+
whereNotIn(column, values) {
|
|
2518
|
+
const v = Array.isArray(values) ? bunSql2(values) : bunSql2`(${values.toSQL()})`;
|
|
2519
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(column))} NOT IN ${v}`;
|
|
2520
|
+
return this;
|
|
2521
|
+
},
|
|
2522
|
+
orWhereNotIn(column, values) {
|
|
2523
|
+
const v = Array.isArray(values) ? bunSql2(values) : bunSql2`(${values.toSQL()})`;
|
|
2524
|
+
built = bunSql2`${built} OR ${bunSql2(String(column))} NOT IN ${v}`;
|
|
2525
|
+
return this;
|
|
2526
|
+
},
|
|
2527
|
+
whereNested(fragment) {
|
|
2528
|
+
built = bunSql2`${built} WHERE (${fragment.toSQL ? fragment.toSQL() : fragment})`;
|
|
2529
|
+
return this;
|
|
2530
|
+
},
|
|
2531
|
+
orWhereNested(fragment) {
|
|
2532
|
+
built = bunSql2`${built} OR (${fragment.toSQL ? fragment.toSQL() : fragment})`;
|
|
2533
|
+
return this;
|
|
2534
|
+
},
|
|
2535
|
+
andWhere(expr, op, value) {
|
|
2536
|
+
if (typeof expr === "string" && op !== undefined) {
|
|
2537
|
+
built = bunSql2`${built} AND ${applyWhere({}, bunSql2``, [expr, op, value])}`;
|
|
2538
|
+
try {
|
|
2539
|
+
addWhereText("AND", `${String(expr)} ${String(op).toUpperCase()} ?`);
|
|
2540
|
+
} catch {}
|
|
2541
|
+
return this;
|
|
2542
|
+
}
|
|
2543
|
+
built = bunSql2`${built} AND ${applyWhere({}, bunSql2``, expr)}`;
|
|
2544
|
+
try {
|
|
2545
|
+
if (Array.isArray(expr)) {
|
|
2546
|
+
const [col, op2] = expr;
|
|
2547
|
+
addWhereText("AND", `${String(col)} ${String(op2).toUpperCase()} ?`);
|
|
2548
|
+
} else if (expr && typeof expr === "object" && !("raw" in expr)) {
|
|
2549
|
+
const parts = [];
|
|
2550
|
+
for (const k of Object.keys(expr)) {
|
|
2551
|
+
const v = expr[k];
|
|
2552
|
+
parts.push(Array.isArray(v) ? `${k} IN (?)` : `${k} = ?`);
|
|
2553
|
+
}
|
|
2554
|
+
if (parts.length)
|
|
2555
|
+
addWhereText("AND", parts.join(" AND "));
|
|
2556
|
+
} else if (expr && typeof expr.raw !== "undefined") {
|
|
2557
|
+
addWhereText("AND", "[raw]");
|
|
2558
|
+
}
|
|
2559
|
+
} catch {}
|
|
2560
|
+
return this;
|
|
2561
|
+
},
|
|
2562
|
+
orWhere(expr, op, value) {
|
|
2563
|
+
if (typeof expr === "string" && op !== undefined) {
|
|
2564
|
+
built = bunSql2`${built} OR ${applyWhere({}, bunSql2``, [expr, op, value])}`;
|
|
2565
|
+
try {
|
|
2566
|
+
addWhereText("OR", `${String(expr)} ${String(op).toUpperCase()} ?`);
|
|
2567
|
+
} catch {}
|
|
2568
|
+
return this;
|
|
2569
|
+
}
|
|
2570
|
+
built = bunSql2`${built} OR ${applyWhere({}, bunSql2``, expr)}`;
|
|
2571
|
+
try {
|
|
2572
|
+
if (Array.isArray(expr)) {
|
|
2573
|
+
const [col, op2] = expr;
|
|
2574
|
+
addWhereText("OR", `${String(col)} ${String(op2).toUpperCase()} ?`);
|
|
2575
|
+
} else if (expr && typeof expr === "object" && !("raw" in expr)) {
|
|
2576
|
+
const parts = [];
|
|
2577
|
+
for (const k of Object.keys(expr)) {
|
|
2578
|
+
const v = expr[k];
|
|
2579
|
+
parts.push(Array.isArray(v) ? `${k} IN (?)` : `${k} = ?`);
|
|
2580
|
+
}
|
|
2581
|
+
if (parts.length)
|
|
2582
|
+
addWhereText("OR", parts.join(" AND "));
|
|
2583
|
+
} else if (expr && typeof expr.raw !== "undefined") {
|
|
2584
|
+
addWhereText("OR", "[raw]");
|
|
2585
|
+
}
|
|
2586
|
+
} catch {}
|
|
2587
|
+
return this;
|
|
2588
|
+
},
|
|
2589
|
+
orderBy(column, direction = "asc") {
|
|
2590
|
+
built = bunSql2`${built} ORDER BY ${bunSql2(String(column))} ${direction === "asc" ? bunSql2`ASC` : bunSql2`DESC`}`;
|
|
2591
|
+
return this;
|
|
2592
|
+
},
|
|
2593
|
+
orderByDesc(column) {
|
|
2594
|
+
built = bunSql2`${built} ORDER BY ${bunSql2(String(column))} DESC`;
|
|
2595
|
+
return this;
|
|
2596
|
+
},
|
|
2597
|
+
inRandomOrder() {
|
|
2598
|
+
const rnd = config2.sql.randomFunction === "RAND()" ? bunSql2`RAND()` : bunSql2`RANDOM()`;
|
|
2599
|
+
built = bunSql2`${built} ORDER BY ${rnd}`;
|
|
2600
|
+
return this;
|
|
2601
|
+
},
|
|
2602
|
+
reorder(column, direction = "asc") {
|
|
2603
|
+
built = bunSql2`${bunSql2(String(built).replace(/ORDER BY[\s\S]*$/i, ""))} ORDER BY ${bunSql2(column)} ${direction === "asc" ? bunSql2`ASC` : bunSql2`DESC`}`;
|
|
2604
|
+
return this;
|
|
2605
|
+
},
|
|
2606
|
+
latest(column) {
|
|
2607
|
+
const col = column ?? config2.timestamps.defaultOrderColumn;
|
|
2608
|
+
built = bunSql2`${built} ORDER BY ${bunSql2(String(col))} DESC`;
|
|
2609
|
+
return this;
|
|
2610
|
+
},
|
|
2611
|
+
oldest(column) {
|
|
2612
|
+
const col = column ?? config2.timestamps.defaultOrderColumn;
|
|
2613
|
+
built = bunSql2`${built} ORDER BY ${bunSql2(String(col))} ASC`;
|
|
2614
|
+
return this;
|
|
2615
|
+
},
|
|
2616
|
+
limit(n) {
|
|
2617
|
+
built = bunSql2`${built} LIMIT ${n}`;
|
|
2618
|
+
return this;
|
|
2619
|
+
},
|
|
2620
|
+
offset(n) {
|
|
2621
|
+
built = bunSql2`${built} OFFSET ${n}`;
|
|
2622
|
+
return this;
|
|
2623
|
+
},
|
|
2624
|
+
join(table2, onLeft, operator, onRight) {
|
|
2625
|
+
built = bunSql2`${built} JOIN ${bunSql2(String(table2))} ON ${bunSql2(String(onLeft))} ${operator} ${bunSql2(String(onRight))}`;
|
|
2626
|
+
joinedTables.add(String(table2));
|
|
2627
|
+
return this;
|
|
2628
|
+
},
|
|
2629
|
+
joinSub(sub, alias, onLeft, operator, onRight) {
|
|
2630
|
+
built = bunSql2`${built} JOIN (${sub.toSQL()}) AS ${bunSql2(alias)} ON ${bunSql2(onLeft)} ${operator} ${bunSql2(onRight)}`;
|
|
2631
|
+
joinedTables.add(alias);
|
|
2632
|
+
return this;
|
|
2633
|
+
},
|
|
2634
|
+
innerJoin(table2, onLeft, operator, onRight) {
|
|
2635
|
+
built = bunSql2`${built} INNER JOIN ${bunSql2(String(table2))} ON ${bunSql2(String(onLeft))} ${operator} ${bunSql2(String(onRight))}`;
|
|
2636
|
+
joinedTables.add(String(table2));
|
|
2637
|
+
return this;
|
|
2638
|
+
},
|
|
2639
|
+
leftJoin(table2, onLeft, operator, onRight) {
|
|
2640
|
+
built = bunSql2`${built} LEFT JOIN ${bunSql2(String(table2))} ON ${bunSql2(String(onLeft))} ${operator} ${bunSql2(String(onRight))}`;
|
|
2641
|
+
joinedTables.add(String(table2));
|
|
2642
|
+
return this;
|
|
2643
|
+
},
|
|
2644
|
+
leftJoinSub(sub, alias, onLeft, operator, onRight) {
|
|
2645
|
+
built = bunSql2`${built} LEFT JOIN (${sub.toSQL()}) AS ${bunSql2(alias)} ON ${bunSql2(onLeft)} ${operator} ${bunSql2(onRight)}`;
|
|
2646
|
+
joinedTables.add(alias);
|
|
2647
|
+
return this;
|
|
2648
|
+
},
|
|
2649
|
+
rightJoin(table2, onLeft, operator, onRight) {
|
|
2650
|
+
built = bunSql2`${built} RIGHT JOIN ${bunSql2(String(table2))} ON ${bunSql2(String(onLeft))} ${operator} ${bunSql2(String(onRight))}`;
|
|
2651
|
+
joinedTables.add(String(table2));
|
|
2652
|
+
return this;
|
|
2653
|
+
},
|
|
2654
|
+
crossJoin(table2) {
|
|
2655
|
+
built = bunSql2`${built} CROSS JOIN ${bunSql2(String(table2))}`;
|
|
2656
|
+
joinedTables.add(String(table2));
|
|
2657
|
+
return this;
|
|
2658
|
+
},
|
|
2659
|
+
crossJoinSub(sub, alias) {
|
|
2660
|
+
built = bunSql2`${built} CROSS JOIN (${sub.toSQL()}) AS ${bunSql2(alias)}`;
|
|
2661
|
+
joinedTables.add(alias);
|
|
2662
|
+
return this;
|
|
2663
|
+
},
|
|
2664
|
+
selectAllRelations() {
|
|
2665
|
+
if (!schema)
|
|
2666
|
+
return this;
|
|
2667
|
+
const parent = String(table);
|
|
2668
|
+
const parentCols = Object.keys(schema[parent]?.columns ?? {});
|
|
2669
|
+
const parts = [];
|
|
2670
|
+
if (parentCols.length > 0)
|
|
2671
|
+
parts.push(bunSql2`${bunSql2(parent)}.*`);
|
|
2672
|
+
for (const jt of joinedTables) {
|
|
2673
|
+
const cols = Object.keys(schema[jt]?.columns ?? {});
|
|
2674
|
+
for (const c of cols) {
|
|
2675
|
+
const alias = config2.aliasing.relationColumnAliasFormat === "camelCase" ? `${jt}_${c}`.replace(/_([a-z])/g, (_, ch) => ch.toUpperCase()) : config2.aliasing.relationColumnAliasFormat === "table.dot.column" ? `${jt}.${c}` : `${jt}_${c}`;
|
|
2676
|
+
parts.push(bunSql2`${bunSql2(`${jt}.${c}`)} AS ${bunSql2(alias)}`);
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
if (parts.length > 0) {
|
|
2680
|
+
built = bunSql2`SELECT ${bunSql2(parts)} FROM ${bunSql2(parent)} ${bunSql2``}`;
|
|
2681
|
+
}
|
|
2682
|
+
return this;
|
|
2683
|
+
},
|
|
2684
|
+
groupBy(...cols) {
|
|
2685
|
+
if (cols.length > 0)
|
|
2686
|
+
built = bunSql2`${built} GROUP BY ${bunSql2(cols)}`;
|
|
2687
|
+
return this;
|
|
2688
|
+
},
|
|
2689
|
+
groupByRaw(fragment) {
|
|
2690
|
+
built = bunSql2`${built} GROUP BY ${fragment}`;
|
|
2691
|
+
return this;
|
|
2692
|
+
},
|
|
2693
|
+
having(expr) {
|
|
2694
|
+
built = bunSql2`${built} HAVING ${applyWhere({}, bunSql2``, expr)}`;
|
|
2695
|
+
return this;
|
|
2696
|
+
},
|
|
2697
|
+
havingRaw(fragment) {
|
|
2698
|
+
built = bunSql2`${built} HAVING ${fragment}`;
|
|
2699
|
+
return this;
|
|
2700
|
+
},
|
|
2701
|
+
orderByRaw(fragment) {
|
|
2702
|
+
built = bunSql2`${built} ORDER BY ${fragment}`;
|
|
2703
|
+
return this;
|
|
2704
|
+
},
|
|
2705
|
+
union(other) {
|
|
2706
|
+
built = bunSql2`${built} UNION ${other.toSQL()}`;
|
|
2707
|
+
return this;
|
|
2708
|
+
},
|
|
2709
|
+
unionAll(other) {
|
|
2710
|
+
built = bunSql2`${built} UNION ALL ${other.toSQL()}`;
|
|
2711
|
+
return this;
|
|
2712
|
+
},
|
|
2713
|
+
forPage(page, perPage) {
|
|
2714
|
+
const p = Math.max(1, Math.floor(page));
|
|
2715
|
+
const pp = Math.max(1, Math.floor(perPage));
|
|
2716
|
+
built = bunSql2`${built} LIMIT ${pp} OFFSET ${(p - 1) * pp}`;
|
|
2717
|
+
return this;
|
|
2718
|
+
},
|
|
2719
|
+
toSQL() {
|
|
2720
|
+
return makeExecutableQuery(built, text);
|
|
2721
|
+
},
|
|
2722
|
+
async value(column) {
|
|
2723
|
+
const q = bunSql2`${built} LIMIT 1`;
|
|
2724
|
+
const rows = await runWithHooks(q, "select", { signal: abortSignal, timeoutMs });
|
|
2725
|
+
const [row] = rows;
|
|
2726
|
+
return row?.[column];
|
|
2727
|
+
},
|
|
2728
|
+
async pluck(column, key) {
|
|
2729
|
+
const rows = await runWithHooks(built, "select", { signal: abortSignal, timeoutMs });
|
|
2730
|
+
if (key) {
|
|
2731
|
+
const out = {};
|
|
2732
|
+
for (const r of rows)
|
|
2733
|
+
out[String(r?.[key])] = r?.[column];
|
|
2734
|
+
return out;
|
|
2735
|
+
}
|
|
2736
|
+
return rows.map((r) => r?.[column]);
|
|
2737
|
+
},
|
|
2738
|
+
async exists() {
|
|
2739
|
+
const q = bunSql2`SELECT EXISTS (${built}) as e`;
|
|
2740
|
+
const rows = await runWithHooks(q, "select", { signal: abortSignal, timeoutMs });
|
|
2741
|
+
const [row] = rows;
|
|
2742
|
+
return Boolean(row?.e);
|
|
2743
|
+
},
|
|
2744
|
+
async doesntExist() {
|
|
2745
|
+
const e = await this.exists();
|
|
2746
|
+
return !e;
|
|
2747
|
+
},
|
|
2748
|
+
async paginate(perPage, page = 1) {
|
|
2749
|
+
const countQ = bunSql2`SELECT COUNT(*) as c FROM (${built}) as sub`;
|
|
2750
|
+
const cRows = await runWithHooks(countQ, "select", { signal: abortSignal, timeoutMs });
|
|
2751
|
+
const [cRow] = cRows;
|
|
2752
|
+
const total = Number(cRow?.c ?? 0);
|
|
2753
|
+
const lastPage = Math.max(1, Math.ceil(total / perPage));
|
|
2754
|
+
const p = Math.max(1, Math.min(page, lastPage));
|
|
2755
|
+
const offset = (p - 1) * perPage;
|
|
2756
|
+
const data = await runWithHooks(bunSql2`${built} LIMIT ${perPage} OFFSET ${offset}`, "select", { signal: abortSignal, timeoutMs });
|
|
2757
|
+
return { data, meta: { perPage, page: p, total, lastPage } };
|
|
2758
|
+
},
|
|
2759
|
+
async simplePaginate(perPage, page = 1) {
|
|
2760
|
+
const p = Math.max(1, page);
|
|
2761
|
+
const offset = (p - 1) * perPage;
|
|
2762
|
+
const data = await runWithHooks(bunSql2`${built} LIMIT ${perPage + 1} OFFSET ${offset}`, "select", { signal: abortSignal, timeoutMs });
|
|
2763
|
+
const hasMore = data.length > perPage;
|
|
2764
|
+
return { data: hasMore ? data.slice(0, perPage) : data, meta: { perPage, page: p, hasMore } };
|
|
2765
|
+
},
|
|
2766
|
+
async cursorPaginate(perPage, cursor, column = "id", direction = "asc") {
|
|
2767
|
+
let q = built;
|
|
2768
|
+
if (cursor !== undefined && cursor !== null) {
|
|
2769
|
+
if (Array.isArray(column)) {
|
|
2770
|
+
const cols = column.map((c) => bunSql2(String(c)));
|
|
2771
|
+
const comp = direction === "asc" ? bunSql2`>` : bunSql2`<`;
|
|
2772
|
+
const tupleCols = bunSql2`(${bunSql2(cols)})`;
|
|
2773
|
+
const tupleVals = bunSql2`(${bunSql2(cursor)})`;
|
|
2774
|
+
q = bunSql2`${q} WHERE ${tupleCols} ${comp} ${tupleVals}`;
|
|
2775
|
+
} else {
|
|
2776
|
+
q = direction === "asc" ? bunSql2`${q} WHERE ${bunSql2(String(column))} > ${cursor}` : bunSql2`${q} WHERE ${bunSql2(String(column))} < ${cursor}`;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
if (Array.isArray(column)) {
|
|
2780
|
+
const orderParts = column.map((c) => bunSql2`${bunSql2(String(c))} ${direction === "asc" ? bunSql2`ASC` : bunSql2`DESC`}`);
|
|
2781
|
+
const orderExpr = orderParts.reduce((acc, p, i) => i === 0 ? p : bunSql2`${acc}, ${p}`);
|
|
2782
|
+
q = bunSql2`${q} ORDER BY ${orderExpr} LIMIT ${perPage + 1}`;
|
|
2783
|
+
} else {
|
|
2784
|
+
q = bunSql2`${q} ORDER BY ${bunSql2(String(column))} ${direction === "asc" ? bunSql2`ASC` : bunSql2`DESC`} LIMIT ${perPage + 1}`;
|
|
2785
|
+
}
|
|
2786
|
+
const rows = await runWithHooks(q, "select", { signal: abortSignal, timeoutMs });
|
|
2787
|
+
const next = rows.length > perPage ? Array.isArray(column) ? column.map((c) => rows[perPage]?.[c]) : rows[perPage]?.[column] : null;
|
|
2788
|
+
const data = rows.slice(0, perPage);
|
|
2789
|
+
const prevCursor = data.length ? Array.isArray(column) ? column.map((c) => data[0]?.[c]) : data[0]?.[column] : null;
|
|
2790
|
+
return { data, meta: { perPage, nextCursor: next ?? null, prevCursor } };
|
|
2791
|
+
},
|
|
2792
|
+
async chunk(size, handler) {
|
|
2793
|
+
let page = 1;
|
|
2794
|
+
while (true) {
|
|
2795
|
+
const { data } = await this.paginate(size, page);
|
|
2796
|
+
if (data.length === 0)
|
|
2797
|
+
break;
|
|
2798
|
+
await handler(data);
|
|
2799
|
+
if (data.length < size)
|
|
2800
|
+
break;
|
|
2801
|
+
page += 1;
|
|
2802
|
+
}
|
|
2803
|
+
},
|
|
2804
|
+
async chunkById(size, column = "id", handler) {
|
|
2805
|
+
let cursor;
|
|
2806
|
+
while (true) {
|
|
2807
|
+
const { data, meta: meta2 } = await this.cursorPaginate(size, cursor, column, "asc");
|
|
2808
|
+
if (data.length === 0)
|
|
2809
|
+
break;
|
|
2810
|
+
if (handler)
|
|
2811
|
+
await handler(data);
|
|
2812
|
+
cursor = meta2.nextCursor;
|
|
2813
|
+
if (!cursor)
|
|
2814
|
+
break;
|
|
2815
|
+
}
|
|
2816
|
+
},
|
|
2817
|
+
async eachById(size, column = "id", handler) {
|
|
2818
|
+
await this.chunkById(size, column, async (rows) => {
|
|
2819
|
+
for (const r of rows)
|
|
2820
|
+
await handler?.(r);
|
|
2821
|
+
});
|
|
2822
|
+
},
|
|
2823
|
+
withTimeout(ms) {
|
|
2824
|
+
timeoutMs = Math.max(1, Math.floor(ms));
|
|
2825
|
+
return this;
|
|
2826
|
+
},
|
|
2827
|
+
abort(signal) {
|
|
2828
|
+
abortSignal = signal;
|
|
2829
|
+
return this;
|
|
2830
|
+
},
|
|
2831
|
+
withTrashed() {
|
|
2832
|
+
includeTrashed = true;
|
|
2833
|
+
onlyTrashed = false;
|
|
2834
|
+
return this;
|
|
2835
|
+
},
|
|
2836
|
+
onlyTrashed() {
|
|
2837
|
+
includeTrashed = true;
|
|
2838
|
+
onlyTrashed = true;
|
|
2839
|
+
return this;
|
|
2840
|
+
},
|
|
2841
|
+
scope(name, value) {
|
|
2842
|
+
const tbl = String(table);
|
|
2843
|
+
const scopeMap = meta?.scopes?.[tbl];
|
|
2844
|
+
const fn = scopeMap?.[name];
|
|
2845
|
+
if (fn)
|
|
2846
|
+
return fn(this, value);
|
|
2847
|
+
return this;
|
|
2848
|
+
},
|
|
2849
|
+
when(condition, then, otherwise) {
|
|
2850
|
+
if (condition)
|
|
2851
|
+
return then(this);
|
|
2852
|
+
if (otherwise)
|
|
2853
|
+
return otherwise(this);
|
|
2854
|
+
return this;
|
|
2855
|
+
},
|
|
2856
|
+
tap(fn) {
|
|
2857
|
+
fn(this);
|
|
2858
|
+
return this;
|
|
2859
|
+
},
|
|
2860
|
+
dump() {
|
|
2861
|
+
console.log(String(built));
|
|
2862
|
+
return this;
|
|
2863
|
+
},
|
|
2864
|
+
dd() {
|
|
2865
|
+
console.log(String(built));
|
|
2866
|
+
throw new Error("Dump and Die");
|
|
2867
|
+
},
|
|
2868
|
+
async explain() {
|
|
2869
|
+
const q = bunSql2`EXPLAIN ${built}`;
|
|
2870
|
+
return await runWithHooks(q, "select", { signal: abortSignal, timeoutMs });
|
|
2871
|
+
},
|
|
2872
|
+
simple() {
|
|
2873
|
+
return built.simple();
|
|
2874
|
+
},
|
|
2875
|
+
toText() {
|
|
2876
|
+
return text;
|
|
2877
|
+
},
|
|
2878
|
+
async get() {
|
|
2879
|
+
if (config2.softDeletes?.enabled && config2.softDeletes.defaultFilter && !includeTrashed) {
|
|
2880
|
+
const col = config2.softDeletes.column;
|
|
2881
|
+
const tbl = String(table);
|
|
2882
|
+
const hasCol = schema ? Boolean(schema[tbl]?.columns?.[col]) : true;
|
|
2883
|
+
if (hasCol && !/\bdeleted_at\b/i.test(text)) {
|
|
2884
|
+
built = bunSql2`${built} WHERE ${bunSql2(String(col))} IS ${onlyTrashed ? bunSql2`NOT NULL` : bunSql2`NULL`}`;
|
|
2885
|
+
addWhereText("WHERE", `${String(col)} IS ${onlyTrashed ? "NOT " : ""}NULL`);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
return await runWithHooks(built, "select", { signal: abortSignal, timeoutMs });
|
|
2889
|
+
},
|
|
2890
|
+
async executeTakeFirst() {
|
|
2891
|
+
const rows = await runWithHooks(built, "select", { signal: abortSignal, timeoutMs });
|
|
2892
|
+
return Array.isArray(rows) ? rows[0] : rows;
|
|
2893
|
+
},
|
|
2894
|
+
async first() {
|
|
2895
|
+
const rows = await runWithHooks(bunSql2`${built} LIMIT 1`, "select", { signal: abortSignal, timeoutMs });
|
|
2896
|
+
const [row] = rows;
|
|
2897
|
+
return row;
|
|
2898
|
+
},
|
|
2899
|
+
async firstOrFail() {
|
|
2900
|
+
const row = await this.first();
|
|
2901
|
+
if (!row)
|
|
2902
|
+
throw new Error("Record not found");
|
|
2903
|
+
return row;
|
|
2904
|
+
},
|
|
2905
|
+
async find(id) {
|
|
2906
|
+
const pk = meta?.primaryKeys[String(table)] ?? "id";
|
|
2907
|
+
const rows = await runWithHooks(bunSql2`${built} WHERE ${bunSql2(pk)} = ${id} LIMIT 1`, "select", { signal: abortSignal, timeoutMs });
|
|
2908
|
+
const [row] = rows;
|
|
2909
|
+
return row;
|
|
2910
|
+
},
|
|
2911
|
+
async findOrFail(id) {
|
|
2912
|
+
const row = await this.find(id);
|
|
2913
|
+
if (!row)
|
|
2914
|
+
throw new Error("Record not found");
|
|
2915
|
+
return row;
|
|
2916
|
+
},
|
|
2917
|
+
async findMany(ids) {
|
|
2918
|
+
const pk = meta?.primaryKeys[String(table)] ?? "id";
|
|
2919
|
+
const rows = await runWithHooks(bunSql2`${built} WHERE ${bunSql2(String(pk))} IN ${bunSql2(ids)}`, "select", { signal: abortSignal, timeoutMs });
|
|
2920
|
+
return rows;
|
|
2921
|
+
},
|
|
2922
|
+
async* lazy() {
|
|
2923
|
+
let cursor;
|
|
2924
|
+
const pk = meta?.primaryKeys[String(table)] ?? "id";
|
|
2925
|
+
while (true) {
|
|
2926
|
+
const q = cursor == null ? bunSql2`${built} ORDER BY ${bunSql2(String(pk))} ASC LIMIT 100` : bunSql2`${built} WHERE ${bunSql2(String(pk))} > ${cursor} ORDER BY ${bunSql2(String(pk))} ASC LIMIT 100`;
|
|
2927
|
+
const rows = await q.execute();
|
|
2928
|
+
if (rows.length === 0)
|
|
2929
|
+
break;
|
|
2930
|
+
for (const r of rows)
|
|
2931
|
+
yield r;
|
|
2932
|
+
cursor = rows[rows.length - 1]?.[pk];
|
|
2933
|
+
if (cursor == null)
|
|
2934
|
+
break;
|
|
2935
|
+
}
|
|
2936
|
+
},
|
|
2937
|
+
async* lazyById() {
|
|
2938
|
+
const pk = meta?.primaryKeys[String(table)] ?? "id";
|
|
2939
|
+
let cursor;
|
|
2940
|
+
while (true) {
|
|
2941
|
+
const q = cursor == null ? bunSql2`${built} ORDER BY ${bunSql2(String(pk))} ASC LIMIT 100` : bunSql2`${built} WHERE ${bunSql2(String(pk))} > ${cursor} ORDER BY ${bunSql2(String(pk))} ASC LIMIT 100`;
|
|
2942
|
+
const rows = await q.execute();
|
|
2943
|
+
if (rows.length === 0)
|
|
2944
|
+
break;
|
|
2945
|
+
for (const r of rows)
|
|
2946
|
+
yield r;
|
|
2947
|
+
cursor = rows[rows.length - 1]?.[pk];
|
|
2948
|
+
if (cursor == null)
|
|
2949
|
+
break;
|
|
2950
|
+
}
|
|
2951
|
+
},
|
|
2952
|
+
pipe(fn) {
|
|
2953
|
+
return fn(this);
|
|
2954
|
+
},
|
|
2955
|
+
async count() {
|
|
2956
|
+
const q = bunSql2`SELECT COUNT(*) as c FROM (${built}) as sub`;
|
|
2957
|
+
const rows = await runWithHooks(q, "select", { signal: abortSignal, timeoutMs });
|
|
2958
|
+
const [row] = rows;
|
|
2959
|
+
return Number(row?.c ?? 0);
|
|
2960
|
+
},
|
|
2961
|
+
lockForUpdate() {
|
|
2962
|
+
built = bunSql2`${built} FOR UPDATE`;
|
|
2963
|
+
return this;
|
|
2964
|
+
},
|
|
2965
|
+
sharedLock() {
|
|
2966
|
+
const syntax = config2.sql.sharedLockSyntax === "LOCK IN SHARE MODE" ? bunSql2`LOCK IN SHARE MODE` : bunSql2`FOR SHARE`;
|
|
2967
|
+
built = bunSql2`${built} ${syntax}`;
|
|
2968
|
+
return this;
|
|
2969
|
+
},
|
|
2970
|
+
withCTE(name, sub) {
|
|
2971
|
+
built = bunSql2`WITH ${bunSql2(name)} AS (${sub.toSQL()}) ${built}`;
|
|
2972
|
+
return this;
|
|
2973
|
+
},
|
|
2974
|
+
withRecursive(name, sub) {
|
|
2975
|
+
built = bunSql2`WITH RECURSIVE ${bunSql2(name)} AS (${sub.toSQL()}) ${built}`;
|
|
2976
|
+
return this;
|
|
2977
|
+
},
|
|
2978
|
+
execute() {
|
|
2979
|
+
return runWithHooks(built, "select", { signal: abortSignal, timeoutMs });
|
|
2980
|
+
},
|
|
2981
|
+
values() {
|
|
2982
|
+
return built.values();
|
|
2983
|
+
},
|
|
2984
|
+
toParams() {
|
|
2985
|
+
return built.values?.() ?? [];
|
|
2986
|
+
},
|
|
2987
|
+
raw() {
|
|
2988
|
+
return built.raw();
|
|
2989
|
+
},
|
|
2990
|
+
get rows() {
|
|
2991
|
+
return;
|
|
2992
|
+
},
|
|
2993
|
+
get row() {
|
|
2994
|
+
return;
|
|
2995
|
+
},
|
|
2996
|
+
cancel() {
|
|
2997
|
+
try {
|
|
2998
|
+
built.cancel();
|
|
2999
|
+
} catch {}
|
|
3000
|
+
}
|
|
3001
|
+
};
|
|
3002
|
+
const proxy = new Proxy(base, {
|
|
3003
|
+
get(target, prop, receiver) {
|
|
3004
|
+
const existing = Reflect.get(target, prop, receiver);
|
|
3005
|
+
if (existing !== undefined)
|
|
3006
|
+
return existing;
|
|
3007
|
+
if (typeof prop === "string" && (prop.startsWith("where") || prop.startsWith("orWhere") || prop.startsWith("andWhere"))) {
|
|
3008
|
+
const isOr = prop.startsWith("orWhere");
|
|
3009
|
+
const isAnd = prop.startsWith("andWhere");
|
|
3010
|
+
const raw = prop.replace(/^or?where/i, "").replace(/^andwhere/i, "");
|
|
3011
|
+
if (!raw)
|
|
3012
|
+
return () => receiver;
|
|
3013
|
+
const lowerFirst = raw.charAt(0).toLowerCase() + raw.slice(1);
|
|
3014
|
+
const toSnake = (s) => s.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
|
|
3015
|
+
const snake = toSnake(raw);
|
|
3016
|
+
const tbl = String(table);
|
|
3017
|
+
const available = schema ? Object.keys(schema[tbl]?.columns ?? {}) : [];
|
|
3018
|
+
const chosen = [snake, lowerFirst, lowerFirst.toLowerCase()].find((n) => available.includes(n)) ?? snake;
|
|
3019
|
+
return (value) => {
|
|
3020
|
+
const expr = Array.isArray(value) ? bunSql2`${bunSql2(String(chosen))} IN ${bunSql2(value)}` : bunSql2`${bunSql2(String(chosen))} = ${value}`;
|
|
3021
|
+
built = isOr ? bunSql2`${built} OR ${expr}` : isAnd ? bunSql2`${built} AND ${expr}` : bunSql2`${built} WHERE ${expr}`;
|
|
3022
|
+
const clause = Array.isArray(value) ? `${String(chosen)} IN (?)` : `${String(chosen)} = ?`;
|
|
3023
|
+
addWhereText(isOr ? "OR" : isAnd ? "AND" : "WHERE", clause);
|
|
3024
|
+
return receiver;
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
3027
|
+
return Reflect.get(target, prop, receiver);
|
|
3028
|
+
}
|
|
3029
|
+
});
|
|
3030
|
+
return proxy;
|
|
3031
|
+
}
|
|
3032
|
+
return {
|
|
3033
|
+
configure(opts) {
|
|
3034
|
+
Object.assign(config2, opts);
|
|
3035
|
+
return this;
|
|
3036
|
+
},
|
|
3037
|
+
id(name) {
|
|
3038
|
+
if (!/^[A-Z_][\w.]*$/i.test(name))
|
|
3039
|
+
throw new Error(`Invalid identifier: ${name}`);
|
|
3040
|
+
return bunSql2(String(name));
|
|
3041
|
+
},
|
|
3042
|
+
ids(...names) {
|
|
3043
|
+
for (const n of names) {
|
|
3044
|
+
if (!/^[A-Z_][\w.]*$/i.test(n))
|
|
3045
|
+
throw new Error(`Invalid identifier: ${n}`);
|
|
3046
|
+
}
|
|
3047
|
+
return bunSql2(names);
|
|
3048
|
+
},
|
|
3049
|
+
select(table, ...columns) {
|
|
3050
|
+
return makeSelect(table, columns);
|
|
3051
|
+
},
|
|
3052
|
+
selectFrom(table) {
|
|
3053
|
+
return makeSelect(table);
|
|
3054
|
+
},
|
|
3055
|
+
selectFromSub(sub, alias) {
|
|
3056
|
+
const q = bunSql2`SELECT * FROM (${sub.toSQL()}) AS ${bunSql2(alias)}`;
|
|
3057
|
+
return {
|
|
3058
|
+
where: (_) => this,
|
|
3059
|
+
andWhere: (_) => this,
|
|
3060
|
+
orWhere: (_) => this,
|
|
3061
|
+
orderBy: () => this,
|
|
3062
|
+
limit: () => this,
|
|
3063
|
+
offset: () => this,
|
|
3064
|
+
toSQL: () => makeExecutableQuery(q),
|
|
3065
|
+
execute: () => runWithHooks(q, "select"),
|
|
3066
|
+
values: () => q.values(),
|
|
3067
|
+
raw: () => q.raw(),
|
|
3068
|
+
cancel: () => {
|
|
3069
|
+
try {
|
|
3070
|
+
q.cancel();
|
|
3071
|
+
} catch {}
|
|
3072
|
+
}
|
|
3073
|
+
};
|
|
3074
|
+
},
|
|
3075
|
+
insertInto(table) {
|
|
3076
|
+
let built = bunSql2`INSERT INTO ${bunSql2(String(table))}`;
|
|
3077
|
+
const api = {
|
|
3078
|
+
values(data) {
|
|
3079
|
+
built = bunSql2`${built} ${bunSql2(data)}`;
|
|
3080
|
+
return api;
|
|
3081
|
+
},
|
|
3082
|
+
returning(...cols) {
|
|
3083
|
+
const q = bunSql2`${built} RETURNING ${bunSql2(cols)}`;
|
|
3084
|
+
const obj = {
|
|
3085
|
+
where: () => obj,
|
|
3086
|
+
andWhere: () => obj,
|
|
3087
|
+
orWhere: () => obj,
|
|
3088
|
+
orderBy: () => obj,
|
|
3089
|
+
limit: () => obj,
|
|
3090
|
+
offset: () => obj,
|
|
3091
|
+
toSQL: () => makeExecutableQuery(q, computeSqlText(q)),
|
|
3092
|
+
execute: () => runWithHooks(q, "insert")
|
|
3093
|
+
};
|
|
3094
|
+
return obj;
|
|
3095
|
+
},
|
|
3096
|
+
toSQL() {
|
|
3097
|
+
return makeExecutableQuery(built, computeSqlText(built));
|
|
3098
|
+
},
|
|
3099
|
+
execute() {
|
|
3100
|
+
return runWithHooks(built, "insert");
|
|
3101
|
+
}
|
|
3102
|
+
};
|
|
3103
|
+
return api;
|
|
3104
|
+
},
|
|
3105
|
+
updateTable(table) {
|
|
3106
|
+
let built = bunSql2`UPDATE ${bunSql2(String(table))}`;
|
|
3107
|
+
return {
|
|
3108
|
+
set(values) {
|
|
3109
|
+
built = bunSql2`${built} SET ${bunSql2(values)}`;
|
|
3110
|
+
return this;
|
|
3111
|
+
},
|
|
3112
|
+
where(expr) {
|
|
3113
|
+
built = applyWhere({}, built, expr);
|
|
3114
|
+
return this;
|
|
3115
|
+
},
|
|
3116
|
+
returning(...cols) {
|
|
3117
|
+
const q = bunSql2`${built} RETURNING ${bunSql2(cols)}`;
|
|
3118
|
+
const obj = {
|
|
3119
|
+
where: () => obj,
|
|
3120
|
+
andWhere: () => obj,
|
|
3121
|
+
orWhere: () => obj,
|
|
3122
|
+
orderBy: () => obj,
|
|
3123
|
+
limit: () => obj,
|
|
3124
|
+
offset: () => obj,
|
|
3125
|
+
toSQL: () => makeExecutableQuery(q, computeSqlText(q)),
|
|
3126
|
+
execute: () => runWithHooks(q, "update")
|
|
3127
|
+
};
|
|
3128
|
+
return obj;
|
|
3129
|
+
},
|
|
3130
|
+
toSQL() {
|
|
3131
|
+
return makeExecutableQuery(built, computeSqlText(built));
|
|
3132
|
+
},
|
|
3133
|
+
execute() {
|
|
3134
|
+
return runWithHooks(built, "update");
|
|
3135
|
+
}
|
|
3136
|
+
};
|
|
3137
|
+
},
|
|
3138
|
+
deleteFrom(table) {
|
|
3139
|
+
let built = bunSql2`DELETE FROM ${bunSql2(String(table))}`;
|
|
3140
|
+
return {
|
|
3141
|
+
where(expr) {
|
|
3142
|
+
built = applyWhere({}, built, expr);
|
|
3143
|
+
return this;
|
|
3144
|
+
},
|
|
3145
|
+
returning(...cols) {
|
|
3146
|
+
const q = bunSql2`${built} RETURNING ${bunSql2(cols)}`;
|
|
3147
|
+
const obj = {
|
|
3148
|
+
where: () => obj,
|
|
3149
|
+
andWhere: () => obj,
|
|
3150
|
+
orWhere: () => obj,
|
|
3151
|
+
orderBy: () => obj,
|
|
3152
|
+
limit: () => obj,
|
|
3153
|
+
offset: () => obj,
|
|
3154
|
+
toSQL: () => makeExecutableQuery(q, computeSqlText(q)),
|
|
3155
|
+
execute: () => runWithHooks(q, "delete")
|
|
3156
|
+
};
|
|
3157
|
+
return obj;
|
|
3158
|
+
},
|
|
3159
|
+
toSQL() {
|
|
3160
|
+
return makeExecutableQuery(built, computeSqlText(built));
|
|
3161
|
+
},
|
|
3162
|
+
execute() {
|
|
3163
|
+
return runWithHooks(built, "delete");
|
|
3164
|
+
}
|
|
3165
|
+
};
|
|
3166
|
+
},
|
|
3167
|
+
sql: bunSql2,
|
|
3168
|
+
raw(strings, ...values) {
|
|
3169
|
+
return bunSql2(strings, ...values);
|
|
3170
|
+
},
|
|
3171
|
+
simple(strings, ...values) {
|
|
3172
|
+
return bunSql2(strings, ...values).simple();
|
|
3173
|
+
},
|
|
3174
|
+
async advisoryLock(key) {
|
|
3175
|
+
if (config2.dialect !== "postgres")
|
|
3176
|
+
return;
|
|
3177
|
+
const s = String(key);
|
|
3178
|
+
let hash = 7;
|
|
3179
|
+
for (let i = 0;i < s.length; i++)
|
|
3180
|
+
hash = hash * 31 + s.charCodeAt(i) | 0;
|
|
3181
|
+
const k = typeof key === "number" ? key : Math.abs(hash);
|
|
3182
|
+
const q = bunSql2`SELECT pg_advisory_lock(${k})`;
|
|
3183
|
+
await runWithHooks(q, "raw");
|
|
3184
|
+
},
|
|
3185
|
+
async tryAdvisoryLock(key) {
|
|
3186
|
+
if (config2.dialect !== "postgres")
|
|
3187
|
+
return false;
|
|
3188
|
+
const s = String(key);
|
|
3189
|
+
let hash = 7;
|
|
3190
|
+
for (let i = 0;i < s.length; i++)
|
|
3191
|
+
hash = hash * 31 + s.charCodeAt(i) | 0;
|
|
3192
|
+
const k = typeof key === "number" ? key : Math.abs(hash);
|
|
3193
|
+
const q = bunSql2`SELECT pg_try_advisory_lock(${k}) as ok`;
|
|
3194
|
+
const rows = await runWithHooks(q, "raw");
|
|
3195
|
+
return Boolean(rows?.[0]?.ok);
|
|
3196
|
+
},
|
|
3197
|
+
unsafe(query, params) {
|
|
3198
|
+
return bunSql2.unsafe(query, params);
|
|
3199
|
+
},
|
|
3200
|
+
file(path, params) {
|
|
3201
|
+
return bunSql2.file(path, params);
|
|
3202
|
+
},
|
|
3203
|
+
async reserve() {
|
|
3204
|
+
const reserved = await bunSql2.reserve();
|
|
3205
|
+
const qb = createQueryBuilder({ sql: reserved, meta, schema });
|
|
3206
|
+
qb.release = () => reserved.release();
|
|
3207
|
+
return qb;
|
|
3208
|
+
},
|
|
3209
|
+
async close(opts) {
|
|
3210
|
+
await bunSql2.close(opts);
|
|
3211
|
+
},
|
|
3212
|
+
async listen(channel, handler) {
|
|
3213
|
+
if (handler) {}
|
|
3214
|
+
},
|
|
3215
|
+
async unlisten(_channel) {},
|
|
3216
|
+
async notify(_channel, _payload) {},
|
|
3217
|
+
async copyTo(_queryOrTable, _options) {
|
|
3218
|
+
throw new Error("COPY TO is not yet supported by Bun.sql; placeholder");
|
|
3219
|
+
},
|
|
3220
|
+
async copyFrom(_queryOrTable, _source, _options) {
|
|
3221
|
+
throw new Error("COPY FROM is not yet supported by Bun.sql; placeholder");
|
|
3222
|
+
},
|
|
3223
|
+
async ping() {
|
|
3224
|
+
try {
|
|
3225
|
+
const q = bunSql2`SELECT 1`;
|
|
3226
|
+
await runWithHooks(q, "select");
|
|
3227
|
+
return true;
|
|
3228
|
+
} catch {
|
|
3229
|
+
return false;
|
|
3230
|
+
}
|
|
3231
|
+
},
|
|
3232
|
+
async waitForReady(opts) {
|
|
3233
|
+
const attempts = Math.max(1, opts?.attempts ?? 10);
|
|
3234
|
+
const delay = Math.max(10, opts?.delayMs ?? 100);
|
|
3235
|
+
for (let i = 0;i < attempts; i++) {
|
|
3236
|
+
if (await this.ping())
|
|
3237
|
+
return;
|
|
3238
|
+
await new Promise((resolve4) => setTimeout(resolve4, delay));
|
|
3239
|
+
}
|
|
3240
|
+
throw new Error("Database not ready after waiting");
|
|
3241
|
+
},
|
|
3242
|
+
async transaction(fn, options) {
|
|
3243
|
+
const defaults = state?.txDefaults;
|
|
3244
|
+
const opts = { ...defaults, ...options };
|
|
3245
|
+
const runWith = async (attempt2) => {
|
|
3246
|
+
opts.logger?.({ type: "start", attempt: attempt2 });
|
|
3247
|
+
const start = Date.now();
|
|
3248
|
+
return await bunSql2.begin(async (tx) => {
|
|
3249
|
+
const qb = createQueryBuilder({ sql: tx, meta, schema });
|
|
3250
|
+
if (opts?.isolation) {
|
|
3251
|
+
const level = opts.isolation;
|
|
3252
|
+
const lvl = level === "read committed" ? bunSql2`READ COMMITTED` : level === "repeatable read" ? bunSql2`REPEATABLE READ` : bunSql2`SERIALIZABLE`;
|
|
3253
|
+
await tx`${bunSql2`SET TRANSACTION ISOLATION LEVEL`} ${lvl}`.execute();
|
|
3254
|
+
}
|
|
3255
|
+
if (opts?.readOnly) {
|
|
3256
|
+
try {
|
|
3257
|
+
await tx`${bunSql2`SET TRANSACTION READ ONLY`}`.execute();
|
|
3258
|
+
} catch {}
|
|
3259
|
+
}
|
|
3260
|
+
const res = await fn(qb);
|
|
3261
|
+
const durationMs = Date.now() - start;
|
|
3262
|
+
opts.logger?.({ type: "commit", attempt: attempt2, durationMs });
|
|
3263
|
+
return res;
|
|
3264
|
+
});
|
|
3265
|
+
};
|
|
3266
|
+
const retries = Math.max(0, opts?.retries ?? 0);
|
|
3267
|
+
let attempt = 0;
|
|
3268
|
+
for (;; ) {
|
|
3269
|
+
try {
|
|
3270
|
+
const out = await runWith(attempt + 1);
|
|
3271
|
+
opts?.afterCommit?.();
|
|
3272
|
+
return out;
|
|
3273
|
+
} catch (err) {
|
|
3274
|
+
const retriable = isRetriableTxError(err) || matchesSqlState(err, opts.sqlStates);
|
|
3275
|
+
if (attempt < retries && retriable) {
|
|
3276
|
+
attempt++;
|
|
3277
|
+
opts?.onRetry?.(attempt, err);
|
|
3278
|
+
const delay = computeBackoffMs(attempt, opts.backoff);
|
|
3279
|
+
if (delay > 0)
|
|
3280
|
+
await sleep(delay);
|
|
3281
|
+
continue;
|
|
3282
|
+
}
|
|
3283
|
+
try {
|
|
3284
|
+
opts.onRollback?.(err);
|
|
3285
|
+
} catch {}
|
|
3286
|
+
try {
|
|
3287
|
+
opts.afterRollback?.();
|
|
3288
|
+
} catch {}
|
|
3289
|
+
throw err;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
},
|
|
3293
|
+
async savepoint(fn) {
|
|
3294
|
+
const s = _sql;
|
|
3295
|
+
if (!s || typeof s.savepoint !== "function")
|
|
3296
|
+
throw new Error("savepoint() must be called inside a transaction");
|
|
3297
|
+
return await s.savepoint(async (sp) => {
|
|
3298
|
+
const qb = createQueryBuilder({ sql: sp, meta, schema });
|
|
3299
|
+
return await fn(qb);
|
|
3300
|
+
});
|
|
3301
|
+
},
|
|
3302
|
+
async beginDistributed(name, fn) {
|
|
3303
|
+
const res = await bunSql2.beginDistributed(name, async (tx) => {
|
|
3304
|
+
const qb = createQueryBuilder({ sql: tx, meta, schema });
|
|
3305
|
+
return await fn(qb);
|
|
3306
|
+
});
|
|
3307
|
+
return res;
|
|
3308
|
+
},
|
|
3309
|
+
async commitDistributed(name) {
|
|
3310
|
+
await bunSql2.commitDistributed(name);
|
|
3311
|
+
},
|
|
3312
|
+
async rollbackDistributed(name) {
|
|
3313
|
+
await bunSql2.rollbackDistributed(name);
|
|
3314
|
+
},
|
|
3315
|
+
setTransactionDefaults(defaults) {
|
|
3316
|
+
state = { ...state, txDefaults: { ...state?.txDefaults, ...defaults } };
|
|
3317
|
+
},
|
|
3318
|
+
transactional(fn, options) {
|
|
3319
|
+
return (...args) => {
|
|
3320
|
+
return this.transaction((tx) => fn(tx, ...args), options);
|
|
3321
|
+
};
|
|
3322
|
+
},
|
|
3323
|
+
async insertOrIgnore(table, values) {
|
|
3324
|
+
const built = bunSql2`INSERT INTO ${bunSql2(String(table))} ${bunSql2(values)} ON CONFLICT DO NOTHING`;
|
|
3325
|
+
return built.execute();
|
|
3326
|
+
},
|
|
3327
|
+
async insertGetId(table, values, idColumn = "id") {
|
|
3328
|
+
const q = bunSql2`INSERT INTO ${bunSql2(String(table))} ${bunSql2(values)} RETURNING ${bunSql2(String(idColumn))} as id`;
|
|
3329
|
+
const [row] = await q.execute();
|
|
3330
|
+
return row?.id;
|
|
3331
|
+
},
|
|
3332
|
+
async updateOrInsert(table, match, values) {
|
|
3333
|
+
const whereParts = Object.keys(match).map((k) => bunSql2`${bunSql2(String(k))} = ${bunSql2(match[k])}`);
|
|
3334
|
+
const existsQ = bunSql2`SELECT 1 FROM ${bunSql2(String(table))} WHERE ${bunSql2(whereParts)} LIMIT 1`;
|
|
3335
|
+
const existsRows = await existsQ.execute();
|
|
3336
|
+
if (existsRows.length) {
|
|
3337
|
+
const upd = bunSql2`UPDATE ${bunSql2(String(table))} SET ${bunSql2(values)} WHERE ${bunSql2(whereParts)}`;
|
|
3338
|
+
await upd.execute();
|
|
3339
|
+
return true;
|
|
3340
|
+
} else {
|
|
3341
|
+
const ins = bunSql2`INSERT INTO ${bunSql2(String(table))} ${bunSql2({ ...match, ...values })}`;
|
|
3342
|
+
await ins.execute();
|
|
3343
|
+
return true;
|
|
3344
|
+
}
|
|
3345
|
+
},
|
|
3346
|
+
async upsert(table, rows, conflictColumns, mergeColumns) {
|
|
3347
|
+
const targetCols = conflictColumns.map((c) => String(c));
|
|
3348
|
+
const setCols = (mergeColumns ?? []).map((c) => String(c));
|
|
3349
|
+
const built = bunSql2`INSERT INTO ${bunSql2(String(table))} ${bunSql2(rows)} ON CONFLICT (${bunSql2(targetCols)}) DO UPDATE SET ${bunSql2(setCols.reduce((acc, c) => ({ ...acc, [c]: bunSql2(`EXCLUDED.${c}`) }), {}))}`;
|
|
3350
|
+
return built.execute();
|
|
3351
|
+
},
|
|
3352
|
+
async save(table, values) {
|
|
3353
|
+
const pk = meta?.primaryKeys[String(table)] ?? "id";
|
|
3354
|
+
const id = values[pk];
|
|
3355
|
+
if (id != null) {
|
|
3356
|
+
await this.updateTable(table).set(values).where({ [pk]: id }).execute();
|
|
3357
|
+
const row = await this.selectFrom(table).find(id);
|
|
3358
|
+
if (!row)
|
|
3359
|
+
throw new Error("save() failed to retrieve updated row");
|
|
3360
|
+
return row;
|
|
3361
|
+
}
|
|
3362
|
+
return await this.create(table, values);
|
|
3363
|
+
},
|
|
3364
|
+
async remove(table, id) {
|
|
3365
|
+
return await this.deleteFrom(table).where({ id }).execute();
|
|
3366
|
+
},
|
|
3367
|
+
async find(table, id) {
|
|
3368
|
+
return await this.selectFrom(table).find(id);
|
|
3369
|
+
},
|
|
3370
|
+
async findOrFail(table, id) {
|
|
3371
|
+
return await this.selectFrom(table).findOrFail(id);
|
|
3372
|
+
},
|
|
3373
|
+
async findMany(table, ids) {
|
|
3374
|
+
return await this.selectFrom(table).findMany(ids);
|
|
3375
|
+
},
|
|
3376
|
+
async latest(table, column) {
|
|
3377
|
+
return await this.selectFrom(table).latest(column).first();
|
|
3378
|
+
},
|
|
3379
|
+
async oldest(table, column) {
|
|
3380
|
+
return await this.selectFrom(table).oldest(column).first();
|
|
3381
|
+
},
|
|
3382
|
+
skip(table, count) {
|
|
3383
|
+
return this.selectFrom(table).offset(count);
|
|
3384
|
+
},
|
|
3385
|
+
async rawQuery(query) {
|
|
3386
|
+
const start = Date.now();
|
|
3387
|
+
try {
|
|
3388
|
+
config2.hooks?.onQueryStart?.({ sql: query, kind: "raw" });
|
|
3389
|
+
const res = await bunSql2.unsafe(query);
|
|
3390
|
+
config2.hooks?.onQueryEnd?.({ sql: query, durationMs: Date.now() - start, kind: "raw" });
|
|
3391
|
+
return res;
|
|
3392
|
+
} catch (err) {
|
|
3393
|
+
config2.hooks?.onQueryError?.({ sql: query, error: err, durationMs: Date.now() - start, kind: "raw" });
|
|
3394
|
+
throw err;
|
|
3395
|
+
}
|
|
3396
|
+
},
|
|
3397
|
+
async create(table, values) {
|
|
3398
|
+
const pk = meta?.primaryKeys[String(table)] ?? "id";
|
|
3399
|
+
const id = await this.insertGetId(table, values, pk);
|
|
3400
|
+
const row = await this.selectFrom(table).find(id);
|
|
3401
|
+
if (!row)
|
|
3402
|
+
throw new Error("create() failed to retrieve inserted row");
|
|
3403
|
+
return row;
|
|
3404
|
+
},
|
|
3405
|
+
async createMany(table, rows) {
|
|
3406
|
+
await this.insertInto(table).values(rows).execute();
|
|
3407
|
+
},
|
|
3408
|
+
async firstOrCreate(table, match, defaults) {
|
|
3409
|
+
const existing = await this.selectFrom(table).where(match).first();
|
|
3410
|
+
if (existing)
|
|
3411
|
+
return existing;
|
|
3412
|
+
return await this.create(table, { ...match, ...defaults });
|
|
3413
|
+
},
|
|
3414
|
+
async updateOrCreate(table, match, values) {
|
|
3415
|
+
const existing = await this.selectFrom(table).where(match).first();
|
|
3416
|
+
if (existing) {
|
|
3417
|
+
await this.updateTable(table).set(values).where(match).execute();
|
|
3418
|
+
const pk = meta?.primaryKeys[String(table)] ?? "id";
|
|
3419
|
+
const id = existing[pk];
|
|
3420
|
+
const refreshed = id != null ? await this.selectFrom(table).find(id) : await this.selectFrom(table).where(match).first();
|
|
3421
|
+
if (!refreshed)
|
|
3422
|
+
throw new Error("updateOrCreate() failed to retrieve updated row");
|
|
3423
|
+
return refreshed;
|
|
3424
|
+
}
|
|
3425
|
+
return await this.create(table, { ...match, ...values });
|
|
3426
|
+
},
|
|
3427
|
+
async count(table, column) {
|
|
3428
|
+
const col = column ? bunSql2(String(column)) : bunSql2`*`;
|
|
3429
|
+
const q = bunSql2`SELECT COUNT(${col}) as c FROM ${bunSql2(String(table))}`;
|
|
3430
|
+
const [row] = await q.execute();
|
|
3431
|
+
return Number(row?.c ?? 0);
|
|
3432
|
+
},
|
|
3433
|
+
async sum(table, column) {
|
|
3434
|
+
const q = bunSql2`SELECT SUM(${bunSql2(String(column))}) as s FROM ${bunSql2(String(table))}`;
|
|
3435
|
+
const [row] = await q.execute();
|
|
3436
|
+
return Number(row?.s ?? 0);
|
|
3437
|
+
},
|
|
3438
|
+
async avg(table, column) {
|
|
3439
|
+
const q = bunSql2`SELECT AVG(${bunSql2(String(column))}) as a FROM ${bunSql2(String(table))}`;
|
|
3440
|
+
const [row] = await q.execute();
|
|
3441
|
+
return Number(row?.a ?? 0);
|
|
3442
|
+
},
|
|
3443
|
+
async min(table, column) {
|
|
3444
|
+
const q = bunSql2`SELECT MIN(${bunSql2(String(column))}) as m FROM ${bunSql2(String(table))}`;
|
|
3445
|
+
const [row] = await q.execute();
|
|
3446
|
+
return row?.m;
|
|
3447
|
+
},
|
|
3448
|
+
async max(table, column) {
|
|
3449
|
+
const q = bunSql2`SELECT MAX(${bunSql2(String(column))}) as m FROM ${bunSql2(String(table))}`;
|
|
3450
|
+
const [row] = await q.execute();
|
|
3451
|
+
return row?.m;
|
|
3452
|
+
}
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
// src/factory.ts
|
|
3456
|
+
function buildDatabaseSchema(models) {
|
|
3457
|
+
const schema = {};
|
|
3458
|
+
for (const modelName of Object.keys(models)) {
|
|
3459
|
+
const m = models[modelName];
|
|
3460
|
+
const table = m.table || `${String(m.name).toLowerCase()}s`;
|
|
3461
|
+
const attrs = m.attributes ?? {};
|
|
3462
|
+
const columns = {};
|
|
3463
|
+
for (const key of Object.keys(attrs))
|
|
3464
|
+
columns[key] = undefined;
|
|
3465
|
+
schema[table] = {
|
|
3466
|
+
columns,
|
|
3467
|
+
primaryKey: m.primaryKey ?? "id"
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
return schema;
|
|
3471
|
+
}
|
|
3472
|
+
// src/loader.ts
|
|
3473
|
+
import { readdirSync as readdirSync3, statSync } from "fs";
|
|
3474
|
+
import { basename, extname } from "path";
|
|
3475
|
+
import process7 from "process";
|
|
3476
|
+
async function loadModels(options) {
|
|
3477
|
+
const cwd = options.cwd ?? process7.cwd();
|
|
3478
|
+
const dir = options.modelsDir.startsWith("/") ? options.modelsDir : `${cwd}/${options.modelsDir}`;
|
|
3479
|
+
const result = {};
|
|
3480
|
+
const entries = readdirSync3(dir);
|
|
3481
|
+
for (const entry of entries) {
|
|
3482
|
+
const full = `${dir}/${entry}`;
|
|
3483
|
+
const st = statSync(full);
|
|
3484
|
+
if (st.isDirectory())
|
|
3485
|
+
continue;
|
|
3486
|
+
const ext = extname(full);
|
|
3487
|
+
if (![".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"].includes(ext))
|
|
3488
|
+
continue;
|
|
3489
|
+
const mod = await import(full);
|
|
3490
|
+
const def = mod.default ?? mod;
|
|
3491
|
+
const fileName = basename(entry, ext);
|
|
3492
|
+
const name = def.name ?? fileName;
|
|
3493
|
+
result[name] = {
|
|
3494
|
+
...def,
|
|
3495
|
+
name
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
return result;
|
|
3499
|
+
}
|
|
3500
|
+
// src/meta.ts
|
|
3501
|
+
function buildSchemaMeta(models) {
|
|
3502
|
+
const modelToTable = {};
|
|
3503
|
+
const tableToModel = {};
|
|
3504
|
+
const primaryKeys = {};
|
|
3505
|
+
const relations = {};
|
|
3506
|
+
const scopesByTable = {};
|
|
3507
|
+
for (const name of Object.keys(models)) {
|
|
3508
|
+
const m = models[name];
|
|
3509
|
+
const table = m.table || `${String(m.name).toLowerCase()}s`;
|
|
3510
|
+
modelToTable[name] = table;
|
|
3511
|
+
tableToModel[table] = name;
|
|
3512
|
+
primaryKeys[table] = m.primaryKey ?? "id";
|
|
3513
|
+
const toRecord = (v) => {
|
|
3514
|
+
if (!v)
|
|
3515
|
+
return {};
|
|
3516
|
+
if (Array.isArray(v)) {
|
|
3517
|
+
const rec = {};
|
|
3518
|
+
for (const relName of v)
|
|
3519
|
+
rec[relName] = relName;
|
|
3520
|
+
return rec;
|
|
3521
|
+
}
|
|
3522
|
+
return v;
|
|
3523
|
+
};
|
|
3524
|
+
relations[table] = {
|
|
3525
|
+
hasOne: toRecord(m.hasOne),
|
|
3526
|
+
hasMany: toRecord(m.hasMany),
|
|
3527
|
+
belongsTo: toRecord(m.belongsTo),
|
|
3528
|
+
belongsToMany: toRecord(m.belongsToMany)
|
|
3529
|
+
};
|
|
3530
|
+
if (m.scopes && typeof m.scopes === "object") {
|
|
3531
|
+
scopesByTable[table] = {};
|
|
3532
|
+
for (const key of Object.keys(m.scopes)) {
|
|
3533
|
+
const fn = m.scopes[key];
|
|
3534
|
+
if (typeof fn === "function")
|
|
3535
|
+
scopesByTable[table][key] = fn;
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
return { modelToTable, tableToModel, primaryKeys, relations, scopes: scopesByTable };
|
|
3540
|
+
}
|
|
3541
|
+
// src/migrations.ts
|
|
3542
|
+
function guessTypeFromName(columnName) {
|
|
3543
|
+
if (columnName.endsWith("_id"))
|
|
3544
|
+
return "bigint";
|
|
3545
|
+
if (columnName.endsWith("_at"))
|
|
3546
|
+
return "datetime";
|
|
3547
|
+
if (columnName.startsWith("is_") || columnName.startsWith("has_"))
|
|
3548
|
+
return "boolean";
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
function normalizeDefaultValue(value) {
|
|
3552
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || value instanceof Date) {
|
|
3553
|
+
return value;
|
|
3554
|
+
}
|
|
3555
|
+
return;
|
|
3556
|
+
}
|
|
3557
|
+
function buildMigrationPlan(models, options) {
|
|
3558
|
+
const meta = buildSchemaMeta(models);
|
|
3559
|
+
const tables = [];
|
|
3560
|
+
for (const modelName of Object.keys(models)) {
|
|
3561
|
+
const model = models[modelName];
|
|
3562
|
+
const table = model.table || `${String(model.name).toLowerCase()}s`;
|
|
3563
|
+
const primaryKey = model.primaryKey ?? "id";
|
|
3564
|
+
const attrs = model.attributes ?? {};
|
|
3565
|
+
const columns = [];
|
|
3566
|
+
const indexes = [];
|
|
3567
|
+
for (const attrName of Object.keys(attrs)) {
|
|
3568
|
+
const attr = attrs[attrName];
|
|
3569
|
+
const isNullable = true;
|
|
3570
|
+
let inferred = guessTypeFromName(attrName);
|
|
3571
|
+
if (!inferred) {
|
|
3572
|
+
const dv = normalizeDefaultValue(attr.default);
|
|
3573
|
+
if (typeof dv === "string")
|
|
3574
|
+
inferred = dv.length > 255 ? "text" : "string";
|
|
3575
|
+
else if (typeof dv === "number")
|
|
3576
|
+
inferred = Number.isInteger(dv) ? "integer" : "float";
|
|
3577
|
+
else if (typeof dv === "boolean")
|
|
3578
|
+
inferred = "boolean";
|
|
3579
|
+
else if (typeof dv === "bigint")
|
|
3580
|
+
inferred = "bigint";
|
|
3581
|
+
else if (dv instanceof Date)
|
|
3582
|
+
inferred = "datetime";
|
|
3583
|
+
}
|
|
3584
|
+
const isPk = attrName === primaryKey;
|
|
3585
|
+
if (!inferred) {
|
|
3586
|
+
inferred = isPk ? "bigint" : "string";
|
|
3587
|
+
}
|
|
3588
|
+
const col = {
|
|
3589
|
+
name: attrName,
|
|
3590
|
+
type: inferred,
|
|
3591
|
+
isPrimaryKey: isPk,
|
|
3592
|
+
isUnique: Boolean(attr.unique),
|
|
3593
|
+
isNullable,
|
|
3594
|
+
hasDefault: typeof attr.default !== "undefined",
|
|
3595
|
+
defaultValue: normalizeDefaultValue(attr.default)
|
|
3596
|
+
};
|
|
3597
|
+
if (attrName.endsWith("_id")) {
|
|
3598
|
+
const base = attrName.replace(/_id$/, "");
|
|
3599
|
+
const maybeModel = base.charAt(0).toUpperCase() + base.slice(1);
|
|
3600
|
+
const refTable = meta.modelToTable[maybeModel];
|
|
3601
|
+
if (refTable) {
|
|
3602
|
+
const refPk = meta.primaryKeys[refTable] ?? "id";
|
|
3603
|
+
col.references = { table: refTable, column: refPk };
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
columns.push(col);
|
|
3607
|
+
}
|
|
3608
|
+
for (const idx of model.indexes ?? []) {
|
|
3609
|
+
indexes.push({ name: idx.name, columns: idx.columns, type: "index" });
|
|
3610
|
+
}
|
|
3611
|
+
for (const c of columns) {
|
|
3612
|
+
if (c.isUnique && !c.isPrimaryKey)
|
|
3613
|
+
indexes.push({ name: `${table}_${c.name}_unique`, columns: [c.name], type: "unique" });
|
|
3614
|
+
}
|
|
3615
|
+
tables.push({ table, columns, indexes });
|
|
3616
|
+
}
|
|
3617
|
+
return { dialect: options.dialect, tables };
|
|
3618
|
+
}
|
|
3619
|
+
function generateSql(plan) {
|
|
3620
|
+
const statements = [];
|
|
3621
|
+
const q = (id) => plan.dialect === "mysql" ? `\`${id}\`` : `"${id}"`;
|
|
3622
|
+
const columnSql = (c) => {
|
|
3623
|
+
const typeSql = (() => {
|
|
3624
|
+
if (plan.dialect === "postgres" && c.isPrimaryKey) {
|
|
3625
|
+
switch (c.type) {
|
|
3626
|
+
case "integer":
|
|
3627
|
+
return "SERIAL";
|
|
3628
|
+
case "bigint":
|
|
3629
|
+
return "BIGSERIAL";
|
|
3630
|
+
default:
|
|
3631
|
+
break;
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
switch (c.type) {
|
|
3635
|
+
case "string":
|
|
3636
|
+
return plan.dialect === "mysql" ? "varchar(255)" : "varchar(255)";
|
|
3637
|
+
case "text":
|
|
3638
|
+
return "text";
|
|
3639
|
+
case "boolean":
|
|
3640
|
+
return plan.dialect === "mysql" ? "tinyint(1)" : "boolean";
|
|
3641
|
+
case "integer":
|
|
3642
|
+
return plan.dialect === "sqlite" ? "integer" : "integer";
|
|
3643
|
+
case "bigint":
|
|
3644
|
+
return "bigint";
|
|
3645
|
+
case "float":
|
|
3646
|
+
return "real";
|
|
3647
|
+
case "double":
|
|
3648
|
+
return "double precision";
|
|
3649
|
+
case "decimal":
|
|
3650
|
+
return "decimal(10,2)";
|
|
3651
|
+
case "date":
|
|
3652
|
+
return "date";
|
|
3653
|
+
case "datetime":
|
|
3654
|
+
return plan.dialect === "mysql" ? "datetime" : "timestamp";
|
|
3655
|
+
case "json":
|
|
3656
|
+
return plan.dialect === "mysql" ? "json" : plan.dialect === "postgres" ? "jsonb" : "text";
|
|
3657
|
+
default:
|
|
3658
|
+
return "text";
|
|
3659
|
+
}
|
|
3660
|
+
})();
|
|
3661
|
+
const parts = [q(c.name), typeSql];
|
|
3662
|
+
if (c.isPrimaryKey)
|
|
3663
|
+
parts.push("primary key");
|
|
3664
|
+
if (!c.isNullable && !c.isPrimaryKey)
|
|
3665
|
+
parts.push("not null");
|
|
3666
|
+
if (c.hasDefault) {
|
|
3667
|
+
const dv = c.defaultValue;
|
|
3668
|
+
if (typeof dv === "string")
|
|
3669
|
+
parts.push(`default '${dv.replace(/'/g, "''")}'`);
|
|
3670
|
+
else if (typeof dv === "number" || typeof dv === "bigint")
|
|
3671
|
+
parts.push(`default ${dv}`);
|
|
3672
|
+
else if (typeof dv === "boolean")
|
|
3673
|
+
parts.push(`default ${dv ? 1 : 0}`);
|
|
3674
|
+
else if (dv instanceof Date)
|
|
3675
|
+
parts.push(`default '${dv.toISOString()}'`);
|
|
3676
|
+
}
|
|
3677
|
+
return parts.join(" ");
|
|
3678
|
+
};
|
|
3679
|
+
for (const t of plan.tables) {
|
|
3680
|
+
statements.push(`CREATE TABLE ${q(t.table)} (
|
|
3681
|
+
${t.columns.map((c) => columnSql(c)).join(`,
|
|
3682
|
+
`)}
|
|
3683
|
+
);`);
|
|
3684
|
+
}
|
|
3685
|
+
for (const t of plan.tables) {
|
|
3686
|
+
for (const c of t.columns) {
|
|
3687
|
+
if (c.references) {
|
|
3688
|
+
const fkName = `${t.table}_${c.name}_fk`;
|
|
3689
|
+
statements.push(`ALTER TABLE ${q(t.table)} ADD CONSTRAINT ${q(fkName)} FOREIGN KEY (${q(c.name)}) REFERENCES ${q(c.references.table)}(${q(c.references.column)});`);
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
for (const t of plan.tables) {
|
|
3694
|
+
for (const idx of t.indexes) {
|
|
3695
|
+
const kind = idx.type === "unique" ? "UNIQUE " : "";
|
|
3696
|
+
const idxName = `${t.table}_${idx.name}`;
|
|
3697
|
+
statements.push(`CREATE ${kind}INDEX ${q(idxName)} ON ${q(t.table)} (${idx.columns.map((c) => q(c)).join(", ")});`);
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
return statements;
|
|
3701
|
+
}
|
|
3702
|
+
function generateSqlString(plan) {
|
|
3703
|
+
return generateSql(plan).join(`
|
|
3704
|
+
`);
|
|
3705
|
+
}
|
|
3706
|
+
function generateDiffSqlString(previous, next) {
|
|
3707
|
+
return generateDiffSql(previous, next).join(`
|
|
3708
|
+
`);
|
|
3709
|
+
}
|
|
3710
|
+
function hashMigrationPlan(plan) {
|
|
3711
|
+
const crypto = __require("crypto");
|
|
3712
|
+
function canonicalize(value) {
|
|
3713
|
+
if (value == null || typeof value !== "object" || value instanceof Date)
|
|
3714
|
+
return value;
|
|
3715
|
+
if (Array.isArray(value))
|
|
3716
|
+
return value.map((v) => canonicalize(v));
|
|
3717
|
+
const out = {};
|
|
3718
|
+
for (const key of Object.keys(value).sort())
|
|
3719
|
+
out[key] = canonicalize(value[key]);
|
|
3720
|
+
return out;
|
|
3721
|
+
}
|
|
3722
|
+
const canon = JSON.stringify(canonicalize(plan));
|
|
3723
|
+
return crypto.createHash("sha256").update(canon).digest("hex");
|
|
3724
|
+
}
|
|
3725
|
+
function mapTablesByName(tables) {
|
|
3726
|
+
const map = {};
|
|
3727
|
+
for (const t of tables)
|
|
3728
|
+
map[t.table] = t;
|
|
3729
|
+
return map;
|
|
3730
|
+
}
|
|
3731
|
+
function mapColumnsByName(columns) {
|
|
3732
|
+
const map = {};
|
|
3733
|
+
for (const c of columns)
|
|
3734
|
+
map[c.name] = c;
|
|
3735
|
+
return map;
|
|
3736
|
+
}
|
|
3737
|
+
function mapIndexesByKey(indexes) {
|
|
3738
|
+
const map = {};
|
|
3739
|
+
for (const i of indexes) {
|
|
3740
|
+
const key = `${i.type}:${i.name}:${i.columns.join(",")}`;
|
|
3741
|
+
map[key] = i;
|
|
3742
|
+
}
|
|
3743
|
+
return map;
|
|
3744
|
+
}
|
|
3745
|
+
function generateDiffSql(previous, next) {
|
|
3746
|
+
if (!previous || previous.dialect !== next.dialect)
|
|
3747
|
+
return generateSql(next);
|
|
3748
|
+
const chunks = [];
|
|
3749
|
+
const q = (id) => next.dialect === "mysql" ? `\`${id}\`` : `"${id}"`;
|
|
3750
|
+
const prevTables = mapTablesByName(previous.tables);
|
|
3751
|
+
const nextTables = mapTablesByName(next.tables);
|
|
3752
|
+
for (const tableName of Object.keys(nextTables)) {
|
|
3753
|
+
if (!prevTables[tableName]) {
|
|
3754
|
+
const t = nextTables[tableName];
|
|
3755
|
+
chunks.push(`CREATE TABLE ${q(t.table)} (
|
|
3756
|
+
${t.columns.map((c) => {
|
|
3757
|
+
const tmp = c;
|
|
3758
|
+
const typeSql = (() => {
|
|
3759
|
+
switch (tmp.type) {
|
|
3760
|
+
case "string":
|
|
3761
|
+
return next.dialect === "mysql" ? "varchar(255)" : "varchar(255)";
|
|
3762
|
+
case "text":
|
|
3763
|
+
return "text";
|
|
3764
|
+
case "boolean":
|
|
3765
|
+
return next.dialect === "mysql" ? "tinyint(1)" : "boolean";
|
|
3766
|
+
case "integer":
|
|
3767
|
+
return next.dialect === "sqlite" ? "integer" : "integer";
|
|
3768
|
+
case "bigint":
|
|
3769
|
+
return "bigint";
|
|
3770
|
+
case "float":
|
|
3771
|
+
return "real";
|
|
3772
|
+
case "double":
|
|
3773
|
+
return "double precision";
|
|
3774
|
+
case "decimal":
|
|
3775
|
+
return "decimal(10,2)";
|
|
3776
|
+
case "date":
|
|
3777
|
+
return "date";
|
|
3778
|
+
case "datetime":
|
|
3779
|
+
return next.dialect === "mysql" ? "datetime" : "timestamp";
|
|
3780
|
+
case "json":
|
|
3781
|
+
return next.dialect === "mysql" ? "json" : next.dialect === "postgres" ? "jsonb" : "text";
|
|
3782
|
+
default:
|
|
3783
|
+
return "text";
|
|
3784
|
+
}
|
|
3785
|
+
})();
|
|
3786
|
+
const parts = [q(tmp.name), typeSql];
|
|
3787
|
+
if (tmp.isPrimaryKey)
|
|
3788
|
+
parts.push("primary key");
|
|
3789
|
+
if (!tmp.isNullable && !tmp.isPrimaryKey)
|
|
3790
|
+
parts.push("not null");
|
|
3791
|
+
if (tmp.hasDefault) {
|
|
3792
|
+
const dv = tmp.defaultValue;
|
|
3793
|
+
if (typeof dv === "string")
|
|
3794
|
+
parts.push(`default '${dv.replace(/'/g, "''")}'`);
|
|
3795
|
+
else if (typeof dv === "number" || typeof dv === "bigint")
|
|
3796
|
+
parts.push(`default ${dv}`);
|
|
3797
|
+
else if (typeof dv === "boolean")
|
|
3798
|
+
parts.push(`default ${dv ? 1 : 0}`);
|
|
3799
|
+
else if (dv instanceof Date)
|
|
3800
|
+
parts.push(`default '${dv.toISOString()}'`);
|
|
3801
|
+
}
|
|
3802
|
+
return parts.join(" ");
|
|
3803
|
+
}).join(`,
|
|
3804
|
+
`)}
|
|
3805
|
+
);`);
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
for (const tableName of Object.keys(nextTables)) {
|
|
3809
|
+
if (!prevTables[tableName]) {
|
|
3810
|
+
const t = nextTables[tableName];
|
|
3811
|
+
for (const c of t.columns) {
|
|
3812
|
+
if (c.references) {
|
|
3813
|
+
const fkName = `${t.table}_${c.name}_fk`;
|
|
3814
|
+
chunks.push(`ALTER TABLE ${q(t.table)} ADD CONSTRAINT ${q(fkName)} FOREIGN KEY (${q(c.name)}) REFERENCES ${q(c.references.table)}(${q(c.references.column)});`);
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
for (const tableName of Object.keys(nextTables)) {
|
|
3820
|
+
if (!prevTables[tableName]) {
|
|
3821
|
+
const t = nextTables[tableName];
|
|
3822
|
+
for (const idx of t.indexes) {
|
|
3823
|
+
const kind = idx.type === "unique" ? "UNIQUE " : "";
|
|
3824
|
+
const idxName = `${t.table}_${idx.name}`;
|
|
3825
|
+
chunks.push(`CREATE ${kind}INDEX ${q(idxName)} ON ${q(t.table)} (${idx.columns.map((c) => q(c)).join(", ")});`);
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
for (const tableName of Object.keys(nextTables)) {
|
|
3830
|
+
const prev = prevTables[tableName];
|
|
3831
|
+
const curr = nextTables[tableName];
|
|
3832
|
+
if (!prev)
|
|
3833
|
+
continue;
|
|
3834
|
+
const prevCols = mapColumnsByName(prev.columns);
|
|
3835
|
+
const currCols = mapColumnsByName(curr.columns);
|
|
3836
|
+
for (const colName of Object.keys(currCols)) {
|
|
3837
|
+
if (!prevCols[colName]) {
|
|
3838
|
+
const c = currCols[colName];
|
|
3839
|
+
const typeSql = (() => {
|
|
3840
|
+
switch (c.type) {
|
|
3841
|
+
case "string":
|
|
3842
|
+
return next.dialect === "mysql" ? "varchar(255)" : "varchar(255)";
|
|
3843
|
+
case "text":
|
|
3844
|
+
return "text";
|
|
3845
|
+
case "boolean":
|
|
3846
|
+
return next.dialect === "mysql" ? "tinyint(1)" : "boolean";
|
|
3847
|
+
case "integer":
|
|
3848
|
+
return next.dialect === "sqlite" ? "integer" : "integer";
|
|
3849
|
+
case "bigint":
|
|
3850
|
+
return "bigint";
|
|
3851
|
+
case "float":
|
|
3852
|
+
return "real";
|
|
3853
|
+
case "double":
|
|
3854
|
+
return "double precision";
|
|
3855
|
+
case "decimal":
|
|
3856
|
+
return "decimal(10,2)";
|
|
3857
|
+
case "date":
|
|
3858
|
+
return "date";
|
|
3859
|
+
case "datetime":
|
|
3860
|
+
return next.dialect === "mysql" ? "datetime" : "timestamp";
|
|
3861
|
+
case "json":
|
|
3862
|
+
return next.dialect === "mysql" ? "json" : next.dialect === "postgres" ? "jsonb" : "text";
|
|
3863
|
+
default:
|
|
3864
|
+
return "text";
|
|
3865
|
+
}
|
|
3866
|
+
})();
|
|
3867
|
+
const parts = [q(c.name), typeSql];
|
|
3868
|
+
if (!c.isNullable && !c.isPrimaryKey)
|
|
3869
|
+
parts.push("not null");
|
|
3870
|
+
if (c.hasDefault) {
|
|
3871
|
+
const dv = c.defaultValue;
|
|
3872
|
+
if (typeof dv === "string")
|
|
3873
|
+
parts.push(`default '${dv.replace(/'/g, "''")}'`);
|
|
3874
|
+
else if (typeof dv === "number" || typeof dv === "bigint")
|
|
3875
|
+
parts.push(`default ${dv}`);
|
|
3876
|
+
else if (typeof dv === "boolean")
|
|
3877
|
+
parts.push(`default ${dv ? 1 : 0}`);
|
|
3878
|
+
else if (dv instanceof Date)
|
|
3879
|
+
parts.push(`default '${dv.toISOString()}'`);
|
|
3880
|
+
}
|
|
3881
|
+
chunks.push(`ALTER TABLE ${q(curr.table)} ADD COLUMN ${parts.join(" ")};`);
|
|
3882
|
+
if (c.references) {
|
|
3883
|
+
const fkName = `${curr.table}_${c.name}_fk`;
|
|
3884
|
+
chunks.push(`ALTER TABLE ${q(curr.table)} ADD CONSTRAINT ${q(fkName)} FOREIGN KEY (${q(c.name)}) REFERENCES ${q(c.references.table)}(${q(c.references.column)});`);
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
const prevIdx = mapIndexesByKey(prev.indexes);
|
|
3889
|
+
const currIdx = mapIndexesByKey(curr.indexes);
|
|
3890
|
+
for (const key of Object.keys(currIdx)) {
|
|
3891
|
+
if (!prevIdx[key]) {
|
|
3892
|
+
const idx = currIdx[key];
|
|
3893
|
+
const kind = idx.type === "unique" ? "UNIQUE " : "";
|
|
3894
|
+
const idxName = `${curr.table}_${idx.name}`;
|
|
3895
|
+
chunks.push(`CREATE ${kind}INDEX ${q(idxName)} ON ${q(curr.table)} (${idx.columns.map((c) => q(c)).join(", ")});`);
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
if (chunks.length === 0)
|
|
3900
|
+
return ["-- No changes detected"];
|
|
3901
|
+
return chunks;
|
|
3902
|
+
}
|
|
3903
|
+
// src/schema.ts
|
|
3904
|
+
function defineModel(model) {
|
|
3905
|
+
return model;
|
|
3906
|
+
}
|
|
3907
|
+
function defineModels(models) {
|
|
3908
|
+
return models;
|
|
3909
|
+
}
|
|
3910
|
+
export {
|
|
3911
|
+
waitReady,
|
|
3912
|
+
unsafe,
|
|
3913
|
+
sql,
|
|
3914
|
+
ping,
|
|
3915
|
+
loadModels,
|
|
3916
|
+
introspect,
|
|
3917
|
+
hashMigrationPlan,
|
|
3918
|
+
generateSqlString,
|
|
3919
|
+
generateSql,
|
|
3920
|
+
generateMigration,
|
|
3921
|
+
generateDiffSqlString,
|
|
3922
|
+
generateDiffSql,
|
|
3923
|
+
file,
|
|
3924
|
+
explain,
|
|
3925
|
+
executeMigration,
|
|
3926
|
+
defineModels,
|
|
3927
|
+
defineModel,
|
|
3928
|
+
defaultConfig2 as defaultConfig,
|
|
3929
|
+
createQueryBuilder,
|
|
3930
|
+
config2 as config,
|
|
3931
|
+
buildSchemaMeta,
|
|
3932
|
+
buildMigrationPlan,
|
|
3933
|
+
buildDatabaseSchema
|
|
3934
|
+
};
|