turbine-orm 0.35.0 → 0.36.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.
Files changed (68) hide show
  1. package/README.md +18 -16
  2. package/dist/cjs/cli/index.js +109 -16
  3. package/dist/cjs/cli/migrate.js +78 -3
  4. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  5. package/dist/cjs/cli/studio.js +333 -22
  6. package/dist/cjs/cli/ui.js +7 -1
  7. package/dist/cjs/dialect.js +1 -1
  8. package/dist/cjs/generate.js +23 -2
  9. package/dist/cjs/index.js +2 -1
  10. package/dist/cjs/mssql.js +22 -5
  11. package/dist/cjs/powdb.js +41 -1
  12. package/dist/cjs/powql.js +80 -25
  13. package/dist/cjs/query/aggregates.js +683 -0
  14. package/dist/cjs/query/batched-loader.js +2 -0
  15. package/dist/cjs/query/builder.js +297 -4504
  16. package/dist/cjs/query/filters.js +12 -0
  17. package/dist/cjs/query/relations.js +1698 -0
  18. package/dist/cjs/query/where-compile.js +180 -0
  19. package/dist/cjs/query/where.js +1491 -0
  20. package/dist/cjs/query/writes.js +680 -0
  21. package/dist/cjs/schema-builder.js +6 -0
  22. package/dist/cjs/schema-metadata.js +4 -0
  23. package/dist/cjs/schema-sql.js +265 -3
  24. package/dist/cjs/sqlite.js +1 -1
  25. package/dist/cli/index.d.ts +8 -2
  26. package/dist/cli/index.js +111 -18
  27. package/dist/cli/migrate.d.ts +24 -1
  28. package/dist/cli/migrate.js +77 -3
  29. package/dist/cli/studio-ui.generated.js +1 -1
  30. package/dist/cli/studio.d.ts +46 -13
  31. package/dist/cli/studio.js +331 -23
  32. package/dist/cli/ui.js +7 -1
  33. package/dist/dialect.d.ts +15 -6
  34. package/dist/dialect.js +1 -1
  35. package/dist/generate.js +23 -2
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.js +1 -1
  38. package/dist/mssql.js +22 -5
  39. package/dist/powdb.d.ts +20 -0
  40. package/dist/powdb.js +40 -0
  41. package/dist/powql.d.ts +33 -1
  42. package/dist/powql.js +80 -25
  43. package/dist/query/aggregates.d.ts +74 -0
  44. package/dist/query/aggregates.js +641 -0
  45. package/dist/query/batched-loader.d.ts +6 -0
  46. package/dist/query/batched-loader.js +2 -0
  47. package/dist/query/builder.d.ts +62 -829
  48. package/dist/query/builder.js +302 -4509
  49. package/dist/query/deferred.d.ts +7 -0
  50. package/dist/query/filters.d.ts +7 -0
  51. package/dist/query/filters.js +11 -0
  52. package/dist/query/relations.d.ts +441 -0
  53. package/dist/query/relations.js +1627 -0
  54. package/dist/query/types.d.ts +15 -0
  55. package/dist/query/where-compile.d.ts +139 -0
  56. package/dist/query/where-compile.js +175 -0
  57. package/dist/query/where.d.ts +494 -0
  58. package/dist/query/where.js +1431 -0
  59. package/dist/query/writes.d.ts +131 -0
  60. package/dist/query/writes.js +626 -0
  61. package/dist/schema-builder.d.ts +18 -3
  62. package/dist/schema-builder.js +6 -0
  63. package/dist/schema-metadata.js +4 -0
  64. package/dist/schema-sql.d.ts +60 -3
  65. package/dist/schema-sql.js +261 -4
  66. package/dist/schema.d.ts +10 -0
  67. package/dist/sqlite.js +1 -1
  68. package/package.json +4 -4
package/dist/cjs/powdb.js CHANGED
@@ -90,7 +90,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
90
90
  return (mod && mod.__esModule) ? mod : { "default": mod };
91
91
  };
92
92
  Object.defineProperty(exports, "__esModule", { value: true });
93
- exports.PowqlInterface = exports.introspectPowdbDatabase = exports.PowdbEmbeddedPool = exports.PowdbPool = exports.DEFAULT_TX_QUEUE_TIMEOUT_MS = exports.POWQL_KEYWORDS = exports.ALL_POWDB_CAPABILITIES = exports.MIN_POWDB_VERSION = exports.PowdbJsonParam = exports.PowdbFloatParam = exports.powdbDialect = void 0;
93
+ exports.PowqlInterface = exports.introspectPowdbDatabase = exports.PowdbEmbeddedPool = exports.POWQL_LEXER_TESTED_CEILING = exports.PowdbPool = exports.DEFAULT_TX_QUEUE_TIMEOUT_MS = exports.POWQL_KEYWORDS = exports.ALL_POWDB_CAPABILITIES = exports.MIN_POWDB_VERSION = exports.PowdbJsonParam = exports.PowdbFloatParam = exports.powdbDialect = void 0;
94
94
  exports.parsePowdbUrl = parsePowdbUrl;
95
95
  exports.assertSupportedPowdbVersion = assertSupportedPowdbVersion;
96
96
  exports.capabilitiesFromVersion = capabilitiesFromVersion;
@@ -1430,6 +1430,26 @@ function encodePowqlLiteral(value) {
1430
1430
  return encodePowqlString(value);
1431
1431
  throw new errors_js_1.ValidationError(`[turbine] Value of type ${typeof value} cannot be encoded as a PowDB literal.`);
1432
1432
  }
1433
+ /**
1434
+ * The newest PowDB engine LINE (major.minor) whose lexer escape handling
1435
+ * {@link encodePowqlString} is VERIFIED against by reading
1436
+ * `crates/query/src/lexer.rs`. The legacy materialize path
1437
+ * ({@link materializePowql}) inlines encoded string literals directly into query
1438
+ * text, so it is only injection-safe while the lexer recognizes exactly the
1439
+ * escape set the escaper emits (`\"`, `\\`, `\n`, `\t`, everything else raw). If
1440
+ * a future engine line teaches the lexer new escapes (e.g. `\u`, `\x`), a string
1441
+ * that the escaper leaves raw could be re-interpreted by the lexer and break out
1442
+ * of the literal, turning the fallback into an injection primitive.
1443
+ *
1444
+ * CONTRACT: bump this ceiling ONLY after re-verifying the escape handling in
1445
+ * `crates/query/src/lexer.rs` for the newer line AND confirming
1446
+ * {@link encodePowqlString} still escapes every breakout vector the lexer
1447
+ * recognizes. The legacy path guards on this value (see
1448
+ * {@link PowdbEmbeddedPool.exec}): an embedded addon whose engine line exceeds
1449
+ * the ceiling yet still routes through the string wire is refused rather than
1450
+ * materialized.
1451
+ */
1452
+ exports.POWQL_LEXER_TESTED_CEILING = '0.16';
1433
1453
  /** Escape a string into a PowQL `"…"` literal, matching the engine lexer's escape rules. */
1434
1454
  function encodePowqlString(s) {
1435
1455
  let out = '"';
@@ -1532,6 +1552,26 @@ class PowdbEmbeddedPool {
1532
1552
  // Legacy string wire (addon < 0.14): the engine takes no params array, so
1533
1553
  // materialize each `$N` into a PowQL literal. Byte-for-byte unchanged, kept
1534
1554
  // live and tested as the pre-0.14 fallback.
1555
+ //
1556
+ // Safety assertion (see {@link POWQL_LEXER_TESTED_CEILING}): an addon whose
1557
+ // engine line EXCEEDS the escaper's verified lexer ceiling must never be
1558
+ // materialized: a newer lexer may recognize escapes the escaper leaves raw,
1559
+ // making the inline path an injection primitive. Reachability: addons >= 0.14
1560
+ // expose `queryWithParams` and take the native path above, so the ONLY way to
1561
+ // reach this branch with a newer-than-ceiling engine is the anomalous
1562
+ // newer-addon-without-native case (feature-detect failed). Refuse it rather
1563
+ // than inline-encode against an unverified lexer.
1564
+ const engineSem = parsePowdbSemver(this.capabilities.engineVersion);
1565
+ const ceiling = parsePowdbSemver(exports.POWQL_LEXER_TESTED_CEILING);
1566
+ if (engineSem &&
1567
+ ceiling &&
1568
+ (engineSem.major > ceiling.major || (engineSem.major === ceiling.major && engineSem.minor > ceiling.minor))) {
1569
+ throw new errors_js_1.ValidationError(`[turbine] Refusing the PowDB legacy string wire: this embedded addon reports engine ` +
1570
+ `${this.capabilities.engineVersion}, which is newer than the escaper's verified lexer range ` +
1571
+ `(<= ${exports.POWQL_LEXER_TESTED_CEILING}) AND such an addon exposes the parameterized native API, so ` +
1572
+ `reaching the legacy materialize path means the queryWithParams feature-detect failed. Upgrade ` +
1573
+ `turbine-orm so its PowQL escaper is verified against this engine line before running on it.`);
1574
+ }
1535
1575
  const materialized = materializePowql(powql, params);
1536
1576
  return adaptResult(normalizeEmbeddedResult(this.db.query(materialized)));
1537
1577
  }
package/dist/cjs/powql.js CHANGED
@@ -653,10 +653,17 @@ class PowqlInterface {
653
653
  // -------------------------------------------------------------------------
654
654
  // Projection / order
655
655
  // -------------------------------------------------------------------------
656
- /** Resolve the set of columns to project, honouring `select` / `omit`. */
657
- projectedColumns(select, omit) {
656
+ /**
657
+ * Resolve the set of columns to project, honouring `select` / `omit` and the
658
+ * query-level `includePii` opt-in. PII-tagged (`defineSchema` `pii: true`)
659
+ * columns are EXCLUDED from a default (or omit-only) projection unless
660
+ * `includePii` is true; an explicit `select` naming a PII column IS the opt-in
661
+ * and returns it regardless. Untagged tables project exactly as before.
662
+ */
663
+ projectedColumns(select, omit, includePii) {
658
664
  let cols = this.meta.columns.map((c) => c.name);
659
- if (select && Object.keys(select).length) {
665
+ const hasSelect = select && Object.keys(select).length;
666
+ if (hasSelect) {
660
667
  const picked = new Set(Object.entries(select)
661
668
  .filter(([, v]) => v)
662
669
  .map(([k]) => this.column(k).name));
@@ -665,6 +672,14 @@ class PowqlInterface {
665
672
  picked.add(pk);
666
673
  cols = cols.filter((c) => picked.has(c));
667
674
  }
675
+ else if (!includePii) {
676
+ // Default / omit-only projection: drop PII columns (kept above only when a
677
+ // caller names them in `select`). PK is never PII in practice; if one is
678
+ // tagged it is still dropped here, so tag sensitive data, not keys.
679
+ const pii = this.piiColumnNames();
680
+ if (pii.size)
681
+ cols = cols.filter((c) => !pii.has(c));
682
+ }
668
683
  if (omit && Object.keys(omit).length) {
669
684
  const dropped = new Set(Object.entries(omit)
670
685
  .filter(([, v]) => v)
@@ -673,6 +688,47 @@ class PowqlInterface {
673
688
  }
674
689
  return cols;
675
690
  }
691
+ /**
692
+ * The snake_case names of this table's PII-tagged columns. Empty for a table
693
+ * with no `pii: true` column, so untagged tables keep their prior projection.
694
+ */
695
+ piiColumnNames() {
696
+ const out = new Set();
697
+ for (const col of this.meta.columns) {
698
+ if (col.pii)
699
+ out.add(col.name);
700
+ }
701
+ return out;
702
+ }
703
+ /**
704
+ * The camelCase field names of this table's PII-tagged columns: the read
705
+ * policy applied to a write's returned row (create/update/upsert/delete accept
706
+ * no `includePii`/`select`, so their result always drops PII; you may still
707
+ * write PII fields freely).
708
+ *
709
+ * SPEC LIMITATION (PowQL): the driver contract
710
+ * (`docs/integrations/powql-for-drivers.md`) exposes `returning` only as a
711
+ * bare keyword that hands back every column; it accepts NO column list, so
712
+ * (unlike the SQL engines, which emit an explicit non-PII `RETURNING`/`OUTPUT`
713
+ * projection) the create/update/delete `returning` paths cannot exclude PII at
714
+ * the query-language level and must strip it here after the fact. This is the
715
+ * client-side strip of last resort, not defense-in-depth, for those paths; we
716
+ * do NOT reverse-engineer an undocumented projection form. The upsert path is
717
+ * different: it has no `returning` and reselects by PK through the read
718
+ * projection ({@link projectedColumns}), which already omits PII, so PII never
719
+ * crosses the wire there. If a future spec revision lets `returning` take a
720
+ * projection, switch the write paths to emit the non-PII list and this strip
721
+ * becomes a no-op like {@link parseWriteRow} on the SQL engines.
722
+ */
723
+ stripWritePii(entity) {
724
+ if (!entity)
725
+ return entity;
726
+ for (const col of this.meta.columns) {
727
+ if (col.pii)
728
+ delete entity[col.field];
729
+ }
730
+ return entity;
731
+ }
676
732
  /** `{ .c1, .c2, … }` projection clause. */
677
733
  projection(cols) {
678
734
  return `{ ${cols.map((c) => `.${c}`).join(', ')} }`;
@@ -877,10 +933,7 @@ class PowqlInterface {
877
933
  const { rows, native, resolvedWhere } = await this.runFind(args, 'findMany');
878
934
  const entities = this.shape(rows, native);
879
935
  if (args.with) {
880
- await this.loadRelations(entities, args.with, args.timeout, 0, {
881
- args,
882
- resolvedWhere,
883
- });
936
+ await this.loadRelations(entities, args.with, args.timeout, 0, { args, resolvedWhere }, args.includePii === true);
884
937
  }
885
938
  return entities;
886
939
  });
@@ -897,7 +950,7 @@ class PowqlInterface {
897
950
  }
898
951
  const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
899
952
  const where = this.buildWhere(resolvedWhere, params);
900
- const cols = this.projectedColumns(args.select, args.omit);
953
+ const cols = this.projectedColumns(args.select, args.omit, args.includePii === true);
901
954
  const distinct = args.distinct?.length ? ' distinct' : '';
902
955
  const filter = where ? ` filter ${where}` : '';
903
956
  const order = this.buildOrder(args.orderBy, params);
@@ -949,7 +1002,7 @@ class PowqlInterface {
949
1002
  return null;
950
1003
  const entities = this.shape(rows, native);
951
1004
  if (args.with)
952
- await this.loadRelations(entities, args.with, args.timeout);
1005
+ await this.loadRelations(entities, args.with, args.timeout, 0, undefined, args.includePii === true);
953
1006
  return entities[0];
954
1007
  });
955
1008
  }
@@ -960,7 +1013,7 @@ class PowqlInterface {
960
1013
  return null;
961
1014
  const entities = this.shape(rows, native);
962
1015
  if (args.with)
963
- await this.loadRelations(entities, args.with, args.timeout);
1016
+ await this.loadRelations(entities, args.with, args.timeout, 0, undefined, args.includePii === true);
964
1017
  return entities[0];
965
1018
  });
966
1019
  }
@@ -990,7 +1043,7 @@ class PowqlInterface {
990
1043
  * the default `'batched'` strategy) keeps the loaders. Output is byte-equal
991
1044
  * either way (the join reuses the same stitch / shape helpers).
992
1045
  */
993
- async loadRelations(parents, withClause, timeout, depth = 0, parent) {
1046
+ async loadRelations(parents, withClause, timeout, depth = 0, parent, includePii = false) {
994
1047
  if (depth >= 10) {
995
1048
  throw new errors_js_1.ValidationError(`[turbine] Nested 'with' on PowDB exceeded depth 10 (relation cycle?).`);
996
1049
  }
@@ -1010,7 +1063,7 @@ class PowqlInterface {
1010
1063
  throw new errors_js_1.ValidationError(`[turbine] Unknown relation "${relName}" on "${this.table}".`);
1011
1064
  if (strategyIsJoin && parent && this.joinEligible(rel, opt, parent.args, parents.length)) {
1012
1065
  if (this.capabilities.serverJoins) {
1013
- await this.loadRelationViaJoin(parents, rel, relName, opt, parent, timeout);
1066
+ await this.loadRelationViaJoin(parents, rel, relName, opt, parent, timeout, includePii);
1014
1067
  continue;
1015
1068
  }
1016
1069
  // An otherwise-eligible relation the engine cannot join: a PER-QUERY
@@ -1022,7 +1075,7 @@ class PowqlInterface {
1022
1075
  }
1023
1076
  }
1024
1077
  if (rel.type === 'manyToMany') {
1025
- await this.loadManyToMany(parents, rel, relName, opt, timeout);
1078
+ await this.loadManyToMany(parents, rel, relName, opt, timeout, includePii);
1026
1079
  continue;
1027
1080
  }
1028
1081
  const fk = (0, schema_js_1.normalizeKeyColumns)(rel.foreignKey);
@@ -1079,6 +1132,7 @@ class PowqlInterface {
1079
1132
  where: childWhere,
1080
1133
  with: options.with,
1081
1134
  timeout: options.timeout ?? timeout,
1135
+ includePii,
1082
1136
  }));
1083
1137
  for (const child of children) {
1084
1138
  const k = this.joinKey(child[childKeyField]);
@@ -1115,7 +1169,7 @@ class PowqlInterface {
1115
1169
  * single-key N+1 loaders; the junction's source/target columns must be single
1116
1170
  * (composite junction keys would need PowQL tuple-`in`, which it lacks).
1117
1171
  */
1118
- async loadManyToMany(parents, rel, relName, opt, timeout) {
1172
+ async loadManyToMany(parents, rel, relName, opt, timeout, includePii = false) {
1119
1173
  const through = rel.through;
1120
1174
  if (!through)
1121
1175
  throw new errors_js_1.ValidationError(`[turbine] manyToMany relation "${relName}" is missing its junction (\`through\`).`);
@@ -1179,6 +1233,7 @@ class PowqlInterface {
1179
1233
  where,
1180
1234
  with: options.with,
1181
1235
  timeout: options.timeout ?? timeout,
1236
+ includePii,
1182
1237
  }));
1183
1238
  for (const t of targets)
1184
1239
  targetByPk.set(String(t[targetPkField]), t);
@@ -1296,9 +1351,9 @@ class PowqlInterface {
1296
1351
  return tableMeta.indexes.some((idx) => idx.unique && !idx.docPath && idx.columns.length === 1 && idx.columns[0] === col);
1297
1352
  }
1298
1353
  /** Dispatch one eligible relation to the correct native-join loader. */
1299
- async loadRelationViaJoin(parents, rel, relName, opt, parent, timeout) {
1354
+ async loadRelationViaJoin(parents, rel, relName, opt, parent, timeout, includePii = false) {
1300
1355
  if (rel.type === 'manyToMany') {
1301
- await this.loadManyToManyViaJoin(parents, rel, relName, opt, parent, timeout);
1356
+ await this.loadManyToManyViaJoin(parents, rel, relName, opt, parent, timeout, includePii);
1302
1357
  return;
1303
1358
  }
1304
1359
  const options = (opt === true ? {} : opt);
@@ -1316,7 +1371,7 @@ class PowqlInterface {
1316
1371
  const childKeyCol = rel.type === 'belongsTo' ? rk[0] : fk[0];
1317
1372
  const parentKeyField = this.meta.reverseColumnMap[parentKeyCol] ?? parentKeyCol;
1318
1373
  const params = [];
1319
- const childCols = this.joinChildCols(targetQi, options);
1374
+ const childCols = this.joinChildCols(targetQi, options, includePii);
1320
1375
  const filter = await this.joinFilter(targetQi, parent.resolvedWhere, options.where, 'c', params, options.timeout ?? timeout);
1321
1376
  const order = targetQi.buildOrder(options.orderBy, params, 'c');
1322
1377
  const limitClause = options.limit !== undefined ? ` limit ${this.param(options.limit, params)}` : '';
@@ -1340,7 +1395,7 @@ class PowqlInterface {
1340
1395
  * → the already-fetched side (alias `p`), correlating `__tpk` from the
1341
1396
  * junction's source key. Always a list, stitched exactly like the loader.
1342
1397
  */
1343
- async loadManyToManyViaJoin(parents, rel, relName, opt, parent, timeout) {
1398
+ async loadManyToManyViaJoin(parents, rel, relName, opt, parent, timeout, includePii = false) {
1344
1399
  const through = rel.through;
1345
1400
  if (!through)
1346
1401
  throw new errors_js_1.ValidationError(`[turbine] manyToMany relation "${relName}" is missing its junction (\`through\`).`);
@@ -1355,7 +1410,7 @@ class PowqlInterface {
1355
1410
  const targetPkCol = targetMeta.primaryKey[0];
1356
1411
  const parentRefField = this.meta.reverseColumnMap[sourceRefCol] ?? sourceRefCol;
1357
1412
  const params = [];
1358
- const childCols = this.joinChildCols(targetQi, options);
1413
+ const childCols = this.joinChildCols(targetQi, options, includePii);
1359
1414
  const filter = await this.joinFilter(targetQi, parent.resolvedWhere, options.where, 't', params, options.timeout ?? timeout);
1360
1415
  const proj = this.joinProjection(childCols, `j.${(0, powdb_js_1.quotePowqlIdent)(sourceJCol)}`, 't');
1361
1416
  const powql = `${targetQi.qt} as t ` +
@@ -1374,8 +1429,8 @@ class PowqlInterface {
1374
1429
  * with a loud guard: a real column named `__tpk` would collide with the
1375
1430
  * reserved correlation alias, so refuse rather than silently mis-stitch.
1376
1431
  */
1377
- joinChildCols(targetQi, options) {
1378
- const cols = targetQi.projectedColumns(options.select, options.omit);
1432
+ joinChildCols(targetQi, options, includePii = false) {
1433
+ const cols = targetQi.projectedColumns(options.select, options.omit, includePii);
1379
1434
  if (cols.includes('__tpk')) {
1380
1435
  throw new errors_js_1.ValidationError(`[turbine] relation target "${targetQi.table}" has a column named "__tpk", which collides with the reserved ` +
1381
1436
  `join correlation alias. Rename the column or load this relation with relationLoadStrategy: 'batched'.`);
@@ -1504,7 +1559,7 @@ class PowqlInterface {
1504
1559
  .join(', ');
1505
1560
  // `returning` surfaces the inserted row (all columns, schema order) in one round-trip.
1506
1561
  const { rows, native } = await this.exec(`insert ${this.qt} { ${body} } returning`, params, args.timeout, 'create');
1507
- const row = rows.length ? this.shape(rows, native)[0] : null;
1562
+ const row = rows.length ? this.stripWritePii(this.shape(rows, native)[0]) : null;
1508
1563
  if (!row)
1509
1564
  throw new errors_js_1.NotFoundError({ table: this.table, where: data });
1510
1565
  return row;
@@ -1522,7 +1577,7 @@ class PowqlInterface {
1522
1577
  });
1523
1578
  // Multi-row insert with `returning` hands back every inserted row in one round-trip.
1524
1579
  const { rows, native } = await this.exec(`insert ${this.qt} ${tuples.join(', ')} returning`, params, args.timeout, 'createMany');
1525
- return this.shape(rows, native);
1580
+ return this.shape(rows, native).map((r) => this.stripWritePii(r));
1526
1581
  });
1527
1582
  }
1528
1583
  async update(args) {
@@ -1537,7 +1592,7 @@ class PowqlInterface {
1537
1592
  const setClause = this.buildUpdateAssignments(args.data, params);
1538
1593
  // `returning` hands back the post-update row(s); take the first (single-row contract).
1539
1594
  const { rows, native } = await this.exec(`${this.qt} filter ${where} update { ${setClause} } returning`, params, args.timeout, 'update');
1540
- const row = rows.length ? this.shape(rows, native)[0] : null;
1595
+ const row = rows.length ? this.stripWritePii(this.shape(rows, native)[0]) : null;
1541
1596
  if (!row)
1542
1597
  throw new errors_js_1.NotFoundError({ table: this.table, where: args.where });
1543
1598
  return row;
@@ -1683,7 +1738,7 @@ class PowqlInterface {
1683
1738
  this.assertCompiledWhere(where, false, 'delete');
1684
1739
  // `returning` hands back the deleted row(s) — no separate pre-image reselect needed.
1685
1740
  const { rows, native } = await this.exec(`${this.qt} filter ${where} delete returning`, params, args.timeout, 'delete');
1686
- const row = rows.length ? this.shape(rows, native)[0] : null;
1741
+ const row = rows.length ? this.stripWritePii(this.shape(rows, native)[0]) : null;
1687
1742
  if (!row)
1688
1743
  throw new errors_js_1.NotFoundError({ table: this.table, where: args.where });
1689
1744
  return row;