@depup/typeorm-extension 4.0.0-depup.0 → 4.2.0-depup.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.mjs CHANGED
@@ -5,13 +5,27 @@ import fs from "node:fs";
5
5
  import { Brackets, DataSource, InstanceChecker, MigrationExecutor, MssqlParameter, Repository, Table } from "typeorm";
6
6
  import { oneOf, read as read$1, readArray, readBool, readInt, toArray, toBool } from "envix";
7
7
  import { createMerger } from "smob";
8
+ import { PlatformTools } from "typeorm/platform/PlatformTools.js";
8
9
  import { DriverFactory } from "typeorm/driver/DriverFactory.js";
9
- import { pascalCase } from "pascal-case";
10
- import process$1 from "node:process";
10
+ import { CommandUtils } from "typeorm/commands/CommandUtils.js";
11
11
  import { MigrationGenerateCommand } from "typeorm/commands/MigrationGenerateCommand.js";
12
+ import { pascalCase } from "pascal-case";
12
13
  //#region src/errors/base.ts
13
14
  var TypeormExtensionError = class extends Error {};
14
15
  //#endregion
16
+ //#region src/errors/database-lock.ts
17
+ var DatabaseLockError = class DatabaseLockError extends TypeormExtensionError {
18
+ static timeout(name, timeout) {
19
+ return new DatabaseLockError(`The database lock ${name} could not be acquired within ${timeout}ms.`);
20
+ }
21
+ static transactionActive(name) {
22
+ return new DatabaseLockError(`The database lock ${name} can not be taken inside a transaction, it would outlive a rollback.`);
23
+ }
24
+ static transactionLeftOpen(name) {
25
+ return new DatabaseLockError(`The callback of the database lock ${name} left a transaction open, which has been rolled back.`);
26
+ }
27
+ };
28
+ //#endregion
15
29
  //#region src/errors/driver.ts
16
30
  var DriverError = class DriverError extends TypeormExtensionError {
17
31
  constructor(message) {
@@ -26,6 +40,9 @@ var DriverError = class DriverError extends TypeormExtensionError {
26
40
  static schemaAlterationNotSupported(driverName) {
27
41
  return new DriverError(`Schema alterations are not supported for the driver ${driverName}.`);
28
42
  }
43
+ static lockNotSupported(driverName) {
44
+ return new DriverError(`Database locks are not supported for the driver ${driverName}.`);
45
+ }
29
46
  static columnConversionExpressionNotSupported(driverName) {
30
47
  return new DriverError(`A conversion expression (\`using\`) can not be expressed for the driver ${driverName}, which would convert the values on its own terms instead.`);
31
48
  }
@@ -92,6 +109,12 @@ var OptionsError = class OptionsError extends TypeormExtensionError {
92
109
  static notFound() {
93
110
  return new OptionsError("The database options could not be located/loaded.");
94
111
  }
112
+ static timezoneUnsupported(timezone) {
113
+ return new OptionsError(`The database timezone ${String(timezone)} is not supported, only UTC is.`);
114
+ }
115
+ static timezoneConflict(detail) {
116
+ return new OptionsError(`The database timezone can not be pinned: ${detail}`);
117
+ }
95
118
  static databaseNotDefined() {
96
119
  return new OptionsError("The database name to connect to is not defined.");
97
120
  }
@@ -925,6 +948,8 @@ let EnvironmentVariableName = /* @__PURE__ */ function(EnvironmentVariableName)
925
948
  EnvironmentVariableName["MAX_QUERY_EXECUTION_TIME_ALT"] = "TYPEORM_MAX_QUERY_EXECUTION_TIME";
926
949
  EnvironmentVariableName["DEBUG"] = "DB_DEBUG";
927
950
  EnvironmentVariableName["DEBUG_ALT"] = "TYPEORM_DEBUG";
951
+ EnvironmentVariableName["PIN_TIMEZONE"] = "DB_PIN_TIMEZONE";
952
+ EnvironmentVariableName["PIN_TIMEZONE_ALT"] = "TYPEORM_PIN_TIMEZONE";
928
953
  EnvironmentVariableName["UUID_EXTENSION"] = "DB_UUID_EXTENSION";
929
954
  EnvironmentVariableName["UUID_EXTENSION_ALT"] = "TYPEORM_UUID_EXTENSION";
930
955
  EnvironmentVariableName["CACHE"] = "DB_CACHE";
@@ -1062,7 +1087,8 @@ function useEnv(key) {
1062
1087
  maxQueryExecutionTime: oneOf([readInt("DB_MAX_QUERY_EXECUTION_TIME"), readInt("TYPEORM_MAX_QUERY_EXECUTION_TIME")]),
1063
1088
  debug: oneOf([read$1("DB_DEBUG"), read$1("TYPEORM_DEBUG")]),
1064
1089
  cache: transformCache(oneOf([read$1("DB_CACHE"), read$1("TYPEORM_CACHE")])),
1065
- uuidExtension: oneOf([read$1("DB_UUID_EXTENSION"), read$1("TYPEORM_UUID_EXTENSION")])
1090
+ uuidExtension: oneOf([read$1("DB_UUID_EXTENSION"), read$1("TYPEORM_UUID_EXTENSION")]),
1091
+ pinTimezone: oneOf([read$1("DB_PIN_TIMEZONE"), read$1("TYPEORM_PIN_TIMEZONE")])
1066
1092
  };
1067
1093
  if (output.extra) output.extra = JSON.parse(output.extra);
1068
1094
  let type;
@@ -1093,12 +1119,726 @@ function mergeDataSourceOptions(target, source) {
1093
1119
  return merge(target, source);
1094
1120
  }
1095
1121
  //#endregion
1122
+ //#region src/data-source/options/timezone/utils.ts
1123
+ function isDataSourceTimezone(input) {
1124
+ return typeof input === "string" && input.toUpperCase() === "UTC";
1125
+ }
1126
+ function isDate(input) {
1127
+ return input instanceof Date || Object.prototype.toString.call(input) === "[object Date]";
1128
+ }
1129
+ /**
1130
+ * Everything this module installs (wrapped drivers, clients, parsers,
1131
+ * session callbacks) is recorded here, so a second application recognises
1132
+ * the first one exactly instead of guessing from option values.
1133
+ */
1134
+ const installed = /* @__PURE__ */ new WeakSet();
1135
+ function markInstalled(value) {
1136
+ installed.add(value);
1137
+ return value;
1138
+ }
1139
+ function isInstalled(value) {
1140
+ return (typeof value === "object" || typeof value === "function") && value !== null && installed.has(value);
1141
+ }
1142
+ /**
1143
+ * Whether the options carry a pin applied earlier, in any of the places a
1144
+ * dialect installs one.
1145
+ */
1146
+ function hasInstalledTimezone(options) {
1147
+ if (isInstalled(options.driver)) return true;
1148
+ const extra = options.extra && typeof options.extra === "object" ? options.extra : {};
1149
+ return isInstalled(extra.types) || isInstalled(extra.Client) || isInstalled(extra.sessionCallback);
1150
+ }
1151
+ //#endregion
1152
+ //#region src/data-source/options/timezone/mysql.ts
1153
+ const MYSQL_SESSION_TIMEZONE_SQL = "SET time_zone = '+00:00'";
1154
+ /**
1155
+ * The mysql2 `timezone` values meaning UTC.
1156
+ */
1157
+ const MYSQL_UTC_TIMEZONE = /^(?:z|[+-]00:00)$/i;
1158
+ /**
1159
+ * Wrap a `mysql2` module so every pool pins its connections to UTC. The pool
1160
+ * emits `connection` synchronously, before it hands a new connection out, and
1161
+ * a connection runs its queries in order, so the `SET` is the first statement
1162
+ * every connection executes. A connection whose `SET` fails is destroyed
1163
+ * rather than left running in another zone.
1164
+ */
1165
+ function createMysqlUTCDriver(driver) {
1166
+ return markInstalled({
1167
+ ...driver,
1168
+ createPool(...args) {
1169
+ const pool = driver.createPool(...args);
1170
+ pool.on("connection", (connection) => {
1171
+ connection.query(MYSQL_SESSION_TIMEZONE_SQL, (err) => {
1172
+ if (err) connection.destroy();
1173
+ });
1174
+ });
1175
+ return pool;
1176
+ }
1177
+ });
1178
+ }
1179
+ function readSetting(options, key) {
1180
+ const extra = options.extra ?? {};
1181
+ return typeof extra[key] !== "undefined" ? extra[key] : options[key];
1182
+ }
1183
+ /**
1184
+ * Pin a mysql / mariadb data source: the session runs `SET time_zone` on
1185
+ * every new pool connection, and mysql2 reads and writes DATETIME and
1186
+ * TIMESTAMP values as UTC (`timezone: 'Z'`). DATE values are returned as
1187
+ * strings (`dateStrings: ['DATE']`): a calendar date has no instant, and
1188
+ * mysql2 would otherwise hand it over as UTC midnight, which typeorm then
1189
+ * formats back in the process zone, a day early west of UTC.
1190
+ *
1191
+ * A `timezone` naming UTC and `dateStrings: ['DATE']` are accepted as they
1192
+ * are. Another `timezone`, other `dateStrings`, a custom `typeCast` or a
1193
+ * replication setup (a pool cluster has no per-connection hook) is a
1194
+ * conflict, and so is a pin altered after it was applied.
1195
+ */
1196
+ function applyMysqlTimezone(options) {
1197
+ if (isInstalled(options.driver)) {
1198
+ const timezone = readSetting(options, "timezone");
1199
+ const dateStrings = readSetting(options, "dateStrings");
1200
+ if (timezone === "Z" && Array.isArray(dateStrings) && dateStrings.length === 1 && dateStrings[0] === "DATE" && typeof readSetting(options, "typeCast") === "undefined") return options;
1201
+ throw OptionsError.timezoneConflict("the mysql pin was altered after it was applied.");
1202
+ }
1203
+ if (typeof options.replication !== "undefined") throw OptionsError.timezoneConflict("a mysql replication setup has no per-connection hook to pin the session with.");
1204
+ const timezone = readSetting(options, "timezone");
1205
+ if (typeof timezone !== "undefined" && !(typeof timezone === "string" && MYSQL_UTC_TIMEZONE.test(timezone))) throw OptionsError.timezoneConflict(`the mysql timezone is set to ${String(timezone)}.`);
1206
+ const dateStrings = readSetting(options, "dateStrings");
1207
+ if (typeof dateStrings !== "undefined" && dateStrings !== false && !(Array.isArray(dateStrings) && dateStrings.every((type) => type === "DATE"))) throw OptionsError.timezoneConflict("mysql dateStrings would hand DATETIME or TIMESTAMP values over as zone-less strings.");
1208
+ if (typeof readSetting(options, "typeCast") !== "undefined") throw OptionsError.timezoneConflict("a custom mysql typeCast decides how date values are read.");
1209
+ const driver = options.driver ?? PlatformTools.load("mysql2");
1210
+ const extra = { ...options.extra ?? {} };
1211
+ delete extra.timezone;
1212
+ delete extra.dateStrings;
1213
+ return {
1214
+ ...options,
1215
+ extra,
1216
+ timezone: "Z",
1217
+ dateStrings: ["DATE"],
1218
+ driver: createMysqlUTCDriver(driver)
1219
+ };
1220
+ }
1221
+ //#endregion
1222
+ //#region src/data-source/options/timezone/oracle.ts
1223
+ const ORACLE_SESSION_TIMEZONE_SQL = "ALTER SESSION SET TIME_ZONE = '+00:00'";
1224
+ /**
1225
+ * Re-read the local fields of a Date as UTC. node-oracledb builds a zone-less
1226
+ * `TIMESTAMP` as a local Date from its fields, and offers no switch for it.
1227
+ */
1228
+ function readLocalDateAsUTC(date) {
1229
+ const output = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()));
1230
+ const year = date.getFullYear();
1231
+ if (year >= 0 && year < 100) output.setUTCFullYear(year);
1232
+ return output;
1233
+ }
1234
+ /**
1235
+ * The reverse: a local Date whose fields are the UTC fields of the given one.
1236
+ * node-oracledb binds a Date as a zone-less `TIMESTAMP` from its local
1237
+ * fields, so this sends the UTC wall clock with the column's own type, which
1238
+ * keeps an index on the column usable (a `TIMESTAMP WITH TIME ZONE` bind
1239
+ * would convert the column side of every comparison).
1240
+ */
1241
+ function writeUTCAsLocalDate(date) {
1242
+ const output = new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());
1243
+ const year = date.getUTCFullYear();
1244
+ if (year >= 0 && year < 100) output.setFullYear(year);
1245
+ return output;
1246
+ }
1247
+ /**
1248
+ * Proxy an object, replacing the named methods. Every function is bound to
1249
+ * the object itself, so its internals never re-enter the proxy, and cached,
1250
+ * so reading it twice gives the same function. A member carrying a
1251
+ * `prototype` which is not replaced (an exported class) passes through
1252
+ * untouched, which keeps `instanceof` and statics working.
1253
+ */
1254
+ function proxyMethods(target, methods) {
1255
+ const cache = /* @__PURE__ */ new Map();
1256
+ return new Proxy(target, { get(object, property) {
1257
+ const value = Reflect.get(object, property, object);
1258
+ if (typeof value !== "function") return value;
1259
+ const cached = cache.get(property);
1260
+ if (cached && cached.source === value) return cached.value;
1261
+ const replace = typeof property === "string" ? methods[property] : void 0;
1262
+ let output;
1263
+ if (replace) output = replace(value.bind(object));
1264
+ else if (Object.prototype.hasOwnProperty.call(value, "prototype")) output = value;
1265
+ else output = value.bind(object);
1266
+ cache.set(property, {
1267
+ source: value,
1268
+ value: output
1269
+ });
1270
+ return output;
1271
+ } });
1272
+ }
1273
+ /**
1274
+ * Wrap a `node-oracledb` module so the connections it hands out (pooled or
1275
+ * standalone, the latter with the session pinned too) read a zone-less
1276
+ * `TIMESTAMP` as UTC, return `TIMESTAMP`
1277
+ * out-binds as UTC and send Date parameters as UTC wall clock. The module
1278
+ * exposes the read conversion only process-wide (`oracledb.fetchTypeHandler`)
1279
+ * or per `execute()` call, so the handler is added to every call a wrapped
1280
+ * connection makes. A handler the caller passes, or the process-wide one,
1281
+ * still decides first.
1282
+ *
1283
+ * In a process zone with daylight saving, a UTC wall clock falling into the
1284
+ * local spring-forward hour has no local Date to travel as: such a value is
1285
+ * read, and written, an hour late. A process running in UTC is exact.
1286
+ */
1287
+ function createOracleUTCDriver(driver) {
1288
+ const handleTimestamp = (metadata) => {
1289
+ if (metadata.dbType !== driver.DB_TYPE_TIMESTAMP) return;
1290
+ return { converter: (value) => isDate(value) ? readLocalDateAsUTC(value) : value };
1291
+ };
1292
+ const withHandler = (options) => {
1293
+ const custom = options?.fetchTypeHandler ?? driver.fetchTypeHandler;
1294
+ return {
1295
+ ...options ?? {},
1296
+ fetchTypeHandler: (metadata) => {
1297
+ const result = typeof custom === "function" ? custom(metadata) : void 0;
1298
+ return typeof result === "undefined" ? handleTimestamp(metadata) : result;
1299
+ }
1300
+ };
1301
+ };
1302
+ const toBindValue = (value) => {
1303
+ if (isDate(value)) return writeUTCAsLocalDate(value);
1304
+ if (Array.isArray(value)) return value.map((element) => isDate(element) ? writeUTCAsLocalDate(element) : element);
1305
+ return value;
1306
+ };
1307
+ const isZonelessType = (type) => typeof type === "undefined" || type === driver.DB_TYPE_TIMESTAMP;
1308
+ const toBind = (value) => {
1309
+ if (value && typeof value === "object" && !Array.isArray(value) && !isDate(value) && "val" in value) {
1310
+ const definition = value;
1311
+ return isZonelessType(definition.type) ? {
1312
+ ...definition,
1313
+ val: toBindValue(definition.val)
1314
+ } : definition;
1315
+ }
1316
+ return toBindValue(value);
1317
+ };
1318
+ /**
1319
+ * A row of `executeMany` holds plain values whose types, if any, come from
1320
+ * `bindDefs`: only a Date without one, or typed TIMESTAMP, is shifted.
1321
+ */
1322
+ const toRow = (row, definitions) => {
1323
+ const shift = (value, definition) => {
1324
+ const type = definition && typeof definition === "object" ? definition.type : void 0;
1325
+ return isZonelessType(type) ? toBindValue(value) : value;
1326
+ };
1327
+ if (Array.isArray(row)) return row.map((value, index) => shift(value, Array.isArray(definitions) ? definitions[index] : void 0));
1328
+ if (row && typeof row === "object") {
1329
+ const output = {};
1330
+ const keys = Object.keys(row);
1331
+ for (const key of keys) {
1332
+ const definition = definitions && typeof definitions === "object" && !Array.isArray(definitions) ? definitions[key] : void 0;
1333
+ output[key] = shift(row[key], definition);
1334
+ }
1335
+ return output;
1336
+ }
1337
+ return row;
1338
+ };
1339
+ const toBinds = (binds) => {
1340
+ if (Array.isArray(binds)) return binds.map((value) => toBind(value));
1341
+ if (binds && typeof binds === "object") {
1342
+ const output = {};
1343
+ const keys = Object.keys(binds);
1344
+ for (const key of keys) output[key] = toBind(binds[key]);
1345
+ return output;
1346
+ }
1347
+ return binds;
1348
+ };
1349
+ const isTimestampOut = (definition) => {
1350
+ if (!definition || typeof definition !== "object" || isDate(definition)) return false;
1351
+ const { type, dir, val } = definition;
1352
+ if (dir !== driver.BIND_OUT && dir !== driver.BIND_INOUT) return false;
1353
+ return type === driver.DB_TYPE_TIMESTAMP || typeof type === "undefined" && dir === driver.BIND_INOUT && isDate(val);
1354
+ };
1355
+ const readOut = (value) => {
1356
+ if (isDate(value)) return readLocalDateAsUTC(value);
1357
+ if (Array.isArray(value)) return value.map((element) => isDate(element) ? readLocalDateAsUTC(element) : element);
1358
+ return value;
1359
+ };
1360
+ const convertOutBinds = (outBinds, definitions) => {
1361
+ if (!outBinds || typeof outBinds !== "object" || !definitions || typeof definitions !== "object") return outBinds;
1362
+ if (Array.isArray(definitions)) {
1363
+ if (!Array.isArray(outBinds)) return outBinds;
1364
+ const outs = definitions.filter((definition) => !!definition && typeof definition === "object" && !isDate(definition) && (definition.dir === driver.BIND_OUT || definition.dir === driver.BIND_INOUT));
1365
+ return outBinds.map((value, index) => isTimestampOut(outs[index]) ? readOut(value) : value);
1366
+ }
1367
+ const output = { ...outBinds };
1368
+ const keys = Object.keys(output);
1369
+ for (const key of keys) if (isTimestampOut(definitions[key])) output[key] = readOut(output[key]);
1370
+ return output;
1371
+ };
1372
+ const convertResult = (result, definitions) => {
1373
+ if (result && typeof result === "object" && typeof result.outBinds !== "undefined") result.outBinds = convertOutBinds(result.outBinds, definitions);
1374
+ return result;
1375
+ };
1376
+ const convertManyResult = (result, definitions) => {
1377
+ if (result && typeof result === "object" && Array.isArray(result.outBinds)) result.outBinds = result.outBinds.map((row) => convertOutBinds(row, definitions));
1378
+ return result;
1379
+ };
1380
+ const settle = (outcome, definitions, callback, convert) => {
1381
+ if (typeof callback === "function") return outcome;
1382
+ return outcome && typeof outcome.then === "function" ? outcome.then((result) => convert(result, definitions)) : outcome;
1383
+ };
1384
+ const wrapConnection = (connection) => proxyMethods(connection, {
1385
+ execute: (original) => (sql, binds, options, callback) => {
1386
+ if (typeof binds === "function") return original(sql, {}, withHandler(), binds);
1387
+ const input = binds ?? {};
1388
+ const optionsOrCallback = typeof options === "function" ? void 0 : options;
1389
+ const done = typeof options === "function" ? options : callback;
1390
+ if (typeof done === "function") return original(sql, toBinds(input), withHandler(optionsOrCallback), (err, result) => {
1391
+ done(err, err ? result : convertResult(result, input));
1392
+ });
1393
+ return settle(original(sql, toBinds(input), withHandler(optionsOrCallback)), input, void 0, convertResult);
1394
+ },
1395
+ executeMany: (original) => (sql, binds, options, callback) => {
1396
+ const optionsOrCallback = typeof options === "function" ? void 0 : options;
1397
+ const done = typeof options === "function" ? options : callback;
1398
+ const definitions = optionsOrCallback?.bindDefs;
1399
+ const rows = Array.isArray(binds) ? binds.map((row) => toRow(row, definitions)) : binds;
1400
+ if (typeof done === "function") return original(sql, rows, optionsOrCallback ?? {}, (err, result) => {
1401
+ done(err, err ? result : convertManyResult(result, definitions));
1402
+ });
1403
+ return settle(original(sql, rows, optionsOrCallback ?? {}), definitions, void 0, convertManyResult);
1404
+ },
1405
+ queryStream: (original) => (sql, binds, options) => original(sql, toBinds(binds ?? {}), withHandler(options))
1406
+ });
1407
+ const wrapPool = (pool) => proxyMethods(pool, { getConnection: (original) => (...args) => {
1408
+ const callback = args[args.length - 1];
1409
+ if (typeof callback === "function") return original(...args.slice(0, -1), (err, connection, ...rest) => {
1410
+ callback(err, connection ? wrapConnection(connection) : connection, ...rest);
1411
+ });
1412
+ return original(...args).then((connection) => wrapConnection(connection));
1413
+ } });
1414
+ const wrapFactory = (wrap) => (original) => (...args) => {
1415
+ const callback = args[args.length - 1];
1416
+ if (typeof callback === "function") return original(...args.slice(0, -1), (err, value) => {
1417
+ callback(err, value ? wrap(value) : value);
1418
+ });
1419
+ const output = original(...args);
1420
+ if (output && typeof output.then === "function") return output.then((value) => wrap(value));
1421
+ return output ? wrap(output) : output;
1422
+ };
1423
+ const pinConnection = (connection) => Promise.resolve(connection.execute(ORACLE_SESSION_TIMEZONE_SQL)).then(() => wrapConnection(connection), (error) => Promise.resolve(connection.close()).catch(() => void 0).then(() => Promise.reject(error)));
1424
+ return markInstalled(proxyMethods(driver, {
1425
+ createPool: wrapFactory(wrapPool),
1426
+ getPool: (original) => (...args) => {
1427
+ const pool = original(...args);
1428
+ return pool ? wrapPool(pool) : pool;
1429
+ },
1430
+ getConnection: (original) => (...args) => {
1431
+ const callback = args[args.length - 1];
1432
+ if (typeof callback === "function") return original(...args.slice(0, -1), (err, connection) => {
1433
+ if (err || !connection) {
1434
+ callback(err, connection);
1435
+ return;
1436
+ }
1437
+ pinConnection(connection).then((value) => callback(null, value), (error) => callback(error));
1438
+ });
1439
+ return original(...args).then((connection) => pinConnection(connection));
1440
+ }
1441
+ }));
1442
+ }
1443
+ /**
1444
+ * Pin an oracle data source: a pool `sessionCallback` running
1445
+ * `ALTER SESSION SET TIME_ZONE`, and a driver whose connections read a
1446
+ * zone-less `TIMESTAMP` as UTC and send Date parameters as UTC wall clock.
1447
+ * A `sessionCallback` function the caller set runs after the pin; one naming
1448
+ * a PL/SQL procedure can not be combined and is a conflict.
1449
+ */
1450
+ function applyOracleTimezone(options) {
1451
+ if (isInstalled(options.driver)) {
1452
+ if (isInstalled(options.extra?.sessionCallback)) return options;
1453
+ throw OptionsError.timezoneConflict("the oracle pin was altered after it was applied.");
1454
+ }
1455
+ const existing = options.extra?.sessionCallback;
1456
+ if (typeof existing !== "undefined" && typeof existing !== "function") throw OptionsError.timezoneConflict("the oracle sessionCallback names a PL/SQL procedure, which can not run after the session pin.");
1457
+ const sessionCallback = markInstalled((connection, requestedTag, callback) => {
1458
+ connection.execute(ORACLE_SESSION_TIMEZONE_SQL, (err) => {
1459
+ if (err || typeof existing !== "function") {
1460
+ callback(err ?? void 0);
1461
+ return;
1462
+ }
1463
+ existing(connection, requestedTag, callback);
1464
+ });
1465
+ });
1466
+ const driver = options.driver ?? PlatformTools.load("oracledb");
1467
+ return {
1468
+ ...options,
1469
+ driver: createOracleUTCDriver(driver),
1470
+ extra: {
1471
+ ...options.extra ?? {},
1472
+ sessionCallback
1473
+ }
1474
+ };
1475
+ }
1476
+ //#endregion
1477
+ //#region src/data-source/options/timezone/postgres.ts
1478
+ /**
1479
+ * `timestamp without time zone` and its array.
1480
+ */
1481
+ const POSTGRES_TIMESTAMP_OID = 1114;
1482
+ const POSTGRES_TIMESTAMP_ARRAY_OID = 1115;
1483
+ /**
1484
+ * A `TimeZone` assignment among the postgres startup options, in either
1485
+ * spelling the server accepts (`-c TimeZone=...`, `--TimeZone=...`). The
1486
+ * name is matched whole: `log_timezone` sets something else entirely.
1487
+ */
1488
+ const POSTGRES_TIMEZONE_OPTION = /(?:^|\s)(?:-c\s*|--)timezone=(\S+)/gi;
1489
+ const POSTGRES_UTC_ZONE = /^(?:utc|uct|etc\/utc|etc\/uct|universal|etc\/universal|zulu|etc\/zulu|z|gmt|gmt0|etc\/gmt|etc\/gmt0|etc\/gmt[+-]0|greenwich|etc\/greenwich|[+-]?0{1,2}(?::?00)?)$/i;
1490
+ /**
1491
+ * The text form of a `timestamp without time zone` (ISO DateStyle).
1492
+ */
1493
+ const POSTGRES_TIMESTAMP = /^(\d{4,})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d+)?( BC)?$/;
1494
+ /**
1495
+ * The exact form pg's `prepareValue` gives a Date (the process offset
1496
+ * included), which pg-cursor applies in its constructor, before a stream
1497
+ * reaches the client.
1498
+ */
1499
+ const POSTGRES_LOCAL_DATE = /^(\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})([+-])(\d{2}):(\d{2})( BC)?$/;
1500
+ /**
1501
+ * Serialize a Date as pg does with `parseInputDatesAsUTC`, which is only
1502
+ * available process-wide there: UTC fields, an explicit `+00:00`, and a
1503
+ * ` BC` suffix for years before 1.
1504
+ */
1505
+ function serializePostgresDateAsUTC(date) {
1506
+ let year = date.getUTCFullYear();
1507
+ const isBCYear = year < 1;
1508
+ if (isBCYear) year = Math.abs(year) + 1;
1509
+ const pad = (value, length = 2) => String(value).padStart(length, "0");
1510
+ let output = `${pad(year, 4)}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}T${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}.${pad(date.getUTCMilliseconds(), 3)}+00:00`;
1511
+ if (isBCYear) output += " BC";
1512
+ return output;
1513
+ }
1514
+ /**
1515
+ * Re-serialize a Date which pg already turned into its local form (the
1516
+ * process offset included) as UTC. Any other value is returned unchanged.
1517
+ */
1518
+ function reserializePostgresLocalDateAsUTC(value) {
1519
+ if (typeof value !== "string") return value;
1520
+ const match = POSTGRES_LOCAL_DATE.exec(value);
1521
+ if (!match) return value;
1522
+ const [, year, month, day, hour, minute, second, millisecond, sign, offsetHours, offsetMinutes, bc] = match;
1523
+ let fullYear = Number(year);
1524
+ if (bc) fullYear = 1 - fullYear;
1525
+ const date = /* @__PURE__ */ new Date(0);
1526
+ date.setUTCFullYear(fullYear, Number(month) - 1, Number(day));
1527
+ date.setUTCHours(Number(hour), Number(minute), Number(second), Number(millisecond));
1528
+ const offset = (Number(offsetHours) * 60 + Number(offsetMinutes)) * (sign === "-" ? -1 : 1);
1529
+ date.setTime(date.getTime() - offset * 6e4);
1530
+ return serializePostgresDateAsUTC(date);
1531
+ }
1532
+ function toPostgresUTCParameter(value) {
1533
+ if (isDate(value)) return serializePostgresDateAsUTC(value);
1534
+ if (Array.isArray(value)) return value.map((element) => toPostgresUTCParameter(element));
1535
+ return value;
1536
+ }
1537
+ /**
1538
+ * A submittable (pg Query, pg-cursor, pg-query-stream) may already hold its
1539
+ * values in pg's prepared form: pg-cursor applies pg's `prepareValue` in its
1540
+ * constructor, before the client sees it.
1541
+ */
1542
+ function toPostgresUTCSubmittedParameter(value) {
1543
+ if (isDate(value)) return serializePostgresDateAsUTC(value);
1544
+ return reserializePostgresLocalDateAsUTC(value);
1545
+ }
1546
+ /**
1547
+ * Parse the text form of a `timestamp without time zone` as UTC: the same
1548
+ * fields, BC years and `infinity` pg's own parser handles, read in UTC rather
1549
+ * than in the zone of the process. Another text form (a non-ISO DateStyle)
1550
+ * gives `null`, as it does there.
1551
+ */
1552
+ function parsePostgresTimestampAsUTC(value) {
1553
+ if (value === "infinity") return Infinity;
1554
+ if (value === "-infinity") return -Infinity;
1555
+ const match = POSTGRES_TIMESTAMP.exec(value);
1556
+ if (!match) return null;
1557
+ const [, year, month, day, hour, minute, second, fraction, bc] = match;
1558
+ let fullYear = Number(year);
1559
+ if (bc) fullYear = 1 - fullYear;
1560
+ const date = /* @__PURE__ */ new Date(0);
1561
+ date.setUTCFullYear(fullYear, Number(month) - 1, Number(day));
1562
+ date.setUTCHours(Number(hour), Number(minute), Number(second), fraction ? Math.floor(Number(fraction) * 1e3) : 0);
1563
+ return date;
1564
+ }
1565
+ /**
1566
+ * Parse the text form of a postgres array, handing each element to the given
1567
+ * parser. Covers what pg emits for a `timestamp[]`: quoted and unquoted
1568
+ * elements, `NULL` and nested arrays.
1569
+ */
1570
+ function parsePostgresArray(value, parse) {
1571
+ let position = 0;
1572
+ const parseLevel = () => {
1573
+ const output = [];
1574
+ position++;
1575
+ while (position < value.length) {
1576
+ const char = value[position];
1577
+ if (char === "}") {
1578
+ position++;
1579
+ return output;
1580
+ }
1581
+ if (char === ",") position++;
1582
+ else if (char === "{") output.push(parseLevel());
1583
+ else if (char === "\"") {
1584
+ let element = "";
1585
+ position++;
1586
+ while (position < value.length && value[position] !== "\"") {
1587
+ if (value[position] === "\\") position++;
1588
+ element += value[position];
1589
+ position++;
1590
+ }
1591
+ position++;
1592
+ output.push(parse(element));
1593
+ } else {
1594
+ let element = "";
1595
+ while (position < value.length && value[position] !== "," && value[position] !== "}") {
1596
+ element += value[position];
1597
+ position++;
1598
+ }
1599
+ output.push(element === "NULL" ? null : parse(element));
1600
+ }
1601
+ }
1602
+ return output;
1603
+ };
1604
+ const brace = value.indexOf("{");
1605
+ if (brace === -1) return [];
1606
+ position = brace;
1607
+ return parseLevel();
1608
+ }
1609
+ const parsePostgresTimestampArrayAsUTC = (value) => parsePostgresArray(value, parsePostgresTimestampAsUTC);
1610
+ /**
1611
+ * Build postgres type parsers which read `timestamp without time zone` (and
1612
+ * its array) as UTC and delegate every other type to the given registry.
1613
+ */
1614
+ function createPostgresUTCTypes(types) {
1615
+ return markInstalled({ getTypeParser(oid, format) {
1616
+ if (format !== "binary") {
1617
+ if (oid === POSTGRES_TIMESTAMP_OID) return parsePostgresTimestampAsUTC;
1618
+ if (oid === POSTGRES_TIMESTAMP_ARRAY_OID) return parsePostgresTimestampArrayAsUTC;
1619
+ }
1620
+ return types.getTypeParser(oid, format);
1621
+ } });
1622
+ }
1623
+ /**
1624
+ * Whether a registry still parses zone-less timestamps the way pg does by
1625
+ * default (a local Date), judged by behaviour: a custom parser registered for
1626
+ * them (strings, another date library) is a choice the pin would override.
1627
+ */
1628
+ function isDefaultTimestampParsing(types) {
1629
+ const expected = new Date(2e3, 0, 2, 3, 4, 5).getTime();
1630
+ try {
1631
+ const single = types.getTypeParser(POSTGRES_TIMESTAMP_OID)("2000-01-02 03:04:05");
1632
+ const many = types.getTypeParser(POSTGRES_TIMESTAMP_ARRAY_OID)("{\"2000-01-02 03:04:05\"}");
1633
+ return single instanceof Date && single.getTime() === expected && Array.isArray(many) && many[0] instanceof Date && many[0].getTime() === expected;
1634
+ } catch {
1635
+ return false;
1636
+ }
1637
+ }
1638
+ /**
1639
+ * Derive a pg `Client` class which sends Date parameters as UTC. Left to pg,
1640
+ * a Date is sent with the offset of the Node process, which postgres drops
1641
+ * for a `timestamp without time zone`, storing the process's wall clock.
1642
+ * pg-pool takes the class through its `Client` option, so this stays scoped
1643
+ * to one pool.
1644
+ *
1645
+ * A stream (pg-query-stream / pg-cursor) prepares its values before the
1646
+ * client sees them, so those arrive in pg's local form and are re-serialized;
1647
+ * a Date inside an array value of a stream arrives as an array literal and is
1648
+ * not.
1649
+ */
1650
+ function createPostgresUTCClient(Base) {
1651
+ const Client = class extends Base {
1652
+ query(config, values, callback) {
1653
+ if (Array.isArray(values)) return super.query(config, values.map((value) => toPostgresUTCParameter(value)), callback);
1654
+ if (config && typeof config === "object") {
1655
+ if (config.cursor && Array.isArray(config.cursor.values)) config.cursor.values = config.cursor.values.map((value) => toPostgresUTCSubmittedParameter(value));
1656
+ else if (Array.isArray(config.values)) {
1657
+ if (typeof config.submit === "function") config.values = config.values.map((value) => toPostgresUTCSubmittedParameter(value));
1658
+ else return super.query({
1659
+ ...config,
1660
+ values: config.values.map((value) => toPostgresUTCParameter(value))
1661
+ }, values, callback);
1662
+ }
1663
+ }
1664
+ return super.query(config, values, callback);
1665
+ }
1666
+ };
1667
+ return markInstalled(Client);
1668
+ }
1669
+ /**
1670
+ * The TimeZone a set of startup options assigns. Postgres applies them in
1671
+ * order, so the last assignment is the one in effect.
1672
+ */
1673
+ function readTimezoneOption(options) {
1674
+ if (typeof options !== "string") return;
1675
+ let zone;
1676
+ for (const match of options.matchAll(POSTGRES_TIMEZONE_OPTION)) [, zone] = match;
1677
+ return typeof zone === "string" ? zone.replace(/^'(.*)'$/, "$1") : void 0;
1678
+ }
1679
+ function isUTCZone(zone) {
1680
+ return POSTGRES_UTC_ZONE.test(zone);
1681
+ }
1682
+ /**
1683
+ * Add `-c TimeZone=UTC` to a set of startup options, or accept a TimeZone
1684
+ * already in effect there when it names UTC. Any other zone is a conflict.
1685
+ */
1686
+ function pinTimezoneOption(options, source) {
1687
+ const zone = readTimezoneOption(options);
1688
+ if (typeof zone === "undefined") return `${options} -c TimeZone=UTC`.trim();
1689
+ if (isUTCZone(zone)) return options;
1690
+ throw OptionsError.timezoneConflict(`${source} sets the postgres TimeZone to ${zone}.`);
1691
+ }
1692
+ /**
1693
+ * Take the `options` parameter out of a connection string, leaving every
1694
+ * other byte of it as it was (re-encoding it can break a string pg would
1695
+ * have accepted). Postgres uses the last occurrence, and so does this.
1696
+ */
1697
+ function extractPostgresUrlOptions(url) {
1698
+ const index = url.indexOf("?");
1699
+ if (index === -1) return { url };
1700
+ const hash = url.indexOf("#", index);
1701
+ const query = url.slice(index + 1, hash === -1 ? void 0 : hash);
1702
+ const rest = hash === -1 ? "" : url.slice(hash);
1703
+ let options;
1704
+ const kept = [];
1705
+ for (const segment of query.split("&")) {
1706
+ const params = new URLSearchParams(segment);
1707
+ if (params.has("options")) options = params.get("options") ?? "";
1708
+ else if (segment.length > 0) kept.push(segment);
1709
+ }
1710
+ return {
1711
+ url: url.slice(0, index) + (kept.length > 0 ? `?${kept.join("&")}` : "") + rest,
1712
+ options
1713
+ };
1714
+ }
1715
+ function isPostgresNativeInUse(nativeDriver, driver) {
1716
+ try {
1717
+ let native = nativeDriver;
1718
+ if (native === null || typeof native === "undefined") native = PlatformTools.load("pg-native");
1719
+ return !!(native && driver.native);
1720
+ } catch {
1721
+ return false;
1722
+ }
1723
+ }
1724
+ /**
1725
+ * Pin a postgres data source: `-c TimeZone=UTC` for the session, parsers
1726
+ * reading `timestamp without time zone` (and its array) as UTC, and a client
1727
+ * sending Date parameters as UTC.
1728
+ *
1729
+ * pg takes the startup options from the connection string first, then
1730
+ * `extra.options`, then `PGOPTIONS`; the options in effect are moved into
1731
+ * `extra.options` (out of the connection string, byte for byte otherwise)
1732
+ * and the pin is added there. A user's `extra.types` (for other types) and
1733
+ * `extra.Client` are built upon. A foreign TimeZone in effect, timestamp
1734
+ * parsing customised process-wide or in `extra.types`, or a replication node
1735
+ * url carrying options (shared `extra` can not hold per-node options) is a
1736
+ * conflict, and so is a pin altered after it was applied.
1737
+ */
1738
+ function applyPostgresTimezone(options) {
1739
+ const extra = { ...options.extra ?? {} };
1740
+ if (isInstalled(extra.types) || isInstalled(extra.Client)) {
1741
+ const zone = readTimezoneOption(extra.options);
1742
+ const nodes = options.replication ? [options.replication.master, ...options.replication.slaves] : [];
1743
+ const urls = [
1744
+ options.url,
1745
+ extra.connectionString,
1746
+ ...nodes.map((node) => node.url)
1747
+ ].filter((url) => typeof url === "string");
1748
+ if (isInstalled(extra.types) && isInstalled(extra.Client) && typeof zone === "string" && isUTCZone(zone) && urls.every((url) => typeof extractPostgresUrlOptions(url).options === "undefined")) return options;
1749
+ throw OptionsError.timezoneConflict("the postgres pin was altered after it was applied.");
1750
+ }
1751
+ const driver = options.driver ?? PlatformTools.load("pg");
1752
+ if (!isDefaultTimestampParsing(driver.types)) throw OptionsError.timezoneConflict("timestamp parsing is customised process-wide (pg.types.setTypeParser).");
1753
+ const own = extra.types;
1754
+ if (own && !isDefaultTimestampParsing(own)) throw OptionsError.timezoneConflict("extra.types customises timestamp parsing.");
1755
+ const output = { ...options };
1756
+ let fromUrl;
1757
+ if (typeof options.url === "string") {
1758
+ const extracted = extractPostgresUrlOptions(options.url);
1759
+ output.url = extracted.url;
1760
+ fromUrl = extracted.options;
1761
+ }
1762
+ if (typeof extra.connectionString === "string") {
1763
+ const extracted = extractPostgresUrlOptions(extra.connectionString);
1764
+ extra.connectionString = extracted.url;
1765
+ fromUrl = extracted.options;
1766
+ }
1767
+ if (options.replication) {
1768
+ if ([options.replication.master, ...options.replication.slaves].some((node) => typeof node.url === "string" && typeof extractPostgresUrlOptions(node.url).options !== "undefined")) throw OptionsError.timezoneConflict("a replication node url carries startup options, which can not be combined with the pin.");
1769
+ }
1770
+ let source = "PGOPTIONS";
1771
+ let base = process.env.PGOPTIONS ?? "";
1772
+ if (typeof fromUrl === "string") {
1773
+ source = "the connection url";
1774
+ base = fromUrl;
1775
+ } else if (typeof extra.options === "string") {
1776
+ source = "extra.options";
1777
+ base = extra.options;
1778
+ }
1779
+ extra.options = pinTimezoneOption(base, source);
1780
+ extra.types = createPostgresUTCTypes(own ?? driver.types);
1781
+ extra.Client = createPostgresUTCClient(extra.Client ?? (isPostgresNativeInUse(options.nativeDriver, driver) ? driver.native.Client : driver.Client));
1782
+ output.extra = extra;
1783
+ return output;
1784
+ }
1785
+ //#endregion
1786
+ //#region src/data-source/options/timezone/module.ts
1787
+ /**
1788
+ * Pin a data source to a timezone (only UTC) on EVERY side of a zone-less
1789
+ * date column: the database session that stamps it (`now()`,
1790
+ * `CURRENT_TIMESTAMP`), and the driver that reads it and serializes Date
1791
+ * parameters into it. Left alone, the database stamps in its own session
1792
+ * zone while the driver reads and writes in the zone of the Node process,
1793
+ * which agree only while both clocks do.
1794
+ *
1795
+ * Each driver lives in its own file (`postgres.ts`, `mysql.ts`, `oracle.ts`);
1796
+ * every other driver needs nothing and is returned unchanged. A setting the
1797
+ * caller already made is kept when it agrees with the pin (a UTC `timezone`,
1798
+ * a UTC `TimeZone`) or can be built upon (a pg `Client`, pg `types` leaving
1799
+ * timestamps alone, an oracle `sessionCallback` function), and throws an
1800
+ * `OptionsError` when it contradicts it: half a pin shifts values instead of
1801
+ * fixing them. Applying it twice returns the pinned options unchanged.
1802
+ *
1803
+ * Only values written from then on are affected: rows a database stamped in
1804
+ * another zone before keep that wall clock.
1805
+ */
1806
+ function pinTimezone(options, timezone) {
1807
+ if (!isDataSourceTimezone(timezone)) throw OptionsError.timezoneUnsupported(timezone);
1808
+ switch (options.type) {
1809
+ case "mysql":
1810
+ case "mariadb": return applyMysqlTimezone(options);
1811
+ case "postgres": return applyPostgresTimezone(options);
1812
+ case "oracle": return applyOracleTimezone(options);
1813
+ default: return options;
1814
+ }
1815
+ }
1816
+ //#endregion
1096
1817
  //#region src/data-source/options/utils/env.ts
1097
1818
  function hasEnvDataSourceOptions() {
1098
1819
  return !!useEnv("type");
1099
1820
  }
1100
- /* istanbul ignore next */
1821
+ /**
1822
+ * Apply `DB_PIN_TIMEZONE`, when set, once the options are complete: applied
1823
+ * before a merge, a deep merge would reach into the wrapped driver module.
1824
+ * Options pinned in code are re-checked after the merge as well, so an
1825
+ * environment value undoing part of the pin (a `DB_DRIVER_EXTRA`
1826
+ * timezone, say)
1827
+ * fails instead of leaving it half applied.
1828
+ */
1829
+ function applyEnvTimezone(options) {
1830
+ const timezone = useEnv("pinTimezone");
1831
+ if (typeof timezone === "undefined" || timezone === "") return hasInstalledTimezone(options) ? pinTimezone(options, "UTC") : options;
1832
+ if (!isDataSourceTimezone(timezone)) throw OptionsError.timezoneUnsupported(timezone);
1833
+ return pinTimezone(options, "UTC");
1834
+ }
1101
1835
  function readDataSourceOptionsFromEnv() {
1836
+ const options = readRawDataSourceOptionsFromEnv();
1837
+ if (!options) return;
1838
+ return applyEnvTimezone(options);
1839
+ }
1840
+ /* istanbul ignore next */
1841
+ function readRawDataSourceOptionsFromEnv() {
1102
1842
  if (!hasEnvDataSourceOptions()) return;
1103
1843
  const base = {
1104
1844
  type: useEnv("type"),
@@ -1167,9 +1907,9 @@ function readDataSourceOptionsFromEnv() {
1167
1907
  };
1168
1908
  }
1169
1909
  function mergeDataSourceOptionsWithEnv(options) {
1170
- const env = readDataSourceOptionsFromEnv();
1171
- if (!env) return options;
1172
- return mergeDataSourceOptions(env, options);
1910
+ const env = readRawDataSourceOptionsFromEnv();
1911
+ if (!env) return applyEnvTimezone(options);
1912
+ return applyEnvTimezone(mergeDataSourceOptions(env, options));
1173
1913
  }
1174
1914
  //#endregion
1175
1915
  //#region src/data-source/options/module.ts
@@ -1232,7 +1972,8 @@ async function useDataSource(alias) {
1232
1972
  if (!existing.isInitialized) await existing.initialize();
1233
1973
  return existing;
1234
1974
  }
1235
- const dataSource = new DataSource(await useDataSourceOptions(key));
1975
+ const options = await useDataSourceOptions(key);
1976
+ const dataSource = new DataSource(options);
1236
1977
  await dataSource.initialize();
1237
1978
  return dataSource;
1238
1979
  });
@@ -1814,7 +2555,7 @@ function matchesColumnType(column, input, describer) {
1814
2555
  const { normalizeType, describeType } = describer;
1815
2556
  if (normalizeType(input.type).toLowerCase() !== normalizeType(column.type).toLowerCase()) return false;
1816
2557
  if (typeof input.length !== "undefined" && describeType(input.type, input.length).toLowerCase() !== describeType(column.type, column.length).toLowerCase()) return false;
1817
- return !(typeof input.nullable === "boolean" && input.nullable !== column.isNullable);
2558
+ return typeof input.nullable !== "boolean" || input.nullable === column.isNullable;
1818
2559
  }
1819
2560
  //#endregion
1820
2561
  //#region src/database/schema/alter/checks.ts
@@ -2434,73 +3175,44 @@ async function dropDatabase(input = {}) {
2434
3175
  }
2435
3176
  //#endregion
2436
3177
  //#region src/database/utils/migration.ts
3178
+ /**
3179
+ * typeorm keeps the statement escaping, the query-parameter formatting and both file
3180
+ * templates as protected statics on its migration:generate command. Subclassing is the
3181
+ * only way to reach them, and reusing them keeps the generated file byte-identical to
3182
+ * the output of `typeorm migration:generate`.
3183
+ */
2437
3184
  var GenerateCommand = class extends MigrationGenerateCommand {
2438
- static prettify(query) {
2439
- return this.prettifyQuery(query);
3185
+ static buildStatement(query, parameters, prettify) {
3186
+ const statement = prettify ? this.prettifyQuery(query) : query;
3187
+ return `await queryRunner.query(\`${this.escapeTemplateLiteral(statement)}\`${this.queryParams(parameters)});`;
3188
+ }
3189
+ static buildContent(context, up, down) {
3190
+ const upStatements = up.map((statement) => ` ${statement}`);
3191
+ const downStatements = down.map((statement) => ` ${statement}`);
3192
+ if (context.language === "js") return this.getJavascriptTemplate(context.name, context.timestamp, upStatements, downStatements, context.esm);
3193
+ return this.getTemplate(context.name, context.timestamp, upStatements, downStatements);
2440
3194
  }
2441
3195
  };
2442
- function queryParams(parameters) {
2443
- if (!parameters || !parameters.length) return "";
2444
- return `, ${JSON.stringify(parameters)}`;
2445
- }
2446
- function buildTemplate(name, timestamp, upStatements, downStatements) {
2447
- const migrationName = `${pascalCase(name)}${timestamp}`;
2448
- const up = upStatements.map((statement) => ` ${statement}`);
2449
- const down = downStatements.map((statement) => ` ${statement}`);
2450
- return `import type { MigrationInterface, QueryRunner } from 'typeorm';
2451
-
2452
- export class ${migrationName} implements MigrationInterface {
2453
- name = '${migrationName}';
2454
-
2455
- public async up(queryRunner: QueryRunner): Promise<void> {
2456
- ${up.join(`
2457
- `)}
2458
- }
2459
- public async down(queryRunner: QueryRunner): Promise<void> {
2460
- ${down.join(`
2461
- `)}
2462
- }
2463
- }
2464
- `;
2465
- }
2466
3196
  async function generateMigration(context) {
2467
- context.name = context.name || "Default";
2468
- const timestamp = context.timestamp || (/* @__PURE__ */ new Date()).getTime();
2469
- const fileName = `${timestamp}-${context.name}.ts`;
2470
- const { dataSource } = context;
2471
- const up = [];
2472
- const down = [];
2473
- const sqlInMemory = await dataSource.driver.createSchemaBuilder().log();
2474
- if (context.prettify) {
2475
- sqlInMemory.upQueries.forEach((upQuery) => {
2476
- upQuery.query = GenerateCommand.prettify(upQuery.query);
2477
- });
2478
- sqlInMemory.downQueries.forEach((downQuery) => {
2479
- downQuery.query = GenerateCommand.prettify(downQuery.query);
2480
- });
2481
- }
2482
- sqlInMemory.upQueries.forEach((upQuery) => {
2483
- up.push(`await queryRunner.query(\`${upQuery.query.replace(/`/g, "\\`")}\`${queryParams(upQuery.parameters)});`);
2484
- });
2485
- sqlInMemory.downQueries.forEach((downQuery) => {
2486
- down.push(`await queryRunner.query(\`${downQuery.query.replace(/`/g, "\\`")}\`${queryParams(downQuery.parameters)});`);
2487
- });
3197
+ const name = context.name || "Default";
3198
+ const timestamp = context.timestamp ?? Date.now();
3199
+ const language = context.language || "ts";
3200
+ const sqlInMemory = await context.dataSource.driver.createSchemaBuilder().log();
3201
+ const up = sqlInMemory.upQueries.map((query) => GenerateCommand.buildStatement(query.query, query.parameters, context.prettify));
3202
+ const down = sqlInMemory.downQueries.map((query) => GenerateCommand.buildStatement(query.query, query.parameters, context.prettify)).reverse();
2488
3203
  if (up.length === 0 && down.length === 0) return {
2489
3204
  up,
2490
3205
  down
2491
3206
  };
2492
- const content = buildTemplate(context.name, timestamp, up, down.reverse());
3207
+ const content = GenerateCommand.buildContent({
3208
+ name,
3209
+ timestamp,
3210
+ language,
3211
+ esm: context.esm || false
3212
+ }, up, down);
2493
3213
  if (!context.preview) {
2494
- let directoryPath;
2495
- if (context.directoryPath) if (!path.isAbsolute(context.directoryPath)) directoryPath = path.join(process$1.cwd(), context.directoryPath);
2496
- else directoryPath = context.directoryPath;
2497
- else directoryPath = path.join(process$1.cwd(), "migrations");
2498
- try {
2499
- await fs.promises.access(directoryPath, fs.constants.R_OK | fs.constants.W_OK);
2500
- } catch {
2501
- await fs.promises.mkdir(directoryPath, { recursive: true });
2502
- }
2503
- await fs.promises.writeFile(path.join(directoryPath, fileName), content, { encoding: "utf-8" });
3214
+ const directoryPath = resolveFilePath(context.directoryPath || "migrations");
3215
+ await CommandUtils.createFile(path.join(directoryPath, `${timestamp}-${name}.${language}`), content);
2504
3216
  }
2505
3217
  return {
2506
3218
  up,
@@ -2612,6 +3324,95 @@ async function dropSQLiteDatabase(input = {}) {
2612
3324
  return executeDatabaseDrop("better-sqlite3", await buildDatabaseDropContext(input));
2613
3325
  }
2614
3326
  //#endregion
3327
+ //#region src/database/lock/module.ts
3328
+ const MYSQL_LOCK_STATEMENTS = {
3329
+ acquire: "SELECT GET_LOCK(SHA2(CONCAT(COALESCE(DATABASE(), ''), ':', ?), 256), 0) AS `acquired`",
3330
+ release: "SELECT RELEASE_LOCK(SHA2(CONCAT(COALESCE(DATABASE(), ''), ':', ?), 256)) AS `released`"
3331
+ };
3332
+ /**
3333
+ * cockroachdb accepts the postgres advisory lock functions, but they do not
3334
+ * lock anything, so it is deliberately missing.
3335
+ */
3336
+ const statements = /* @__PURE__ */ new Map([
3337
+ ["postgres", {
3338
+ acquire: "SELECT pg_try_advisory_lock(hashtextextended($1, 0)) AS \"acquired\"",
3339
+ release: "SELECT pg_advisory_unlock(hashtextextended($1, 0)) AS \"released\""
3340
+ }],
3341
+ ["mysql", MYSQL_LOCK_STATEMENTS],
3342
+ ["mariadb", MYSQL_LOCK_STATEMENTS]
3343
+ ]);
3344
+ const POLL_INTERVAL = 100;
3345
+ /**
3346
+ * pg answers with a boolean, mysql with the string '1' / '0', mariadb with
3347
+ * the number 1 / 0 (either may be NULL). A truthiness check would read '0'
3348
+ * as acquired.
3349
+ */
3350
+ function isDatabaseLockAcquired(rows) {
3351
+ if (!Array.isArray(rows) || rows.length === 0) return false;
3352
+ const value = Object.values(rows[0] ?? {})[0];
3353
+ return value === true || value === 1 || value === "1";
3354
+ }
3355
+ /**
3356
+ * Run a callback while holding a named advisory lock, so that only one
3357
+ * session at a time (across processes) runs it. The lock is released
3358
+ * afterwards, also when the callback throws. A transaction the callback
3359
+ * leaves open on the query runner is rolled back first.
3360
+ *
3361
+ * The lock is session scoped: the query runner must stay on one connection
3362
+ * for the whole call, which a typeorm QueryRunner does until it is released.
3363
+ * Nested calls with the same name on the same query runner do not block.
3364
+ *
3365
+ * Supported for postgres, mysql and mariadb.
3366
+ *
3367
+ * @throws DriverError on any other driver, unless `strict` is false.
3368
+ * @throws DatabaseLockError if the lock can not be acquired within `timeout`,
3369
+ * the query runner is in a transaction, or the callback left one open.
3370
+ */
3371
+ async function withDatabaseLock(queryRunner, name, fn, options = {}) {
3372
+ const { type } = queryRunner.dataSource.options;
3373
+ const statement = statements.get(type);
3374
+ if (!statement) {
3375
+ if (options.strict === false) return fn();
3376
+ throw DriverError.lockNotSupported(type);
3377
+ }
3378
+ if (queryRunner.isTransactionActive) throw DatabaseLockError.transactionActive(name);
3379
+ const timeout = options.timeout ?? Infinity;
3380
+ const deadline = Date.now() + timeout;
3381
+ while (!isDatabaseLockAcquired(await queryRunner.query(statement.acquire, [name]))) {
3382
+ const remaining = deadline - Date.now();
3383
+ if (!(remaining > 0)) throw DatabaseLockError.timeout(name, timeout);
3384
+ await new Promise((resolve) => {
3385
+ setTimeout(resolve, Math.min(POLL_INTERVAL, remaining));
3386
+ });
3387
+ }
3388
+ let output;
3389
+ try {
3390
+ output = await fn();
3391
+ } catch (e) {
3392
+ await rollbackTransaction(queryRunner);
3393
+ await queryRunner.query(statement.release, [name]).catch(() => void 0);
3394
+ throw e;
3395
+ }
3396
+ const transactionLeftOpen = await rollbackTransaction(queryRunner);
3397
+ await queryRunner.query(statement.release, [name]);
3398
+ if (transactionLeftOpen) throw DatabaseLockError.transactionLeftOpen(name);
3399
+ return output;
3400
+ }
3401
+ /**
3402
+ * A transaction the callback left open (or postgres aborted) would make
3403
+ * postgres refuse the release, and the lock would go back to the pool held.
3404
+ *
3405
+ * Nested transactions are savepoints in typeorm: one rollback only unwinds
3406
+ * the innermost, so repeat until the outer one is gone (or a rollback fails).
3407
+ */
3408
+ async function rollbackTransaction(queryRunner) {
3409
+ if (!queryRunner.isTransactionActive) return false;
3410
+ try {
3411
+ while (queryRunner.isTransactionActive) await queryRunner.rollbackTransaction();
3412
+ } catch {}
3413
+ return true;
3414
+ }
3415
+ //#endregion
2615
3416
  //#region src/helpers/entity/name.ts
2616
3417
  function getEntityName(entity) {
2617
3418
  if (typeof entity === "string") return entity;
@@ -2889,7 +3690,10 @@ var SeederEntity = class {
2889
3690
  this.id = ctx.id;
2890
3691
  this.timestamp = ctx.timestamp;
2891
3692
  this.name = ctx.name;
2892
- if (ctx.constructor) this.instance = new ctx.constructor();
3693
+ if (ctx.constructor) {
3694
+ const SeederClass = ctx.constructor;
3695
+ this.instance = new SeederClass();
3696
+ }
2893
3697
  this.fileName = ctx.fileName;
2894
3698
  this.filePath = ctx.filePath;
2895
3699
  }
@@ -3282,6 +4086,6 @@ async function runSeeders(dataSource, options) {
3282
4086
  return new SeederExecutor(dataSource).execute(options);
3283
4087
  }
3284
4088
  //#endregion
3285
- export { CockroachDBDialect, CodeTransformation, DriverError, EntityMetadataError, EntityRelationLookupError, EnvironmentName, EnvironmentVariableName, MYSQL_FOREIGN_KEY_CHECKS_OFF, MYSQL_FOREIGN_KEY_CHECKS_ON, MYSQL_FOREIGN_KEY_CHECKS_SELECT, MongoDBDialect, MsSQLDialect, MySQLDialect, OptionsError, OracleDialect, PathResolver, PathResolverMode, PostgresDialect, SQLiteDialect, SchemaAlterationError, SchemaDriftError, SeederEntity, SeederExecutor, SeederFactory, SeederFactoryManager, TypeormExtensionError, adjustFilePath, adjustFilePaths, assertSchemaMatchesMetadata, buildCockroachDBCreateDatabaseQuery, buildCockroachDBDropDatabaseQuery, buildConnectionParams, buildDataSourceOptions, buildDatabaseCreateContext, buildDatabaseDropContext, buildMongoDBConnectionUri, buildMongoDBDropDatabaseCommand, buildMsSQLCreateDatabaseQuery, buildMsSQLDropDatabaseQuery, buildMySQLCreateDatabaseQuery, buildMySQLDropDatabaseQuery, buildOracleConnectString, buildOracleCreateDatabaseQuery, buildPostgresCreateDatabaseQuery, buildPostgresCreateSchemaQuery, buildPostgresDatabaseExistsQuery, buildPostgresDropDatabaseQuery, buildSeederFileName, buildSeederFileTemplate, canReplaceWindowsSeparator, changeColumnType, checkDatabase, createCockroachDBDatabase, createDatabase, createMongoDBDatabase, createMsSQLDatabase, createMySQLDatabase, createOracleDatabase, createPathResolver, createPostgresDatabase, createSQLiteDatabase, deriveMySQLCharacterSet, detectCodeTransformation, dropCockroachDBDatabase, dropDatabase, dropMongoDBDatabase, dropMsSQLDatabase, dropMySQLDatabase, dropOracleDatabase, dropPostgresDatabase, dropSQLiteDatabase, existsQuery, extendObject, findDataSource, generateMigration, getEntityMetadata, getEntityName, getEntityPropertyNames, getSchemaDrift, hasDataSource, hasDataSourceOptions, hasEnvDataSourceOptions, hasOwnProperty, hasResultRows, hasStringProperty, hasTrailingSlash, isCodeTransformation, isDirectory, isEntityUnique, isObject, isPromise, mergeDataSourceOptions, mergeDataSourceOptionsWithEnv, parseFilePath, pickRecord, prepareSeederFactories, prepareSeederSeeds, readDataSourceOptionsFromEnv, readTSConfig, renameForeignKey, renameIndex, replaceWindowSeparator, resetEnv, resetSeederFactoryManager, resolveFilePath, resolveFilePaths, resolveFilePatterns, resolveSQLiteDatabaseDirectory, resolveSQLiteDatabasePath, resolveSeederConfig, runSeeder, runSeeders, safeReplaceWindowsSeparator, setDataSource, setDataSourceOptions, setSeederFactory, synchronizeDatabaseSchema, transformFilePath, unsetDataSource, useDataSource, useDataSourceOptions, useEnv, useSeederFactory, useSeederFactoryManager, validateEntityJoinColumns, withForeignKeyChecksDisabled, withoutTrailingSlash };
4089
+ export { CockroachDBDialect, CodeTransformation, DatabaseLockError, DriverError, EntityMetadataError, EntityRelationLookupError, EnvironmentName, EnvironmentVariableName, MYSQL_FOREIGN_KEY_CHECKS_OFF, MYSQL_FOREIGN_KEY_CHECKS_ON, MYSQL_FOREIGN_KEY_CHECKS_SELECT, MongoDBDialect, MsSQLDialect, MySQLDialect, OptionsError, OracleDialect, PathResolver, PathResolverMode, PostgresDialect, SQLiteDialect, SchemaAlterationError, SchemaDriftError, SeederEntity, SeederExecutor, SeederFactory, SeederFactoryManager, TypeormExtensionError, adjustFilePath, adjustFilePaths, assertSchemaMatchesMetadata, buildCockroachDBCreateDatabaseQuery, buildCockroachDBDropDatabaseQuery, buildConnectionParams, buildDataSourceOptions, buildDatabaseCreateContext, buildDatabaseDropContext, buildMongoDBConnectionUri, buildMongoDBDropDatabaseCommand, buildMsSQLCreateDatabaseQuery, buildMsSQLDropDatabaseQuery, buildMySQLCreateDatabaseQuery, buildMySQLDropDatabaseQuery, buildOracleConnectString, buildOracleCreateDatabaseQuery, buildPostgresCreateDatabaseQuery, buildPostgresCreateSchemaQuery, buildPostgresDatabaseExistsQuery, buildPostgresDropDatabaseQuery, buildSeederFileName, buildSeederFileTemplate, canReplaceWindowsSeparator, changeColumnType, checkDatabase, createCockroachDBDatabase, createDatabase, createMongoDBDatabase, createMsSQLDatabase, createMySQLDatabase, createOracleDatabase, createPathResolver, createPostgresDatabase, createSQLiteDatabase, deriveMySQLCharacterSet, detectCodeTransformation, dropCockroachDBDatabase, dropDatabase, dropMongoDBDatabase, dropMsSQLDatabase, dropMySQLDatabase, dropOracleDatabase, dropPostgresDatabase, dropSQLiteDatabase, existsQuery, extendObject, findDataSource, generateMigration, getEntityMetadata, getEntityName, getEntityPropertyNames, getSchemaDrift, hasDataSource, hasDataSourceOptions, hasEnvDataSourceOptions, hasOwnProperty, hasResultRows, hasStringProperty, hasTrailingSlash, isCodeTransformation, isDirectory, isEntityUnique, isObject, isPromise, mergeDataSourceOptions, mergeDataSourceOptionsWithEnv, parseFilePath, pickRecord, pinTimezone, prepareSeederFactories, prepareSeederSeeds, readDataSourceOptionsFromEnv, readTSConfig, renameForeignKey, renameIndex, replaceWindowSeparator, resetEnv, resetSeederFactoryManager, resolveFilePath, resolveFilePaths, resolveFilePatterns, resolveSQLiteDatabaseDirectory, resolveSQLiteDatabasePath, resolveSeederConfig, runSeeder, runSeeders, safeReplaceWindowsSeparator, setDataSource, setDataSourceOptions, setSeederFactory, synchronizeDatabaseSchema, transformFilePath, unsetDataSource, useDataSource, useDataSourceOptions, useEnv, useSeederFactory, useSeederFactoryManager, validateEntityJoinColumns, withDatabaseLock, withForeignKeyChecksDisabled, withoutTrailingSlash };
3286
4090
 
3287
4091
  //# sourceMappingURL=index.mjs.map