@vsrepo/drizzle-adapter 0.1.0 → 0.1.1

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.cjs CHANGED
@@ -20,7 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- DrizzleAdapter: () => DrizzleAdapter
23
+ DrizzleAdapter: () => DrizzleAdapter,
24
+ VSRepoDrizzleAdapter: () => DrizzleAdapter
24
25
  });
25
26
  module.exports = __toCommonJS(index_exports);
26
27
 
@@ -331,16 +332,21 @@ function validateRelations(table, dialect, relations) {
331
332
  }
332
333
 
333
334
  // src/parsers/columns.parser.ts
334
- function parseColumns(select) {
335
+ function parseColumns(select, relationsKeysSet) {
335
336
  const columns = {};
336
337
  let withResult;
337
338
  for (const [key, value] of Object.entries(select)) {
338
339
  if (value === void 0) continue;
339
- if (isPlainObject(value)) {
340
+ if (typeof value !== "boolean") {
340
341
  withResult ??= {};
341
342
  const nested = parseColumns(value);
342
343
  withResult[key] = nested.with ? { columns: nested.columns, with: nested.with } : { columns: nested.columns };
343
344
  } else {
345
+ if (relationsKeysSet?.has(key)) {
346
+ withResult ??= {};
347
+ withResult[key] = value;
348
+ continue;
349
+ }
344
350
  columns[key] = value;
345
351
  }
346
352
  }
@@ -893,7 +899,7 @@ async function resolveFkHereField(tx, relation, field, currentFkValue) {
893
899
  }
894
900
  const pkValue = field[relation.relatedPk];
895
901
  if (pkValue === void 0) {
896
- const [created] = await tx.insert(relation.table).values(field).returning();
902
+ const [created] = await tx.insert(relation.table).values(field).returning({ [relation.relatedPk]: relation.table[relation.relatedPk] });
897
903
  return created[relation.relatedPk];
898
904
  }
899
905
  const existing = await tx.select({ pk: pkColumn }).from(relation.table).where((0, import_drizzle_orm8.eq)(pkColumn, pkValue)).limit(1);
@@ -931,7 +937,7 @@ async function resolveOtmField(tx, relation, items, ownPkValue) {
931
937
  const withPk = items.filter((item) => item[relation.relatedPk] !== void 0);
932
938
  const connectedIds = [];
933
939
  for (const item of withoutPk) {
934
- const [insertedRow] = await tx.insert(relation.table).values({ ...item, [relation.fkThere]: ownPkValue }).returning();
940
+ const [insertedRow] = await tx.insert(relation.table).values({ ...item, [relation.fkThere]: ownPkValue }).returning({ [relation.relatedPk]: relation.table[relation.relatedPk] });
935
941
  if (insertedRow?.[relation.relatedPk] !== void 0) {
936
942
  connectedIds.push(insertedRow[relation.relatedPk]);
937
943
  }
@@ -963,22 +969,25 @@ async function resolveOtoFkThereField(tx, relation, field, ownPkValue) {
963
969
  return;
964
970
  }
965
971
  const pkValue = field[relation.relatedPk];
966
- let keepPk = pkValue;
972
+ const resolveSetData = async () => {
973
+ const setData = relation.restriction === "set" ? { ...omitKey(field, relation.relatedPk), [relation.fkThere]: ownPkValue } : { [relation.fkThere]: ownPkValue };
974
+ await tx.update(relation.table).set(setData).where((0, import_drizzle_orm8.eq)(pkColumn, pkValue));
975
+ };
967
976
  if (pkValue === void 0) {
968
- const [insertedRow] = await tx.insert(relation.table).values({ ...field, [relation.fkThere]: ownPkValue }).returning();
969
- keepPk = insertedRow?.[relation.relatedPk];
977
+ const [otoRel] = await tx.select({ pk: pkColumn }).from(relation.table).where((0, import_drizzle_orm8.eq)(fkColumn, ownPkValue)).limit(1);
978
+ if (!otoRel) {
979
+ await tx.insert(relation.table).values({ ...field, [relation.fkThere]: ownPkValue });
980
+ } else {
981
+ await resolveSetData();
982
+ }
970
983
  } else {
971
984
  const existing = await tx.select({ pk: pkColumn }).from(relation.table).where((0, import_drizzle_orm8.eq)(pkColumn, pkValue)).limit(1);
972
985
  if (existing.length === 0) {
973
986
  await tx.insert(relation.table).values({ ...field, [relation.fkThere]: ownPkValue });
974
987
  } else {
975
- const setData = relation.restriction === "set" ? { ...omitKey(field, relation.relatedPk), [relation.fkThere]: ownPkValue } : { [relation.fkThere]: ownPkValue };
976
- await tx.update(relation.table).set(setData).where((0, import_drizzle_orm8.eq)(pkColumn, pkValue));
988
+ await resolveSetData();
977
989
  }
978
990
  }
979
- if (relation.restriction === "set" && keepPk !== void 0) {
980
- await tx.delete(relation.table).where((0, import_drizzle_orm8.and)((0, import_drizzle_orm8.eq)(fkColumn, ownPkValue), (0, import_drizzle_orm8.ne)(pkColumn, keepPk)));
981
- }
982
991
  }
983
992
  async function resolveFkThereFields(tx, entries, ownPkValue) {
984
993
  for (const [key, relation, value] of entries) {
@@ -1012,6 +1021,7 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1012
1021
  pk;
1013
1022
  queryKey;
1014
1023
  relations;
1024
+ relationsKeysSet;
1015
1025
  constructor(db, config) {
1016
1026
  super();
1017
1027
  const validated = validateDrizzleAdapterConfig(db, config);
@@ -1022,6 +1032,9 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1022
1032
  const fieldsConfig = resolveFieldsConfig(this.table);
1023
1033
  this.pk = fieldsConfig.pk;
1024
1034
  this.relations = validateRelations(this.table, this.dialect, validated.config.relations);
1035
+ if (this.relations) {
1036
+ this.relationsKeysSet = new Set(Object.keys(this.relations));
1037
+ }
1025
1038
  }
1026
1039
  /**
1027
1040
  * Returns `db.query[queryKey]` (the relational query builder entry for
@@ -1044,7 +1057,7 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1044
1057
  let columns;
1045
1058
  let withArg;
1046
1059
  if (options.select) {
1047
- const parsedSelect = parseColumns(options.select);
1060
+ const parsedSelect = parseColumns(options.select, this.relationsKeysSet);
1048
1061
  columns = parsedSelect.columns;
1049
1062
  withArg = parsedSelect.with;
1050
1063
  } else if (options.relations) {
@@ -1123,6 +1136,21 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1123
1136
  paginationApplied: opts?.limit !== void 0 || opts?.offset !== void 0
1124
1137
  };
1125
1138
  }
1139
+ /**
1140
+ * Fetches the current row matching `where` (or `null` when none exists),
1141
+ * using the same where-resolution path as `update` — the single place
1142
+ * "find the row behind a where" is materialized.
1143
+ *
1144
+ * Internal callers that already fetched a row in their own transaction
1145
+ * (`save`, `upsert`) use this to detect/create, then hand that same row
1146
+ * to `updateCore` as `current`, so the row is only ever read once.
1147
+ */
1148
+ async findCurrentByWhere(where, opts) {
1149
+ const arg = {
1150
+ where: (await this.resolveFindWhere(where, { db: opts?.db, limit: 1 })).where
1151
+ };
1152
+ return await this.getQueryBuilder(opts?.db).findFirst(arg) ?? null;
1153
+ }
1126
1154
  /**
1127
1155
  * Strips relation fields from a payload — used by `createMany`/`updateMany`/
1128
1156
  * `updateManyReturning`, since batch statements only accept flat column
@@ -1232,12 +1260,18 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1232
1260
  return await this.create(obj, options);
1233
1261
  }
1234
1262
  return await this.runTransactional(options?.db, async (tx) => {
1235
- const pkColumn = this.table[this.pk];
1236
- const existing = await tx.select({ pk: pkColumn }).from(this.table).where((0, import_drizzle_orm9.eq)(pkColumn, pkValue)).limit(1);
1237
- if (existing.length === 0) {
1263
+ const current = await this.findCurrentByWhere({ [this.pk]: pkValue }, {
1264
+ db: tx
1265
+ });
1266
+ if (current === null) {
1238
1267
  return this.create(obj, { ...options, db: tx });
1239
1268
  }
1240
- return this.update({ [this.pk]: pkValue }, obj, { ...options, db: tx });
1269
+ return this.updateCore(
1270
+ { [this.pk]: pkValue },
1271
+ obj,
1272
+ { ...options, db: tx },
1273
+ current
1274
+ );
1241
1275
  });
1242
1276
  } catch (error) {
1243
1277
  throw mapDrizzleError(error, "save", this.dialect);
@@ -1264,7 +1298,7 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1264
1298
  false
1265
1299
  );
1266
1300
  await resolveFkHereFields(tx, fkHereEntries, scalarFields, void 0);
1267
- const [created] = await tx.insert(this.table).values(scalarFields).returning();
1301
+ const [created] = await tx.insert(this.table).values(scalarFields).returning({ [this.pk]: this.table[this.pk] });
1268
1302
  const ownPkValue = created[this.pk];
1269
1303
  await resolveFkThereFields(tx, fkThereEntries, ownPkValue);
1270
1304
  const readArg = await this.resolveReadArgs(
@@ -1272,7 +1306,7 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1272
1306
  options
1273
1307
  );
1274
1308
  const result = await this.getQueryBuilder(tx).findFirst(readArg);
1275
- return result ?? created;
1309
+ return result;
1276
1310
  });
1277
1311
  } catch (error) {
1278
1312
  throw mapDrizzleError(error, "create", this.dialect);
@@ -1286,7 +1320,7 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1286
1320
  if (options?.ignoreConflicts) qb = qb.onConflictDoNothing();
1287
1321
  const result = await qb;
1288
1322
  const affected = resolveRawResult(this.dialect, result, true);
1289
- return { count: affected ?? data.length };
1323
+ return { count: affected };
1290
1324
  } catch (error) {
1291
1325
  throw mapDrizzleError(error, "createMany", this.dialect);
1292
1326
  }
@@ -1344,42 +1378,53 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1344
1378
  }
1345
1379
  async update(where, obj, options) {
1346
1380
  try {
1347
- return await this.runTransactional(options?.db, async (tx) => {
1348
- const current = await this.getQueryBuilder(tx).findFirst({
1349
- where: (await this.resolveFindWhere(where, { db: tx, limit: 1 })).where
1350
- });
1351
- if (!current) {
1352
- throw new import_vsrepo10.VSRepoAdapterError(
1353
- "'update' found no record matching the given 'where'.",
1354
- import_vsrepo10.AdapterErrorCode.NOT_FOUND,
1355
- null
1356
- );
1357
- }
1358
- const ownPkValue = current[this.pk];
1359
- const objAny = obj;
1360
- const { scalarFields, fkHereEntries, fkThereEntries } = splitWritePayload(
1361
- objAny,
1362
- this.relations,
1363
- this.pk,
1364
- true
1365
- );
1366
- await resolveFkHereFields(tx, fkHereEntries, scalarFields, current);
1367
- if (Object.keys(scalarFields).length > 0) {
1368
- const pkColumn = this.table[this.pk];
1369
- await tx.update(this.table).set(scalarFields).where((0, import_drizzle_orm9.eq)(pkColumn, ownPkValue));
1370
- }
1371
- await resolveFkThereFields(tx, fkThereEntries, ownPkValue);
1372
- const readArg = await this.resolveReadArgs(
1373
- { [this.pk]: ownPkValue },
1374
- options
1375
- );
1376
- const result = await this.getQueryBuilder(tx).findFirst(readArg);
1377
- return result ?? current;
1378
- });
1381
+ return await this.updateCore(where, obj, options);
1379
1382
  } catch (error) {
1380
1383
  throw mapDrizzleError(error, "update", this.dialect);
1381
1384
  }
1382
1385
  }
1386
+ /**
1387
+ * Shared implementation behind `update` — also reached from `save` and
1388
+ * `upsert`, which pass in `current` (the row they already fetched in the
1389
+ * same transaction) so the row is read only once instead of once per
1390
+ * method. The `'update' found no record...` contract applies whenever it
1391
+ * runs without a preloaded row (i.e. when called through the public
1392
+ * `update`), and always as a safety net.
1393
+ *
1394
+ * Invariant: when `current` is provided, it must be the full row read
1395
+ * from the exact `tx` referenced by `options.db` (never from outside a
1396
+ * transaction, and never a pk-only projection — `resolveFkHereFields`
1397
+ * reads the current FK values off it).
1398
+ */
1399
+ async updateCore(where, obj, options, current) {
1400
+ return this.runTransactional(options?.db, async (tx) => {
1401
+ const resolved = current ?? await this.findCurrentByWhere(where, { db: tx });
1402
+ if (!resolved) {
1403
+ throw new import_vsrepo10.VSRepoAdapterError(
1404
+ "'update' found no record matching the given 'where'.",
1405
+ import_vsrepo10.AdapterErrorCode.NOT_FOUND,
1406
+ null
1407
+ );
1408
+ }
1409
+ const ownPkValue = resolved[this.pk];
1410
+ const objAny = obj;
1411
+ const { scalarFields, fkHereEntries, fkThereEntries } = splitWritePayload(
1412
+ objAny,
1413
+ this.relations,
1414
+ this.pk,
1415
+ true
1416
+ );
1417
+ await resolveFkHereFields(tx, fkHereEntries, scalarFields, resolved);
1418
+ if (Object.keys(scalarFields).length > 0) {
1419
+ const pkColumn = this.table[this.pk];
1420
+ await tx.update(this.table).set(scalarFields).where((0, import_drizzle_orm9.eq)(pkColumn, ownPkValue));
1421
+ }
1422
+ await resolveFkThereFields(tx, fkThereEntries, ownPkValue);
1423
+ const readArg = await this.resolveReadArgs({ [this.pk]: ownPkValue }, options);
1424
+ const result = await this.getQueryBuilder(tx).findFirst(readArg);
1425
+ return result;
1426
+ });
1427
+ }
1383
1428
  async updateMany(where, obj, options) {
1384
1429
  try {
1385
1430
  const executor = options?.db ?? this.db;
@@ -1441,15 +1486,15 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1441
1486
  async upsert(where, create, update, options) {
1442
1487
  try {
1443
1488
  return await this.runTransactional(options?.db, async (tx) => {
1444
- const current = await this.getQueryBuilder(tx).findFirst({
1445
- where: (await this.resolveFindWhere(where, { db: tx, limit: 1 })).where
1446
- });
1489
+ const current = await this.findCurrentByWhere(where, { db: tx });
1447
1490
  if (current) {
1448
1491
  const ownPkValue = current[this.pk];
1449
- return this.update({ [this.pk]: ownPkValue }, update, {
1450
- ...options,
1451
- db: tx
1452
- });
1492
+ return this.updateCore(
1493
+ { [this.pk]: ownPkValue },
1494
+ update,
1495
+ { ...options, db: tx },
1496
+ current
1497
+ );
1453
1498
  }
1454
1499
  return this.create(create, { ...options, db: tx });
1455
1500
  });
@@ -1462,7 +1507,11 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1462
1507
  try {
1463
1508
  return await this.runTransactional(options?.db, async (tx) => {
1464
1509
  const current = await this.getQueryBuilder(tx).findFirst({
1465
- where: (await this.resolveFindWhere(where, { db: tx, limit: 1 })).where
1510
+ where: (await this.resolveFindWhere(where, { db: tx, limit: 1 })).where,
1511
+ // Only the pk is needed: atomic updates touch a single numeric
1512
+ // column and never resolve relations — a full-row read here
1513
+ // would ship the whole entity back to the client for nothing.
1514
+ columns: { [this.pk]: true }
1466
1515
  });
1467
1516
  if (!current) {
1468
1517
  throw new import_vsrepo10.VSRepoAdapterError(
@@ -1526,6 +1575,7 @@ var DrizzleAdapter = class extends import_vsrepo10.VSRepoAdapter {
1526
1575
  };
1527
1576
  // Annotate the CommonJS export names for ESM import in node:
1528
1577
  0 && (module.exports = {
1529
- DrizzleAdapter
1578
+ DrizzleAdapter,
1579
+ VSRepoDrizzleAdapter
1530
1580
  });
1531
1581
  //# sourceMappingURL=index.cjs.map