@restura/core 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -506,8 +506,9 @@ var ResponseValidator = class _ResponseValidator {
506
506
  static isCustomRoute(route) {
507
507
  return route.type === "CUSTOM_ONE" || route.type === "CUSTOM_ARRAY" || route.type === "CUSTOM_PAGED";
508
508
  }
509
+ static VALIDATOR_STRINGS = ["boolean", "string", "number", "object", "any"];
509
510
  static validatorIsValidString(validator) {
510
- return !validator.includes("|");
511
+ return _ResponseValidator.VALIDATOR_STRINGS.includes(validator);
511
512
  }
512
513
  };
513
514
 
@@ -2858,7 +2859,11 @@ async function getNewPublicSchemaAndScratchPool(targetPool, scratchDbName) {
2858
2859
  connectionTimeoutMillis: targetPool.poolConfig.connectionTimeoutMillis
2859
2860
  });
2860
2861
  await scratchPool.runQuery(`DROP SCHEMA public CASCADE;`, [], systemUser);
2861
- await scratchPool.runQuery(`CREATE SCHEMA public AUTHORIZATION ${escapeColumnName(targetPool.poolConfig.user)};`, [], systemUser);
2862
+ await scratchPool.runQuery(
2863
+ `CREATE SCHEMA public AUTHORIZATION ${escapeColumnName(targetPool.poolConfig.user)};`,
2864
+ [],
2865
+ systemUser
2866
+ );
2862
2867
  const schemaComment = await targetPool.runQuery(
2863
2868
  `
2864
2869
  SELECT pg_description.description
@@ -2869,7 +2874,8 @@ async function getNewPublicSchemaAndScratchPool(targetPool, scratchDbName) {
2869
2874
  systemUser
2870
2875
  );
2871
2876
  if (schemaComment[0]?.description) {
2872
- await scratchPool.runQuery(`COMMENT ON SCHEMA public IS $1;`, [schemaComment[0].description], systemUser);
2877
+ const escaped = schemaComment[0].description.replace(/'/g, "''");
2878
+ await scratchPool.runQuery(`COMMENT ON SCHEMA public IS '${escaped}';`, [], systemUser);
2873
2879
  }
2874
2880
  return scratchPool;
2875
2881
  }
@@ -2989,11 +2995,9 @@ var PsqlEngine = class extends SqlEngine {
2989
2995
  });
2990
2996
  try {
2991
2997
  await this.triggerClient.connect();
2992
- const promises = [];
2993
- promises.push(this.triggerClient.query("LISTEN insert"));
2994
- promises.push(this.triggerClient.query("LISTEN update"));
2995
- promises.push(this.triggerClient.query("LISTEN delete"));
2996
- await Promise.all(promises);
2998
+ for (const channel of ["insert", "update", "delete"]) {
2999
+ await this.triggerClient.query(`LISTEN ${channel}`);
3000
+ }
2997
3001
  this.triggerClient.on("error", async (error) => {
2998
3002
  logger.error(`Trigger client error: ${error}`);
2999
3003
  await this.reconnectTriggerClient();
@@ -3949,7 +3953,7 @@ async function introspectDatabase(pool) {
3949
3953
  const order = row.indexdef.toUpperCase().includes(" DESC") ? "DESC" : "ASC";
3950
3954
  const columnMatch = row.indexdef.match(/\((.+?)\)(?:\s+WHERE\s+(.+))?$/i);
3951
3955
  const columns = columnMatch ? columnMatch[1].split(",").map(
3952
- (colExpr) => colExpr.trim().replace(/^"(.*)"$/, "$1").replace(/\s+(ASC|DESC)$/i, "")
3956
+ (colExpr) => colExpr.trim().replace(/\s+(ASC|DESC)$/i, "").replace(/^"(.*)"$/, "$1")
3953
3957
  ) : [];
3954
3958
  const whereClause = columnMatch?.[2] ?? null;
3955
3959
  table.indexes.push({
@@ -3996,7 +4000,7 @@ function diffSchemaToDatabase(schema, snapshot) {
3996
4000
  const changedChecksPerTable = /* @__PURE__ */ new Map();
3997
4001
  for (const desired of tablesToAlter) {
3998
4002
  const live = liveTableMap.get(desired.name);
3999
- const desiredFkNames = new Set(desired.foreignKeys.map((fk) => fk.name));
4003
+ const desiredFkNames = new Set(desired.foreignKeys.map((fk) => pgTruncate(fk.name)));
4000
4004
  for (const liveFk of live.foreignKeys) {
4001
4005
  if (!desiredFkNames.has(liveFk.name) || isFkChanged(desired, liveFk)) {
4002
4006
  statements.push(`ALTER TABLE "${desired.name}" DROP CONSTRAINT "${liveFk.name}";`);
@@ -4004,11 +4008,14 @@ function diffSchemaToDatabase(schema, snapshot) {
4004
4008
  }
4005
4009
  const desiredCheckExprMap = /* @__PURE__ */ new Map();
4006
4010
  for (const check of desired.checkConstraints) {
4007
- desiredCheckExprMap.set(check.name, check.check);
4011
+ desiredCheckExprMap.set(pgTruncate(check.name), check.check);
4008
4012
  }
4009
4013
  for (const col of desired.columns) {
4010
4014
  if (col.type === "ENUM" && col.value) {
4011
- desiredCheckExprMap.set(`${desired.name}_${col.name}_check`, `"${col.name}" IN (${col.value})`);
4015
+ desiredCheckExprMap.set(
4016
+ pgTruncate(`${desired.name}_${col.name}_check`),
4017
+ `"${col.name}" IN (${col.value})`
4018
+ );
4012
4019
  }
4013
4020
  }
4014
4021
  const changedChecks = /* @__PURE__ */ new Set();
@@ -4024,18 +4031,27 @@ function diffSchemaToDatabase(schema, snapshot) {
4024
4031
  const desiredIdxSignatures = /* @__PURE__ */ new Map();
4025
4032
  for (const idx of desired.indexes) {
4026
4033
  if (idx.isPrimaryKey) continue;
4027
- desiredIdxSignatures.set(idx.name, indexSignature(idx.name, idx.columns, idx.isUnique, idx.order, idx.where));
4034
+ desiredIdxSignatures.set(
4035
+ pgTruncate(idx.name),
4036
+ indexSignature(pgTruncate(idx.name), idx.columns, idx.isUnique, idx.order, idx.where)
4037
+ );
4028
4038
  }
4029
4039
  const autoUniqueNames = /* @__PURE__ */ new Set();
4030
4040
  for (const col of desired.columns) {
4031
4041
  if (col.isUnique) {
4032
- autoUniqueNames.add(`${desired.name}_${col.name}_unique_index`);
4042
+ autoUniqueNames.add(pgTruncate(`${desired.name}_${col.name}_unique_index`));
4033
4043
  }
4034
4044
  }
4035
4045
  for (const liveIdx of live.indexes) {
4036
4046
  if (liveIdx.isPrimary) continue;
4037
4047
  if (autoUniqueNames.has(liveIdx.name)) continue;
4038
- const liveSig = indexSignature(liveIdx.name, liveIdx.columns, liveIdx.isUnique, liveIdx.order, liveIdx.where);
4048
+ const liveSig = indexSignature(
4049
+ liveIdx.name,
4050
+ liveIdx.columns,
4051
+ liveIdx.isUnique,
4052
+ liveIdx.order,
4053
+ liveIdx.where
4054
+ );
4039
4055
  const desiredSig = desiredIdxSignatures.get(liveIdx.name);
4040
4056
  if (!desiredSig || desiredSig !== liveSig) {
4041
4057
  statements.push(`DROP INDEX "${liveIdx.name}";`);
@@ -4065,8 +4081,14 @@ function diffSchemaToDatabase(schema, snapshot) {
4065
4081
  }
4066
4082
  for (const index of desired.indexes) {
4067
4083
  if (index.isPrimaryKey) continue;
4068
- const desiredSig = indexSignature(index.name, index.columns, index.isUnique, index.order, index.where);
4069
- const liveSig = liveIdxSignatures.get(index.name);
4084
+ const desiredSig = indexSignature(
4085
+ pgTruncate(index.name),
4086
+ index.columns,
4087
+ index.isUnique,
4088
+ index.order,
4089
+ index.where
4090
+ );
4091
+ const liveSig = liveIdxSignatures.get(pgTruncate(index.name));
4070
4092
  if (!liveSig || liveSig !== desiredSig) {
4071
4093
  statements.push(buildCreateIndex(desired.name, index));
4072
4094
  }
@@ -4083,9 +4105,9 @@ function diffSchemaToDatabase(schema, snapshot) {
4083
4105
  const live = liveTableMap.get(desired.name);
4084
4106
  const liveFkNames = new Set(live.foreignKeys.map((fk) => fk.name));
4085
4107
  for (const fk of desired.foreignKeys) {
4086
- if (!liveFkNames.has(fk.name) || isFkChanged(
4108
+ if (!liveFkNames.has(pgTruncate(fk.name)) || isFkChanged(
4087
4109
  desired,
4088
- liveTableMap.get(desired.name).foreignKeys.find((liveFk) => liveFk.name === fk.name)
4110
+ liveTableMap.get(desired.name).foreignKeys.find((liveFk) => liveFk.name === pgTruncate(fk.name))
4089
4111
  )) {
4090
4112
  statements.push(buildAddForeignKey(desired.name, fk));
4091
4113
  }
@@ -4096,13 +4118,13 @@ function diffSchemaToDatabase(schema, snapshot) {
4096
4118
  const liveCheckNames = new Set(live.checkConstraints.map((check) => check.name));
4097
4119
  const changedChecks = changedChecksPerTable.get(desired.name) ?? /* @__PURE__ */ new Set();
4098
4120
  for (const check of desired.checkConstraints) {
4099
- if (!liveCheckNames.has(check.name) || changedChecks.has(check.name)) {
4121
+ if (!liveCheckNames.has(pgTruncate(check.name)) || changedChecks.has(pgTruncate(check.name))) {
4100
4122
  statements.push(buildAddCheckConstraint(desired.name, check));
4101
4123
  }
4102
4124
  }
4103
4125
  for (const col of desired.columns) {
4104
4126
  if (col.type === "ENUM" && col.value) {
4105
- const checkName = `${desired.name}_${col.name}_check`;
4127
+ const checkName = pgTruncate(`${desired.name}_${col.name}_check`);
4106
4128
  if (!liveCheckNames.has(checkName) || changedChecks.has(checkName)) {
4107
4129
  statements.push(
4108
4130
  `ALTER TABLE "${desired.name}" ADD CONSTRAINT "${checkName}" CHECK ("${col.name}" IN (${col.value}));`
@@ -4154,8 +4176,8 @@ function diffColumns(desired, live, statements) {
4154
4176
  }
4155
4177
  }
4156
4178
  function topologicalSortTables(tables) {
4157
- const tableNames = new Set(tables.map((t) => t.name));
4158
- const tableMap = new Map(tables.map((t) => [t.name, t]));
4179
+ const tableNames = new Set(tables.map((table) => table.name));
4180
+ const tableMap = new Map(tables.map((table) => [table.name, table]));
4159
4181
  const inDegree = /* @__PURE__ */ new Map();
4160
4182
  const tableDeps = /* @__PURE__ */ new Map();
4161
4183
  const reverseDeps = /* @__PURE__ */ new Map();
@@ -4189,7 +4211,7 @@ function topologicalSortTables(tables) {
4189
4211
  }
4190
4212
  const sortedSet = new Set(sorted);
4191
4213
  const deferredFkNames = /* @__PURE__ */ new Set();
4192
- const cycleTables = tables.filter((t) => !sortedSet.has(t.name));
4214
+ const cycleTables = tables.filter((table) => !sortedSet.has(table.name));
4193
4215
  const placed = new Set(sorted);
4194
4216
  for (const table of cycleTables) {
4195
4217
  sorted.push(table.name);
@@ -4207,7 +4229,8 @@ function buildCreateTable(table, deferredFkNames = /* @__PURE__ */ new Set()) {
4207
4229
  for (const column of table.columns) {
4208
4230
  let definition = `"${column.name}" ${buildColumnType(column)}`;
4209
4231
  if (column.isPrimary) definition += " PRIMARY KEY";
4210
- if (column.isUnique) definition += ` CONSTRAINT "${table.name}_${column.name}_unique_index" UNIQUE`;
4232
+ if (column.isUnique)
4233
+ definition += ` CONSTRAINT "${pgTruncate(`${table.name}_${column.name}_unique_index`)}" UNIQUE`;
4211
4234
  if (!column.isNullable) definition += " NOT NULL";
4212
4235
  else definition += " NULL";
4213
4236
  if (column.default) definition += ` DEFAULT ${column.default}`;
@@ -4216,16 +4239,16 @@ function buildCreateTable(table, deferredFkNames = /* @__PURE__ */ new Set()) {
4216
4239
  for (const fk of table.foreignKeys) {
4217
4240
  if (deferredFkNames.has(fk.name)) continue;
4218
4241
  definitions.push(
4219
- `CONSTRAINT "${fk.name}" FOREIGN KEY ("${fk.column}") REFERENCES "${fk.refTable}" ("${fk.refColumn}") ON DELETE ${fk.onDelete} ON UPDATE ${fk.onUpdate}`
4242
+ `CONSTRAINT "${pgTruncate(fk.name)}" FOREIGN KEY ("${fk.column}") REFERENCES "${fk.refTable}" ("${fk.refColumn}") ON DELETE ${fk.onDelete} ON UPDATE ${fk.onUpdate}`
4220
4243
  );
4221
4244
  }
4222
4245
  for (const check of table.checkConstraints) {
4223
- definitions.push(`CONSTRAINT "${check.name}" CHECK (${check.check})`);
4246
+ definitions.push(`CONSTRAINT "${pgTruncate(check.name)}" CHECK (${check.check})`);
4224
4247
  }
4225
4248
  for (const col of table.columns) {
4226
4249
  if (col.type === "ENUM" && col.value) {
4227
4250
  definitions.push(
4228
- `CONSTRAINT "${table.name}_${col.name}_check" CHECK ("${col.name}" IN (${col.value}))`
4251
+ `CONSTRAINT "${pgTruncate(`${table.name}_${col.name}_check`)}" CHECK ("${col.name}" IN (${col.value}))`
4229
4252
  );
4230
4253
  }
4231
4254
  }
@@ -4249,7 +4272,7 @@ function buildColumnType(column) {
4249
4272
  function buildAddColumn(tableName, column) {
4250
4273
  let definition = `ALTER TABLE "${tableName}" ADD COLUMN "${column.name}" ${buildColumnType(column)}`;
4251
4274
  if (column.isPrimary) definition += " PRIMARY KEY";
4252
- if (column.isUnique) definition += ` CONSTRAINT "${tableName}_${column.name}_unique_index" UNIQUE`;
4275
+ if (column.isUnique) definition += ` CONSTRAINT "${pgTruncate(`${tableName}_${column.name}_unique_index`)}" UNIQUE`;
4253
4276
  if (!column.isNullable) definition += " NOT NULL";
4254
4277
  else definition += " NULL";
4255
4278
  if (column.default) definition += ` DEFAULT ${column.default}`;
@@ -4258,36 +4281,88 @@ function buildAddColumn(tableName, column) {
4258
4281
  }
4259
4282
  function buildCreateIndex(tableName, index) {
4260
4283
  const unique = index.isUnique ? "UNIQUE " : "";
4261
- let sql = `CREATE ${unique}INDEX "${index.name}" ON "${tableName}" (${index.columns.map((column) => `"${column}" ${index.order}`).join(", ")})`;
4284
+ let sql = `CREATE ${unique}INDEX "${pgTruncate(index.name)}" ON "${tableName}" (${index.columns.map((column) => `"${column}" ${index.order}`).join(", ")})`;
4262
4285
  if (index.where) sql += ` WHERE ${index.where}`;
4263
4286
  sql += ";";
4264
4287
  return sql;
4265
4288
  }
4266
4289
  function buildAddForeignKey(tableName, foreignKey) {
4267
- return `ALTER TABLE "${tableName}" ADD CONSTRAINT "${foreignKey.name}" FOREIGN KEY ("${foreignKey.column}") REFERENCES "${foreignKey.refTable}" ("${foreignKey.refColumn}") ON DELETE ${foreignKey.onDelete} ON UPDATE ${foreignKey.onUpdate};`;
4290
+ return `ALTER TABLE "${tableName}" ADD CONSTRAINT "${pgTruncate(foreignKey.name)}" FOREIGN KEY ("${foreignKey.column}") REFERENCES "${foreignKey.refTable}" ("${foreignKey.refColumn}") ON DELETE ${foreignKey.onDelete} ON UPDATE ${foreignKey.onUpdate};`;
4268
4291
  }
4269
4292
  function buildAddCheckConstraint(tableName, constraint) {
4270
- return `ALTER TABLE "${tableName}" ADD CONSTRAINT "${constraint.name}" CHECK (${constraint.check});`;
4293
+ return `ALTER TABLE "${tableName}" ADD CONSTRAINT "${pgTruncate(constraint.name)}" CHECK (${constraint.check});`;
4294
+ }
4295
+ function lowercaseOutsideStrings(s) {
4296
+ return s.replace(/'(?:[^']|'')*'|[^']+/g, (match) => {
4297
+ if (match.startsWith("'")) return match;
4298
+ return match.toLowerCase();
4299
+ });
4300
+ }
4301
+ function stripLeafParens(expr) {
4302
+ let prev = "";
4303
+ let curr = expr;
4304
+ while (prev !== curr) {
4305
+ prev = curr;
4306
+ curr = curr.replace(/\(([^()]*)\)/g, (match, inner) => {
4307
+ if (/\b(?:and|or)\b/i.test(inner)) return match;
4308
+ return inner;
4309
+ });
4310
+ }
4311
+ return curr;
4312
+ }
4313
+ function normalizeWhere(whereExpr) {
4314
+ if (!whereExpr) return "";
4315
+ let normalized = whereExpr.replace(/::\w+(\[\])?/g, "").replace(/"(\w+)"/g, "$1").replace(/\((\w+)\)/g, "$1").replace(/\s+/g, " ").trim();
4316
+ normalized = lowercaseOutsideStrings(normalized);
4317
+ while (normalized.startsWith("(") && normalized.endsWith(")")) {
4318
+ const inner = normalized.slice(1, -1);
4319
+ let depth = 0;
4320
+ let balanced = true;
4321
+ for (const char of inner) {
4322
+ if (char === "(") depth++;
4323
+ if (char === ")") depth--;
4324
+ if (depth < 0) {
4325
+ balanced = false;
4326
+ break;
4327
+ }
4328
+ }
4329
+ if (balanced && depth === 0) normalized = inner.trim();
4330
+ else break;
4331
+ }
4332
+ normalized = stripLeafParens(normalized);
4333
+ return normalized;
4271
4334
  }
4272
4335
  function indexSignature(name, columns, isUnique, order, where) {
4273
- return `${name}|${columns.join(",")}|${isUnique}|${order}|${where || ""}`;
4336
+ return `${name}|${columns.join(",")}|${isUnique}|${order}|${normalizeWhere(where)}`;
4274
4337
  }
4275
4338
  function isFkChanged(desired, liveFk) {
4276
- const desiredFk = desired.foreignKeys.find((fk) => fk.name === liveFk.name);
4339
+ const desiredFk = desired.foreignKeys.find((fk) => pgTruncate(fk.name) === liveFk.name);
4277
4340
  if (!desiredFk) return true;
4278
4341
  return desiredFk.column !== liveFk.column || desiredFk.refTable !== liveFk.refTable || desiredFk.refColumn !== liveFk.refColumn || desiredFk.onDelete !== liveFk.onDelete || desiredFk.onUpdate !== liveFk.onUpdate;
4279
4342
  }
4343
+ var PG_MAX_IDENTIFIER = 63;
4344
+ function pgTruncate(name) {
4345
+ const buf = Buffer.from(name, "utf8");
4346
+ if (buf.length <= PG_MAX_IDENTIFIER) return name;
4347
+ let end = PG_MAX_IDENTIFIER;
4348
+ while (end > 0 && (buf[end] & 192) === 128) end--;
4349
+ return buf.subarray(0, end).toString("utf8");
4350
+ }
4280
4351
  function normalizeCheckExpression(expr) {
4281
- let s = expr;
4282
- const checkMatch = s.match(/^CHECK\s*\(([\s\S]*)\)\s*$/i);
4283
- if (checkMatch) s = checkMatch[1];
4284
- s = s.replace(/::\w+(\[\])?/g, "");
4285
- s = s.replace(/=\s*ANY\s*\(\s*\(?\s*ARRAY\s*\[([^\]]*)\]\s*\)?\s*\)/gi, "IN ($1)");
4286
- s = s.replace(/"(\w+)"/g, "$1");
4287
- s = s.replace(/\((\w+)\)/g, "$1");
4288
- s = s.replace(/\s+/g, " ").trim().toLowerCase();
4289
- while (s.startsWith("(") && s.endsWith(")")) {
4290
- const inner = s.slice(1, -1);
4352
+ let normalized = expr;
4353
+ const checkMatch = normalized.match(/^CHECK\s*\(([\s\S]*)\)\s*$/i);
4354
+ if (checkMatch) normalized = checkMatch[1];
4355
+ normalized = normalized.replace(/::\w+(\[\])?/g, "");
4356
+ normalized = normalized.replace(/=\s*ANY\s*\(\s*\(\s*ARRAY\s*\[([^\]]*)\]\s*\)\s*\)/gi, "IN ($1)");
4357
+ normalized = normalized.replace(/=\s*ANY\s*\(\s*ARRAY\s*\[([^\]]*)\]\s*\)/gi, "IN ($1)");
4358
+ normalized = normalized.replace(/\bIN\s*\(\s*('(?:[^']|'')*')\s*\)/gi, "= $1");
4359
+ normalized = normalized.replace(/"(\w+)"/g, "$1");
4360
+ normalized = normalized.replace(/\((\w+)\)/g, "$1");
4361
+ normalized = normalized.replace(/\s+/g, " ").trim();
4362
+ normalized = lowercaseOutsideStrings(normalized);
4363
+ normalized = normalized.replace(/<>/g, "!=");
4364
+ while (normalized.startsWith("(") && normalized.endsWith(")")) {
4365
+ const inner = normalized.slice(1, -1);
4291
4366
  let depth = 0;
4292
4367
  let balanced = true;
4293
4368
  for (const char of inner) {
@@ -4298,11 +4373,12 @@ function normalizeCheckExpression(expr) {
4298
4373
  break;
4299
4374
  }
4300
4375
  }
4301
- if (balanced && depth === 0) s = inner.trim();
4376
+ if (balanced && depth === 0) normalized = inner.trim();
4302
4377
  else break;
4303
4378
  }
4304
- s = s.replace(/\s*,\s*/g, ", ");
4305
- return s;
4379
+ normalized = stripLeafParens(normalized);
4380
+ normalized = normalized.replace(/\s*,\s*/g, ", ");
4381
+ return normalized;
4306
4382
  }
4307
4383
  function isSerialMatch(column, liveCol) {
4308
4384
  if (column.hasAutoIncrement || column.type === "BIGSERIAL" || column.type === "SERIAL") {
@@ -4315,7 +4391,7 @@ function modifiersDiffer(column, liveColumn) {
4315
4391
  const desiredLength = column.length ?? null;
4316
4392
  if (desiredLength !== liveColumn.characterMaximumLength) return true;
4317
4393
  if (column.type === "DECIMAL" && column.value) {
4318
- const parts = column.value.replace(/['"]/g, "").split("-");
4394
+ const parts = column.value.replace(/['"]/g, "").split(/[-,]/);
4319
4395
  const desiredPrecision = parseInt(parts[0], 10);
4320
4396
  const desiredScale = parts.length > 1 ? parseInt(parts[1], 10) : 0;
4321
4397
  if (liveColumn.numericPrecision !== desiredPrecision || liveColumn.numericScale !== desiredScale) return true;
@@ -4350,8 +4426,8 @@ function defaultsMatch(desired, live, column) {
4350
4426
  if (column.hasAutoIncrement || column.type === "BIGSERIAL" || column.type === "SERIAL") return true;
4351
4427
  if (desired === null && live === null) return true;
4352
4428
  if (desired === null || live === null) return false;
4353
- const normalizedLive = live.replace(/::[^\s]+$/g, "").trim();
4354
- return desired.trim() === normalizedLive;
4429
+ const normalizedLive = live.replace(/::[a-z][a-z0-9_ ]*(\[\])?$/gi, "").trim();
4430
+ return lowercaseOutsideStrings(desired.trim()) === lowercaseOutsideStrings(normalizedLive);
4355
4431
  }
4356
4432
 
4357
4433
  // src/restura/sql/PsqlTransaction.ts