@vsrepo/drizzle-adapter 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/dist/index.js ADDED
@@ -0,0 +1,1525 @@
1
+ // src/drizzle.adapter.ts
2
+ import { avg, count as countFn, eq as eq3, max as maxFn, min as minFn, sql as sql2, sum as sumFn } from "drizzle-orm";
3
+ import {
4
+ AdapterErrorCode as AdapterErrorCode10,
5
+ VSRepoAdapter,
6
+ VSRepoAdapterError as VSRepoAdapterError10
7
+ } from "vsrepo";
8
+
9
+ // src/resolvers/fields-config.resolver.ts
10
+ import { AdapterErrorCode, VSRepoAdapterError } from "vsrepo";
11
+ import { getColumns, getTableName } from "drizzle-orm";
12
+ function resolveFieldsConfig(table) {
13
+ let pk;
14
+ for (const column of Object.values(getColumns(table))) {
15
+ if (column.primary) {
16
+ pk = column.name;
17
+ break;
18
+ }
19
+ }
20
+ if (!pk) {
21
+ throw new VSRepoAdapterError(
22
+ `Table '${getTableName(table)}' has no primary key defined. VSRepoDrizzleAdapter requires every table to declare a primary key.`,
23
+ AdapterErrorCode.INVALID_ADAPTER_CONFIG,
24
+ null
25
+ );
26
+ }
27
+ return { pk };
28
+ }
29
+
30
+ // src/resolvers/isolation-level.resolver.ts
31
+ var isolationRecord = {
32
+ ReadCommitted: "read committed",
33
+ ReadUncommitted: "read uncommitted",
34
+ RepeatableRead: "repeatable read",
35
+ Serializable: "serializable"
36
+ };
37
+ function resolveIsolationLevel(vsrepoIsolation) {
38
+ return isolationRecord[vsrepoIsolation];
39
+ }
40
+
41
+ // src/resolvers/raw-sql.resolver.ts
42
+ import { SQL, StringChunk, Param } from "drizzle-orm";
43
+ var PLACEHOLDER_PATTERNS = {
44
+ postgresql: /\$(\d+)/g,
45
+ cockroach: /\$(\d+)/g,
46
+ sqlite: /\?/g
47
+ };
48
+ var NUMBERED_DIALECTS = /* @__PURE__ */ new Set(["postgresql", "cockroach"]);
49
+ function resolveRawSql(dialect, query, args = []) {
50
+ const pattern = PLACEHOLDER_PATTERNS[dialect];
51
+ const isNumbered = NUMBERED_DIALECTS.has(dialect);
52
+ const chunks = [];
53
+ let lastIndex = 0;
54
+ let positionalIndex = 0;
55
+ for (const match of query.matchAll(pattern)) {
56
+ chunks.push(new StringChunk(query.slice(lastIndex, match.index)));
57
+ const argIndex = isNumbered ? Number(match[1]) - 1 : positionalIndex++;
58
+ chunks.push(new Param(args[argIndex]));
59
+ lastIndex = match.index + match[0].length;
60
+ }
61
+ chunks.push(new StringChunk(query.slice(lastIndex)));
62
+ return new SQL(chunks);
63
+ }
64
+
65
+ // src/resolvers/raw-result.resolver.ts
66
+ function resolveRawResult(dialect, result, modifying) {
67
+ switch (dialect) {
68
+ case "postgresql":
69
+ case "cockroach":
70
+ return modifying ? result.rowCount ?? 0 : result.rows;
71
+ // assume node-postgres
72
+ case "sqlite":
73
+ return modifying ? result.changes ?? 0 : result;
74
+ }
75
+ }
76
+
77
+ // src/resolvers/map-drizzle-error.resolver.ts
78
+ import { AdapterErrorCode as AdapterErrorCode2, VSRepoAdapterError as VSRepoAdapterError2 } from "vsrepo";
79
+ import { DrizzleQueryError, TransactionRollbackError } from "drizzle-orm";
80
+ var POSTGRES_SQLSTATE_MAP = {
81
+ "23505": AdapterErrorCode2.UNIQUE_CONSTRAINT_VIOLATION,
82
+ "23503": AdapterErrorCode2.FOREIGN_KEY_VIOLATION,
83
+ "23502": AdapterErrorCode2.NOT_NULL_VIOLATION,
84
+ "23514": AdapterErrorCode2.CHECK_VIOLATION,
85
+ "23000": AdapterErrorCode2.CONSTRAINT_VIOLATION,
86
+ "22001": AdapterErrorCode2.VALUE_TOO_LONG,
87
+ "22P02": AdapterErrorCode2.CONVERSION_ERROR,
88
+ "42601": AdapterErrorCode2.INVALID_QUERY,
89
+ "42703": AdapterErrorCode2.TABLE_OR_COLUMN_NOT_FOUND,
90
+ "42P01": AdapterErrorCode2.TABLE_OR_COLUMN_NOT_FOUND,
91
+ "40P01": AdapterErrorCode2.DEADLOCK,
92
+ "40001": AdapterErrorCode2.TRANSACTION_CONFLICT,
93
+ "55P03": AdapterErrorCode2.LOCK_TIMEOUT,
94
+ "57014": AdapterErrorCode2.TIMEOUT,
95
+ "28000": AdapterErrorCode2.INVALID_CREDENTIALS,
96
+ "28P01": AdapterErrorCode2.INVALID_CREDENTIALS,
97
+ "3D000": AdapterErrorCode2.CONNECTION_FAILED
98
+ };
99
+ var SQLITE_CODE_MAP = {
100
+ SQLITE_CONSTRAINT_UNIQUE: AdapterErrorCode2.UNIQUE_CONSTRAINT_VIOLATION,
101
+ SQLITE_CONSTRAINT_PRIMARYKEY: AdapterErrorCode2.UNIQUE_CONSTRAINT_VIOLATION,
102
+ SQLITE_CONSTRAINT_FOREIGNKEY: AdapterErrorCode2.FOREIGN_KEY_VIOLATION,
103
+ SQLITE_CONSTRAINT_NOTNULL: AdapterErrorCode2.NOT_NULL_VIOLATION,
104
+ SQLITE_CONSTRAINT_CHECK: AdapterErrorCode2.CHECK_VIOLATION,
105
+ SQLITE_CONSTRAINT: AdapterErrorCode2.CONSTRAINT_VIOLATION,
106
+ SQLITE_BUSY: AdapterErrorCode2.LOCK_TIMEOUT,
107
+ SQLITE_LOCKED: AdapterErrorCode2.LOCKED,
108
+ SQLITE_MISUSE: AdapterErrorCode2.INVALID_QUERY,
109
+ SQLITE_CANTOPEN: AdapterErrorCode2.CONNECTION_FAILED
110
+ };
111
+ function resolveCodeFromDriverError(dialect, error) {
112
+ switch (dialect) {
113
+ case "postgresql":
114
+ case "cockroach":
115
+ return typeof error.code === "string" ? POSTGRES_SQLSTATE_MAP[error.code] : void 0;
116
+ case "sqlite":
117
+ return typeof error.code === "string" ? SQLITE_CODE_MAP[error.code] : void 0;
118
+ }
119
+ }
120
+ function mapDrizzleError(error, operation, dialect) {
121
+ if (error instanceof VSRepoAdapterError2) return error;
122
+ if (error instanceof TransactionRollbackError) {
123
+ return new VSRepoAdapterError2(
124
+ `'${operation}' was rolled back intentionally via 'tx.rollback()'`,
125
+ AdapterErrorCode2.UNKNOWN,
126
+ error
127
+ );
128
+ }
129
+ const driverError = error instanceof DrizzleQueryError && error.cause ? error.cause : error;
130
+ const code = resolveCodeFromDriverError(dialect, driverError);
131
+ return new VSRepoAdapterError2(
132
+ `'${operation}' failed: ${driverError?.message ?? "unknown error"}`,
133
+ code ?? AdapterErrorCode2.UNKNOWN,
134
+ error
135
+ );
136
+ }
137
+
138
+ // src/validators/validate-adapter-config.validator.ts
139
+ import { is, Table as Table2 } from "drizzle-orm";
140
+ import { AdapterErrorCode as AdapterErrorCode3, VSRepoAdapterError as VSRepoAdapterError3 } from "vsrepo";
141
+ var SUPPORTED_DIALECTS = /* @__PURE__ */ new Set(["postgresql", "sqlite", "cockroach"]);
142
+ function validateDrizzleAdapterConfig(db, config) {
143
+ if (db === null || db === void 0) {
144
+ throw new VSRepoAdapterError3(
145
+ "Missing Drizzle client: the first constructor argument (db) is null/undefined.",
146
+ AdapterErrorCode3.MISSING_DB_CLIENT,
147
+ null
148
+ );
149
+ }
150
+ const dbLike = db;
151
+ if (typeof dbLike.query !== "object" || dbLike.query === null) {
152
+ throw new VSRepoAdapterError3(
153
+ "Invalid Drizzle client: 'db.query' is undefined \u2014 the client must be created with the 'relations' config (e.g. 'drizzle(connection, { relations })') for DrizzleAdapter to run relational queries.",
154
+ AdapterErrorCode3.MISSING_DB_CLIENT,
155
+ null
156
+ );
157
+ }
158
+ if (config === null || config === void 0) {
159
+ throw new VSRepoAdapterError3(
160
+ "Invalid constructor config: the second constructor argument (config) is null/undefined.",
161
+ AdapterErrorCode3.INVALID_ADAPTER_CONFIG,
162
+ null
163
+ );
164
+ }
165
+ const { table, dialect, queryKey } = config;
166
+ if (!is(table, Table2)) {
167
+ throw new VSRepoAdapterError3(
168
+ "Invalid constructor config (table): expected a Drizzle 'Table' instance (the object exported by your schema, e.g. 'userTable').",
169
+ AdapterErrorCode3.INVALID_ADAPTER_CONFIG,
170
+ null
171
+ );
172
+ }
173
+ if (dialect !== void 0 && !SUPPORTED_DIALECTS.has(dialect)) {
174
+ throw new VSRepoAdapterError3(
175
+ `Invalid constructor config (dialect): '${String(dialect)}' is not supported. Expected one of: ${[...SUPPORTED_DIALECTS].join(", ")}.`,
176
+ AdapterErrorCode3.INVALID_ADAPTER_CONFIG,
177
+ null
178
+ );
179
+ }
180
+ if (typeof queryKey !== "string" || queryKey.length === 0) {
181
+ throw new VSRepoAdapterError3(
182
+ "Invalid constructor config (queryKey): expected a non-empty string matching a key of 'db.query' (the name your schema exports the table under, e.g. 'userTable').",
183
+ AdapterErrorCode3.INVALID_ADAPTER_CONFIG,
184
+ null
185
+ );
186
+ }
187
+ const queryEntry = dbLike.query[queryKey];
188
+ if (typeof queryEntry !== "object" || queryEntry === null || typeof queryEntry.findFirst !== "function" || typeof queryEntry.findMany !== "function") {
189
+ throw new VSRepoAdapterError3(
190
+ `Invalid constructor config (queryKey): no relational query builder found for '${queryKey}' on 'db.query' (expected 'db.query.${queryKey}' to exist \u2014 check that '${queryKey}' matches the name your schema exports the table under, and that the table is included in the 'relations' config).`,
191
+ AdapterErrorCode3.MODEL_NOT_FOUND,
192
+ null
193
+ );
194
+ }
195
+ return { db, config };
196
+ }
197
+
198
+ // src/validators/validate-relations.validator.ts
199
+ import { getColumns as getColumns2, getTableName as getTableName2, is as is2, Table as Table3 } from "drizzle-orm";
200
+ import { AdapterErrorCode as AdapterErrorCode4, VSRepoAdapterError as VSRepoAdapterError4 } from "vsrepo";
201
+
202
+ // src/validators/is-plain-object.validator.ts
203
+ function isPlainObject(value) {
204
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
205
+ }
206
+
207
+ // src/validators/validate-relations.validator.ts
208
+ var MODES = /* @__PURE__ */ new Set(["otm", "mto", "oto"]);
209
+ var RESTRICTIONS = /* @__PURE__ */ new Set(["set", "add"]);
210
+ function fail(message) {
211
+ throw new VSRepoAdapterError4(message, AdapterErrorCode4.INVALID_ADAPTER_CONFIG, null);
212
+ }
213
+ function validateRelations(table, dialect, relations) {
214
+ if (relations === void 0) return void 0;
215
+ if (!isPlainObject(relations)) {
216
+ fail(
217
+ "Invalid constructor config (relations): expected an object mapping relation field names to their config."
218
+ );
219
+ }
220
+ const hereColumns = new Set(Object.keys(getColumns2(table)));
221
+ const resolved = /* @__PURE__ */ new Map();
222
+ for (const [key, rawRelation] of Object.entries(relations)) {
223
+ if (rawRelation === void 0) continue;
224
+ if (!isPlainObject(rawRelation)) {
225
+ fail(`Invalid constructor config (relations.${key}): expected an object (an 'AdapterRelation').`);
226
+ }
227
+ const { mode, restriction, table: relatedTable, fkHere, fkThere, nullable } = rawRelation;
228
+ if (!is2(relatedTable, Table3)) {
229
+ fail(
230
+ `Invalid constructor config (relations.${key}.table): expected a Drizzle 'Table' instance (the object exported by your schema).`
231
+ );
232
+ }
233
+ if (!MODES.has(mode)) {
234
+ fail(
235
+ `Invalid constructor config (relations.${key}.mode): expected one of 'otm' | 'mto' | 'oto', got '${String(mode)}'.`
236
+ );
237
+ }
238
+ if (!RESTRICTIONS.has(restriction)) {
239
+ fail(
240
+ `Invalid constructor config (relations.${key}.restriction): expected one of 'set' | 'add', got '${String(restriction)}'.`
241
+ );
242
+ }
243
+ const thereColumns = new Set(Object.keys(getColumns2(relatedTable)));
244
+ if (mode === "otm") {
245
+ if (fkHere !== void 0) {
246
+ fail(`Invalid constructor config (relations.${key}): mode 'otm' doesn't accept 'fkHere'.`);
247
+ }
248
+ if (typeof fkThere !== "string" || fkThere.length === 0) {
249
+ fail(
250
+ `Invalid constructor config (relations.${key}): mode 'otm' requires 'fkThere' (a column of the related table).`
251
+ );
252
+ }
253
+ if (!thereColumns.has(fkThere)) {
254
+ fail(
255
+ `Invalid constructor config (relations.${key}.fkThere): '${fkThere}' is not a column of table '${getTableName2(relatedTable)}'.`
256
+ );
257
+ }
258
+ } else if (mode === "mto") {
259
+ if (fkThere !== void 0) {
260
+ fail(`Invalid constructor config (relations.${key}): mode 'mto' doesn't accept 'fkThere'.`);
261
+ }
262
+ if (typeof fkHere !== "string" || fkHere.length === 0) {
263
+ fail(
264
+ `Invalid constructor config (relations.${key}): mode 'mto' requires 'fkHere' (a column of this adapter's table).`
265
+ );
266
+ }
267
+ if (!hereColumns.has(fkHere)) {
268
+ fail(
269
+ `Invalid constructor config (relations.${key}.fkHere): '${fkHere}' is not a column of table '${getTableName2(table)}'.`
270
+ );
271
+ }
272
+ if (nullable !== void 0 && typeof nullable !== "boolean") {
273
+ fail(`Invalid constructor config (relations.${key}.nullable): expected a boolean.`);
274
+ }
275
+ } else {
276
+ const hasHere = fkHere !== void 0;
277
+ const hasThere = fkThere !== void 0;
278
+ if (hasHere === hasThere) {
279
+ fail(
280
+ `Invalid constructor config (relations.${key}): mode 'oto' requires exactly one of 'fkHere' / 'fkThere'.`
281
+ );
282
+ }
283
+ if (hasHere) {
284
+ if (typeof fkHere !== "string" || !hereColumns.has(fkHere)) {
285
+ fail(
286
+ `Invalid constructor config (relations.${key}.fkHere): '${String(fkHere)}' is not a column of table '${getTableName2(table)}'.`
287
+ );
288
+ }
289
+ } else {
290
+ if (typeof fkThere !== "string" || !thereColumns.has(fkThere)) {
291
+ fail(
292
+ `Invalid constructor config (relations.${key}.fkThere): '${String(fkThere)}' is not a column of table '${getTableName2(relatedTable)}'.`
293
+ );
294
+ }
295
+ }
296
+ }
297
+ const relatedFieldsConfig = resolveFieldsConfig(relatedTable);
298
+ resolved.set(key, {
299
+ mode,
300
+ restriction,
301
+ table: relatedTable,
302
+ fkHere,
303
+ fkThere,
304
+ nullable,
305
+ relatedPk: relatedFieldsConfig.pk
306
+ });
307
+ }
308
+ return resolved;
309
+ }
310
+
311
+ // src/parsers/columns.parser.ts
312
+ function parseColumns(select) {
313
+ const columns = {};
314
+ let withResult;
315
+ for (const [key, value] of Object.entries(select)) {
316
+ if (value === void 0) continue;
317
+ if (isPlainObject(value)) {
318
+ withResult ??= {};
319
+ const nested = parseColumns(value);
320
+ withResult[key] = nested.with ? { columns: nested.columns, with: nested.with } : { columns: nested.columns };
321
+ } else {
322
+ columns[key] = value;
323
+ }
324
+ }
325
+ return { columns, with: withResult };
326
+ }
327
+
328
+ // src/parsers/with.parser.ts
329
+ function parseWith(relations) {
330
+ const result = {};
331
+ for (const [key, value] of Object.entries(relations)) {
332
+ if (value === void 0) continue;
333
+ result[key] = isPlainObject(value) ? { with: parseWith(value) } : value;
334
+ }
335
+ return result;
336
+ }
337
+
338
+ // src/parsers/where.parser.ts
339
+ import { AdapterErrorCode as AdapterErrorCode5, VSRepoAdapterError as VSRepoAdapterError5 } from "vsrepo";
340
+ var FIELD_OPERATOR_KEYS = /* @__PURE__ */ new Set([
341
+ "equals",
342
+ "not",
343
+ "in",
344
+ "notIn",
345
+ "gt",
346
+ "gte",
347
+ "lt",
348
+ "lte",
349
+ "between",
350
+ "contains",
351
+ "startsWith",
352
+ "endsWith",
353
+ "ignoreCase"
354
+ ]);
355
+ function isFieldOperatorObject(value) {
356
+ return Object.keys(value).some((key) => FIELD_OPERATOR_KEYS.has(key));
357
+ }
358
+ function isArrayRelationFilter(value) {
359
+ return "_some" in value || "_every" in value || "_none" in value;
360
+ }
361
+ function isObjectRelationFilter(value) {
362
+ return "_with" in value || "_without" in value;
363
+ }
364
+ function parseFieldOperators(value, dialect) {
365
+ const result = {};
366
+ const likeKey = value.ignoreCase === true && dialect !== "sqlite" ? "ilike" : "like";
367
+ for (const [key, val] of Object.entries(value)) {
368
+ if (val === void 0) continue;
369
+ switch (key) {
370
+ case "ignoreCase":
371
+ break;
372
+ case "equals":
373
+ result.eq = val;
374
+ break;
375
+ case "between": {
376
+ const [min, max] = val;
377
+ if (min !== void 0) result.gte = min;
378
+ if (max !== void 0) result.lte = max;
379
+ break;
380
+ }
381
+ case "contains":
382
+ result[likeKey] = `%${val}%`;
383
+ break;
384
+ case "startsWith":
385
+ result[likeKey] = `${val}%`;
386
+ break;
387
+ case "endsWith":
388
+ result[likeKey] = `%${val}`;
389
+ break;
390
+ case "not":
391
+ result.NOT = isPlainObject(val) && isFieldOperatorObject(val) ? parseFieldOperators(val, dialect) : val;
392
+ break;
393
+ default:
394
+ result[key] = val;
395
+ }
396
+ }
397
+ return result;
398
+ }
399
+ function parseArrayRelationFilter(value, dialect) {
400
+ if (value._every !== void 0 || value._none !== void 0) {
401
+ throw new VSRepoAdapterError5(
402
+ "Drizzle's relational query API has no native 'every'/'none' filter for to-many relations (only an implicit 'some'/exists filter, via '_some') \u2014 rewrite the filter using '_some', or fall back to 'query()' with raw SQL.",
403
+ AdapterErrorCode5.NOT_SUPPORTED,
404
+ null
405
+ );
406
+ }
407
+ return parsePlainWhere(value._some, dialect) ?? {};
408
+ }
409
+ function parseObjectRelationFilter(value, dialect) {
410
+ if (value._with !== void 0) {
411
+ return parsePlainWhere(value._with, dialect) ?? {};
412
+ }
413
+ if (value._without !== void 0) {
414
+ return { NOT: parsePlainWhere(value._without, dialect) };
415
+ }
416
+ return {};
417
+ }
418
+ function parseFieldValue(value, dialect) {
419
+ if (value === null || typeof value !== "object" || value instanceof Date) {
420
+ return value;
421
+ }
422
+ if (Array.isArray(value)) {
423
+ return { in: value };
424
+ }
425
+ const obj = value;
426
+ if (isArrayRelationFilter(obj)) return parseArrayRelationFilter(obj, dialect);
427
+ if (isObjectRelationFilter(obj)) return parseObjectRelationFilter(obj, dialect);
428
+ if (isFieldOperatorObject(obj)) return parseFieldOperators(obj, dialect);
429
+ return parsePlainWhere(obj, dialect);
430
+ }
431
+ function parsePlainWhere(where, dialect) {
432
+ if (where === void 0 || where === null) return void 0;
433
+ const result = {};
434
+ for (const [key, value] of Object.entries(where)) {
435
+ if (value === void 0) continue;
436
+ result[key] = parseFieldValue(value, dialect);
437
+ }
438
+ return result;
439
+ }
440
+ function parseWhere(where, dialect) {
441
+ if (where === void 0 || where === null) return void 0;
442
+ const result = {};
443
+ let andList;
444
+ for (const [key, value] of Object.entries(where)) {
445
+ if (value === void 0) continue;
446
+ if (key === "AND") {
447
+ const list = Array.isArray(value) ? value : [value];
448
+ andList = [...andList ?? [], ...list.map((v) => parseWhere(v, dialect))];
449
+ continue;
450
+ }
451
+ if (key === "OR") {
452
+ const list = Array.isArray(value) ? value : [value];
453
+ result.OR = list.map((v) => parseWhere(v, dialect));
454
+ continue;
455
+ }
456
+ if (key === "NOT") {
457
+ if (Array.isArray(value)) {
458
+ andList = [...andList ?? [], ...value.map((v) => ({ NOT: parseWhere(v, dialect) }))];
459
+ } else {
460
+ result.NOT = parseWhere(value, dialect);
461
+ }
462
+ continue;
463
+ }
464
+ result[key] = parseFieldValue(value, dialect);
465
+ }
466
+ if (andList) result.AND = andList;
467
+ return result;
468
+ }
469
+ function parseDrizzleWhere(where, dialect) {
470
+ return parseWhere(where, dialect);
471
+ }
472
+ function hasQuantifierFilter(where) {
473
+ if (where === null || where === void 0) return false;
474
+ if (Array.isArray(where)) {
475
+ return where.some(hasQuantifierFilter);
476
+ }
477
+ if (typeof where !== "object" || where instanceof Date) {
478
+ return false;
479
+ }
480
+ for (const [key, value] of Object.entries(where)) {
481
+ if (value === void 0) continue;
482
+ if (key === "_every" || key === "_none") return true;
483
+ if (hasQuantifierFilter(value)) return true;
484
+ }
485
+ return false;
486
+ }
487
+
488
+ // src/parsers/sql-where.parser.ts
489
+ import {
490
+ and,
491
+ eq,
492
+ exists,
493
+ getTableName as getTableName3,
494
+ gt,
495
+ gte,
496
+ inArray,
497
+ isNotNull,
498
+ isNull,
499
+ like,
500
+ lt,
501
+ lte,
502
+ not,
503
+ notInArray,
504
+ or,
505
+ sql
506
+ } from "drizzle-orm";
507
+ import { AdapterErrorCode as AdapterErrorCode6, VSRepoAdapterError as VSRepoAdapterError6 } from "vsrepo";
508
+ var FIELD_OPERATOR_KEYS2 = /* @__PURE__ */ new Set([
509
+ "equals",
510
+ "not",
511
+ "in",
512
+ "notIn",
513
+ "gt",
514
+ "gte",
515
+ "lt",
516
+ "lte",
517
+ "between",
518
+ "contains",
519
+ "startsWith",
520
+ "endsWith",
521
+ "ignoreCase"
522
+ ]);
523
+ function isFieldOperatorObject2(value) {
524
+ return Object.keys(value).some((key) => FIELD_OPERATOR_KEYS2.has(key));
525
+ }
526
+ function isArrayRelationFilter2(value) {
527
+ return "_some" in value || "_every" in value || "_none" in value;
528
+ }
529
+ function isObjectRelationFilter2(value) {
530
+ return "_with" in value || "_without" in value;
531
+ }
532
+ function buildFieldOperators(column, ops) {
533
+ const parts = [];
534
+ const likeFn = ops.ignoreCase === true ? (column2, pattern) => sql`lower(${column2}) like lower(${pattern})` : like;
535
+ for (const [key, val] of Object.entries(ops)) {
536
+ if (val === void 0) continue;
537
+ switch (key) {
538
+ case "ignoreCase":
539
+ break;
540
+ case "equals":
541
+ parts.push(val === null ? isNull(column) : eq(column, val));
542
+ break;
543
+ case "not": {
544
+ if (val === null) {
545
+ parts.push(isNotNull(column));
546
+ break;
547
+ }
548
+ const inner = isPlainObject(val) && isFieldOperatorObject2(val) ? buildFieldOperators(column, val) : eq(column, val);
549
+ if (inner) parts.push(not(inner));
550
+ break;
551
+ }
552
+ case "in":
553
+ parts.push(inArray(column, val));
554
+ break;
555
+ case "notIn":
556
+ parts.push(notInArray(column, val));
557
+ break;
558
+ case "gt":
559
+ parts.push(gt(column, val));
560
+ break;
561
+ case "gte":
562
+ parts.push(gte(column, val));
563
+ break;
564
+ case "lt":
565
+ parts.push(lt(column, val));
566
+ break;
567
+ case "lte":
568
+ parts.push(lte(column, val));
569
+ break;
570
+ case "between": {
571
+ const [min, max] = val;
572
+ if (min !== void 0) parts.push(gte(column, min));
573
+ if (max !== void 0) parts.push(lte(column, max));
574
+ break;
575
+ }
576
+ case "contains":
577
+ parts.push(likeFn(column, `%${val}%`));
578
+ break;
579
+ case "startsWith":
580
+ parts.push(likeFn(column, `${val}%`));
581
+ break;
582
+ case "endsWith":
583
+ parts.push(likeFn(column, `%${val}`));
584
+ break;
585
+ }
586
+ }
587
+ if (parts.length === 0) return void 0;
588
+ return parts.length === 1 ? parts[0] : and(...parts);
589
+ }
590
+ function buildJoinCondition(relation, ctx) {
591
+ const relatedColumns = relation.table;
592
+ const hereColumns = ctx.table;
593
+ if (relation.fkHere) {
594
+ return eq(relatedColumns[relation.relatedPk], hereColumns[relation.fkHere]);
595
+ }
596
+ return eq(relatedColumns[relation.fkThere], hereColumns[ctx.pk]);
597
+ }
598
+ function buildExists(relation, condition, ctx) {
599
+ const subquery = ctx.db.select({ one: sql`1` }).from(relation.table).where(condition);
600
+ return exists(subquery);
601
+ }
602
+ function buildRelationCondition(key, value, relation, ctx) {
603
+ const nestedCtx = { ...ctx, table: relation.table, relations: void 0 };
604
+ if (isArrayRelationFilter2(value)) {
605
+ if (relation.mode !== "otm") {
606
+ throw new VSRepoAdapterError6(
607
+ `Field '${key}': '_some'/'_every'/'_none' can only be used on a to-many ('otm') relation.`,
608
+ AdapterErrorCode6.INVALID_DATA,
609
+ null
610
+ );
611
+ }
612
+ const join2 = buildJoinCondition(relation, ctx);
613
+ if (value._some !== void 0) {
614
+ const nested3 = parsePlainWhere2(value._some, nestedCtx);
615
+ return buildExists(relation, nested3 ? and(join2, nested3) : join2, ctx);
616
+ }
617
+ if (value._none !== void 0) {
618
+ const nested3 = parsePlainWhere2(value._none, nestedCtx);
619
+ return not(buildExists(relation, nested3 ? and(join2, nested3) : join2, ctx));
620
+ }
621
+ const nested2 = parsePlainWhere2(value._every, nestedCtx);
622
+ if (!nested2) return sql`(1 = 1)`;
623
+ return not(buildExists(relation, and(join2, not(nested2)), ctx));
624
+ }
625
+ if (isObjectRelationFilter2(value)) {
626
+ const isWithout = "_without" in value;
627
+ const filter = isWithout ? value._without : value._with;
628
+ const join2 = buildJoinCondition(relation, ctx);
629
+ const nested2 = parsePlainWhere2(filter, nestedCtx);
630
+ const condition = nested2 ? and(join2, isWithout ? not(nested2) : nested2) : join2;
631
+ return buildExists(relation, condition, ctx);
632
+ }
633
+ if (relation.mode === "otm") {
634
+ throw new VSRepoAdapterError6(
635
+ `Field '${key}': to-many relation filters require '_some' (or the unsupported '_every'/'_none').`,
636
+ AdapterErrorCode6.INVALID_DATA,
637
+ null
638
+ );
639
+ }
640
+ const join = buildJoinCondition(relation, ctx);
641
+ const nested = parsePlainWhere2(value, nestedCtx);
642
+ return buildExists(relation, nested ? and(join, nested) : join, ctx);
643
+ }
644
+ function buildFieldCondition(column, value) {
645
+ if (value === null) return isNull(column);
646
+ if (value instanceof Date || typeof value !== "object") {
647
+ return eq(column, value);
648
+ }
649
+ if (Array.isArray(value)) {
650
+ return inArray(column, value);
651
+ }
652
+ return buildFieldOperators(column, value);
653
+ }
654
+ function parsePlainWhere2(where, ctx) {
655
+ if (where === void 0 || where === null) return void 0;
656
+ const tableColumns = ctx.table;
657
+ const parts = [];
658
+ for (const [key, value] of Object.entries(where)) {
659
+ if (value === void 0) continue;
660
+ const relation = ctx.relations?.get(key);
661
+ if (relation) {
662
+ parts.push(buildRelationCondition(key, value, relation, ctx));
663
+ continue;
664
+ }
665
+ const column = tableColumns[key];
666
+ if (column === void 0) {
667
+ throw new VSRepoAdapterError6(
668
+ `Unknown field '${key}' in 'where': no column or configured relation with that name on table '${getTableName3(ctx.table)}'.`,
669
+ AdapterErrorCode6.FIELD_NOT_FOUND,
670
+ null
671
+ );
672
+ }
673
+ const condition = buildFieldCondition(column, value);
674
+ if (condition) parts.push(condition);
675
+ }
676
+ if (parts.length === 0) return void 0;
677
+ return parts.length === 1 ? parts[0] : and(...parts);
678
+ }
679
+ function parseWhere2(where, ctx) {
680
+ if (where === void 0 || where === null) return void 0;
681
+ const parts = [];
682
+ let orPart;
683
+ const plainEntries = {};
684
+ for (const [key, value] of Object.entries(where)) {
685
+ if (value === void 0) continue;
686
+ if (key === "AND") {
687
+ const list = Array.isArray(value) ? value : [value];
688
+ for (const v of list) {
689
+ const c = parseWhere2(v, ctx);
690
+ if (c) parts.push(c);
691
+ }
692
+ continue;
693
+ }
694
+ if (key === "OR") {
695
+ const list = Array.isArray(value) ? value : [value];
696
+ const ors = list.map((v) => parseWhere2(v, ctx)).filter((c) => c !== void 0);
697
+ if (ors.length > 0) orPart = ors.length === 1 ? ors[0] : or(...ors);
698
+ continue;
699
+ }
700
+ if (key === "NOT") {
701
+ const list = Array.isArray(value) ? value : [value];
702
+ for (const v of list) {
703
+ const c = parseWhere2(v, ctx);
704
+ if (c) parts.push(not(c));
705
+ }
706
+ continue;
707
+ }
708
+ plainEntries[key] = value;
709
+ }
710
+ const plainCondition = parsePlainWhere2(plainEntries, ctx);
711
+ if (plainCondition) parts.push(plainCondition);
712
+ if (orPart) parts.push(orPart);
713
+ if (parts.length === 0) return void 0;
714
+ return parts.length === 1 ? parts[0] : and(...parts);
715
+ }
716
+ function parseSqlWhere(where, ctx) {
717
+ return parseWhere2(where, ctx);
718
+ }
719
+
720
+ // src/parsers/order-by.parser.ts
721
+ import { AdapterErrorCode as AdapterErrorCode7, VSRepoAdapterError as VSRepoAdapterError7 } from "vsrepo";
722
+ function isSortDirection(value) {
723
+ return value === "asc" || value === "desc" || value === "ASC" || value === "DESC";
724
+ }
725
+ function parseOrderByField(order) {
726
+ const result = {};
727
+ for (const [key, value] of Object.entries(order)) {
728
+ if (value === void 0) continue;
729
+ if (isSortDirection(value)) {
730
+ result[key] = value.toLowerCase();
731
+ continue;
732
+ }
733
+ if (isPlainObject(value)) {
734
+ throw new VSRepoAdapterError7(
735
+ `Ordering by a nested relation field ('${key}') isn't supported at the top level of a Drizzle relational query \u2014 nest the ordering inside that relation's own 'with' config instead.`,
736
+ AdapterErrorCode7.NOT_SUPPORTED,
737
+ null
738
+ );
739
+ }
740
+ result[key] = value;
741
+ }
742
+ return result;
743
+ }
744
+ function parseOrderBy(order) {
745
+ if (order === void 0 || order === null) return void 0;
746
+ if (!Array.isArray(order)) return parseOrderByField(order);
747
+ return order.reduce((acc, field) => ({ ...acc, ...parseOrderByField(field) }), {});
748
+ }
749
+
750
+ // src/parsers/sql-order-by.parser.ts
751
+ import { asc, desc } from "drizzle-orm";
752
+ import { AdapterErrorCode as AdapterErrorCode8, VSRepoAdapterError as VSRepoAdapterError8 } from "vsrepo";
753
+ function isSortDirection2(value) {
754
+ return value === "asc" || value === "desc" || value === "ASC" || value === "DESC";
755
+ }
756
+ function pushOrderByField(table, order, acc) {
757
+ const tableColumns = table;
758
+ for (const [key, value] of Object.entries(order)) {
759
+ if (value === void 0) continue;
760
+ if (!isSortDirection2(value)) {
761
+ if (isPlainObject(value)) {
762
+ throw new VSRepoAdapterError8(
763
+ `Ordering by a nested relation field ('${key}') isn't supported by this adapter.`,
764
+ AdapterErrorCode8.NOT_SUPPORTED,
765
+ null
766
+ );
767
+ }
768
+ continue;
769
+ }
770
+ const column = tableColumns[key];
771
+ if (!column) continue;
772
+ acc.push(value.toString().toLowerCase() === "desc" ? desc(column) : asc(column));
773
+ }
774
+ }
775
+ function parseSqlOrderBy(table, order) {
776
+ if (order === void 0 || order === null) return void 0;
777
+ const acc = [];
778
+ const list = Array.isArray(order) ? order : [order];
779
+ for (const field of list) pushOrderByField(table, field, acc);
780
+ return acc.length > 0 ? acc : void 0;
781
+ }
782
+
783
+ // src/resolvers/merge-entities.resolver.ts
784
+ function deepMergeValue(target, source) {
785
+ if (Array.isArray(target) && Array.isArray(source)) {
786
+ return [...target, ...source];
787
+ }
788
+ if (isPlainObject(target) && isPlainObject(source)) {
789
+ return deepMergePlain(target, source);
790
+ }
791
+ return source;
792
+ }
793
+ function deepMergePlain(target, source) {
794
+ const merged = { ...target };
795
+ for (const [key, value] of Object.entries(source)) {
796
+ if (value === void 0) continue;
797
+ merged[key] = key in target ? deepMergeValue(target[key], value) : value;
798
+ }
799
+ return merged;
800
+ }
801
+ function mergeToManyRelation(target, source, relatedPk) {
802
+ const targetByPk = /* @__PURE__ */ new Map();
803
+ const targetWithoutPk = [];
804
+ for (const item of target) {
805
+ if (item[relatedPk] !== void 0) {
806
+ targetByPk.set(item[relatedPk], item);
807
+ } else {
808
+ targetWithoutPk.push(item);
809
+ }
810
+ }
811
+ const sourceWithoutPk = [];
812
+ for (const item of source) {
813
+ if (item[relatedPk] === void 0) {
814
+ sourceWithoutPk.push(item);
815
+ continue;
816
+ }
817
+ const existing = targetByPk.get(item[relatedPk]);
818
+ targetByPk.set(item[relatedPk], existing ? deepMergePlain(existing, item) : item);
819
+ }
820
+ return [...targetByPk.values(), ...targetWithoutPk, ...sourceWithoutPk];
821
+ }
822
+ function mergeEntities(result, obj, relations) {
823
+ if (!relations) {
824
+ return deepMergePlain(result, obj);
825
+ }
826
+ const merged = { ...result };
827
+ for (const [key, field] of Object.entries(obj)) {
828
+ if (field === void 0) continue;
829
+ const relation = relations.get(key);
830
+ if (!relation) {
831
+ merged[key] = field;
832
+ continue;
833
+ }
834
+ if (relation.mode !== "otm" && isPlainObject(merged[key])) {
835
+ merged[key] = field === null ? null : deepMergePlain(merged[key], field);
836
+ continue;
837
+ }
838
+ if (relation.mode === "otm" && Array.isArray(merged[key])) {
839
+ merged[key] = mergeToManyRelation(merged[key], field, relation.relatedPk);
840
+ continue;
841
+ }
842
+ merged[key] = field;
843
+ }
844
+ return merged;
845
+ }
846
+
847
+ // src/resolvers/relation-writes.resolver.ts
848
+ import { and as and2, eq as eq2, ne, notInArray as notInArray2 } from "drizzle-orm";
849
+ import { AdapterErrorCode as AdapterErrorCode9, VSRepoAdapterError as VSRepoAdapterError9 } from "vsrepo";
850
+ var SKIP = /* @__PURE__ */ Symbol("skip");
851
+ function omitKey(item, key) {
852
+ const clone = { ...item };
853
+ delete clone[key];
854
+ return clone;
855
+ }
856
+ function splitWritePayload(obj, relations, pkName, excludePk) {
857
+ const scalarFields = {};
858
+ const fkHereEntries = [];
859
+ const fkThereEntries = [];
860
+ for (const [key, value] of Object.entries(obj)) {
861
+ if (value === void 0) continue;
862
+ const relation = relations?.get(key);
863
+ if (!relation) {
864
+ if (excludePk && key === pkName) continue;
865
+ scalarFields[key] = value;
866
+ continue;
867
+ }
868
+ if (relation.fkHere) {
869
+ fkHereEntries.push([key, relation, value]);
870
+ } else {
871
+ fkThereEntries.push([key, relation, value]);
872
+ }
873
+ }
874
+ return { scalarFields, fkHereEntries, fkThereEntries };
875
+ }
876
+ async function resolveFkHereField(tx, relation, field, currentFkValue) {
877
+ const relatedTable = relation.table;
878
+ const pkColumn = relatedTable[relation.relatedPk];
879
+ if (field === null) {
880
+ if (relation.mode === "mto" && relation.nullable) return null;
881
+ if (relation.mode === "oto" && relation.restriction === "set") {
882
+ if (currentFkValue !== void 0 && currentFkValue !== null) {
883
+ await tx.delete(relation.table).where(eq2(pkColumn, currentFkValue));
884
+ }
885
+ return null;
886
+ }
887
+ return SKIP;
888
+ }
889
+ const pkValue = field[relation.relatedPk];
890
+ if (pkValue === void 0) {
891
+ const [created] = await tx.insert(relation.table).values(field).returning();
892
+ return created[relation.relatedPk];
893
+ }
894
+ const existing = await tx.select({ pk: pkColumn }).from(relation.table).where(eq2(pkColumn, pkValue)).limit(1);
895
+ if (existing.length === 0) {
896
+ await tx.insert(relation.table).values(field);
897
+ } else if (relation.restriction === "set") {
898
+ const dataWithoutPk = omitKey(field, relation.relatedPk);
899
+ if (Object.keys(dataWithoutPk).length > 0) {
900
+ await tx.update(relation.table).set(dataWithoutPk).where(eq2(pkColumn, pkValue));
901
+ }
902
+ }
903
+ return pkValue;
904
+ }
905
+ async function resolveFkHereFields(tx, entries, scalarFields, currentRow) {
906
+ for (const [key, relation, value] of entries) {
907
+ if (value !== null && typeof value !== "object") {
908
+ throw new VSRepoAdapterError9(
909
+ `Field '${key}': expected an object or 'null' for a to-one relation, got '${typeof value}'.`,
910
+ AdapterErrorCode9.INVALID_DATA,
911
+ null
912
+ );
913
+ }
914
+ const currentFkValue = currentRow?.[relation.fkHere];
915
+ const resolved = await resolveFkHereField(tx, relation, value, currentFkValue);
916
+ if (resolved !== SKIP) {
917
+ scalarFields[relation.fkHere] = resolved;
918
+ }
919
+ }
920
+ }
921
+ async function resolveOtmField(tx, relation, items, ownPkValue) {
922
+ const relatedTable = relation.table;
923
+ const fkColumn = relatedTable[relation.fkThere];
924
+ const pkColumn = relatedTable[relation.relatedPk];
925
+ const withoutPk = items.filter((item) => item[relation.relatedPk] === void 0);
926
+ const withPk = items.filter((item) => item[relation.relatedPk] !== void 0);
927
+ const connectedIds = [];
928
+ for (const item of withoutPk) {
929
+ const [insertedRow] = await tx.insert(relation.table).values({ ...item, [relation.fkThere]: ownPkValue }).returning();
930
+ if (insertedRow?.[relation.relatedPk] !== void 0) {
931
+ connectedIds.push(insertedRow[relation.relatedPk]);
932
+ }
933
+ }
934
+ for (const item of withPk) {
935
+ const pkValue = item[relation.relatedPk];
936
+ connectedIds.push(pkValue);
937
+ const existing = await tx.select({ pk: pkColumn }).from(relation.table).where(eq2(pkColumn, pkValue)).limit(1);
938
+ if (existing.length === 0) {
939
+ await tx.insert(relation.table).values({ ...item, [relation.fkThere]: ownPkValue });
940
+ continue;
941
+ }
942
+ const setData = relation.restriction === "set" ? { ...omitKey(item, relation.relatedPk), [relation.fkThere]: ownPkValue } : { [relation.fkThere]: ownPkValue };
943
+ await tx.update(relation.table).set(setData).where(eq2(pkColumn, pkValue));
944
+ }
945
+ if (relation.restriction === "set") {
946
+ const condition = connectedIds.length > 0 ? and2(eq2(fkColumn, ownPkValue), notInArray2(pkColumn, connectedIds)) : eq2(fkColumn, ownPkValue);
947
+ await tx.delete(relation.table).where(condition);
948
+ }
949
+ }
950
+ async function resolveOtoFkThereField(tx, relation, field, ownPkValue) {
951
+ const relatedTable = relation.table;
952
+ const fkColumn = relatedTable[relation.fkThere];
953
+ const pkColumn = relatedTable[relation.relatedPk];
954
+ if (field === null) {
955
+ if (relation.restriction === "set") {
956
+ await tx.delete(relation.table).where(eq2(fkColumn, ownPkValue));
957
+ }
958
+ return;
959
+ }
960
+ const pkValue = field[relation.relatedPk];
961
+ let keepPk = pkValue;
962
+ if (pkValue === void 0) {
963
+ const [insertedRow] = await tx.insert(relation.table).values({ ...field, [relation.fkThere]: ownPkValue }).returning();
964
+ keepPk = insertedRow?.[relation.relatedPk];
965
+ } else {
966
+ const existing = await tx.select({ pk: pkColumn }).from(relation.table).where(eq2(pkColumn, pkValue)).limit(1);
967
+ if (existing.length === 0) {
968
+ await tx.insert(relation.table).values({ ...field, [relation.fkThere]: ownPkValue });
969
+ } else {
970
+ const setData = relation.restriction === "set" ? { ...omitKey(field, relation.relatedPk), [relation.fkThere]: ownPkValue } : { [relation.fkThere]: ownPkValue };
971
+ await tx.update(relation.table).set(setData).where(eq2(pkColumn, pkValue));
972
+ }
973
+ }
974
+ if (relation.restriction === "set" && keepPk !== void 0) {
975
+ await tx.delete(relation.table).where(and2(eq2(fkColumn, ownPkValue), ne(pkColumn, keepPk)));
976
+ }
977
+ }
978
+ async function resolveFkThereFields(tx, entries, ownPkValue) {
979
+ for (const [key, relation, value] of entries) {
980
+ if (relation.mode === "otm") {
981
+ if (!Array.isArray(value)) {
982
+ throw new VSRepoAdapterError9(
983
+ `Field '${key}': expected an array for a to-many relation, got '${typeof value}'.`,
984
+ AdapterErrorCode9.INVALID_DATA,
985
+ null
986
+ );
987
+ }
988
+ await resolveOtmField(tx, relation, value, ownPkValue);
989
+ continue;
990
+ }
991
+ if (value !== null && typeof value !== "object") {
992
+ throw new VSRepoAdapterError9(
993
+ `Field '${key}': expected an object or 'null' for a to-one relation, got '${typeof value}'.`,
994
+ AdapterErrorCode9.INVALID_DATA,
995
+ null
996
+ );
997
+ }
998
+ await resolveOtoFkThereField(tx, relation, value, ownPkValue);
999
+ }
1000
+ }
1001
+
1002
+ // src/drizzle.adapter.ts
1003
+ var DrizzleAdapter = class extends VSRepoAdapter {
1004
+ table;
1005
+ db;
1006
+ dialect;
1007
+ pk;
1008
+ queryKey;
1009
+ relations;
1010
+ constructor(db, config) {
1011
+ super();
1012
+ const validated = validateDrizzleAdapterConfig(db, config);
1013
+ this.db = validated.db;
1014
+ this.table = validated.config.table;
1015
+ this.dialect = validated.config.dialect ?? "postgresql";
1016
+ this.queryKey = validated.config.queryKey;
1017
+ const fieldsConfig = resolveFieldsConfig(this.table);
1018
+ this.pk = fieldsConfig.pk;
1019
+ this.relations = validateRelations(this.table, this.dialect, validated.config.relations);
1020
+ }
1021
+ /**
1022
+ * Returns `db.query[queryKey]` (the relational query builder entry for
1023
+ * this adapter's table), reading from `db` when a transaction/executor
1024
+ * override is passed via `options.db`, otherwise from the root client.
1025
+ */
1026
+ getQueryBuilder(db) {
1027
+ const executor = db ?? this.db;
1028
+ return executor.query[this.queryKey];
1029
+ }
1030
+ /**
1031
+ * Resolves the "read" part of a query arg: `columns`/`with`/`where`/
1032
+ * `orderBy`/pagination.
1033
+ *
1034
+ * Per the adapter contract: when both `select` and `relations` are
1035
+ * given, `select` wins and `relations` is ignored entirely.
1036
+ */
1037
+ async resolveReadArgs(where, options, single = false) {
1038
+ options ??= {};
1039
+ let columns;
1040
+ let withArg;
1041
+ if (options.select) {
1042
+ const parsedSelect = parseColumns(options.select);
1043
+ columns = parsedSelect.columns;
1044
+ withArg = parsedSelect.with;
1045
+ } else if (options.relations) {
1046
+ withArg = parseWith(options.relations);
1047
+ }
1048
+ const found = await this.resolveFindWhere(where, {
1049
+ db: options.db,
1050
+ order: options.order,
1051
+ limit: single ? 1 : options.pagination?.limit,
1052
+ offset: single ? void 0 : options.pagination?.offset
1053
+ });
1054
+ return {
1055
+ where: found.where,
1056
+ columns,
1057
+ with: withArg,
1058
+ // When the prefetch already applied limit/offset, `IN (pks)` no longer preserves that
1059
+ // order on its own, so the same ordering has to be re-applied at this level too — but
1060
+ // limit/offset themselves must NOT be re-applied, since the pk set is already the exact page.
1061
+ orderBy: found.paginationApplied ? found.orderBy : parseOrderBy(options.order),
1062
+ limit: found.paginationApplied ? void 0 : options.pagination?.limit,
1063
+ offset: found.paginationApplied ? void 0 : options.pagination?.offset
1064
+ };
1065
+ }
1066
+ /** Builds the context `parseSqlWhere` needs to resolve `_with`/`_without`/`_some`/`_every`/`_none` relation filters. */
1067
+ getSqlWhereContext(db) {
1068
+ return {
1069
+ table: this.table,
1070
+ pk: this.pk,
1071
+ relations: this.relations,
1072
+ db: db ?? this.db
1073
+ };
1074
+ }
1075
+ /**
1076
+ * Resolves a user-supplied `VSRepoWhere<T>` into the `where` shape the
1077
+ * relational query API (`db.query[queryKey].findFirst/findMany`) accepts.
1078
+ *
1079
+ * The relational API's own object-shaped `where` (`where.parser.ts`) has
1080
+ * no native `_every`/`_none` semantics for to-many relations — so when
1081
+ * `where` contains one (`hasQuantifierFilter`), this instead:
1082
+ * 1. Resolves `where` into a `SQL` condition via `sql-where.parser.ts`
1083
+ * (which DOES support `_every`/`_none`, via `NOT EXISTS`);
1084
+ * 2. Runs `db.select({pk}).from(table).where(condition)`, applying the
1085
+ * SAME `order`/`limit`/`offset` the final query would've used
1086
+ * (`sql-order-by.parser.ts`), so the prefetch only ever pulls the
1087
+ * rows the caller actually needs, instead of every matching row;
1088
+ * 3. Returns `parseDrizzleWhere({ [pk]: { in: pks } })` instead — the
1089
+ * relational API then only has to filter by pk (trivial for it),
1090
+ * while still handling `columns`/`with` on the correct result set.
1091
+ *
1092
+ * Since SQL `IN (...)` doesn't preserve the given list's order, `resolveReadArgs`
1093
+ * re-applies `orderBy` (but NOT `limit`/`offset`, already baked into the pk set)
1094
+ * on the final relational query when `paginationApplied` comes back `true`.
1095
+ *
1096
+ * This keeps `_every`/`_none` support consistent between this adapter's
1097
+ * two `where` parsers — from the outside, `findOne`/`findMany`/etc. never
1098
+ * throw `NOT_SUPPORTED` for them, at the cost of an extra round-trip only
1099
+ * when they're actually used.
1100
+ */
1101
+ async resolveFindWhere(where, opts) {
1102
+ if (!hasQuantifierFilter(where)) {
1103
+ return { where: parseDrizzleWhere(where, this.dialect), paginationApplied: false };
1104
+ }
1105
+ const executor = opts?.db ?? this.db;
1106
+ const pkColumn = this.table[this.pk];
1107
+ const condition = parseSqlWhere(where, this.getSqlWhereContext(executor));
1108
+ const sqlOrderBy = parseSqlOrderBy(this.table, opts?.order);
1109
+ let qb = executor.select({ pk: pkColumn }).from(this.table).where(condition);
1110
+ if (sqlOrderBy) qb = qb.orderBy(...sqlOrderBy);
1111
+ if (opts?.limit !== void 0) qb = qb.limit(opts.limit);
1112
+ if (opts?.offset !== void 0) qb = qb.offset(opts.offset);
1113
+ const rows = await qb;
1114
+ const pks = rows.map((row) => row.pk);
1115
+ return {
1116
+ where: parseDrizzleWhere({ [this.pk]: { in: pks } }, this.dialect),
1117
+ orderBy: parseOrderBy(opts?.order),
1118
+ paginationApplied: opts?.limit !== void 0 || opts?.offset !== void 0
1119
+ };
1120
+ }
1121
+ /**
1122
+ * Strips relation fields from a payload — used by `createMany`/`updateMany`/
1123
+ * `updateManyReturning`, since batch statements only accept flat column
1124
+ * data (no nested writes). Throws `VSRepoAdapterError` (code
1125
+ * `NOT_SUPPORTED`) instead of silently dropping the field, when a
1126
+ * configured relation field is present in the payload.
1127
+ */
1128
+ stripRelationFields(obj) {
1129
+ if (!this.relations) return obj;
1130
+ const data = {};
1131
+ for (const [key, value] of Object.entries(obj)) {
1132
+ if (value === void 0) continue;
1133
+ if (this.relations.has(key)) {
1134
+ throw new VSRepoAdapterError10(
1135
+ `Field '${key}' is a configured relation, but this adapter's *Many operations don't support nested relation writes.`,
1136
+ AdapterErrorCode10.NOT_SUPPORTED,
1137
+ null
1138
+ );
1139
+ }
1140
+ data[key] = value;
1141
+ }
1142
+ return data;
1143
+ }
1144
+ isRootClient(db) {
1145
+ return typeof db?.rollback !== "function";
1146
+ }
1147
+ async runTransactional(db, fn) {
1148
+ if (db && !this.isRootClient(db)) {
1149
+ return fn(db);
1150
+ }
1151
+ return (db ?? this.db).transaction(fn);
1152
+ }
1153
+ async runInTransaction(fn, options) {
1154
+ if (options?.timeoutMs !== void 0) {
1155
+ throw new VSRepoAdapterError10(
1156
+ "This adapter doesn't support 'timeoutMs' in transactions.",
1157
+ AdapterErrorCode10.NOT_SUPPORTED,
1158
+ null
1159
+ );
1160
+ }
1161
+ try {
1162
+ return await this.db.transaction(fn, {
1163
+ isolationLevel: options?.isolationLevel && resolveIsolationLevel(options.isolationLevel)
1164
+ });
1165
+ } catch (error) {
1166
+ throw mapDrizzleError(error, "runInTransaction", this.dialect);
1167
+ }
1168
+ }
1169
+ getDbClient() {
1170
+ return this.db;
1171
+ }
1172
+ async query(rawQuery, options) {
1173
+ const executor = options?.db ?? this.db;
1174
+ try {
1175
+ const sqlQuery = resolveRawSql(this.dialect, rawQuery, options?.args);
1176
+ const result = await executor.execute(sqlQuery);
1177
+ return resolveRawResult(this.dialect, result, options?.modifying ?? false);
1178
+ } catch (error) {
1179
+ throw mapDrizzleError(error, "query", this.dialect);
1180
+ }
1181
+ }
1182
+ async findOne(where, options) {
1183
+ try {
1184
+ const arg = await this.resolveReadArgs(where, options, true);
1185
+ const result = await this.getQueryBuilder(options?.db).findFirst(arg);
1186
+ return result ?? null;
1187
+ } catch (error) {
1188
+ throw mapDrizzleError(error, "findOne", this.dialect);
1189
+ }
1190
+ }
1191
+ async findOneOrThrow(where, options) {
1192
+ try {
1193
+ const arg = await this.resolveReadArgs(where, options, true);
1194
+ const result = await this.getQueryBuilder(options?.db).findFirst(arg);
1195
+ if (!result) {
1196
+ throw new VSRepoAdapterError10(
1197
+ "'findOneOrThrow' found no record matching the given 'where'.",
1198
+ AdapterErrorCode10.NOT_FOUND,
1199
+ null
1200
+ );
1201
+ }
1202
+ return result;
1203
+ } catch (error) {
1204
+ throw mapDrizzleError(error, "findOneOrThrow", this.dialect);
1205
+ }
1206
+ }
1207
+ async findMany(where, options) {
1208
+ if (options?.distinct !== void 0) {
1209
+ throw new VSRepoAdapterError10(
1210
+ "This adapter doesn't support 'distinct' in 'findMany': Drizzle's relational query API (db.query[queryKey].findMany) has no 'distinct' option.",
1211
+ AdapterErrorCode10.NOT_SUPPORTED,
1212
+ null
1213
+ );
1214
+ }
1215
+ try {
1216
+ const arg = await this.resolveReadArgs(where, options);
1217
+ return await this.getQueryBuilder(options?.db).findMany(arg);
1218
+ } catch (error) {
1219
+ throw mapDrizzleError(error, "findMany", this.dialect);
1220
+ }
1221
+ }
1222
+ async save(obj, options) {
1223
+ try {
1224
+ const objAny = obj;
1225
+ const pkValue = objAny[this.pk];
1226
+ if (pkValue === void 0) {
1227
+ return await this.create(obj, options);
1228
+ }
1229
+ return await this.runTransactional(options?.db, async (tx) => {
1230
+ const pkColumn = this.table[this.pk];
1231
+ const existing = await tx.select({ pk: pkColumn }).from(this.table).where(eq3(pkColumn, pkValue)).limit(1);
1232
+ if (existing.length === 0) {
1233
+ return this.create(obj, { ...options, db: tx });
1234
+ }
1235
+ return this.update({ [this.pk]: pkValue }, obj, { ...options, db: tx });
1236
+ });
1237
+ } catch (error) {
1238
+ throw mapDrizzleError(error, "save", this.dialect);
1239
+ }
1240
+ }
1241
+ async saveMany(objs, options) {
1242
+ try {
1243
+ return await this.runTransactional(
1244
+ options?.db,
1245
+ (tx) => Promise.all(objs.map((obj) => this.save(obj, { ...options, db: tx })))
1246
+ );
1247
+ } catch (error) {
1248
+ throw mapDrizzleError(error, "saveMany", this.dialect);
1249
+ }
1250
+ }
1251
+ async create(obj, options) {
1252
+ try {
1253
+ return await this.runTransactional(options?.db, async (tx) => {
1254
+ const objAny = obj;
1255
+ const { scalarFields, fkHereEntries, fkThereEntries } = splitWritePayload(
1256
+ objAny,
1257
+ this.relations,
1258
+ this.pk,
1259
+ false
1260
+ );
1261
+ await resolveFkHereFields(tx, fkHereEntries, scalarFields, void 0);
1262
+ const [created] = await tx.insert(this.table).values(scalarFields).returning();
1263
+ const ownPkValue = created[this.pk];
1264
+ await resolveFkThereFields(tx, fkThereEntries, ownPkValue);
1265
+ const readArg = await this.resolveReadArgs(
1266
+ { [this.pk]: ownPkValue },
1267
+ options
1268
+ );
1269
+ const result = await this.getQueryBuilder(tx).findFirst(readArg);
1270
+ return result ?? created;
1271
+ });
1272
+ } catch (error) {
1273
+ throw mapDrizzleError(error, "create", this.dialect);
1274
+ }
1275
+ }
1276
+ async createMany(objs, options) {
1277
+ try {
1278
+ const data = objs.map((obj) => this.stripRelationFields(obj));
1279
+ const executor = options?.db ?? this.db;
1280
+ let qb = executor.insert(this.table).values(data);
1281
+ if (options?.ignoreConflicts) qb = qb.onConflictDoNothing();
1282
+ const result = await qb;
1283
+ const affected = resolveRawResult(this.dialect, result, true);
1284
+ return { count: affected ?? data.length };
1285
+ } catch (error) {
1286
+ throw mapDrizzleError(error, "createMany", this.dialect);
1287
+ }
1288
+ }
1289
+ async createManyReturning(objs, options) {
1290
+ try {
1291
+ const data = objs.map((obj) => this.stripRelationFields(obj));
1292
+ const executor = options?.db ?? this.db;
1293
+ let qb = executor.insert(this.table).values(data);
1294
+ if (options?.ignoreConflicts) qb = qb.onConflictDoNothing();
1295
+ return await qb.returning();
1296
+ } catch (error) {
1297
+ throw mapDrizzleError(error, "createManyReturning", this.dialect);
1298
+ }
1299
+ }
1300
+ async delete(where, options) {
1301
+ try {
1302
+ return await this.runTransactional(options?.db, async (tx) => {
1303
+ const readArg = await this.resolveReadArgs(where, options, true);
1304
+ const current = await this.getQueryBuilder(tx).findFirst(readArg);
1305
+ if (!current) {
1306
+ throw new VSRepoAdapterError10(
1307
+ "'delete' found no record matching the given 'where'.",
1308
+ AdapterErrorCode10.NOT_FOUND,
1309
+ null
1310
+ );
1311
+ }
1312
+ const pkColumn = this.table[this.pk];
1313
+ await tx.delete(this.table).where(eq3(pkColumn, current[this.pk]));
1314
+ return current;
1315
+ });
1316
+ } catch (error) {
1317
+ throw mapDrizzleError(error, "delete", this.dialect);
1318
+ }
1319
+ }
1320
+ async deleteMany(where, options) {
1321
+ try {
1322
+ const executor = options?.db ?? this.db;
1323
+ const condition = parseSqlWhere(where, this.getSqlWhereContext(executor));
1324
+ const result = await executor.delete(this.table).where(condition);
1325
+ const affected = resolveRawResult(this.dialect, result, true);
1326
+ return { count: affected ?? 0 };
1327
+ } catch (error) {
1328
+ throw mapDrizzleError(error, "deleteMany", this.dialect);
1329
+ }
1330
+ }
1331
+ async deleteManyReturning(where, options) {
1332
+ try {
1333
+ const executor = options?.db ?? this.db;
1334
+ const condition = parseSqlWhere(where, this.getSqlWhereContext(executor));
1335
+ return await executor.delete(this.table).where(condition).returning();
1336
+ } catch (error) {
1337
+ throw mapDrizzleError(error, "deleteManyReturning", this.dialect);
1338
+ }
1339
+ }
1340
+ async update(where, obj, options) {
1341
+ try {
1342
+ return await this.runTransactional(options?.db, async (tx) => {
1343
+ const current = await this.getQueryBuilder(tx).findFirst({
1344
+ where: (await this.resolveFindWhere(where, { db: tx, limit: 1 })).where
1345
+ });
1346
+ if (!current) {
1347
+ throw new VSRepoAdapterError10(
1348
+ "'update' found no record matching the given 'where'.",
1349
+ AdapterErrorCode10.NOT_FOUND,
1350
+ null
1351
+ );
1352
+ }
1353
+ const ownPkValue = current[this.pk];
1354
+ const objAny = obj;
1355
+ const { scalarFields, fkHereEntries, fkThereEntries } = splitWritePayload(
1356
+ objAny,
1357
+ this.relations,
1358
+ this.pk,
1359
+ true
1360
+ );
1361
+ await resolveFkHereFields(tx, fkHereEntries, scalarFields, current);
1362
+ if (Object.keys(scalarFields).length > 0) {
1363
+ const pkColumn = this.table[this.pk];
1364
+ await tx.update(this.table).set(scalarFields).where(eq3(pkColumn, ownPkValue));
1365
+ }
1366
+ await resolveFkThereFields(tx, fkThereEntries, ownPkValue);
1367
+ const readArg = await this.resolveReadArgs(
1368
+ { [this.pk]: ownPkValue },
1369
+ options
1370
+ );
1371
+ const result = await this.getQueryBuilder(tx).findFirst(readArg);
1372
+ return result ?? current;
1373
+ });
1374
+ } catch (error) {
1375
+ throw mapDrizzleError(error, "update", this.dialect);
1376
+ }
1377
+ }
1378
+ async updateMany(where, obj, options) {
1379
+ try {
1380
+ const executor = options?.db ?? this.db;
1381
+ const data = this.stripRelationFields(obj);
1382
+ const condition = parseSqlWhere(where, this.getSqlWhereContext(executor));
1383
+ const result = await executor.update(this.table).set(data).where(condition);
1384
+ const affected = resolveRawResult(this.dialect, result, true);
1385
+ return { count: affected ?? 0 };
1386
+ } catch (error) {
1387
+ throw mapDrizzleError(error, "updateMany", this.dialect);
1388
+ }
1389
+ }
1390
+ async updateManyReturning(where, obj, options) {
1391
+ try {
1392
+ const executor = options?.db ?? this.db;
1393
+ const data = this.stripRelationFields(obj);
1394
+ const condition = parseSqlWhere(where, this.getSqlWhereContext(executor));
1395
+ return await executor.update(this.table).set(data).where(condition).returning();
1396
+ } catch (error) {
1397
+ throw mapDrizzleError(error, "updateManyReturning", this.dialect);
1398
+ }
1399
+ }
1400
+ async count(where, options) {
1401
+ try {
1402
+ const executor = options?.db ?? this.db;
1403
+ const condition = parseSqlWhere(where, this.getSqlWhereContext(executor));
1404
+ const [row] = await executor.select({ value: countFn() }).from(this.table).where(condition);
1405
+ return Number(row?.value ?? 0);
1406
+ } catch (error) {
1407
+ throw mapDrizzleError(error, "count", this.dialect);
1408
+ }
1409
+ }
1410
+ async exists(where, options) {
1411
+ try {
1412
+ const executor = options?.db ?? this.db;
1413
+ const condition = parseSqlWhere(where, this.getSqlWhereContext(executor));
1414
+ const rows = await executor.select({ one: sql2`1` }).from(this.table).where(condition).limit(1);
1415
+ return rows.length > 0;
1416
+ } catch (error) {
1417
+ throw mapDrizzleError(error, "exists", this.dialect);
1418
+ }
1419
+ }
1420
+ async merge(where, obj, options) {
1421
+ try {
1422
+ const readArg = await this.resolveReadArgs(where, options);
1423
+ const result = await this.getQueryBuilder(options?.db).findFirst(readArg);
1424
+ if (!result) {
1425
+ throw new VSRepoAdapterError10(
1426
+ "'merge' found no record matching the given 'where'.",
1427
+ AdapterErrorCode10.NOT_FOUND,
1428
+ null
1429
+ );
1430
+ }
1431
+ return mergeEntities(result, obj, this.relations);
1432
+ } catch (error) {
1433
+ throw mapDrizzleError(error, "merge", this.dialect);
1434
+ }
1435
+ }
1436
+ async upsert(where, create, update, options) {
1437
+ try {
1438
+ return await this.runTransactional(options?.db, async (tx) => {
1439
+ const current = await this.getQueryBuilder(tx).findFirst({
1440
+ where: (await this.resolveFindWhere(where, { db: tx, limit: 1 })).where
1441
+ });
1442
+ if (current) {
1443
+ const ownPkValue = current[this.pk];
1444
+ return this.update({ [this.pk]: ownPkValue }, update, {
1445
+ ...options,
1446
+ db: tx
1447
+ });
1448
+ }
1449
+ return this.create(create, { ...options, db: tx });
1450
+ });
1451
+ } catch (error) {
1452
+ throw mapDrizzleError(error, "upsert", this.dialect);
1453
+ }
1454
+ }
1455
+ /** Shared implementation behind `incrementOne`/`decrementOne`/`multiplyOne`/`divideOne`. */
1456
+ async atomicUpdate(operation, field, toExpression, value, where, options) {
1457
+ try {
1458
+ return await this.runTransactional(options?.db, async (tx) => {
1459
+ const current = await this.getQueryBuilder(tx).findFirst({
1460
+ where: (await this.resolveFindWhere(where, { db: tx, limit: 1 })).where
1461
+ });
1462
+ if (!current) {
1463
+ throw new VSRepoAdapterError10(
1464
+ `'${operation}' found no record matching the given 'where'.`,
1465
+ AdapterErrorCode10.NOT_FOUND,
1466
+ null
1467
+ );
1468
+ }
1469
+ const ownPkValue = current[this.pk];
1470
+ const pkColumn = this.table[this.pk];
1471
+ const column = this.table[field];
1472
+ await tx.update(this.table).set({ [field]: toExpression(column, value) }).where(eq3(pkColumn, ownPkValue));
1473
+ const readArg = await this.resolveReadArgs(
1474
+ { [this.pk]: ownPkValue },
1475
+ options
1476
+ );
1477
+ return await this.getQueryBuilder(tx).findFirst(readArg);
1478
+ });
1479
+ } catch (error) {
1480
+ throw mapDrizzleError(error, operation, this.dialect);
1481
+ }
1482
+ }
1483
+ incrementOne(field, value, where, options) {
1484
+ return this.atomicUpdate("incrementOne", field, (column, v) => sql2`${column} + ${v}`, value, where, options);
1485
+ }
1486
+ decrementOne(field, value, where, options) {
1487
+ return this.atomicUpdate("decrementOne", field, (column, v) => sql2`${column} - ${v}`, value, where, options);
1488
+ }
1489
+ multiplyOne(field, value, where, options) {
1490
+ return this.atomicUpdate("multiplyOne", field, (column, v) => sql2`${column} * ${v}`, value, where, options);
1491
+ }
1492
+ divideOne(field, value, where, options) {
1493
+ return this.atomicUpdate("divideOne", field, (column, v) => sql2`${column} / ${v}`, value, where, options);
1494
+ }
1495
+ /** Shared implementation behind `sum`/`average`/`min`/`max`. */
1496
+ async aggregate(operation, fn, field, where, options) {
1497
+ try {
1498
+ const executor = options?.db ?? this.db;
1499
+ const condition = parseSqlWhere(where, this.getSqlWhereContext(executor));
1500
+ const column = this.table[field];
1501
+ const [row] = await executor.select({ value: fn(column) }).from(this.table).where(condition);
1502
+ const raw = row?.value;
1503
+ if (raw === null || raw === void 0) return null;
1504
+ return typeof raw === "number" ? raw : Number(raw);
1505
+ } catch (error) {
1506
+ throw mapDrizzleError(error, operation, this.dialect);
1507
+ }
1508
+ }
1509
+ sum(field, where, options) {
1510
+ return this.aggregate("sum", sumFn, field, where, options);
1511
+ }
1512
+ average(field, where, options) {
1513
+ return this.aggregate("average", avg, field, where, options);
1514
+ }
1515
+ min(field, where, options) {
1516
+ return this.aggregate("min", minFn, field, where, options);
1517
+ }
1518
+ max(field, where, options) {
1519
+ return this.aggregate("max", maxFn, field, where, options);
1520
+ }
1521
+ };
1522
+ export {
1523
+ DrizzleAdapter
1524
+ };
1525
+ //# sourceMappingURL=index.js.map