@mateusseiboth/ember-orm 0.1.0
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 +21 -0
- package/README.md +125 -0
- package/dist/cli/bin.cjs +3265 -0
- package/dist/cli/bin.cjs.map +1 -0
- package/dist/cli/bin.d.cts +1 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +3241 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/client/index.cjs +2434 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +2 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +2396 -0
- package/dist/client/index.js.map +1 -0
- package/dist/editor.cjs +1113 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +22 -0
- package/dist/editor.d.ts +22 -0
- package/dist/editor.js +1077 -0
- package/dist/editor.js.map +1 -0
- package/dist/index-0lWi8TMM.d.ts +72 -0
- package/dist/index-BSXZjDUd.d.ts +459 -0
- package/dist/index-CKqkQhZx.d.cts +459 -0
- package/dist/index-CMeqhmVc.d.cts +72 -0
- package/dist/index-D0xIdtCl.d.cts +145 -0
- package/dist/index-D0xIdtCl.d.ts +145 -0
- package/dist/index.cjs +5013 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +288 -0
- package/dist/index.d.ts +288 -0
- package/dist/index.js +4935 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,2434 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/client/index.ts
|
|
31
|
+
var client_exports = {};
|
|
32
|
+
__export(client_exports, {
|
|
33
|
+
EmberClientBase: () => EmberClientBase,
|
|
34
|
+
createClient: () => createClient
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(client_exports);
|
|
37
|
+
|
|
38
|
+
// src/driver/firebird-driver.ts
|
|
39
|
+
var import_node_async_hooks = require("async_hooks");
|
|
40
|
+
var import_node_firebird = __toESM(require("node-firebird"), 1);
|
|
41
|
+
|
|
42
|
+
// src/errors/index.ts
|
|
43
|
+
var EmberError = class extends Error {
|
|
44
|
+
constructor(message) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = new.target.name;
|
|
47
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var QueryValidationError = class extends EmberError {
|
|
51
|
+
};
|
|
52
|
+
var DatabaseError = class extends EmberError {
|
|
53
|
+
constructor(message, cause, sql) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.cause = cause;
|
|
56
|
+
this.sql = sql;
|
|
57
|
+
}
|
|
58
|
+
cause;
|
|
59
|
+
sql;
|
|
60
|
+
};
|
|
61
|
+
var RecordNotFoundError = class extends EmberError {
|
|
62
|
+
constructor(model) {
|
|
63
|
+
super(`No '${model}' record found matching the given criteria.`);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/driver/firebird-driver.ts
|
|
68
|
+
var fb = import_node_firebird.default;
|
|
69
|
+
function isolationConstant(level) {
|
|
70
|
+
switch (level) {
|
|
71
|
+
case "READ_COMMITTED_READ_ONLY":
|
|
72
|
+
return fb.ISOLATION_READ_COMMITTED_READ_ONLY;
|
|
73
|
+
case "REPEATABLE_READ":
|
|
74
|
+
return fb.ISOLATION_REPEATABLE_READ;
|
|
75
|
+
case "SERIALIZABLE":
|
|
76
|
+
return fb.ISOLATION_SERIALIZABLE;
|
|
77
|
+
case "READ_COMMITTED":
|
|
78
|
+
default:
|
|
79
|
+
return fb.ISOLATION_READ_COMMITTED;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
var FirebirdDriver = class {
|
|
83
|
+
pool = null;
|
|
84
|
+
options;
|
|
85
|
+
poolMax;
|
|
86
|
+
onQuery;
|
|
87
|
+
activeTx = new import_node_async_hooks.AsyncLocalStorage();
|
|
88
|
+
constructor(config, driverOptions) {
|
|
89
|
+
this.poolMax = config.poolMax ?? 5;
|
|
90
|
+
this.onQuery = driverOptions?.onQuery;
|
|
91
|
+
this.options = {
|
|
92
|
+
host: config.host,
|
|
93
|
+
port: config.port,
|
|
94
|
+
database: config.database,
|
|
95
|
+
user: config.user,
|
|
96
|
+
password: config.password,
|
|
97
|
+
role: config.role ?? "",
|
|
98
|
+
pageSize: config.pageSize ?? 4096,
|
|
99
|
+
encoding: config.encoding ?? "UTF8",
|
|
100
|
+
blobAsText: config.blobAsText ?? true,
|
|
101
|
+
lowercase_keys: config.lowercaseKeys ?? false,
|
|
102
|
+
retryConnectionInterval: 1e3,
|
|
103
|
+
// FB3+ secure auth (Srp) is negotiated by default; set explicitly to force
|
|
104
|
+
// a plugin, or "Legacy_Auth" for Firebird 2.1/2.5 servers.
|
|
105
|
+
...config.authPlugin ? { pluginName: config.authPlugin } : {},
|
|
106
|
+
...config.wireCompression != null ? { wireCompression: config.wireCompression } : {}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async connect() {
|
|
110
|
+
if (this.pool) return;
|
|
111
|
+
this.pool = fb.pool(this.poolMax, this.options);
|
|
112
|
+
}
|
|
113
|
+
async disconnect() {
|
|
114
|
+
if (!this.pool) return;
|
|
115
|
+
this.pool.destroy();
|
|
116
|
+
this.pool = null;
|
|
117
|
+
}
|
|
118
|
+
async transaction(fn, options) {
|
|
119
|
+
const existing = this.activeTx.getStore();
|
|
120
|
+
if (existing) {
|
|
121
|
+
return fn(existing);
|
|
122
|
+
}
|
|
123
|
+
await this.connect();
|
|
124
|
+
const db = await this.acquire();
|
|
125
|
+
const tr = await this.begin(db, options?.isolation);
|
|
126
|
+
const ctx = {
|
|
127
|
+
query: (sql, params) => this.runOnTransaction(tr, sql, params)
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
const result = await this.activeTx.run(ctx, () => fn(ctx));
|
|
131
|
+
await this.commit(tr);
|
|
132
|
+
return result;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
await this.safeRollback(tr);
|
|
135
|
+
throw err;
|
|
136
|
+
} finally {
|
|
137
|
+
db.detach();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ---- promise wrappers over node-firebird -------------------------------
|
|
141
|
+
acquire() {
|
|
142
|
+
return new Promise((resolve2, reject) => {
|
|
143
|
+
this.pool.get((err, db) => {
|
|
144
|
+
if (err) return reject(wrap(err, "Failed to acquire connection"));
|
|
145
|
+
resolve2(db);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
begin(db, isolation) {
|
|
150
|
+
return new Promise((resolve2, reject) => {
|
|
151
|
+
db.transaction(isolationConstant(isolation), (err, tr) => {
|
|
152
|
+
if (err) return reject(wrap(err, "Failed to start transaction"));
|
|
153
|
+
resolve2(tr);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
runOnTransaction(tr, sql, params = []) {
|
|
158
|
+
const start = this.onQuery ? performance.now() : 0;
|
|
159
|
+
return new Promise((resolve2, reject) => {
|
|
160
|
+
tr.query(sql, [...params], (err, result) => {
|
|
161
|
+
if (err) return reject(wrap(err, "Query failed", sql));
|
|
162
|
+
const rows = normalizeRows(result);
|
|
163
|
+
if (this.onQuery) {
|
|
164
|
+
this.onQuery({
|
|
165
|
+
sql,
|
|
166
|
+
params,
|
|
167
|
+
durationMs: performance.now() - start,
|
|
168
|
+
rowCount: rows.length
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
resolve2(rows);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
commit(tr) {
|
|
176
|
+
return new Promise((resolve2, reject) => {
|
|
177
|
+
tr.commit((err) => {
|
|
178
|
+
if (err) return reject(wrap(err, "Failed to commit transaction"));
|
|
179
|
+
resolve2();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
safeRollback(tr) {
|
|
184
|
+
return new Promise((resolve2) => {
|
|
185
|
+
tr.rollback(() => resolve2());
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
function normalizeRows(result) {
|
|
190
|
+
if (Array.isArray(result)) return result;
|
|
191
|
+
if (result === void 0 || result === null) return [];
|
|
192
|
+
return [result];
|
|
193
|
+
}
|
|
194
|
+
function wrap(err, message, sql) {
|
|
195
|
+
const detail = err && typeof err === "object" && "message" in err ? String(err.message) : String(err);
|
|
196
|
+
return new DatabaseError(`${message}: ${detail}`, err, sql);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/driver/url.ts
|
|
200
|
+
function parseConnectionUrl(url) {
|
|
201
|
+
let parsed;
|
|
202
|
+
try {
|
|
203
|
+
parsed = new URL(url);
|
|
204
|
+
} catch {
|
|
205
|
+
throw new EmberError(`Invalid Firebird connection URL: ${url}`);
|
|
206
|
+
}
|
|
207
|
+
if (!/^firebird:?$/.test(parsed.protocol.replace(":", "") + ":")) {
|
|
208
|
+
if (parsed.protocol !== "firebird:") {
|
|
209
|
+
throw new EmberError(
|
|
210
|
+
`Unsupported protocol '${parsed.protocol}'. Expected 'firebird:'.`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const database = normalizeDatabasePath(parsed.pathname);
|
|
215
|
+
if (!database) {
|
|
216
|
+
throw new EmberError(`Connection URL is missing a database path: ${url}`);
|
|
217
|
+
}
|
|
218
|
+
const params = parsed.searchParams;
|
|
219
|
+
const config = {
|
|
220
|
+
host: parsed.hostname || "127.0.0.1",
|
|
221
|
+
port: parsed.port ? Number(parsed.port) : 3050,
|
|
222
|
+
database,
|
|
223
|
+
user: decodeURIComponent(parsed.username || "SYSDBA"),
|
|
224
|
+
password: decodeURIComponent(parsed.password || "masterkey"),
|
|
225
|
+
encoding: params.get("encoding") ?? "UTF8"
|
|
226
|
+
};
|
|
227
|
+
const role = params.get("role");
|
|
228
|
+
if (role) config.role = role;
|
|
229
|
+
const poolMax = params.get("poolMax") ?? params.get("connection_limit");
|
|
230
|
+
if (poolMax) config.poolMax = Number(poolMax);
|
|
231
|
+
const pageSize = params.get("pageSize");
|
|
232
|
+
if (pageSize) config.pageSize = Number(pageSize);
|
|
233
|
+
const auth = (params.get("authPlugin") ?? params.get("auth"))?.toLowerCase();
|
|
234
|
+
if (auth === "legacy" || auth === "legacy_auth") config.authPlugin = "Legacy_Auth";
|
|
235
|
+
else if (auth === "srp") config.authPlugin = "Srp";
|
|
236
|
+
const wireCompression = params.get("wireCompression");
|
|
237
|
+
if (wireCompression != null) {
|
|
238
|
+
config.wireCompression = wireCompression !== "false" && wireCompression !== "0";
|
|
239
|
+
}
|
|
240
|
+
const version = params.get("version");
|
|
241
|
+
if (version) config.version = normalizeVersion(version);
|
|
242
|
+
return config;
|
|
243
|
+
}
|
|
244
|
+
var VERSIONS = /* @__PURE__ */ new Set(["2.1", "2.5", "3", "4", "5"]);
|
|
245
|
+
function normalizeVersion(raw) {
|
|
246
|
+
const trimmed = raw.trim();
|
|
247
|
+
if (VERSIONS.has(trimmed)) return trimmed;
|
|
248
|
+
if (/^2\.1/.test(trimmed)) return "2.1";
|
|
249
|
+
if (/^2\.5/.test(trimmed)) return "2.5";
|
|
250
|
+
const major = trimmed.split(".")[0];
|
|
251
|
+
if (major && VERSIONS.has(major)) return major;
|
|
252
|
+
return void 0;
|
|
253
|
+
}
|
|
254
|
+
function normalizeDatabasePath(pathname) {
|
|
255
|
+
let p = decodeURIComponent(pathname);
|
|
256
|
+
if (p.startsWith("//")) p = p.slice(1);
|
|
257
|
+
if (/^\/[A-Za-z]:\//.test(p)) p = p.slice(1);
|
|
258
|
+
return p;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/driver/index.ts
|
|
262
|
+
function createDriver(source, options) {
|
|
263
|
+
const config = typeof source === "string" ? parseConnectionUrl(source) : source;
|
|
264
|
+
return new FirebirdDriver(config, options);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/sql/fragment.ts
|
|
268
|
+
var Sql = class _Sql {
|
|
269
|
+
parts = [];
|
|
270
|
+
params = [];
|
|
271
|
+
static raw(text) {
|
|
272
|
+
return new _Sql().push(text);
|
|
273
|
+
}
|
|
274
|
+
static value(value) {
|
|
275
|
+
return new _Sql().bind(value);
|
|
276
|
+
}
|
|
277
|
+
static join(fragments, separator) {
|
|
278
|
+
const out = new _Sql();
|
|
279
|
+
fragments.forEach((frag, i) => {
|
|
280
|
+
if (i > 0) out.push(separator);
|
|
281
|
+
out.append(frag);
|
|
282
|
+
});
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
/** Append raw, trusted SQL text (keywords, already-escaped identifiers). */
|
|
286
|
+
push(text) {
|
|
287
|
+
this.parts.push(text);
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
/** Append a `?` placeholder bound to `value`. */
|
|
291
|
+
bind(value) {
|
|
292
|
+
this.parts.push("?");
|
|
293
|
+
this.params.push(value);
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
/** Append a comma-separated list of placeholders bound to `values`. */
|
|
297
|
+
bindList(values) {
|
|
298
|
+
this.parts.push(values.map(() => "?").join(", "));
|
|
299
|
+
this.params.push(...values);
|
|
300
|
+
return this;
|
|
301
|
+
}
|
|
302
|
+
/** Merge another fragment (text + params) into this one. */
|
|
303
|
+
append(other) {
|
|
304
|
+
this.parts.push(other.text);
|
|
305
|
+
this.params.push(...other.params);
|
|
306
|
+
return this;
|
|
307
|
+
}
|
|
308
|
+
get text() {
|
|
309
|
+
return this.parts.join("");
|
|
310
|
+
}
|
|
311
|
+
isEmpty() {
|
|
312
|
+
return this.text.trim().length === 0;
|
|
313
|
+
}
|
|
314
|
+
toQuery() {
|
|
315
|
+
return { sql: this.text, params: this.params };
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// src/sql/dialect.ts
|
|
320
|
+
var FirebirdDialect = class {
|
|
321
|
+
supportsReturning = true;
|
|
322
|
+
version;
|
|
323
|
+
supportsBooleanType;
|
|
324
|
+
supportsIdentity;
|
|
325
|
+
supportsWindowFunctions;
|
|
326
|
+
constructor(options = {}) {
|
|
327
|
+
this.version = options.version ?? "3";
|
|
328
|
+
const rank = versionRank(this.version);
|
|
329
|
+
this.supportsBooleanType = rank >= 30;
|
|
330
|
+
this.supportsIdentity = rank >= 30;
|
|
331
|
+
this.supportsWindowFunctions = rank >= 30;
|
|
332
|
+
}
|
|
333
|
+
booleanColumnType() {
|
|
334
|
+
return this.supportsBooleanType ? "BOOLEAN" : "SMALLINT";
|
|
335
|
+
}
|
|
336
|
+
quoteId(name) {
|
|
337
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
338
|
+
}
|
|
339
|
+
quoteRef(table, column) {
|
|
340
|
+
return `${this.quoteId(table)}.${this.quoteId(column)}`;
|
|
341
|
+
}
|
|
342
|
+
paginationClause(take, skip) {
|
|
343
|
+
const parts = [];
|
|
344
|
+
if (typeof take === "number" && take >= 0) {
|
|
345
|
+
parts.push(`FIRST ${Math.trunc(take)}`);
|
|
346
|
+
}
|
|
347
|
+
if (typeof skip === "number" && skip > 0) {
|
|
348
|
+
parts.push(`SKIP ${Math.trunc(skip)}`);
|
|
349
|
+
}
|
|
350
|
+
return parts.join(" ");
|
|
351
|
+
}
|
|
352
|
+
caseInsensitive(expr) {
|
|
353
|
+
return `UPPER(${expr})`;
|
|
354
|
+
}
|
|
355
|
+
defaultFunctionSql(name) {
|
|
356
|
+
switch (name) {
|
|
357
|
+
case "now":
|
|
358
|
+
return "CURRENT_TIMESTAMP";
|
|
359
|
+
case "uuid":
|
|
360
|
+
case "cuid":
|
|
361
|
+
return "UUID_TO_CHAR(GEN_UUID())";
|
|
362
|
+
case "autoincrement":
|
|
363
|
+
return null;
|
|
364
|
+
// handled via generators/identity, not an inline default
|
|
365
|
+
default:
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
coerceValue(value) {
|
|
370
|
+
if (value === null || value === void 0) return null;
|
|
371
|
+
if (value instanceof Date) return value;
|
|
372
|
+
if (Buffer.isBuffer(value)) return value;
|
|
373
|
+
if (typeof value === "boolean") {
|
|
374
|
+
return this.supportsBooleanType ? value : value ? 1 : 0;
|
|
375
|
+
}
|
|
376
|
+
if (typeof value === "bigint") return value;
|
|
377
|
+
if (typeof value === "number") return value;
|
|
378
|
+
if (typeof value === "string") return value;
|
|
379
|
+
return JSON.stringify(value);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
function versionRank(version) {
|
|
383
|
+
switch (version) {
|
|
384
|
+
case "2.1":
|
|
385
|
+
return 21;
|
|
386
|
+
case "2.5":
|
|
387
|
+
return 25;
|
|
388
|
+
case "3":
|
|
389
|
+
return 30;
|
|
390
|
+
case "4":
|
|
391
|
+
return 40;
|
|
392
|
+
case "5":
|
|
393
|
+
return 50;
|
|
394
|
+
default:
|
|
395
|
+
return 30;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/ast/index.ts
|
|
400
|
+
function findModel(schema, name) {
|
|
401
|
+
return schema.models.find((m) => m.name === name);
|
|
402
|
+
}
|
|
403
|
+
function fieldColumn(field) {
|
|
404
|
+
return field.dbName ?? field.name.toUpperCase();
|
|
405
|
+
}
|
|
406
|
+
function modelTable(model) {
|
|
407
|
+
return model.dbName ?? model.name.toUpperCase();
|
|
408
|
+
}
|
|
409
|
+
function scalarFields(model) {
|
|
410
|
+
return model.fields.filter((f) => f.kind !== "object");
|
|
411
|
+
}
|
|
412
|
+
function relationFields(model) {
|
|
413
|
+
return model.fields.filter((f) => f.kind === "object");
|
|
414
|
+
}
|
|
415
|
+
function idFields(model) {
|
|
416
|
+
if (model.primaryKey.length > 0) {
|
|
417
|
+
return model.primaryKey.map((n) => model.fields.find((f) => f.name === n)).filter((f) => !!f);
|
|
418
|
+
}
|
|
419
|
+
return model.fields.filter((f) => f.isId);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/utils/index.ts
|
|
423
|
+
function isPlainObject(v) {
|
|
424
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Date) && !Buffer.isBuffer(v);
|
|
425
|
+
}
|
|
426
|
+
function uniq(items) {
|
|
427
|
+
return [...new Set(items)];
|
|
428
|
+
}
|
|
429
|
+
function lowerFirst(input) {
|
|
430
|
+
return input.charAt(0).toLowerCase() + input.slice(1);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/query/order.ts
|
|
434
|
+
function compileOrderBy(model, tableAlias, orderBy, dialect) {
|
|
435
|
+
if (!orderBy) return new Sql();
|
|
436
|
+
const entries = normalize(orderBy);
|
|
437
|
+
if (entries.length === 0) return new Sql();
|
|
438
|
+
const parts = entries.map(([fieldName, dir]) => {
|
|
439
|
+
const field = model.fields.find((f) => f.name === fieldName);
|
|
440
|
+
if (!field || field.kind === "object") {
|
|
441
|
+
throw new QueryValidationError(
|
|
442
|
+
`Cannot order '${model.name}' by '${fieldName}'.`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
const ref = dialect.quoteRef(tableAlias, fieldColumn(field));
|
|
446
|
+
return Sql.raw(`${ref} ${dir === "desc" ? "DESC" : "ASC"}`);
|
|
447
|
+
});
|
|
448
|
+
return Sql.join(parts, ", ");
|
|
449
|
+
}
|
|
450
|
+
function normalize(orderBy) {
|
|
451
|
+
const list = Array.isArray(orderBy) ? orderBy : [orderBy];
|
|
452
|
+
const out = [];
|
|
453
|
+
for (const obj of list) {
|
|
454
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
455
|
+
if (value === "asc" || value === "desc") out.push([key, value]);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return out;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/query/relations.ts
|
|
462
|
+
function resolveRelation(schema, model, field) {
|
|
463
|
+
if (field.kind !== "object") {
|
|
464
|
+
throw new QueryValidationError(
|
|
465
|
+
`Field '${model.name}.${field.name}' is not a relation.`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
const relatedModel = findModel(schema, field.type);
|
|
469
|
+
if (!relatedModel) {
|
|
470
|
+
throw new QueryValidationError(
|
|
471
|
+
`Relation '${model.name}.${field.name}' points to unknown model '${field.type}'.`
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
if (field.relation?.fields?.length) {
|
|
475
|
+
const fromFields2 = field.relation.fields;
|
|
476
|
+
const toFields2 = field.relation.references ?? idFields(relatedModel).map((f) => f.name);
|
|
477
|
+
return build(field, model, relatedModel, fromFields2, toFields2, true);
|
|
478
|
+
}
|
|
479
|
+
const partner = findPartnerField(schema, model, field, relatedModel);
|
|
480
|
+
if (partner?.relation?.fields?.length) {
|
|
481
|
+
const toFields2 = partner.relation.fields;
|
|
482
|
+
const fromFields2 = partner.relation.references ?? idFields(model).map((f) => f.name);
|
|
483
|
+
return build(field, model, relatedModel, fromFields2, toFields2, false);
|
|
484
|
+
}
|
|
485
|
+
const fromFields = idFields(model).map((f) => f.name);
|
|
486
|
+
const toFields = idFields(relatedModel).map((f) => f.name);
|
|
487
|
+
return build(field, model, relatedModel, fromFields, toFields, false);
|
|
488
|
+
}
|
|
489
|
+
function build(field, model, relatedModel, fromFields, toFields, owns) {
|
|
490
|
+
return {
|
|
491
|
+
field,
|
|
492
|
+
relatedModel,
|
|
493
|
+
fromFields,
|
|
494
|
+
toFields,
|
|
495
|
+
fromColumns: fromFields.map((n) => columnOf(model, n)),
|
|
496
|
+
toColumns: toFields.map((n) => columnOf(relatedModel, n)),
|
|
497
|
+
isList: field.isList,
|
|
498
|
+
owns
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function columnOf(model, fieldName) {
|
|
502
|
+
const f = model.fields.find((x) => x.name === fieldName);
|
|
503
|
+
if (!f) {
|
|
504
|
+
throw new QueryValidationError(
|
|
505
|
+
`Field '${fieldName}' not found on model '${model.name}'.`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
return fieldColumn(f);
|
|
509
|
+
}
|
|
510
|
+
function findPartnerField(_schema, model, field, relatedModel) {
|
|
511
|
+
const candidates = relatedModel.fields.filter(
|
|
512
|
+
(f) => f.kind === "object" && f.type === model.name
|
|
513
|
+
);
|
|
514
|
+
if (field.relation?.name) {
|
|
515
|
+
const byName = candidates.find(
|
|
516
|
+
(f) => f.relation?.name === field.relation?.name
|
|
517
|
+
);
|
|
518
|
+
if (byName) return byName;
|
|
519
|
+
}
|
|
520
|
+
const owning = candidates.find((f) => f.relation?.fields?.length);
|
|
521
|
+
return owning ?? candidates[0];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/query/where.ts
|
|
525
|
+
var SCALAR_OPERATORS = /* @__PURE__ */ new Set([
|
|
526
|
+
"equals",
|
|
527
|
+
"not",
|
|
528
|
+
"in",
|
|
529
|
+
"notIn",
|
|
530
|
+
"lt",
|
|
531
|
+
"lte",
|
|
532
|
+
"gt",
|
|
533
|
+
"gte",
|
|
534
|
+
"contains",
|
|
535
|
+
"startsWith",
|
|
536
|
+
"endsWith",
|
|
537
|
+
"mode"
|
|
538
|
+
]);
|
|
539
|
+
function compileWhere(model, tableAlias, where, ctx) {
|
|
540
|
+
if (!where || Object.keys(where).length === 0) return new Sql();
|
|
541
|
+
const conditions = [];
|
|
542
|
+
for (const [key, value] of Object.entries(where)) {
|
|
543
|
+
if (value === void 0) continue;
|
|
544
|
+
if (key === "AND") {
|
|
545
|
+
conditions.push(combineList(asArray(value), model, tableAlias, ctx, "AND"));
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (key === "OR") {
|
|
549
|
+
conditions.push(combineList(asArray(value), model, tableAlias, ctx, "OR"));
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (key === "NOT") {
|
|
553
|
+
const inner = combineList(asArray(value), model, tableAlias, ctx, "AND");
|
|
554
|
+
if (!inner.isEmpty()) {
|
|
555
|
+
conditions.push(new Sql().push("NOT (").append(inner).push(")"));
|
|
556
|
+
}
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
const field = model.fields.find((f) => f.name === key);
|
|
560
|
+
if (!field) {
|
|
561
|
+
throw new QueryValidationError(
|
|
562
|
+
`Unknown field '${key}' in where clause of model '${model.name}'.`
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
if (field.kind === "object") {
|
|
566
|
+
conditions.push(
|
|
567
|
+
compileRelationFilter(model, tableAlias, field, value, ctx)
|
|
568
|
+
);
|
|
569
|
+
} else {
|
|
570
|
+
conditions.push(compileScalar(tableAlias, field, value, ctx));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const nonEmpty = conditions.filter((c) => !c.isEmpty());
|
|
574
|
+
if (nonEmpty.length === 0) return new Sql();
|
|
575
|
+
return wrapAnd(nonEmpty);
|
|
576
|
+
}
|
|
577
|
+
function combineList(list, model, tableAlias, ctx, op) {
|
|
578
|
+
const parts = list.map((w) => compileWhere(model, tableAlias, w, ctx)).filter((p) => !p.isEmpty());
|
|
579
|
+
if (parts.length === 0) return new Sql();
|
|
580
|
+
if (parts.length === 1) return parts[0];
|
|
581
|
+
const joined = Sql.join(
|
|
582
|
+
parts.map((p) => new Sql().push("(").append(p).push(")")),
|
|
583
|
+
` ${op} `
|
|
584
|
+
);
|
|
585
|
+
return new Sql().push("(").append(joined).push(")");
|
|
586
|
+
}
|
|
587
|
+
function wrapAnd(parts) {
|
|
588
|
+
if (parts.length === 1) return parts[0];
|
|
589
|
+
const joined = Sql.join(
|
|
590
|
+
parts.map((p) => new Sql().push("(").append(p).push(")")),
|
|
591
|
+
" AND "
|
|
592
|
+
);
|
|
593
|
+
return new Sql().push("(").append(joined).push(")");
|
|
594
|
+
}
|
|
595
|
+
function compileScalar(tableAlias, field, value, ctx) {
|
|
596
|
+
const ref = ctx.dialect.quoteRef(tableAlias, fieldColumn(field));
|
|
597
|
+
if (field.type === "Json" && isJsonFilterObject(value)) {
|
|
598
|
+
return compileJsonFilter(ref, field, value, ctx);
|
|
599
|
+
}
|
|
600
|
+
if (!isFilterObject(value)) {
|
|
601
|
+
if (value === null) return Sql.raw(`${ref} IS NULL`);
|
|
602
|
+
return new Sql().push(`${ref} = `).bind(ctx.dialect.coerceValue(value));
|
|
603
|
+
}
|
|
604
|
+
const filter = value;
|
|
605
|
+
const insensitive = filter.mode === "insensitive";
|
|
606
|
+
const parts = [];
|
|
607
|
+
for (const [op, opValue] of Object.entries(filter)) {
|
|
608
|
+
if (op === "mode") continue;
|
|
609
|
+
if (opValue === void 0) continue;
|
|
610
|
+
parts.push(compileOperator(ref, op, opValue, insensitive, ctx));
|
|
611
|
+
}
|
|
612
|
+
if (parts.length === 0) return new Sql();
|
|
613
|
+
return wrapAnd(parts);
|
|
614
|
+
}
|
|
615
|
+
var JSON_OPERATORS = /* @__PURE__ */ new Set([
|
|
616
|
+
"equals",
|
|
617
|
+
"not",
|
|
618
|
+
"string_contains",
|
|
619
|
+
"string_starts_with",
|
|
620
|
+
"string_ends_with",
|
|
621
|
+
"path"
|
|
622
|
+
]);
|
|
623
|
+
function isJsonFilterObject(value) {
|
|
624
|
+
return isPlainObject(value) && Object.keys(value).some((k) => JSON_OPERATORS.has(k));
|
|
625
|
+
}
|
|
626
|
+
function compileJsonFilter(ref, field, filter, ctx) {
|
|
627
|
+
const parts = [];
|
|
628
|
+
for (const [op, raw] of Object.entries(filter)) {
|
|
629
|
+
if (raw === void 0) continue;
|
|
630
|
+
switch (op) {
|
|
631
|
+
case "path":
|
|
632
|
+
throw new QueryValidationError(
|
|
633
|
+
`JSON 'path' filtering is not supported on Firebird (no JSON SQL functions). Filter '${field.name}' in application code or with a generated column.`
|
|
634
|
+
);
|
|
635
|
+
case "equals":
|
|
636
|
+
parts.push(
|
|
637
|
+
raw === null ? Sql.raw(`${ref} IS NULL`) : new Sql().push(`${ref} = `).bind(jsonText(raw))
|
|
638
|
+
);
|
|
639
|
+
break;
|
|
640
|
+
case "not":
|
|
641
|
+
parts.push(
|
|
642
|
+
raw === null ? Sql.raw(`${ref} IS NOT NULL`) : new Sql().push(`${ref} <> `).bind(jsonText(raw))
|
|
643
|
+
);
|
|
644
|
+
break;
|
|
645
|
+
case "string_contains":
|
|
646
|
+
parts.push(jsonLike(ref, `%${escapeLike(String(raw))}%`));
|
|
647
|
+
break;
|
|
648
|
+
case "string_starts_with":
|
|
649
|
+
parts.push(jsonLike(ref, `${escapeLike(String(raw))}%`));
|
|
650
|
+
break;
|
|
651
|
+
case "string_ends_with":
|
|
652
|
+
parts.push(jsonLike(ref, `%${escapeLike(String(raw))}`));
|
|
653
|
+
break;
|
|
654
|
+
default:
|
|
655
|
+
throw new QueryValidationError(`Unsupported JSON filter operator '${op}'.`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return parts.length ? wrapAnd(parts) : new Sql();
|
|
659
|
+
}
|
|
660
|
+
function jsonText(value) {
|
|
661
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
662
|
+
}
|
|
663
|
+
function jsonLike(ref, pattern) {
|
|
664
|
+
return new Sql().push(`${ref} LIKE `).bind(pattern).push(" ESCAPE '\\'");
|
|
665
|
+
}
|
|
666
|
+
function compileOperator(ref, op, value, insensitive, ctx) {
|
|
667
|
+
const d = ctx.dialect;
|
|
668
|
+
const bound = (v) => d.coerceValue(v);
|
|
669
|
+
const lhs = insensitive ? d.caseInsensitive(ref) : ref;
|
|
670
|
+
const rhs = (v) => insensitive && typeof v === "string" ? new Sql().push("UPPER(").bind(bound(v)).push(")") : Sql.value(bound(v));
|
|
671
|
+
switch (op) {
|
|
672
|
+
case "equals":
|
|
673
|
+
if (value === null) return Sql.raw(`${ref} IS NULL`);
|
|
674
|
+
return new Sql().push(`${lhs} = `).append(rhs(value));
|
|
675
|
+
case "not":
|
|
676
|
+
if (value === null) return Sql.raw(`${ref} IS NOT NULL`);
|
|
677
|
+
if (isFilterObject(value)) {
|
|
678
|
+
const inner = compileScalarFromRef(ref, value, insensitive, ctx);
|
|
679
|
+
return new Sql().push("NOT (").append(inner).push(")");
|
|
680
|
+
}
|
|
681
|
+
return new Sql().push(`${lhs} <> `).append(rhs(value));
|
|
682
|
+
case "in":
|
|
683
|
+
return inClause(lhs, asUnknownArray(value), insensitive, ctx, false);
|
|
684
|
+
case "notIn":
|
|
685
|
+
return inClause(lhs, asUnknownArray(value), insensitive, ctx, true);
|
|
686
|
+
case "lt":
|
|
687
|
+
return new Sql().push(`${lhs} < `).append(rhs(value));
|
|
688
|
+
case "lte":
|
|
689
|
+
return new Sql().push(`${lhs} <= `).append(rhs(value));
|
|
690
|
+
case "gt":
|
|
691
|
+
return new Sql().push(`${lhs} > `).append(rhs(value));
|
|
692
|
+
case "gte":
|
|
693
|
+
return new Sql().push(`${lhs} >= `).append(rhs(value));
|
|
694
|
+
case "contains":
|
|
695
|
+
return likeClause(lhs, `%${escapeLike(String(value))}%`, insensitive);
|
|
696
|
+
case "startsWith":
|
|
697
|
+
return likeClause(lhs, `${escapeLike(String(value))}%`, insensitive);
|
|
698
|
+
case "endsWith":
|
|
699
|
+
return likeClause(lhs, `%${escapeLike(String(value))}`, insensitive);
|
|
700
|
+
default:
|
|
701
|
+
throw new QueryValidationError(`Unsupported filter operator '${op}'.`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function compileScalarFromRef(ref, filter, insensitive, ctx) {
|
|
705
|
+
const parts = [];
|
|
706
|
+
for (const [op, opValue] of Object.entries(filter)) {
|
|
707
|
+
if (op === "mode" || opValue === void 0) continue;
|
|
708
|
+
parts.push(compileOperator(ref, op, opValue, insensitive, ctx));
|
|
709
|
+
}
|
|
710
|
+
return parts.length ? wrapAnd(parts) : new Sql();
|
|
711
|
+
}
|
|
712
|
+
function inClause(lhs, values, insensitive, ctx, negate) {
|
|
713
|
+
if (values.length === 0) {
|
|
714
|
+
return Sql.raw(negate ? "1 = 1" : "1 = 0");
|
|
715
|
+
}
|
|
716
|
+
const coerced = values.map(
|
|
717
|
+
(v) => insensitive && typeof v === "string" ? String(v).toUpperCase() : ctx.dialect.coerceValue(v)
|
|
718
|
+
);
|
|
719
|
+
const sql = new Sql().push(`${lhs} ${negate ? "NOT IN" : "IN"} (`);
|
|
720
|
+
sql.bindList(coerced);
|
|
721
|
+
sql.push(")");
|
|
722
|
+
return sql;
|
|
723
|
+
}
|
|
724
|
+
function likeClause(lhs, pattern, insensitive) {
|
|
725
|
+
const value = insensitive ? pattern.toUpperCase() : pattern;
|
|
726
|
+
return new Sql().push(`${lhs} LIKE `).bind(value).push(" ESCAPE '\\'");
|
|
727
|
+
}
|
|
728
|
+
function compileRelationFilter(model, tableAlias, field, value, ctx) {
|
|
729
|
+
const rel = resolveRelation(ctx.schema, model, field);
|
|
730
|
+
const filter = isPlainObject(value) ? value : {};
|
|
731
|
+
const hasOperator = "some" in filter || "every" in filter || "none" in filter || "is" in filter || "isNot" in filter;
|
|
732
|
+
if (!rel.isList && !hasOperator) {
|
|
733
|
+
return existsClause(model, tableAlias, rel, value, ctx, false);
|
|
734
|
+
}
|
|
735
|
+
const parts = [];
|
|
736
|
+
if (filter.some !== void 0) {
|
|
737
|
+
parts.push(existsClause(model, tableAlias, rel, filter.some ?? {}, ctx, false));
|
|
738
|
+
}
|
|
739
|
+
if (filter.none !== void 0) {
|
|
740
|
+
parts.push(existsClause(model, tableAlias, rel, filter.none ?? {}, ctx, true));
|
|
741
|
+
}
|
|
742
|
+
if (filter.every !== void 0) {
|
|
743
|
+
parts.push(
|
|
744
|
+
existsClause(model, tableAlias, rel, filter.every ?? {}, ctx, true, true)
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
if (filter.is !== void 0) {
|
|
748
|
+
if (filter.is === null) {
|
|
749
|
+
parts.push(notExists(model, tableAlias, rel, {}, ctx));
|
|
750
|
+
} else {
|
|
751
|
+
parts.push(existsClause(model, tableAlias, rel, filter.is, ctx, false));
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (filter.isNot !== void 0) {
|
|
755
|
+
if (filter.isNot === null) {
|
|
756
|
+
parts.push(existsClause(model, tableAlias, rel, {}, ctx, false));
|
|
757
|
+
} else {
|
|
758
|
+
parts.push(existsClause(model, tableAlias, rel, filter.isNot, ctx, true));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return parts.length ? wrapAnd(parts) : new Sql();
|
|
762
|
+
}
|
|
763
|
+
function notExists(model, tableAlias, rel, sub, ctx) {
|
|
764
|
+
return existsClause(model, tableAlias, rel, sub, ctx, true);
|
|
765
|
+
}
|
|
766
|
+
function existsClause(_model, tableAlias, rel, sub, ctx, negate, everyMode = false) {
|
|
767
|
+
const childAlias = `r${ctx.alias.next++}`;
|
|
768
|
+
const childTable = ctx.dialect.quoteId(modelTable(rel.relatedModel));
|
|
769
|
+
const sql = new Sql();
|
|
770
|
+
sql.push(`${negate ? "NOT EXISTS" : "EXISTS"} (SELECT 1 FROM ${childTable} ${ctx.dialect.quoteId(childAlias)} WHERE `);
|
|
771
|
+
const joins = rel.fromColumns.map((fromCol, i) => {
|
|
772
|
+
const toCol = rel.toColumns[i];
|
|
773
|
+
return `${ctx.dialect.quoteRef(childAlias, toCol)} = ${ctx.dialect.quoteRef(tableAlias, fromCol)}`;
|
|
774
|
+
});
|
|
775
|
+
sql.push(joins.join(" AND "));
|
|
776
|
+
const subSql = compileWhere(rel.relatedModel, childAlias, sub, ctx);
|
|
777
|
+
if (!subSql.isEmpty()) {
|
|
778
|
+
if (everyMode) {
|
|
779
|
+
sql.push(" AND NOT (").append(subSql).push(")");
|
|
780
|
+
} else {
|
|
781
|
+
sql.push(" AND ").append(subSql);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
sql.push(")");
|
|
785
|
+
return sql;
|
|
786
|
+
}
|
|
787
|
+
function isFilterObject(value) {
|
|
788
|
+
if (!isPlainObject(value)) return false;
|
|
789
|
+
return Object.keys(value).some((k) => SCALAR_OPERATORS.has(k));
|
|
790
|
+
}
|
|
791
|
+
function asArray(value) {
|
|
792
|
+
const list = Array.isArray(value) ? value : [value];
|
|
793
|
+
return list.filter(
|
|
794
|
+
(v) => v !== null && typeof v === "object"
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
function asUnknownArray(value) {
|
|
798
|
+
return Array.isArray(value) ? value : [value];
|
|
799
|
+
}
|
|
800
|
+
function escapeLike(input) {
|
|
801
|
+
return input.replace(/[\\%_]/g, (c) => `\\${c}`);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/query/having.ts
|
|
805
|
+
var AGG_FN = {
|
|
806
|
+
_count: "COUNT",
|
|
807
|
+
_sum: "SUM",
|
|
808
|
+
_avg: "AVG",
|
|
809
|
+
_min: "MIN",
|
|
810
|
+
_max: "MAX"
|
|
811
|
+
};
|
|
812
|
+
var COMPARATORS = {
|
|
813
|
+
equals: "=",
|
|
814
|
+
not: "<>",
|
|
815
|
+
gt: ">",
|
|
816
|
+
gte: ">=",
|
|
817
|
+
lt: "<",
|
|
818
|
+
lte: "<="
|
|
819
|
+
};
|
|
820
|
+
function compileHaving(model, alias, having, ctx) {
|
|
821
|
+
if (!having || Object.keys(having).length === 0) return new Sql();
|
|
822
|
+
const parts = [];
|
|
823
|
+
for (const [key, value] of Object.entries(having)) {
|
|
824
|
+
if (value === void 0) continue;
|
|
825
|
+
if (key === "AND" || key === "OR") {
|
|
826
|
+
const list = Array.isArray(value) ? value : [value];
|
|
827
|
+
const compiled = list.map((w) => compileHaving(model, alias, w, ctx)).filter((s) => !s.isEmpty());
|
|
828
|
+
if (compiled.length) parts.push(joinBool(compiled, key));
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
if (key === "NOT") {
|
|
832
|
+
const inner = compileHaving(model, alias, value, ctx);
|
|
833
|
+
if (!inner.isEmpty()) parts.push(new Sql().push("NOT (").append(inner).push(")"));
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (key in AGG_FN) {
|
|
837
|
+
parts.push(...aggregateConditions(model, alias, key, value, ctx));
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
const field = model.fields.find((f) => f.name === key && f.kind !== "object");
|
|
841
|
+
if (!field) {
|
|
842
|
+
throw new QueryValidationError(
|
|
843
|
+
`Unknown having field '${model.name}.${key}'.`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
if (isAggregateWrapper(value)) {
|
|
847
|
+
const ref = ctx.dialect.quoteRef(alias, fieldColumn(field));
|
|
848
|
+
for (const [agg, filter] of Object.entries(value)) {
|
|
849
|
+
if (!(agg in AGG_FN)) continue;
|
|
850
|
+
parts.push(comparison(`${AGG_FN[agg]}(${ref})`, filter, ctx.dialect));
|
|
851
|
+
}
|
|
852
|
+
} else {
|
|
853
|
+
const cond = compileWhere(model, alias, { [key]: value }, ctx);
|
|
854
|
+
if (!cond.isEmpty()) parts.push(cond);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
const nonEmpty = parts.filter((p) => !p.isEmpty());
|
|
858
|
+
if (nonEmpty.length === 0) return new Sql();
|
|
859
|
+
return joinBool(nonEmpty, "AND");
|
|
860
|
+
}
|
|
861
|
+
function aggregateConditions(model, alias, agg, spec, ctx) {
|
|
862
|
+
if (!isPlainObject(spec)) return [];
|
|
863
|
+
const out = [];
|
|
864
|
+
for (const [fieldName, filter] of Object.entries(spec)) {
|
|
865
|
+
const field = model.fields.find((f) => f.name === fieldName && f.kind !== "object");
|
|
866
|
+
if (!field) continue;
|
|
867
|
+
const ref = ctx.dialect.quoteRef(alias, fieldColumn(field));
|
|
868
|
+
out.push(comparison(`${AGG_FN[agg]}(${ref})`, filter, ctx.dialect));
|
|
869
|
+
}
|
|
870
|
+
return out;
|
|
871
|
+
}
|
|
872
|
+
function comparison(lhs, filter, dialect) {
|
|
873
|
+
if (!isPlainObject(filter)) {
|
|
874
|
+
return new Sql().push(`${lhs} = `).bind(dialect.coerceValue(filter));
|
|
875
|
+
}
|
|
876
|
+
const parts = [];
|
|
877
|
+
for (const [op, v] of Object.entries(filter)) {
|
|
878
|
+
const sqlOp = COMPARATORS[op];
|
|
879
|
+
if (!sqlOp) {
|
|
880
|
+
throw new QueryValidationError(`Unsupported having operator '${op}'.`);
|
|
881
|
+
}
|
|
882
|
+
parts.push(new Sql().push(`${lhs} ${sqlOp} `).bind(dialect.coerceValue(v)));
|
|
883
|
+
}
|
|
884
|
+
return parts.length === 1 ? parts[0] : joinBool(parts, "AND");
|
|
885
|
+
}
|
|
886
|
+
function isAggregateWrapper(value) {
|
|
887
|
+
return isPlainObject(value) && Object.keys(value).some((k) => k in AGG_FN);
|
|
888
|
+
}
|
|
889
|
+
function joinBool(parts, op) {
|
|
890
|
+
if (parts.length === 1) return parts[0];
|
|
891
|
+
const joined = Sql.join(
|
|
892
|
+
parts.map((p) => new Sql().push("(").append(p).push(")")),
|
|
893
|
+
` ${op} `
|
|
894
|
+
);
|
|
895
|
+
return new Sql().push("(").append(joined).push(")");
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// src/query/compiler.ts
|
|
899
|
+
function setAssignments(values) {
|
|
900
|
+
const out = /* @__PURE__ */ new Map();
|
|
901
|
+
for (const [col, value] of values) out.set(col, { kind: "set", value });
|
|
902
|
+
return out;
|
|
903
|
+
}
|
|
904
|
+
var ROOT_ALIAS = "t0";
|
|
905
|
+
function newContext(schema, dialect) {
|
|
906
|
+
return { schema, dialect, alias: { next: 1 } };
|
|
907
|
+
}
|
|
908
|
+
function compileFindMany(model, options, projection, ctx) {
|
|
909
|
+
const d = ctx.dialect;
|
|
910
|
+
const cols = projection.map(
|
|
911
|
+
(f) => `${d.quoteRef(ROOT_ALIAS, fieldColumn(f))} AS ${d.quoteId(f.name)}`
|
|
912
|
+
);
|
|
913
|
+
const sql = new Sql();
|
|
914
|
+
const pagination = d.paginationClause(options.take, options.skip);
|
|
915
|
+
sql.push(pagination ? `SELECT ${pagination} ` : "SELECT ");
|
|
916
|
+
sql.push(cols.join(", "));
|
|
917
|
+
sql.push(` FROM ${d.quoteId(modelTable(model))} ${d.quoteId(ROOT_ALIAS)}`);
|
|
918
|
+
appendWhere(sql, model, options.where, ctx);
|
|
919
|
+
const order = compileOrderBy(model, ROOT_ALIAS, options.orderBy, d);
|
|
920
|
+
if (!order.isEmpty()) sql.push(" ORDER BY ").append(order);
|
|
921
|
+
return { sql, columns: projection };
|
|
922
|
+
}
|
|
923
|
+
function compileDistinctFindMany(model, options, projection, distinctFields, ctx) {
|
|
924
|
+
const d = ctx.dialect;
|
|
925
|
+
const innerOrder = options.orderBy ? compileOrderBy(model, ROOT_ALIAS, options.orderBy, d) : defaultOrder(model, d);
|
|
926
|
+
const partitionBy = distinctFields.map((f) => d.quoteRef(ROOT_ALIAS, fieldColumn(f))).join(", ");
|
|
927
|
+
const inner = new Sql();
|
|
928
|
+
const innerCols = projection.map(
|
|
929
|
+
(f) => `${d.quoteRef(ROOT_ALIAS, fieldColumn(f))} AS ${d.quoteId(f.name)}`
|
|
930
|
+
);
|
|
931
|
+
inner.push(`SELECT ${innerCols.join(", ")}, ROW_NUMBER() OVER (PARTITION BY ${partitionBy} ORDER BY `);
|
|
932
|
+
inner.append(innerOrder);
|
|
933
|
+
inner.push(`) AS ${d.quoteId("__rn")}`);
|
|
934
|
+
inner.push(` FROM ${d.quoteId(modelTable(model))} ${d.quoteId(ROOT_ALIAS)}`);
|
|
935
|
+
appendWhere(inner, model, options.where, ctx);
|
|
936
|
+
const sql = new Sql();
|
|
937
|
+
const pagination = d.paginationClause(options.take, options.skip);
|
|
938
|
+
const sub = "sub";
|
|
939
|
+
const outerCols = projection.map((f) => `${d.quoteRef(sub, f.name)} AS ${d.quoteId(f.name)}`).join(", ");
|
|
940
|
+
sql.push(pagination ? `SELECT ${pagination} ` : "SELECT ");
|
|
941
|
+
sql.push(outerCols);
|
|
942
|
+
sql.push(` FROM (`).append(inner).push(`) ${d.quoteId(sub)}`);
|
|
943
|
+
sql.push(` WHERE ${d.quoteRef(sub, "__rn")} = 1`);
|
|
944
|
+
const outerOrder = outerOrderBy(model, sub, options.orderBy, d);
|
|
945
|
+
if (!outerOrder.isEmpty()) sql.push(" ORDER BY ").append(outerOrder);
|
|
946
|
+
return { sql, columns: projection };
|
|
947
|
+
}
|
|
948
|
+
function compileCount(model, where, ctx, pagination) {
|
|
949
|
+
const d = ctx.dialect;
|
|
950
|
+
const sql = new Sql();
|
|
951
|
+
const clause = pagination ? d.paginationClause(pagination.take, pagination.skip) : "";
|
|
952
|
+
sql.push(clause ? `SELECT ${clause} COUNT(*) AS "_count"` : `SELECT COUNT(*) AS "_count"`);
|
|
953
|
+
sql.push(` FROM ${d.quoteId(modelTable(model))} ${d.quoteId(ROOT_ALIAS)}`);
|
|
954
|
+
appendWhere(sql, model, where, ctx);
|
|
955
|
+
return sql;
|
|
956
|
+
}
|
|
957
|
+
function compileAggregate(model, args, ctx) {
|
|
958
|
+
const d = ctx.dialect;
|
|
959
|
+
const selects = aggregateSelections(model, args, d);
|
|
960
|
+
if (selects.length === 0) {
|
|
961
|
+
throw new QueryValidationError("Aggregate requires at least one operator.");
|
|
962
|
+
}
|
|
963
|
+
const sql = new Sql();
|
|
964
|
+
sql.push(`SELECT ${selects.join(", ")}`);
|
|
965
|
+
sql.push(` FROM ${d.quoteId(modelTable(model))} ${d.quoteId(ROOT_ALIAS)}`);
|
|
966
|
+
appendWhere(sql, model, args.where, ctx);
|
|
967
|
+
return sql;
|
|
968
|
+
}
|
|
969
|
+
function compileGroupBy(model, args, ctx) {
|
|
970
|
+
const d = ctx.dialect;
|
|
971
|
+
const groupColumns = args.by.map((name) => requireScalar(model, name));
|
|
972
|
+
const groupSelects = groupColumns.map(
|
|
973
|
+
(f) => `${d.quoteRef(ROOT_ALIAS, fieldColumn(f))} AS ${d.quoteId(f.name)}`
|
|
974
|
+
);
|
|
975
|
+
const aggSelects = aggregateSelections(model, args, d);
|
|
976
|
+
const sql = new Sql();
|
|
977
|
+
sql.push(`SELECT ${[...groupSelects, ...aggSelects].join(", ")}`);
|
|
978
|
+
sql.push(` FROM ${d.quoteId(modelTable(model))} ${d.quoteId(ROOT_ALIAS)}`);
|
|
979
|
+
appendWhere(sql, model, args.where, ctx);
|
|
980
|
+
sql.push(
|
|
981
|
+
` GROUP BY ${groupColumns.map((f) => d.quoteRef(ROOT_ALIAS, fieldColumn(f))).join(", ")}`
|
|
982
|
+
);
|
|
983
|
+
const having = compileHaving(model, ROOT_ALIAS, args.having, ctx);
|
|
984
|
+
if (!having.isEmpty()) sql.push(" HAVING ").append(having);
|
|
985
|
+
const order = compileOrderBy(model, ROOT_ALIAS, args.orderBy, d);
|
|
986
|
+
if (!order.isEmpty()) sql.push(" ORDER BY ").append(order);
|
|
987
|
+
return { sql, groupColumns };
|
|
988
|
+
}
|
|
989
|
+
function compileInsert(model, row, ctx, returning) {
|
|
990
|
+
const d = ctx.dialect;
|
|
991
|
+
const sql = new Sql();
|
|
992
|
+
const columns = [...row.keys()];
|
|
993
|
+
sql.push(`INSERT INTO ${d.quoteId(modelTable(model))} (`);
|
|
994
|
+
sql.push(columns.map((c) => d.quoteId(c)).join(", "));
|
|
995
|
+
sql.push(") VALUES (");
|
|
996
|
+
sql.bindList(columns.map((c) => row.get(c)));
|
|
997
|
+
sql.push(")");
|
|
998
|
+
appendReturning(sql, d, returning);
|
|
999
|
+
return { sql, columns: returning };
|
|
1000
|
+
}
|
|
1001
|
+
function compileUpdate(model, where, assignments, ctx, returning) {
|
|
1002
|
+
const d = ctx.dialect;
|
|
1003
|
+
if (assignments.size === 0) {
|
|
1004
|
+
throw new QueryValidationError("Update requires at least one field to set.");
|
|
1005
|
+
}
|
|
1006
|
+
const sql = new Sql();
|
|
1007
|
+
sql.push(`UPDATE ${d.quoteId(modelTable(model))} ${d.quoteId(ROOT_ALIAS)} SET `);
|
|
1008
|
+
const cols = [...assignments.keys()];
|
|
1009
|
+
cols.forEach((col, i) => {
|
|
1010
|
+
if (i > 0) sql.push(", ");
|
|
1011
|
+
const assignment = assignments.get(col);
|
|
1012
|
+
const quoted = d.quoteId(col);
|
|
1013
|
+
if (assignment.kind === "arith") {
|
|
1014
|
+
sql.push(`${quoted} = ${quoted} ${assignment.op} `).bind(assignment.value);
|
|
1015
|
+
} else {
|
|
1016
|
+
sql.push(`${quoted} = `).bind(assignment.value);
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
appendWhere(sql, model, where, ctx);
|
|
1020
|
+
if (returning) appendReturning(sql, d, returning);
|
|
1021
|
+
return { sql, columns: returning ?? [] };
|
|
1022
|
+
}
|
|
1023
|
+
function compileDelete(model, where, ctx, returning) {
|
|
1024
|
+
const d = ctx.dialect;
|
|
1025
|
+
const sql = new Sql();
|
|
1026
|
+
sql.push(
|
|
1027
|
+
`DELETE FROM ${d.quoteId(modelTable(model))} ${d.quoteId(ROOT_ALIAS)}`
|
|
1028
|
+
);
|
|
1029
|
+
appendWhere(sql, model, where, ctx);
|
|
1030
|
+
if (returning) appendReturning(sql, d, returning);
|
|
1031
|
+
return { sql, columns: returning ?? [] };
|
|
1032
|
+
}
|
|
1033
|
+
function appendWhere(sql, model, where, ctx) {
|
|
1034
|
+
const compiled = compileWhere(model, ROOT_ALIAS, where, ctx);
|
|
1035
|
+
if (!compiled.isEmpty()) sql.push(" WHERE ").append(compiled);
|
|
1036
|
+
}
|
|
1037
|
+
function defaultOrder(model, d) {
|
|
1038
|
+
const ids = idFields(model);
|
|
1039
|
+
const cols = (ids.length ? ids : model.fields.filter((f) => f.kind !== "object")).map((f) => `${d.quoteRef(ROOT_ALIAS, fieldColumn(f))} ASC`);
|
|
1040
|
+
return Sql.raw(cols.join(", "));
|
|
1041
|
+
}
|
|
1042
|
+
function outerOrderBy(model, alias, orderBy, d) {
|
|
1043
|
+
if (!orderBy) return new Sql();
|
|
1044
|
+
const list = Array.isArray(orderBy) ? orderBy : [orderBy];
|
|
1045
|
+
const parts = [];
|
|
1046
|
+
for (const obj of list) {
|
|
1047
|
+
for (const [name, dir] of Object.entries(obj)) {
|
|
1048
|
+
if (dir !== "asc" && dir !== "desc") continue;
|
|
1049
|
+
if (!model.fields.some((f) => f.name === name && f.kind !== "object")) {
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
parts.push(
|
|
1053
|
+
Sql.raw(`${d.quoteRef(alias, name)} ${dir === "desc" ? "DESC" : "ASC"}`)
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return Sql.join(parts, ", ");
|
|
1058
|
+
}
|
|
1059
|
+
function appendReturning(sql, d, returning) {
|
|
1060
|
+
if (returning.length === 0) return;
|
|
1061
|
+
sql.push(" RETURNING ");
|
|
1062
|
+
sql.push(
|
|
1063
|
+
returning.map((f) => `${d.quoteId(fieldColumn(f))} AS ${d.quoteId(f.name)}`).join(", ")
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
function aggregateSelections(model, args, d) {
|
|
1067
|
+
const out = [];
|
|
1068
|
+
if (args._count) {
|
|
1069
|
+
if (args._count === true) {
|
|
1070
|
+
out.push(`COUNT(*) AS "_count_all"`);
|
|
1071
|
+
} else {
|
|
1072
|
+
for (const field of trueKeys(args._count)) {
|
|
1073
|
+
const col = d.quoteRef(ROOT_ALIAS, fieldColumn(requireScalar(model, field)));
|
|
1074
|
+
out.push(`COUNT(${col}) AS ${d.quoteId(`_count_${field}`)}`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
pushAgg(out, "SUM", "_sum", model, args._sum, d);
|
|
1079
|
+
pushAgg(out, "AVG", "_avg", model, args._avg, d);
|
|
1080
|
+
pushAgg(out, "MIN", "_min", model, args._min, d);
|
|
1081
|
+
pushAgg(out, "MAX", "_max", model, args._max, d);
|
|
1082
|
+
return out;
|
|
1083
|
+
}
|
|
1084
|
+
function pushAgg(out, fn, prefix, model, spec, d) {
|
|
1085
|
+
if (!spec) return;
|
|
1086
|
+
for (const field of trueKeys(spec)) {
|
|
1087
|
+
const col = d.quoteRef(ROOT_ALIAS, fieldColumn(requireScalar(model, field)));
|
|
1088
|
+
out.push(`${fn}(${col}) AS ${d.quoteId(`${prefix}_${field}`)}`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
function trueKeys(spec) {
|
|
1092
|
+
return Object.entries(spec).filter(([, v]) => v === true).map(([k]) => k);
|
|
1093
|
+
}
|
|
1094
|
+
function requireScalar(model, name) {
|
|
1095
|
+
const field = model.fields.find((f) => f.name === name);
|
|
1096
|
+
if (!field || field.kind === "object") {
|
|
1097
|
+
throw new QueryValidationError(
|
|
1098
|
+
`'${name}' is not a scalar field on model '${model.name}'.`
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
return field;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// src/query/coerce.ts
|
|
1105
|
+
function coerceFromDb(value, field) {
|
|
1106
|
+
if (value === null || value === void 0) return null;
|
|
1107
|
+
switch (field.type) {
|
|
1108
|
+
case "Boolean":
|
|
1109
|
+
return toBoolean(value);
|
|
1110
|
+
case "Int":
|
|
1111
|
+
return typeof value === "number" ? value : Number(value);
|
|
1112
|
+
case "BigInt":
|
|
1113
|
+
return typeof value === "bigint" ? value : BigInt(String(value));
|
|
1114
|
+
case "Float":
|
|
1115
|
+
case "Decimal":
|
|
1116
|
+
return typeof value === "number" ? value : Number(value);
|
|
1117
|
+
case "DateTime":
|
|
1118
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
1119
|
+
case "Json":
|
|
1120
|
+
return parseJson(value);
|
|
1121
|
+
case "Bytes":
|
|
1122
|
+
return Buffer.isBuffer(value) ? value : Buffer.from(String(value));
|
|
1123
|
+
case "String":
|
|
1124
|
+
default:
|
|
1125
|
+
return typeof value === "string" ? value : String(value);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
function coerceRow(row, fields) {
|
|
1129
|
+
const out = {};
|
|
1130
|
+
for (const field of fields) {
|
|
1131
|
+
if (field.name in row) {
|
|
1132
|
+
out[field.name] = coerceFromDb(row[field.name], field);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return out;
|
|
1136
|
+
}
|
|
1137
|
+
function toBoolean(value) {
|
|
1138
|
+
if (typeof value === "boolean") return value;
|
|
1139
|
+
if (typeof value === "number") return value !== 0;
|
|
1140
|
+
const s = String(value).trim().toUpperCase();
|
|
1141
|
+
return s === "1" || s === "T" || s === "TRUE" || s === "Y";
|
|
1142
|
+
}
|
|
1143
|
+
function parseJson(value) {
|
|
1144
|
+
if (typeof value !== "string") return value;
|
|
1145
|
+
try {
|
|
1146
|
+
return JSON.parse(value);
|
|
1147
|
+
} catch {
|
|
1148
|
+
return value;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// src/query/defaults.ts
|
|
1153
|
+
var import_node_crypto = require("crypto");
|
|
1154
|
+
function applyCreateDefaults(model, data) {
|
|
1155
|
+
const out = { ...data };
|
|
1156
|
+
for (const field of model.fields) {
|
|
1157
|
+
if (field.kind === "object") continue;
|
|
1158
|
+
if (field.isUpdatedAt && out[field.name] === void 0) {
|
|
1159
|
+
out[field.name] = /* @__PURE__ */ new Date();
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
if (out[field.name] !== void 0) continue;
|
|
1163
|
+
const def = field.default;
|
|
1164
|
+
if (!def) continue;
|
|
1165
|
+
if (def.function) {
|
|
1166
|
+
const computed = computeFunctionDefault(def.function.name);
|
|
1167
|
+
if (computed !== SKIP) out[field.name] = computed;
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
if (def.literal !== void 0) {
|
|
1171
|
+
out[field.name] = def.literal;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
return out;
|
|
1175
|
+
}
|
|
1176
|
+
function applyUpdateDefaults(model, data) {
|
|
1177
|
+
const out = { ...data };
|
|
1178
|
+
for (const field of model.fields) {
|
|
1179
|
+
if (field.isUpdatedAt && out[field.name] === void 0) {
|
|
1180
|
+
out[field.name] = /* @__PURE__ */ new Date();
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return out;
|
|
1184
|
+
}
|
|
1185
|
+
var SKIP = /* @__PURE__ */ Symbol("skip-default");
|
|
1186
|
+
function computeFunctionDefault(name) {
|
|
1187
|
+
switch (name) {
|
|
1188
|
+
case "now":
|
|
1189
|
+
return /* @__PURE__ */ new Date();
|
|
1190
|
+
case "uuid":
|
|
1191
|
+
return (0, import_node_crypto.randomUUID)();
|
|
1192
|
+
case "cuid":
|
|
1193
|
+
return generateCuid();
|
|
1194
|
+
case "autoincrement":
|
|
1195
|
+
return SKIP;
|
|
1196
|
+
default:
|
|
1197
|
+
return SKIP;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function generateCuid() {
|
|
1201
|
+
const time = Date.now().toString(36);
|
|
1202
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
1203
|
+
return `c${time}${rand}`;
|
|
1204
|
+
}
|
|
1205
|
+
function isAutoincrement(field) {
|
|
1206
|
+
return field.default?.function?.name === "autoincrement";
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// src/query/writer.ts
|
|
1210
|
+
var WriteProcessor = class {
|
|
1211
|
+
constructor(schema, dialect, exec) {
|
|
1212
|
+
this.schema = schema;
|
|
1213
|
+
this.dialect = dialect;
|
|
1214
|
+
this.exec = exec;
|
|
1215
|
+
}
|
|
1216
|
+
schema;
|
|
1217
|
+
dialect;
|
|
1218
|
+
exec;
|
|
1219
|
+
ctx() {
|
|
1220
|
+
return newContext(this.schema, this.dialect);
|
|
1221
|
+
}
|
|
1222
|
+
/** Insert one row (with nested writes) and return its key values. */
|
|
1223
|
+
async create(model, data) {
|
|
1224
|
+
const { scalars, owning, children } = this.partition(model, data);
|
|
1225
|
+
for (const [field, op] of owning) {
|
|
1226
|
+
for (const [fieldName, value] of await this.resolveOwning(model, field, op)) {
|
|
1227
|
+
scalars.set(fieldName, value);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
const withDefaults = applyCreateDefaults(model, recordOf(scalars));
|
|
1231
|
+
const row = this.toColumnMap(model, withDefaults);
|
|
1232
|
+
const returning = idFields(model);
|
|
1233
|
+
const stmt = compileInsert(model, row, this.ctx(), returning);
|
|
1234
|
+
const result = await this.exec(stmt.sql);
|
|
1235
|
+
const keys = this.keyValues(model, result[0] ?? {}, withDefaults);
|
|
1236
|
+
for (const [field, op] of children) {
|
|
1237
|
+
await this.writeChildren(model, keys, field, op);
|
|
1238
|
+
}
|
|
1239
|
+
return keys;
|
|
1240
|
+
}
|
|
1241
|
+
/** Update rows matched by `where` (with nested writes). */
|
|
1242
|
+
async updateRow(model, where, data, keys) {
|
|
1243
|
+
const { scalars, owning, children } = this.partition(model, data, true);
|
|
1244
|
+
for (const [field, op] of owning) {
|
|
1245
|
+
for (const [fieldName, value] of await this.resolveOwningForUpdate(
|
|
1246
|
+
model,
|
|
1247
|
+
field,
|
|
1248
|
+
op
|
|
1249
|
+
)) {
|
|
1250
|
+
scalars.set(fieldName, value);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
const withDefaults = applyUpdateDefaults(model, recordOf(scalars));
|
|
1254
|
+
const assignments = this.toUpdateColumnMap(model, withDefaults);
|
|
1255
|
+
if (assignments.size > 0) {
|
|
1256
|
+
const stmt = compileUpdate(model, where, assignments, this.ctx());
|
|
1257
|
+
await this.exec(stmt.sql);
|
|
1258
|
+
}
|
|
1259
|
+
for (const [field, op] of children) {
|
|
1260
|
+
await this.writeChildren(model, keys, field, op);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Scalar-only bulk update (for `updateMany`). Atomic operators are supported;
|
|
1265
|
+
* nested relation writes are rejected, matching Prisma's `updateMany`.
|
|
1266
|
+
*/
|
|
1267
|
+
async updateManyRows(model, where, data) {
|
|
1268
|
+
for (const key of Object.keys(data)) {
|
|
1269
|
+
const field = model.fields.find((f) => f.name === key);
|
|
1270
|
+
if (field?.kind === "object") {
|
|
1271
|
+
throw new QueryValidationError(
|
|
1272
|
+
`updateMany does not support nested relation writes ('${model.name}.${key}'). Use update() per record.`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
const withDefaults = applyUpdateDefaults(model, data);
|
|
1277
|
+
const assignments = this.toUpdateColumnMap(model, withDefaults);
|
|
1278
|
+
if (assignments.size > 0) {
|
|
1279
|
+
const stmt = compileUpdate(model, where, assignments, this.ctx());
|
|
1280
|
+
await this.exec(stmt.sql);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
// ---- partition & mapping ------------------------------------------------
|
|
1284
|
+
partition(model, data, isUpdate = false) {
|
|
1285
|
+
const scalars = /* @__PURE__ */ new Map();
|
|
1286
|
+
const owning = [];
|
|
1287
|
+
const children = [];
|
|
1288
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1289
|
+
if (value === void 0) continue;
|
|
1290
|
+
const field = model.fields.find((f) => f.name === key);
|
|
1291
|
+
if (!field) {
|
|
1292
|
+
throw new QueryValidationError(
|
|
1293
|
+
`Unknown field '${key}' on model '${model.name}'.`
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
if (field.kind !== "object") {
|
|
1297
|
+
scalars.set(key, value);
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
if (!isPlainObject(value)) {
|
|
1301
|
+
throw new QueryValidationError(
|
|
1302
|
+
`Relation '${model.name}.${key}' expects a nested write object.`
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
const rel = resolveRelation(this.schema, model, field);
|
|
1306
|
+
if (rel.owns) owning.push([field, value]);
|
|
1307
|
+
else children.push([field, value]);
|
|
1308
|
+
}
|
|
1309
|
+
return { scalars, owning, children };
|
|
1310
|
+
}
|
|
1311
|
+
toColumnMap(model, record) {
|
|
1312
|
+
const out = /* @__PURE__ */ new Map();
|
|
1313
|
+
for (const [name, value] of Object.entries(record)) {
|
|
1314
|
+
const field = model.fields.find((f) => f.name === name);
|
|
1315
|
+
if (!field || field.kind === "object") continue;
|
|
1316
|
+
if (isAutoincrement(field) && (value === void 0 || value === null)) {
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
out.set(fieldColumn(field), this.dialect.coerceValue(value));
|
|
1320
|
+
}
|
|
1321
|
+
return out;
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Build UPDATE assignments, interpreting atomic scalar operators:
|
|
1325
|
+
* `{ set }`, `{ increment }`, `{ decrement }`, `{ multiply }`, `{ divide }`.
|
|
1326
|
+
* A bare value is a plain `set`.
|
|
1327
|
+
*/
|
|
1328
|
+
toUpdateColumnMap(model, record) {
|
|
1329
|
+
const out = /* @__PURE__ */ new Map();
|
|
1330
|
+
for (const [name, value] of Object.entries(record)) {
|
|
1331
|
+
const field = model.fields.find((f) => f.name === name);
|
|
1332
|
+
if (!field || field.kind === "object") continue;
|
|
1333
|
+
out.set(fieldColumn(field), this.toColumnUpdate(field, value));
|
|
1334
|
+
}
|
|
1335
|
+
return out;
|
|
1336
|
+
}
|
|
1337
|
+
toColumnUpdate(field, value) {
|
|
1338
|
+
const coerce = (v) => this.dialect.coerceValue(v);
|
|
1339
|
+
if (isPlainObject(value)) {
|
|
1340
|
+
if ("set" in value) return { kind: "set", value: coerce(value.set) };
|
|
1341
|
+
const arith = ARITH_OPS.find((op) => op.key in value);
|
|
1342
|
+
if (arith) {
|
|
1343
|
+
requireNumeric(field, arith.key);
|
|
1344
|
+
return { kind: "arith", op: arith.op, value: coerce(value[arith.key]) };
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return { kind: "set", value: coerce(value) };
|
|
1348
|
+
}
|
|
1349
|
+
keyValues(model, returnedRow, writtenData) {
|
|
1350
|
+
const out = {};
|
|
1351
|
+
for (const f of idFields(model)) {
|
|
1352
|
+
out[f.name] = returnedRow[f.name] ?? returnedRow[fieldColumn(f)] ?? writtenData[f.name];
|
|
1353
|
+
}
|
|
1354
|
+
return out;
|
|
1355
|
+
}
|
|
1356
|
+
// ---- owning-side relations ---------------------------------------------
|
|
1357
|
+
async resolveOwning(model, field, op) {
|
|
1358
|
+
const rel = resolveRelation(this.schema, model, field);
|
|
1359
|
+
if (op.connect) {
|
|
1360
|
+
const ref = await this.lookupReference(rel, op.connect);
|
|
1361
|
+
return this.mapFkByField(rel, ref);
|
|
1362
|
+
}
|
|
1363
|
+
if (op.create) {
|
|
1364
|
+
const childKeys = await this.create(
|
|
1365
|
+
rel.relatedModel,
|
|
1366
|
+
op.create
|
|
1367
|
+
);
|
|
1368
|
+
return this.mapFkByField(rel, childKeys);
|
|
1369
|
+
}
|
|
1370
|
+
if (op.connectOrCreate) {
|
|
1371
|
+
const spec = op.connectOrCreate;
|
|
1372
|
+
const existing = await this.tryLookupReference(rel, spec.where);
|
|
1373
|
+
const ref = existing ?? await this.create(rel.relatedModel, spec.create);
|
|
1374
|
+
return this.mapFkByField(rel, ref);
|
|
1375
|
+
}
|
|
1376
|
+
throw new QueryValidationError(
|
|
1377
|
+
`Unsupported nested write on '${model.name}.${field.name}'.`
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
async resolveOwningForUpdate(model, field, op) {
|
|
1381
|
+
if (op.disconnect) {
|
|
1382
|
+
const rel = resolveRelation(this.schema, model, field);
|
|
1383
|
+
return rel.fromFields.map((name) => [name, null]);
|
|
1384
|
+
}
|
|
1385
|
+
return this.resolveOwning(model, field, op);
|
|
1386
|
+
}
|
|
1387
|
+
/** [localFieldName, value] pairs from the referenced model's key values. */
|
|
1388
|
+
mapFkByField(rel, refValues) {
|
|
1389
|
+
return rel.fromFields.map((fromField, i) => {
|
|
1390
|
+
const toField = rel.toFields[i];
|
|
1391
|
+
return [fromField, this.dialect.coerceValue(refValues[toField] ?? null)];
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
async lookupReference(rel, where) {
|
|
1395
|
+
const found = await this.tryLookupReference(rel, where);
|
|
1396
|
+
if (!found) throw new RecordNotFoundError(rel.relatedModel.name);
|
|
1397
|
+
return found;
|
|
1398
|
+
}
|
|
1399
|
+
async tryLookupReference(rel, where) {
|
|
1400
|
+
const fields = rel.toFields.map((n) => requireField(rel.relatedModel, n));
|
|
1401
|
+
const stmt = this.selectKeys(rel.relatedModel, fields, where);
|
|
1402
|
+
const rows = await this.exec(stmt);
|
|
1403
|
+
return rows[0] ?? null;
|
|
1404
|
+
}
|
|
1405
|
+
selectKeys(model, fields, where) {
|
|
1406
|
+
const d = this.dialect;
|
|
1407
|
+
const alias = "t0";
|
|
1408
|
+
const sql = new Sql();
|
|
1409
|
+
sql.push("SELECT FIRST 1 ");
|
|
1410
|
+
sql.push(
|
|
1411
|
+
fields.map((f) => `${d.quoteRef(alias, fieldColumn(f))} AS ${d.quoteId(f.name)}`).join(", ")
|
|
1412
|
+
);
|
|
1413
|
+
sql.push(` FROM ${d.quoteId(modelTable(model))} ${d.quoteId(alias)}`);
|
|
1414
|
+
const cond = compileWhere(model, alias, where, this.ctx());
|
|
1415
|
+
if (!cond.isEmpty()) sql.push(" WHERE ").append(cond);
|
|
1416
|
+
return sql;
|
|
1417
|
+
}
|
|
1418
|
+
// ---- child-side relations ----------------------------------------------
|
|
1419
|
+
async writeChildren(parentModel, parentKeys, field, op) {
|
|
1420
|
+
const rel = resolveRelation(this.schema, parentModel, field);
|
|
1421
|
+
const fkColumns = rel.toColumns.map((toCol, i) => {
|
|
1422
|
+
const fromField = rel.fromFields[i];
|
|
1423
|
+
return [toCol, this.dialect.coerceValue(parentKeys[fromField] ?? null)];
|
|
1424
|
+
});
|
|
1425
|
+
for (const childData of toArray(op.create)) {
|
|
1426
|
+
await this.create(rel.relatedModel, { ...childData, ...fkRecord(rel, parentKeys) });
|
|
1427
|
+
}
|
|
1428
|
+
for (const where of toArray(op.connect)) {
|
|
1429
|
+
await this.setChildForeignKey(rel, where, fkColumns);
|
|
1430
|
+
}
|
|
1431
|
+
if (op.set !== void 0) {
|
|
1432
|
+
await this.clearChildren(rel, parentKeys);
|
|
1433
|
+
for (const where of toArray(op.set)) {
|
|
1434
|
+
await this.setChildForeignKey(rel, where, fkColumns);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
for (const where of toArray(op.disconnect)) {
|
|
1438
|
+
await this.setChildForeignKey(
|
|
1439
|
+
rel,
|
|
1440
|
+
where,
|
|
1441
|
+
rel.toColumns.map((c) => [c, null])
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
for (const where of toArray(op.delete)) {
|
|
1445
|
+
const stmt = compileDelete(rel.relatedModel, where, this.ctx());
|
|
1446
|
+
await this.exec(stmt.sql);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
async setChildForeignKey(rel, where, fkColumns) {
|
|
1450
|
+
const assignments = setAssignments(new Map(fkColumns));
|
|
1451
|
+
const stmt = compileUpdate(rel.relatedModel, where, assignments, this.ctx());
|
|
1452
|
+
await this.exec(stmt.sql);
|
|
1453
|
+
}
|
|
1454
|
+
async clearChildren(rel, parentKeys) {
|
|
1455
|
+
const matchWhere = {};
|
|
1456
|
+
rel.toFields.forEach((toField, i) => {
|
|
1457
|
+
const fromField = rel.fromFields[i];
|
|
1458
|
+
matchWhere[toField] = parentKeys[fromField];
|
|
1459
|
+
});
|
|
1460
|
+
const assignments = setAssignments(
|
|
1461
|
+
new Map(
|
|
1462
|
+
rel.toColumns.map((c) => [c, null])
|
|
1463
|
+
)
|
|
1464
|
+
);
|
|
1465
|
+
const stmt = compileUpdate(rel.relatedModel, matchWhere, assignments, this.ctx());
|
|
1466
|
+
await this.exec(stmt.sql);
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
var ARITH_OPS = [
|
|
1470
|
+
{ key: "increment", op: "+" },
|
|
1471
|
+
{ key: "decrement", op: "-" },
|
|
1472
|
+
{ key: "multiply", op: "*" },
|
|
1473
|
+
{ key: "divide", op: "/" }
|
|
1474
|
+
];
|
|
1475
|
+
var NUMERIC_TYPES = /* @__PURE__ */ new Set(["Int", "BigInt", "Float", "Decimal"]);
|
|
1476
|
+
function requireNumeric(field, op) {
|
|
1477
|
+
if (!NUMERIC_TYPES.has(field.type)) {
|
|
1478
|
+
throw new QueryValidationError(
|
|
1479
|
+
`Operator '${op}' is only valid on numeric fields, not '${field.name}' (${field.type}).`
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
function requireField(model, name) {
|
|
1484
|
+
const f = model.fields.find((x) => x.name === name);
|
|
1485
|
+
if (!f) {
|
|
1486
|
+
throw new QueryValidationError(`Field '${name}' not found on '${model.name}'.`);
|
|
1487
|
+
}
|
|
1488
|
+
return f;
|
|
1489
|
+
}
|
|
1490
|
+
function fkRecord(rel, parentKeys) {
|
|
1491
|
+
const out = {};
|
|
1492
|
+
rel.toFields.forEach((toField, i) => {
|
|
1493
|
+
const fromField = rel.fromFields[i];
|
|
1494
|
+
out[toField] = parentKeys[fromField];
|
|
1495
|
+
});
|
|
1496
|
+
return out;
|
|
1497
|
+
}
|
|
1498
|
+
function recordOf(scalars) {
|
|
1499
|
+
const out = {};
|
|
1500
|
+
for (const [k, v] of scalars) out[k] = v;
|
|
1501
|
+
return out;
|
|
1502
|
+
}
|
|
1503
|
+
function toArray(value) {
|
|
1504
|
+
if (value === void 0 || value === null) return [];
|
|
1505
|
+
return Array.isArray(value) ? value : [value];
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// src/query/engine.ts
|
|
1509
|
+
function readShape(args) {
|
|
1510
|
+
return { select: args.select, include: args.include, omit: args.omit };
|
|
1511
|
+
}
|
|
1512
|
+
var QueryEngine = class {
|
|
1513
|
+
constructor(schema, dialect, driver) {
|
|
1514
|
+
this.schema = schema;
|
|
1515
|
+
this.dialect = dialect;
|
|
1516
|
+
this.driver = driver;
|
|
1517
|
+
}
|
|
1518
|
+
schema;
|
|
1519
|
+
dialect;
|
|
1520
|
+
driver;
|
|
1521
|
+
model(name) {
|
|
1522
|
+
const model = findModel(this.schema, name);
|
|
1523
|
+
if (!model) throw new QueryValidationError(`Unknown model '${name}'.`);
|
|
1524
|
+
return model;
|
|
1525
|
+
}
|
|
1526
|
+
run(fn, options) {
|
|
1527
|
+
return this.driver.transaction(fn, options);
|
|
1528
|
+
}
|
|
1529
|
+
execOn(tx) {
|
|
1530
|
+
return (sql) => tx.query(sql.text, sql.params);
|
|
1531
|
+
}
|
|
1532
|
+
// ---- reads --------------------------------------------------------------
|
|
1533
|
+
findMany(name, args = {}) {
|
|
1534
|
+
const model = this.model(name);
|
|
1535
|
+
return this.run((tx) => this.readMany(model, args, this.execOn(tx)));
|
|
1536
|
+
}
|
|
1537
|
+
async findFirst(name, args = {}) {
|
|
1538
|
+
const rows = await this.findMany(name, { ...args, take: 1 });
|
|
1539
|
+
return rows[0] ?? null;
|
|
1540
|
+
}
|
|
1541
|
+
async findFirstOrThrow(name, args = {}) {
|
|
1542
|
+
const row = await this.findFirst(name, args);
|
|
1543
|
+
if (!row) throw new RecordNotFoundError(name);
|
|
1544
|
+
return row;
|
|
1545
|
+
}
|
|
1546
|
+
async findUnique(name, args) {
|
|
1547
|
+
const model = this.model(name);
|
|
1548
|
+
this.assertUniqueWhere(model, args.where);
|
|
1549
|
+
const rows = await this.run(
|
|
1550
|
+
(tx) => this.readMany(
|
|
1551
|
+
model,
|
|
1552
|
+
{ where: args.where, select: args.select, include: args.include, take: 1 },
|
|
1553
|
+
this.execOn(tx)
|
|
1554
|
+
)
|
|
1555
|
+
);
|
|
1556
|
+
return rows[0] ?? null;
|
|
1557
|
+
}
|
|
1558
|
+
async findUniqueOrThrow(name, args) {
|
|
1559
|
+
const row = await this.findUnique(name, args);
|
|
1560
|
+
if (!row) throw new RecordNotFoundError(name);
|
|
1561
|
+
return row;
|
|
1562
|
+
}
|
|
1563
|
+
// ---- aggregations -------------------------------------------------------
|
|
1564
|
+
count(name, args = {}) {
|
|
1565
|
+
const model = this.model(name);
|
|
1566
|
+
return this.run(async (tx) => {
|
|
1567
|
+
const stmt = compileCount(model, args.where, newContext(this.schema, this.dialect), {
|
|
1568
|
+
take: args.take,
|
|
1569
|
+
skip: args.skip
|
|
1570
|
+
});
|
|
1571
|
+
const rows = await tx.query(stmt.text, stmt.params);
|
|
1572
|
+
return Number(rows[0]?._count ?? 0);
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
aggregate(name, args = {}) {
|
|
1576
|
+
const model = this.model(name);
|
|
1577
|
+
return this.run(async (tx) => {
|
|
1578
|
+
const stmt = compileAggregate(model, args, newContext(this.schema, this.dialect));
|
|
1579
|
+
const rows = await tx.query(stmt.text, stmt.params);
|
|
1580
|
+
return reshapeAggregate(rows[0] ?? {});
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
groupBy(name, args) {
|
|
1584
|
+
const model = this.model(name);
|
|
1585
|
+
return this.run(async (tx) => {
|
|
1586
|
+
const { sql } = compileGroupBy(model, args, newContext(this.schema, this.dialect));
|
|
1587
|
+
const rows = await tx.query(sql.text, sql.params);
|
|
1588
|
+
return rows.map((r) => reshapeGroupRow(model, args.by, r));
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
// ---- writes -------------------------------------------------------------
|
|
1592
|
+
create(name, args) {
|
|
1593
|
+
const model = this.model(name);
|
|
1594
|
+
return this.run(async (tx) => {
|
|
1595
|
+
const writer = new WriteProcessor(this.schema, this.dialect, this.execOn(tx));
|
|
1596
|
+
const keys = await writer.create(model, args.data);
|
|
1597
|
+
return this.readBack(model, keys, readShape(args), this.execOn(tx));
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
createMany(name, args) {
|
|
1601
|
+
const model = this.model(name);
|
|
1602
|
+
return this.run(async (tx) => {
|
|
1603
|
+
const writer = new WriteProcessor(this.schema, this.dialect, this.execOn(tx));
|
|
1604
|
+
let count = 0;
|
|
1605
|
+
for (const data of args.data) {
|
|
1606
|
+
await writer.create(model, data);
|
|
1607
|
+
count++;
|
|
1608
|
+
}
|
|
1609
|
+
return { count };
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
/** Like createMany, but returns the created records (no nested relations). */
|
|
1613
|
+
createManyAndReturn(name, args) {
|
|
1614
|
+
const model = this.model(name);
|
|
1615
|
+
return this.run(async (tx) => {
|
|
1616
|
+
const exec = this.execOn(tx);
|
|
1617
|
+
const writer = new WriteProcessor(this.schema, this.dialect, exec);
|
|
1618
|
+
const out = [];
|
|
1619
|
+
const shape = { select: args.select, omit: args.omit };
|
|
1620
|
+
for (const data of args.data) {
|
|
1621
|
+
const keys = await writer.create(model, data);
|
|
1622
|
+
out.push(await this.readBack(model, keys, shape, exec));
|
|
1623
|
+
}
|
|
1624
|
+
return out;
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
update(name, args) {
|
|
1628
|
+
const model = this.model(name);
|
|
1629
|
+
this.assertUniqueWhere(model, args.where);
|
|
1630
|
+
return this.run(async (tx) => {
|
|
1631
|
+
const exec = this.execOn(tx);
|
|
1632
|
+
const target = await this.requireOne(model, args.where, exec);
|
|
1633
|
+
const keys = this.pickKeys(model, target);
|
|
1634
|
+
const writer = new WriteProcessor(this.schema, this.dialect, exec);
|
|
1635
|
+
await writer.updateRow(model, args.where, args.data, keys);
|
|
1636
|
+
return this.readBack(model, keys, readShape(args), exec);
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
updateMany(name, args) {
|
|
1640
|
+
const model = this.model(name);
|
|
1641
|
+
return this.run(async (tx) => {
|
|
1642
|
+
const exec = this.execOn(tx);
|
|
1643
|
+
const count = await this.countMatching(model, args.where, exec);
|
|
1644
|
+
const writer = new WriteProcessor(this.schema, this.dialect, exec);
|
|
1645
|
+
await writer.updateManyRows(model, args.where, args.data);
|
|
1646
|
+
return { count };
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
upsert(name, args) {
|
|
1650
|
+
const model = this.model(name);
|
|
1651
|
+
this.assertUniqueWhere(model, args.where);
|
|
1652
|
+
return this.run(async (tx) => {
|
|
1653
|
+
const exec = this.execOn(tx);
|
|
1654
|
+
const existing = await this.findOneRaw(model, args.where, exec);
|
|
1655
|
+
const writer = new WriteProcessor(this.schema, this.dialect, exec);
|
|
1656
|
+
if (existing) {
|
|
1657
|
+
const keys2 = this.pickKeys(model, existing);
|
|
1658
|
+
await writer.updateRow(model, args.where, args.update, keys2);
|
|
1659
|
+
return this.readBack(model, keys2, readShape(args), exec);
|
|
1660
|
+
}
|
|
1661
|
+
const keys = await writer.create(model, args.create);
|
|
1662
|
+
return this.readBack(model, keys, readShape(args), exec);
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
delete(name, args) {
|
|
1666
|
+
const model = this.model(name);
|
|
1667
|
+
this.assertUniqueWhere(model, args.where);
|
|
1668
|
+
return this.run(async (tx) => {
|
|
1669
|
+
const exec = this.execOn(tx);
|
|
1670
|
+
const row = await this.readBackOne(model, args.where, readShape(args), exec);
|
|
1671
|
+
if (!row) throw new RecordNotFoundError(name);
|
|
1672
|
+
const stmt = compileDelete(model, args.where, newContext(this.schema, this.dialect));
|
|
1673
|
+
await exec(stmt.sql);
|
|
1674
|
+
return row;
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
deleteMany(name, args = {}) {
|
|
1678
|
+
const model = this.model(name);
|
|
1679
|
+
return this.run(async (tx) => {
|
|
1680
|
+
const exec = this.execOn(tx);
|
|
1681
|
+
const count = await this.countMatching(model, args.where, exec);
|
|
1682
|
+
const stmt = compileDelete(model, args.where, newContext(this.schema, this.dialect));
|
|
1683
|
+
await exec(stmt.sql);
|
|
1684
|
+
return { count };
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
// ---- read pipeline ------------------------------------------------------
|
|
1688
|
+
async readMany(model, args, exec, forceKeep = []) {
|
|
1689
|
+
const relations = this.readRelations(model, args.select, args.include);
|
|
1690
|
+
const countFields = this.readCountRelations(model, args.select, args.include);
|
|
1691
|
+
const selectedScalars = applyOmit(
|
|
1692
|
+
model,
|
|
1693
|
+
selectedScalarNames(model, args.select),
|
|
1694
|
+
args.omit
|
|
1695
|
+
);
|
|
1696
|
+
const distinctFields = normalizeDistinct(model, args.distinct);
|
|
1697
|
+
const useWindowDistinct = distinctFields.length > 0 && this.dialect.supportsWindowFunctions;
|
|
1698
|
+
const memoryPaginate = distinctFields.length > 0 && !useWindowDistinct;
|
|
1699
|
+
const { where, orderBy } = applyCursor(model, args, distinctFields);
|
|
1700
|
+
const relationKeyFields = uniq(
|
|
1701
|
+
[...relations.map((r) => r.field), ...countFields].flatMap(
|
|
1702
|
+
(field) => resolveRelation(this.schema, model, field).fromFields
|
|
1703
|
+
)
|
|
1704
|
+
);
|
|
1705
|
+
const internalKeys = uniq([
|
|
1706
|
+
...relationKeyFields,
|
|
1707
|
+
...distinctFields
|
|
1708
|
+
// fetched for de-dup; stripped below if not selected
|
|
1709
|
+
]);
|
|
1710
|
+
const projectionNames = uniq([
|
|
1711
|
+
...selectedScalars ?? scalarFields(model).map((f) => f.name),
|
|
1712
|
+
...internalKeys,
|
|
1713
|
+
...forceKeep
|
|
1714
|
+
]);
|
|
1715
|
+
const projection = projectionNames.map((n) => fieldByName(model, n));
|
|
1716
|
+
const findOptions = {
|
|
1717
|
+
where,
|
|
1718
|
+
orderBy,
|
|
1719
|
+
take: memoryPaginate ? void 0 : args.take,
|
|
1720
|
+
skip: memoryPaginate ? void 0 : args.skip
|
|
1721
|
+
};
|
|
1722
|
+
const stmt = useWindowDistinct ? compileDistinctFindMany(
|
|
1723
|
+
model,
|
|
1724
|
+
findOptions,
|
|
1725
|
+
projection,
|
|
1726
|
+
distinctFields.map((n) => fieldByName(model, n)),
|
|
1727
|
+
newContext(this.schema, this.dialect)
|
|
1728
|
+
) : compileFindMany(
|
|
1729
|
+
model,
|
|
1730
|
+
findOptions,
|
|
1731
|
+
projection,
|
|
1732
|
+
newContext(this.schema, this.dialect)
|
|
1733
|
+
);
|
|
1734
|
+
const rawRows = await exec(stmt.sql);
|
|
1735
|
+
let rows = rawRows.map((r) => coerceRow(r, projection));
|
|
1736
|
+
if (memoryPaginate) {
|
|
1737
|
+
rows = dedupeBy(rows, distinctFields);
|
|
1738
|
+
rows = applyPagination(rows, args.take, args.skip);
|
|
1739
|
+
}
|
|
1740
|
+
for (const relation of relations) {
|
|
1741
|
+
await this.loadRelation(model, rows, relation, exec);
|
|
1742
|
+
}
|
|
1743
|
+
if (countFields.length > 0) {
|
|
1744
|
+
await this.loadCounts(model, rows, countFields, exec);
|
|
1745
|
+
}
|
|
1746
|
+
if (selectedScalars) {
|
|
1747
|
+
const visible = /* @__PURE__ */ new Set([...selectedScalars, ...forceKeep]);
|
|
1748
|
+
for (const row of rows) {
|
|
1749
|
+
for (const name of internalKeys) {
|
|
1750
|
+
if (!visible.has(name)) delete row[name];
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return rows;
|
|
1755
|
+
}
|
|
1756
|
+
async loadRelation(parentModel, parentRows, relation, exec) {
|
|
1757
|
+
if (parentRows.length === 0) return;
|
|
1758
|
+
const rel = resolveRelation(this.schema, parentModel, relation.field);
|
|
1759
|
+
const fieldName = relation.field.name;
|
|
1760
|
+
const { fromFields, toFields } = rel;
|
|
1761
|
+
const parentTuples = uniqueTuples(parentRows, fromFields);
|
|
1762
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1763
|
+
if (parentTuples.length > 0) {
|
|
1764
|
+
const childWhere = relationKeyWhere(
|
|
1765
|
+
toFields,
|
|
1766
|
+
parentTuples,
|
|
1767
|
+
relation.args.where
|
|
1768
|
+
);
|
|
1769
|
+
const childRows = await this.readMany(
|
|
1770
|
+
rel.relatedModel,
|
|
1771
|
+
{
|
|
1772
|
+
where: childWhere,
|
|
1773
|
+
orderBy: relation.args.orderBy,
|
|
1774
|
+
select: relation.args.select,
|
|
1775
|
+
include: relation.args.include,
|
|
1776
|
+
// take/skip on to-many are applied per parent below; skip here.
|
|
1777
|
+
...rel.isList ? {} : { take: relation.args.take, skip: relation.args.skip }
|
|
1778
|
+
},
|
|
1779
|
+
exec,
|
|
1780
|
+
toFields
|
|
1781
|
+
);
|
|
1782
|
+
for (const child of childRows) {
|
|
1783
|
+
const k = tupleKey(child, toFields);
|
|
1784
|
+
const list = grouped.get(k) ?? [];
|
|
1785
|
+
list.push(child);
|
|
1786
|
+
grouped.set(k, list);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
const visible = selectedScalarNames(rel.relatedModel, relation.args.select);
|
|
1790
|
+
const stripFields = visible ? toFields.filter((f) => !visible.has(f)) : [];
|
|
1791
|
+
for (const parent of parentRows) {
|
|
1792
|
+
const k = tupleKey(parent, fromFields);
|
|
1793
|
+
let matches = grouped.get(k) ?? [];
|
|
1794
|
+
if (rel.isList && (relation.args.take != null || relation.args.skip != null)) {
|
|
1795
|
+
matches = applyPagination(matches, relation.args.take, relation.args.skip);
|
|
1796
|
+
}
|
|
1797
|
+
const attached = stripFields.length ? matches.map((m) => omitAll(m, stripFields)) : matches;
|
|
1798
|
+
parent[fieldName] = rel.isList ? attached : attached[0] ?? null;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
readRelations(model, select, include) {
|
|
1802
|
+
const out = [];
|
|
1803
|
+
const add = (key, value) => {
|
|
1804
|
+
if (!value) return;
|
|
1805
|
+
const field = model.fields.find((f) => f.name === key && f.kind === "object");
|
|
1806
|
+
if (!field) return;
|
|
1807
|
+
out.push({ field, args: typeof value === "object" ? value : {} });
|
|
1808
|
+
};
|
|
1809
|
+
if (include) for (const [k, v] of Object.entries(include)) add(k, v);
|
|
1810
|
+
if (select) {
|
|
1811
|
+
for (const [k, v] of Object.entries(select)) {
|
|
1812
|
+
const field = model.fields.find((f) => f.name === k);
|
|
1813
|
+
if (field?.kind === "object") add(k, v);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
return out;
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Resolve which to-many relations to count for `_count` in select/include.
|
|
1820
|
+
* `_count: true` counts every list relation; `_count: { select: { rel } }`
|
|
1821
|
+
* counts the named ones.
|
|
1822
|
+
*/
|
|
1823
|
+
readCountRelations(model, select, include) {
|
|
1824
|
+
const spec = include?._count ?? select?._count;
|
|
1825
|
+
if (!spec) return [];
|
|
1826
|
+
const listRelations = model.fields.filter(
|
|
1827
|
+
(f) => f.kind === "object" && f.isList
|
|
1828
|
+
);
|
|
1829
|
+
if (spec === true) return listRelations;
|
|
1830
|
+
const selected = spec.select;
|
|
1831
|
+
if (!selected) return listRelations;
|
|
1832
|
+
return listRelations.filter((f) => selected[f.name]);
|
|
1833
|
+
}
|
|
1834
|
+
/** Attach a `_count` object to each row with per-relation child counts. */
|
|
1835
|
+
async loadCounts(model, rows, countFields, exec) {
|
|
1836
|
+
for (const row of rows) row._count = {};
|
|
1837
|
+
for (const field of countFields) {
|
|
1838
|
+
const rel = resolveRelation(this.schema, model, field);
|
|
1839
|
+
const tuples = uniqueTuples(rows, rel.fromFields);
|
|
1840
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1841
|
+
if (tuples.length > 0) {
|
|
1842
|
+
const where = relationKeyWhere(rel.toFields, tuples, void 0);
|
|
1843
|
+
const { sql } = compileGroupBy(
|
|
1844
|
+
rel.relatedModel,
|
|
1845
|
+
{ by: rel.toFields, _count: true, where },
|
|
1846
|
+
newContext(this.schema, this.dialect)
|
|
1847
|
+
);
|
|
1848
|
+
const countRows = await exec(sql);
|
|
1849
|
+
for (const cr of countRows) {
|
|
1850
|
+
grouped.set(tupleKey(cr, rel.toFields), Number(cr._count_all ?? 0));
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
for (const row of rows) {
|
|
1854
|
+
const count = grouped.get(tupleKey(row, rel.fromFields)) ?? 0;
|
|
1855
|
+
row._count[field.name] = count;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
// ---- read-back & helpers ------------------------------------------------
|
|
1860
|
+
async readBack(model, keys, shape, exec) {
|
|
1861
|
+
const where = keysToWhere(model, keys);
|
|
1862
|
+
const row = await this.readBackOne(model, where, shape, exec);
|
|
1863
|
+
if (!row) throw new RecordNotFoundError(model.name);
|
|
1864
|
+
return row;
|
|
1865
|
+
}
|
|
1866
|
+
async readBackOne(model, where, shape, exec) {
|
|
1867
|
+
const rows = await this.readMany(
|
|
1868
|
+
model,
|
|
1869
|
+
{ where, select: shape.select, include: shape.include, omit: shape.omit, take: 1 },
|
|
1870
|
+
exec
|
|
1871
|
+
);
|
|
1872
|
+
return rows[0] ?? null;
|
|
1873
|
+
}
|
|
1874
|
+
async requireOne(model, where, exec) {
|
|
1875
|
+
const row = await this.findOneRaw(model, where, exec);
|
|
1876
|
+
if (!row) throw new RecordNotFoundError(model.name);
|
|
1877
|
+
return row;
|
|
1878
|
+
}
|
|
1879
|
+
async findOneRaw(model, where, exec) {
|
|
1880
|
+
const rows = await this.readMany(model, { where, take: 1 }, exec);
|
|
1881
|
+
return rows[0] ?? null;
|
|
1882
|
+
}
|
|
1883
|
+
async countMatching(model, where, exec) {
|
|
1884
|
+
const stmt = compileCount(model, where, newContext(this.schema, this.dialect));
|
|
1885
|
+
const rows = await exec(stmt);
|
|
1886
|
+
return Number(rows[0]?._count ?? 0);
|
|
1887
|
+
}
|
|
1888
|
+
pickKeys(model, row) {
|
|
1889
|
+
const out = {};
|
|
1890
|
+
for (const f of idFields(model)) out[f.name] = row[f.name];
|
|
1891
|
+
return out;
|
|
1892
|
+
}
|
|
1893
|
+
assertUniqueWhere(model, where) {
|
|
1894
|
+
if (!where || Object.keys(where).length === 0) {
|
|
1895
|
+
throw new QueryValidationError(
|
|
1896
|
+
`A unique 'where' is required for this operation on '${model.name}'.`
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
function fieldByName(model, name) {
|
|
1902
|
+
const f = model.fields.find((x) => x.name === name);
|
|
1903
|
+
if (!f) throw new QueryValidationError(`Field '${name}' not found on '${model.name}'.`);
|
|
1904
|
+
return f;
|
|
1905
|
+
}
|
|
1906
|
+
function selectedScalarNames(model, select) {
|
|
1907
|
+
if (!select) return null;
|
|
1908
|
+
const names = /* @__PURE__ */ new Set();
|
|
1909
|
+
for (const [k, v] of Object.entries(select)) {
|
|
1910
|
+
if (!v) continue;
|
|
1911
|
+
const field = model.fields.find((f) => f.name === k);
|
|
1912
|
+
if (field && field.kind !== "object") names.add(k);
|
|
1913
|
+
}
|
|
1914
|
+
return names;
|
|
1915
|
+
}
|
|
1916
|
+
function applyOmit(model, selected, omit) {
|
|
1917
|
+
if (selected || !omit) return selected;
|
|
1918
|
+
const omitted = new Set(
|
|
1919
|
+
Object.entries(omit).filter(([, v]) => v).map(([k]) => k)
|
|
1920
|
+
);
|
|
1921
|
+
if (omitted.size === 0) return null;
|
|
1922
|
+
return new Set(
|
|
1923
|
+
scalarFields(model).map((f) => f.name).filter((n) => !omitted.has(n))
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
function keysToWhere(model, keys) {
|
|
1927
|
+
const where = {};
|
|
1928
|
+
for (const f of idFields(model)) where[f.name] = keys[f.name];
|
|
1929
|
+
return where;
|
|
1930
|
+
}
|
|
1931
|
+
function keyOf(value) {
|
|
1932
|
+
return value instanceof Date ? value.toISOString() : String(value);
|
|
1933
|
+
}
|
|
1934
|
+
function tupleKey(row, fields) {
|
|
1935
|
+
return fields.map((f) => keyOf(row[f])).join("\0");
|
|
1936
|
+
}
|
|
1937
|
+
function uniqueTuples(rows, fields) {
|
|
1938
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1939
|
+
const out = [];
|
|
1940
|
+
for (const row of rows) {
|
|
1941
|
+
const tuple = fields.map((f) => row[f]);
|
|
1942
|
+
if (tuple.some((v) => v === null || v === void 0)) continue;
|
|
1943
|
+
const key = tuple.map(keyOf).join("\0");
|
|
1944
|
+
if (seen.has(key)) continue;
|
|
1945
|
+
seen.add(key);
|
|
1946
|
+
out.push(tuple);
|
|
1947
|
+
}
|
|
1948
|
+
return out;
|
|
1949
|
+
}
|
|
1950
|
+
function relationKeyWhere(toFields, parentTuples, baseWhere) {
|
|
1951
|
+
const base = baseWhere ?? {};
|
|
1952
|
+
if (toFields.length === 1) {
|
|
1953
|
+
const field = toFields[0];
|
|
1954
|
+
return { ...base, [field]: { in: parentTuples.map((t) => t[0]) } };
|
|
1955
|
+
}
|
|
1956
|
+
const or = parentTuples.map((tuple) => {
|
|
1957
|
+
const cond = {};
|
|
1958
|
+
toFields.forEach((f, i) => {
|
|
1959
|
+
cond[f] = tuple[i];
|
|
1960
|
+
});
|
|
1961
|
+
return cond;
|
|
1962
|
+
});
|
|
1963
|
+
if (Object.keys(base).length === 0) return { OR: or };
|
|
1964
|
+
return { AND: [base, { OR: or }] };
|
|
1965
|
+
}
|
|
1966
|
+
function omitAll(obj, keys) {
|
|
1967
|
+
const out = { ...obj };
|
|
1968
|
+
for (const k of keys) delete out[k];
|
|
1969
|
+
return out;
|
|
1970
|
+
}
|
|
1971
|
+
function applyPagination(rows, take, skip) {
|
|
1972
|
+
const start = skip ?? 0;
|
|
1973
|
+
const end = take != null ? start + take : void 0;
|
|
1974
|
+
return rows.slice(start, end);
|
|
1975
|
+
}
|
|
1976
|
+
function normalizeDistinct(model, distinct) {
|
|
1977
|
+
if (!distinct || distinct.length === 0) return [];
|
|
1978
|
+
for (const name of distinct) {
|
|
1979
|
+
const field = model.fields.find((f) => f.name === name);
|
|
1980
|
+
if (!field || field.kind === "object") {
|
|
1981
|
+
throw new QueryValidationError(
|
|
1982
|
+
`Cannot apply distinct on '${model.name}.${name}' (not a scalar field).`
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
return uniq(distinct);
|
|
1987
|
+
}
|
|
1988
|
+
function dedupeBy(rows, fields) {
|
|
1989
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1990
|
+
const out = [];
|
|
1991
|
+
for (const row of rows) {
|
|
1992
|
+
const key = tupleKey(row, fields);
|
|
1993
|
+
if (seen.has(key)) continue;
|
|
1994
|
+
seen.add(key);
|
|
1995
|
+
out.push(row);
|
|
1996
|
+
}
|
|
1997
|
+
return out;
|
|
1998
|
+
}
|
|
1999
|
+
function applyCursor(model, args, _distinctFields) {
|
|
2000
|
+
if (!args.cursor || Object.keys(args.cursor).length === 0) {
|
|
2001
|
+
return { where: args.where, orderBy: args.orderBy };
|
|
2002
|
+
}
|
|
2003
|
+
const entries = Object.entries(args.cursor).filter(([, v]) => v !== void 0);
|
|
2004
|
+
if (entries.length === 0) return { where: args.where, orderBy: args.orderBy };
|
|
2005
|
+
const fields = entries.map(([name, value]) => {
|
|
2006
|
+
if (!model.fields.some((f) => f.name === name && f.kind !== "object")) {
|
|
2007
|
+
throw new QueryValidationError(
|
|
2008
|
+
`Cursor field '${model.name}.${name}' must be a scalar field.`
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
return { name, value, direction: directionForField(args.orderBy, name) ?? "asc" };
|
|
2012
|
+
});
|
|
2013
|
+
const cursorCond = keysetCondition(fields);
|
|
2014
|
+
const where = args.where ? { AND: [args.where, cursorCond] } : cursorCond;
|
|
2015
|
+
const orderBy = args.orderBy ?? fields.map((f) => ({ [f.name]: f.direction }));
|
|
2016
|
+
return { where, orderBy };
|
|
2017
|
+
}
|
|
2018
|
+
function keysetCondition(fields) {
|
|
2019
|
+
const clauses = [];
|
|
2020
|
+
for (let i = 0; i < fields.length; i++) {
|
|
2021
|
+
const clause = {};
|
|
2022
|
+
for (let j = 0; j < i; j++) {
|
|
2023
|
+
clause[fields[j].name] = fields[j].value;
|
|
2024
|
+
}
|
|
2025
|
+
const last = i === fields.length - 1;
|
|
2026
|
+
const f = fields[i];
|
|
2027
|
+
const op = comparator(f.direction, last);
|
|
2028
|
+
clause[f.name] = { [op]: f.value };
|
|
2029
|
+
clauses.push(clause);
|
|
2030
|
+
}
|
|
2031
|
+
return clauses.length === 1 ? clauses[0] : { OR: clauses };
|
|
2032
|
+
}
|
|
2033
|
+
function comparator(direction, inclusive) {
|
|
2034
|
+
if (direction === "desc") return inclusive ? "lte" : "lt";
|
|
2035
|
+
return inclusive ? "gte" : "gt";
|
|
2036
|
+
}
|
|
2037
|
+
function directionForField(orderBy, field) {
|
|
2038
|
+
if (!orderBy) return void 0;
|
|
2039
|
+
const list = Array.isArray(orderBy) ? orderBy : [orderBy];
|
|
2040
|
+
for (const obj of list) {
|
|
2041
|
+
const dir = obj[field];
|
|
2042
|
+
if (dir === "asc" || dir === "desc") return dir;
|
|
2043
|
+
}
|
|
2044
|
+
return void 0;
|
|
2045
|
+
}
|
|
2046
|
+
function reshapeAggregate(row) {
|
|
2047
|
+
const out = {};
|
|
2048
|
+
for (const [key, value] of Object.entries(row)) {
|
|
2049
|
+
if (key === "_count_all") {
|
|
2050
|
+
out._count ??= {};
|
|
2051
|
+
out._count._all = Number(value);
|
|
2052
|
+
continue;
|
|
2053
|
+
}
|
|
2054
|
+
const m = /^(_count|_sum|_avg|_min|_max)_(.+)$/.exec(key);
|
|
2055
|
+
if (m) {
|
|
2056
|
+
const group = out[m[1]] ??= {};
|
|
2057
|
+
group[m[2]] = value === null ? null : Number(value);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
return out;
|
|
2061
|
+
}
|
|
2062
|
+
function reshapeGroupRow(model, by, row) {
|
|
2063
|
+
const out = {};
|
|
2064
|
+
const groupFields = scalarFields(model).filter((f) => by.includes(f.name));
|
|
2065
|
+
for (const f of groupFields) {
|
|
2066
|
+
out[f.name] = f.name in row ? coerceField(row[f.name], f) : row[f.name];
|
|
2067
|
+
}
|
|
2068
|
+
const aggregates = reshapeAggregate(row);
|
|
2069
|
+
return { ...out, ...aggregates };
|
|
2070
|
+
}
|
|
2071
|
+
function coerceField(value, field) {
|
|
2072
|
+
return coerceRow({ [field.name]: value }, [field])[field.name];
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// src/schema/index.ts
|
|
2076
|
+
var import_node_fs = require("fs");
|
|
2077
|
+
var import_node_path = require("path");
|
|
2078
|
+
function resolveDatasourceUrl(doc, _base) {
|
|
2079
|
+
const ds = doc.datasource;
|
|
2080
|
+
if (!ds) return void 0;
|
|
2081
|
+
if (ds.url.kind === "literal") return ds.url.value;
|
|
2082
|
+
return process.env[ds.url.value];
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
// src/client/runtime.ts
|
|
2086
|
+
var RECORD_OPS = /* @__PURE__ */ new Set([
|
|
2087
|
+
"findMany",
|
|
2088
|
+
"findFirst",
|
|
2089
|
+
"findFirstOrThrow",
|
|
2090
|
+
"findUnique",
|
|
2091
|
+
"findUniqueOrThrow",
|
|
2092
|
+
"create",
|
|
2093
|
+
"createManyAndReturn",
|
|
2094
|
+
"update",
|
|
2095
|
+
"upsert",
|
|
2096
|
+
"delete"
|
|
2097
|
+
]);
|
|
2098
|
+
var ALL_OPS = [
|
|
2099
|
+
"findMany",
|
|
2100
|
+
"findFirst",
|
|
2101
|
+
"findFirstOrThrow",
|
|
2102
|
+
"findUnique",
|
|
2103
|
+
"findUniqueOrThrow",
|
|
2104
|
+
"create",
|
|
2105
|
+
"createMany",
|
|
2106
|
+
"createManyAndReturn",
|
|
2107
|
+
"update",
|
|
2108
|
+
"updateMany",
|
|
2109
|
+
"upsert",
|
|
2110
|
+
"delete",
|
|
2111
|
+
"deleteMany",
|
|
2112
|
+
"count",
|
|
2113
|
+
"aggregate",
|
|
2114
|
+
"groupBy"
|
|
2115
|
+
];
|
|
2116
|
+
var FLUENT_OPS = /* @__PURE__ */ new Set([
|
|
2117
|
+
"findUnique",
|
|
2118
|
+
"findUniqueOrThrow",
|
|
2119
|
+
"findFirst",
|
|
2120
|
+
"findFirstOrThrow"
|
|
2121
|
+
]);
|
|
2122
|
+
function buildDelegate(ctx, modelName) {
|
|
2123
|
+
const model = findModel(ctx.schema, modelName);
|
|
2124
|
+
const delegate = {};
|
|
2125
|
+
for (const operation of ALL_OPS) {
|
|
2126
|
+
delegate[operation] = (args = {}) => runOperation(ctx, modelName, operation, args, delegate);
|
|
2127
|
+
}
|
|
2128
|
+
for (const ext of ctx.extensions) {
|
|
2129
|
+
const methods = {
|
|
2130
|
+
...ext.model?.$allModels ?? {},
|
|
2131
|
+
...ext.model?.[modelName] ?? {}
|
|
2132
|
+
};
|
|
2133
|
+
for (const [name, fn] of Object.entries(methods)) {
|
|
2134
|
+
delegate[name] = (...args) => fn.apply(delegate, args);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
void model;
|
|
2138
|
+
return delegate;
|
|
2139
|
+
}
|
|
2140
|
+
function runOperation(ctx, modelName, operation, args, delegate) {
|
|
2141
|
+
const base = async (a) => {
|
|
2142
|
+
const expanded = expandComputedSelect(ctx, modelName, operation, a);
|
|
2143
|
+
const raw = await runMiddleware(ctx, modelName, operation, expanded.args);
|
|
2144
|
+
return applyResultExtensions(ctx, modelName, operation, raw, expanded.computeOnly);
|
|
2145
|
+
};
|
|
2146
|
+
const hooks = gatherQueryHooks(ctx, modelName, operation);
|
|
2147
|
+
const run = composeHooks(hooks, modelName, operation, base);
|
|
2148
|
+
if (FLUENT_OPS.has(operation)) {
|
|
2149
|
+
return makeFluent(ctx, modelName, args, () => run(args));
|
|
2150
|
+
}
|
|
2151
|
+
void delegate;
|
|
2152
|
+
return run(args);
|
|
2153
|
+
}
|
|
2154
|
+
function gatherQueryHooks(ctx, model, operation) {
|
|
2155
|
+
const hooks = [];
|
|
2156
|
+
for (const ext of ctx.extensions) {
|
|
2157
|
+
const q = ext.query;
|
|
2158
|
+
if (!q) continue;
|
|
2159
|
+
pushHook(hooks, q[model], operation);
|
|
2160
|
+
pushHook(hooks, q.$allModels, operation);
|
|
2161
|
+
}
|
|
2162
|
+
return hooks;
|
|
2163
|
+
}
|
|
2164
|
+
function pushHook(hooks, map, operation) {
|
|
2165
|
+
if (!map) return;
|
|
2166
|
+
if (map[operation]) hooks.push(map[operation]);
|
|
2167
|
+
if (map.$allOperations) hooks.push(map.$allOperations);
|
|
2168
|
+
}
|
|
2169
|
+
function composeHooks(hooks, model, operation, base) {
|
|
2170
|
+
let next = base;
|
|
2171
|
+
for (const hook of hooks) {
|
|
2172
|
+
const inner = next;
|
|
2173
|
+
next = (args) => hook({ model, operation, args, query: (a) => inner(a) });
|
|
2174
|
+
}
|
|
2175
|
+
return next;
|
|
2176
|
+
}
|
|
2177
|
+
function runMiddleware(ctx, model, action, args) {
|
|
2178
|
+
const engine = ctx.engine;
|
|
2179
|
+
const engineCall = (a) => engine[a.action](model, a.args);
|
|
2180
|
+
let next = engineCall;
|
|
2181
|
+
for (const mw of ctx.middlewares) {
|
|
2182
|
+
const inner = next;
|
|
2183
|
+
next = (params) => mw(params, inner);
|
|
2184
|
+
}
|
|
2185
|
+
return next({ model, action, args });
|
|
2186
|
+
}
|
|
2187
|
+
function resultComputers(ctx, model) {
|
|
2188
|
+
const out = [];
|
|
2189
|
+
for (const ext of ctx.extensions) {
|
|
2190
|
+
const r = ext.result?.[model];
|
|
2191
|
+
if (!r) continue;
|
|
2192
|
+
for (const [field, def] of Object.entries(r)) {
|
|
2193
|
+
out.push({ field, compute: def.compute });
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
return out;
|
|
2197
|
+
}
|
|
2198
|
+
function applyResultExtensions(ctx, model, operation, value, computeFields) {
|
|
2199
|
+
if (!RECORD_OPS.has(operation)) return value;
|
|
2200
|
+
const computers = resultComputers(ctx, model).filter(
|
|
2201
|
+
(c) => !computeFields || computeFields.has(c.field)
|
|
2202
|
+
);
|
|
2203
|
+
if (computers.length === 0) return value;
|
|
2204
|
+
const compute = (rec) => {
|
|
2205
|
+
if (!rec || typeof rec !== "object") return rec;
|
|
2206
|
+
const record = rec;
|
|
2207
|
+
for (const c of computers) {
|
|
2208
|
+
try {
|
|
2209
|
+
record[c.field] = c.compute(record);
|
|
2210
|
+
} catch {
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
return record;
|
|
2214
|
+
};
|
|
2215
|
+
return Array.isArray(value) ? value.map(compute) : compute(value);
|
|
2216
|
+
}
|
|
2217
|
+
function expandComputedSelect(ctx, model, operation, args) {
|
|
2218
|
+
const computers = resultComputers(ctx, model);
|
|
2219
|
+
if (computers.length === 0) return { args, computeOnly: null };
|
|
2220
|
+
const select = args.select;
|
|
2221
|
+
if (!select) return { args, computeOnly: null };
|
|
2222
|
+
const byField = new Map(
|
|
2223
|
+
ctx.extensions.flatMap((e) => Object.entries(e.result?.[model] ?? {})).map(([f, def]) => [f, def])
|
|
2224
|
+
);
|
|
2225
|
+
const computeOnly = /* @__PURE__ */ new Set();
|
|
2226
|
+
const newSelect = { ...select };
|
|
2227
|
+
for (const key of Object.keys(select)) {
|
|
2228
|
+
const def = byField.get(key);
|
|
2229
|
+
if (!def) continue;
|
|
2230
|
+
computeOnly.add(key);
|
|
2231
|
+
delete newSelect[key];
|
|
2232
|
+
for (const need of Object.keys(def.needs ?? {})) newSelect[need] = true;
|
|
2233
|
+
}
|
|
2234
|
+
if (computeOnly.size === 0) return { args, computeOnly: null };
|
|
2235
|
+
return { args: { ...args, select: newSelect }, computeOnly };
|
|
2236
|
+
}
|
|
2237
|
+
function makeFluent(ctx, modelName, args, exec) {
|
|
2238
|
+
const model = findModel(ctx.schema, modelName);
|
|
2239
|
+
const where = args.where ?? {};
|
|
2240
|
+
let cached = null;
|
|
2241
|
+
const run = () => cached ??= exec();
|
|
2242
|
+
const fluent = {
|
|
2243
|
+
then: (res, rej) => run().then(res, rej),
|
|
2244
|
+
catch: (rej) => run().catch(rej),
|
|
2245
|
+
finally: (f) => run().finally(f)
|
|
2246
|
+
};
|
|
2247
|
+
for (const rel of relationFields(model)) {
|
|
2248
|
+
fluent[rel.name] = (subArgs = {}) => traverse(ctx, model, rel, where, subArgs);
|
|
2249
|
+
}
|
|
2250
|
+
return fluent;
|
|
2251
|
+
}
|
|
2252
|
+
function traverse(ctx, model, rel, parentWhere, subArgs) {
|
|
2253
|
+
const inverse = inverseField(ctx.schema, model, rel);
|
|
2254
|
+
const relatedWhere = inverse ? inverse.isList ? { [inverse.name]: { some: parentWhere } } : { [inverse.name]: parentWhere } : {};
|
|
2255
|
+
const where = subArgs.where ? { AND: [relatedWhere, subArgs.where] } : relatedWhere;
|
|
2256
|
+
const findArgs = { ...subArgs, where };
|
|
2257
|
+
if (rel.isList) {
|
|
2258
|
+
return ctx.engine.findMany(rel.type, findArgs);
|
|
2259
|
+
}
|
|
2260
|
+
return makeFluent(
|
|
2261
|
+
ctx,
|
|
2262
|
+
rel.type,
|
|
2263
|
+
{ ...subArgs, where },
|
|
2264
|
+
() => ctx.engine.findFirst(rel.type, findArgs)
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2267
|
+
function inverseField(schema, model, rel) {
|
|
2268
|
+
const related = findModel(schema, rel.type);
|
|
2269
|
+
if (!related) return void 0;
|
|
2270
|
+
const candidates = related.fields.filter(
|
|
2271
|
+
(f) => f.kind === "object" && f.type === model.name
|
|
2272
|
+
);
|
|
2273
|
+
if (rel.relation?.name) {
|
|
2274
|
+
const byName = candidates.find((f) => f.relation?.name === rel.relation?.name);
|
|
2275
|
+
if (byName) return byName;
|
|
2276
|
+
}
|
|
2277
|
+
return candidates[0];
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
// src/client/index.ts
|
|
2281
|
+
var EmberClientBase = class {
|
|
2282
|
+
driver;
|
|
2283
|
+
engine;
|
|
2284
|
+
dialect;
|
|
2285
|
+
schema;
|
|
2286
|
+
connected = false;
|
|
2287
|
+
/** Mutated by $extends (per instance) and $use (shared). */
|
|
2288
|
+
extensions = [];
|
|
2289
|
+
middlewares = [];
|
|
2290
|
+
queryListeners = [];
|
|
2291
|
+
baseLog;
|
|
2292
|
+
constructor(options) {
|
|
2293
|
+
this.schema = options.schema;
|
|
2294
|
+
const url = options.datasourceUrl ?? resolveDatasourceUrl(options.schema, process.cwd());
|
|
2295
|
+
if (!options.datasource && !url) {
|
|
2296
|
+
throw new EmberError(
|
|
2297
|
+
"No datasource configured. Provide datasourceUrl, datasource, or a datasource block with a resolvable url."
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
const config = options.datasource ?? parseConnectionUrl(url);
|
|
2301
|
+
this.dialect = new FirebirdDialect({ version: config.version });
|
|
2302
|
+
this.baseLog = buildLogger(options.log);
|
|
2303
|
+
this.driver = createDriver(config, { onQuery: (e) => this.dispatchQuery(e) });
|
|
2304
|
+
this.engine = new QueryEngine(options.schema, this.dialect, this.driver);
|
|
2305
|
+
this.installDelegates();
|
|
2306
|
+
}
|
|
2307
|
+
dispatchQuery(event) {
|
|
2308
|
+
this.baseLog?.(event);
|
|
2309
|
+
for (const listener of this.queryListeners) listener(event);
|
|
2310
|
+
}
|
|
2311
|
+
delegateContext() {
|
|
2312
|
+
return {
|
|
2313
|
+
engine: this.engine,
|
|
2314
|
+
schema: this.schema,
|
|
2315
|
+
extensions: this.extensions,
|
|
2316
|
+
middlewares: this.middlewares
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
/** (Re)define one delegate property per model using the current extensions. */
|
|
2320
|
+
installDelegates() {
|
|
2321
|
+
const ctx = this.delegateContext();
|
|
2322
|
+
for (const model of this.schema.models) {
|
|
2323
|
+
Object.defineProperty(this, lowerFirst(model.name), {
|
|
2324
|
+
value: buildDelegate(ctx, model.name),
|
|
2325
|
+
enumerable: true,
|
|
2326
|
+
configurable: true
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
/**
|
|
2331
|
+
* Prisma-style Client Extensions. Returns a new client (the original is
|
|
2332
|
+
* unchanged) that shares the connection/engine but applies the extension's
|
|
2333
|
+
* `result` / `model` / `query` / `client` definitions.
|
|
2334
|
+
*/
|
|
2335
|
+
$extends(extension) {
|
|
2336
|
+
const clone = Object.create(this);
|
|
2337
|
+
clone.extensions = [...this.extensions, extension];
|
|
2338
|
+
clone.installDelegates();
|
|
2339
|
+
if (extension.client) {
|
|
2340
|
+
for (const [key, value] of Object.entries(extension.client)) {
|
|
2341
|
+
Object.defineProperty(clone, key, {
|
|
2342
|
+
value: typeof value === "function" ? value.bind(clone) : value,
|
|
2343
|
+
enumerable: true,
|
|
2344
|
+
configurable: true
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return clone;
|
|
2349
|
+
}
|
|
2350
|
+
/** Prisma-style middleware: runs around every operation. */
|
|
2351
|
+
$use(middleware) {
|
|
2352
|
+
this.middlewares.push(middleware);
|
|
2353
|
+
}
|
|
2354
|
+
/** Subscribe to query events (mirrors the `log` callback). */
|
|
2355
|
+
$on(event, listener) {
|
|
2356
|
+
if (event === "query") this.queryListeners.push(listener);
|
|
2357
|
+
}
|
|
2358
|
+
/** Type-safe access to a delegate by model name. */
|
|
2359
|
+
model(name) {
|
|
2360
|
+
return this[lowerFirst(name)];
|
|
2361
|
+
}
|
|
2362
|
+
async $connect() {
|
|
2363
|
+
if (this.connected) return;
|
|
2364
|
+
await this.driver.connect();
|
|
2365
|
+
this.connected = true;
|
|
2366
|
+
}
|
|
2367
|
+
async $disconnect() {
|
|
2368
|
+
if (!this.connected) return;
|
|
2369
|
+
await this.driver.disconnect();
|
|
2370
|
+
this.connected = false;
|
|
2371
|
+
}
|
|
2372
|
+
$transaction(arg, options) {
|
|
2373
|
+
if (Array.isArray(arg)) {
|
|
2374
|
+
return this.driver.transaction(async () => {
|
|
2375
|
+
const results = [];
|
|
2376
|
+
for (const thunk of arg) results.push(await thunk(this));
|
|
2377
|
+
return results;
|
|
2378
|
+
}, options);
|
|
2379
|
+
}
|
|
2380
|
+
return this.driver.transaction(() => arg(this), options);
|
|
2381
|
+
}
|
|
2382
|
+
/** Execute a raw read query (returns rows) inside a transaction. */
|
|
2383
|
+
$queryRawUnsafe(sql, ...params) {
|
|
2384
|
+
return this.driver.transaction(
|
|
2385
|
+
(tx) => tx.query(sql, params.map(toSqlValue))
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
2388
|
+
/** Execute a raw write statement inside a transaction; returns affected rows when available. */
|
|
2389
|
+
$executeRawUnsafe(sql, ...params) {
|
|
2390
|
+
return this.driver.transaction(async (tx) => {
|
|
2391
|
+
const rows = await tx.query(sql, params.map(toSqlValue));
|
|
2392
|
+
return Array.isArray(rows) ? rows.length : 0;
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
/** Tagged-template raw query: `client.$queryRaw\`SELECT * FROM T WHERE id = ${id}\``. */
|
|
2396
|
+
$queryRaw(strings, ...values) {
|
|
2397
|
+
const { sql, params } = buildTemplate(strings, values);
|
|
2398
|
+
return this.$queryRawUnsafe(sql, ...params);
|
|
2399
|
+
}
|
|
2400
|
+
$executeRaw(strings, ...values) {
|
|
2401
|
+
const { sql, params } = buildTemplate(strings, values);
|
|
2402
|
+
return this.$executeRawUnsafe(sql, ...params);
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
function buildTemplate(strings, values) {
|
|
2406
|
+
let sql = "";
|
|
2407
|
+
strings.forEach((part, i) => {
|
|
2408
|
+
sql += part;
|
|
2409
|
+
if (i < values.length) sql += "?";
|
|
2410
|
+
});
|
|
2411
|
+
return { sql, params: values };
|
|
2412
|
+
}
|
|
2413
|
+
function toSqlValue(v) {
|
|
2414
|
+
return v;
|
|
2415
|
+
}
|
|
2416
|
+
function buildLogger(log) {
|
|
2417
|
+
if (!log) return void 0;
|
|
2418
|
+
if (typeof log === "function") return log;
|
|
2419
|
+
return (e) => {
|
|
2420
|
+
const params = e.params.length ? ` -- ${JSON.stringify(e.params)}` : "";
|
|
2421
|
+
console.log(
|
|
2422
|
+
`ember:query (${e.durationMs.toFixed(1)}ms, ${e.rowCount} rows) ${e.sql}${params}`
|
|
2423
|
+
);
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
function createClient(options) {
|
|
2427
|
+
return new EmberClientBase(options);
|
|
2428
|
+
}
|
|
2429
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2430
|
+
0 && (module.exports = {
|
|
2431
|
+
EmberClientBase,
|
|
2432
|
+
createClient
|
|
2433
|
+
});
|
|
2434
|
+
//# sourceMappingURL=index.cjs.map
|