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