@noormdev/sdk 1.0.0-alpha.17 → 1.0.0-alpha.19
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/{chunk-4HW4CS2C.js → chunk-BP4PMFJF.js} +2 -2
- package/dist/chunk-BP4PMFJF.js.map +1 -0
- package/dist/{chunk-LJDHQEWW.js → chunk-HB3JXUJV.js} +3 -3
- package/dist/{chunk-LJDHQEWW.js.map → chunk-HB3JXUJV.js.map} +1 -1
- package/dist/{chunk-BTXUEWYQ.js → chunk-UZARPCOH.js} +5 -7
- package/dist/chunk-UZARPCOH.js.map +1 -0
- package/dist/engine-3J25GVCU.js +4 -0
- package/dist/{engine-5K7FA66A.js.map → engine-3J25GVCU.js.map} +1 -1
- package/dist/index.d.ts +2500 -2409
- package/dist/index.js +875 -439
- package/dist/index.js.map +1 -1
- package/dist/{mssql-7VB7EHRX.js → mssql-65M7MY4Q.js} +43 -5
- package/dist/mssql-65M7MY4Q.js.map +1 -0
- package/dist/storage-WTYJE355.js +4 -0
- package/dist/{storage-NDKBEHIZ.js.map → storage-WTYJE355.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-4HW4CS2C.js.map +0 -1
- package/dist/chunk-BTXUEWYQ.js.map +0 -1
- package/dist/engine-5K7FA66A.js +0 -4
- package/dist/mssql-7VB7EHRX.js.map +0 -1
- package/dist/storage-NDKBEHIZ.js +0 -4
package/dist/index.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
import { processFile, DtReader, isTemplate, FORMAT_VERSION, DT_EXTENSIONS, encryptWithPassphrase, ENCODED_TYPES
|
|
2
|
-
import { initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey } from './chunk-
|
|
3
|
-
import { observer } from './chunk-
|
|
1
|
+
import { processFile, DtReader, isTemplate, FORMAT_VERSION, DT_EXTENSIONS, encryptWithPassphrase, ENCODED_TYPES } from './chunk-UZARPCOH.js';
|
|
2
|
+
import { initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey } from './chunk-HB3JXUJV.js';
|
|
3
|
+
import { observer } from './chunk-BP4PMFJF.js';
|
|
4
4
|
import { constants, existsSync, readFileSync, mkdirSync, writeFileSync, createWriteStream } from 'fs';
|
|
5
|
-
import path, { join, dirname } from 'path';
|
|
5
|
+
import path, { join, dirname, resolve } from 'path';
|
|
6
6
|
import { makeNestedConfig, gigabytes, attempt, retry, clone, merge, attemptSync } from '@logosdx/utils';
|
|
7
|
-
import { homedir, userInfo } from 'os';
|
|
7
|
+
import { homedir, availableParallelism, userInfo } from 'os';
|
|
8
8
|
import { stat, mkdir, writeFile, unlink, rename, rm, readdir, readFile, access } from 'fs/promises';
|
|
9
|
-
import { createHash, createDecipheriv, randomBytes, createCipheriv, hkdfSync } from 'crypto';
|
|
9
|
+
import { createHash, createDecipheriv, randomBytes, createCipheriv, randomUUID, hkdfSync } from 'crypto';
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
import { parse, stringify } from 'yaml';
|
|
12
12
|
import { execSync } from 'child_process';
|
|
13
13
|
import { sql } from 'kysely';
|
|
14
14
|
import 'dayjs';
|
|
15
15
|
import ansis from 'ansis';
|
|
16
|
-
import {
|
|
16
|
+
import { Worker, parentPort, workerData } from 'worker_threads';
|
|
17
|
+
import { ObserverRelay } from '@logosdx/observer';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
import { createGzip } from 'zlib';
|
|
17
20
|
import { pipeline } from 'stream/promises';
|
|
18
21
|
import { PassThrough } from 'stream';
|
|
19
22
|
import JSON5 from 'json5';
|
|
@@ -1672,6 +1675,7 @@ async function loadExistingIdentity() {
|
|
|
1672
1675
|
var ConnectionManager = class {
|
|
1673
1676
|
#cached = /* @__PURE__ */ new Map();
|
|
1674
1677
|
#tracked = /* @__PURE__ */ new Map();
|
|
1678
|
+
#bridges = /* @__PURE__ */ new Map();
|
|
1675
1679
|
#nextId = 1;
|
|
1676
1680
|
#shuttingDown = false;
|
|
1677
1681
|
#unsubscribe = null;
|
|
@@ -1753,11 +1757,17 @@ var ConnectionManager = class {
|
|
|
1753
1757
|
}
|
|
1754
1758
|
const trackedEntries = Array.from(this.#tracked.entries());
|
|
1755
1759
|
for (const [id, entry] of trackedEntries) {
|
|
1760
|
+
let timer;
|
|
1756
1761
|
const destroyWithTimeout = Promise.race([
|
|
1757
|
-
entry.conn.destroy()
|
|
1758
|
-
|
|
1762
|
+
entry.conn.destroy().then(() => {
|
|
1763
|
+
clearTimeout(timer);
|
|
1764
|
+
}),
|
|
1765
|
+
new Promise((resolve2) => {
|
|
1766
|
+
timer = setTimeout(resolve2, CLOSE_TIMEOUT);
|
|
1767
|
+
})
|
|
1759
1768
|
]);
|
|
1760
1769
|
const [, err] = await attempt(() => destroyWithTimeout);
|
|
1770
|
+
clearTimeout(timer);
|
|
1761
1771
|
this.#tracked.delete(id);
|
|
1762
1772
|
if (err) {
|
|
1763
1773
|
observer.emit("error", { source: "connection", error: err });
|
|
@@ -1765,6 +1775,28 @@ var ConnectionManager = class {
|
|
|
1765
1775
|
observer.emit("connection:close", { configName: entry.configName });
|
|
1766
1776
|
}
|
|
1767
1777
|
}
|
|
1778
|
+
const bridgeNames = Array.from(this.#bridges.keys());
|
|
1779
|
+
for (const name of bridgeNames) {
|
|
1780
|
+
await this.closeBridge(name);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Track a WorkerBridge connection for a config.
|
|
1785
|
+
*
|
|
1786
|
+
* The bridge is shut down alongside regular connections on app:shutdown.
|
|
1787
|
+
*/
|
|
1788
|
+
trackBridge(configName, bridge) {
|
|
1789
|
+
this.#bridges.set(configName, bridge);
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Close a specific bridge by config name.
|
|
1793
|
+
*/
|
|
1794
|
+
async closeBridge(configName) {
|
|
1795
|
+
const bridge = this.#bridges.get(configName);
|
|
1796
|
+
if (bridge) {
|
|
1797
|
+
await bridge.shutdown();
|
|
1798
|
+
this.#bridges.delete(configName);
|
|
1799
|
+
}
|
|
1768
1800
|
}
|
|
1769
1801
|
/**
|
|
1770
1802
|
* Check if a cached connection exists for a config name.
|
|
@@ -1776,7 +1808,7 @@ var ConnectionManager = class {
|
|
|
1776
1808
|
* Get total number of active connections.
|
|
1777
1809
|
*/
|
|
1778
1810
|
get size() {
|
|
1779
|
-
return this.#cached.size + this.#tracked.size;
|
|
1811
|
+
return this.#cached.size + this.#tracked.size + this.#bridges.size;
|
|
1780
1812
|
}
|
|
1781
1813
|
/**
|
|
1782
1814
|
* Check if shutdown is in progress.
|
|
@@ -1815,7 +1847,7 @@ async function getDialectFactory(dialect) {
|
|
|
1815
1847
|
case "mysql":
|
|
1816
1848
|
return (await import('./mysql-C2DR4TF7.js')).createMysqlConnection;
|
|
1817
1849
|
case "mssql":
|
|
1818
|
-
return (await import('./mssql-
|
|
1850
|
+
return (await import('./mssql-65M7MY4Q.js')).createMssqlConnection;
|
|
1819
1851
|
default:
|
|
1820
1852
|
throw new Error(`Unsupported dialect: ${dialect}`);
|
|
1821
1853
|
}
|
|
@@ -1889,7 +1921,6 @@ async function createConnection(config, configName = "__default__") {
|
|
|
1889
1921
|
destroy: wrappedDestroy
|
|
1890
1922
|
};
|
|
1891
1923
|
observer.emit("connection:open", { configName, dialect: config.dialect });
|
|
1892
|
-
await waitForIdentityToLoad(trackedConn.db, config.dialect);
|
|
1893
1924
|
return trackedConn;
|
|
1894
1925
|
}
|
|
1895
1926
|
var SYSTEM_DATABASES = {
|
|
@@ -2035,79 +2066,170 @@ var INDEXES = [
|
|
|
2035
2066
|
{ name: "idx_change_name_config", table: "change", columns: ["name", "config_name"] },
|
|
2036
2067
|
{ name: "idx_vault_secret_key", table: "vault", columns: ["secret_key"] }
|
|
2037
2068
|
];
|
|
2069
|
+
var PREFIXED_TABLES = {
|
|
2070
|
+
executions: "__noorm_executions__",
|
|
2071
|
+
change: "__noorm_change__",
|
|
2072
|
+
vault: "__noorm_vault__"
|
|
2073
|
+
};
|
|
2038
2074
|
var v2 = {
|
|
2039
2075
|
version: 2,
|
|
2040
2076
|
description: "Move tracking tables to noorm schema",
|
|
2041
2077
|
async up(db, dialect) {
|
|
2042
2078
|
if (dialect === "sqlite" || dialect === "mysql") return;
|
|
2043
2079
|
await db.transaction().execute(async (trx) => {
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2080
|
+
const src = dialect === "postgres" ? "public" : "dbo";
|
|
2081
|
+
async function tableExistsIn(schema, table) {
|
|
2082
|
+
if (dialect === "postgres") {
|
|
2083
|
+
const { rows: rows2 } = await sql`
|
|
2084
|
+
SELECT 1 AS n FROM information_schema.tables
|
|
2085
|
+
WHERE table_schema = ${schema} AND table_name = ${table}
|
|
2086
|
+
`.execute(trx);
|
|
2087
|
+
return rows2.length > 0;
|
|
2088
|
+
}
|
|
2050
2089
|
const { rows } = await sql`
|
|
2051
|
-
SELECT
|
|
2052
|
-
|
|
2053
|
-
WHERE
|
|
2054
|
-
AND constraint_type = 'FOREIGN KEY'
|
|
2090
|
+
SELECT 1 AS n FROM sys.tables t
|
|
2091
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
2092
|
+
WHERE t.name = ${table} AND s.name = ${schema}
|
|
2055
2093
|
`.execute(trx);
|
|
2056
|
-
|
|
2057
|
-
|
|
2094
|
+
return rows.length > 0;
|
|
2095
|
+
}
|
|
2096
|
+
async function getForeignKeys(schema, table) {
|
|
2097
|
+
if (!await tableExistsIn(schema, table)) return [];
|
|
2098
|
+
if (dialect === "postgres") {
|
|
2099
|
+
const { rows: rows2 } = await sql`
|
|
2100
|
+
SELECT tc.constraint_name AS name
|
|
2101
|
+
FROM information_schema.table_constraints tc
|
|
2102
|
+
WHERE tc.table_schema = ${schema}
|
|
2103
|
+
AND tc.table_name = ${table}
|
|
2104
|
+
AND tc.constraint_type = 'FOREIGN KEY'
|
|
2105
|
+
`.execute(trx);
|
|
2106
|
+
return rows2.map((r) => r.name);
|
|
2058
2107
|
}
|
|
2059
|
-
} else {
|
|
2060
2108
|
const { rows } = await sql`
|
|
2061
|
-
SELECT fk.name
|
|
2109
|
+
SELECT fk.name
|
|
2062
2110
|
FROM sys.foreign_keys fk
|
|
2063
2111
|
JOIN sys.tables t ON fk.parent_object_id = t.object_id
|
|
2064
|
-
|
|
2112
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
2113
|
+
WHERE t.name = ${table} AND s.name = ${schema}
|
|
2065
2114
|
`.execute(trx);
|
|
2066
|
-
|
|
2067
|
-
|
|
2115
|
+
return rows.map((r) => r.name);
|
|
2116
|
+
}
|
|
2117
|
+
async function indexExistsIn(schema, idxName) {
|
|
2118
|
+
if (dialect === "postgres") {
|
|
2119
|
+
const { rows: rows2 } = await sql`
|
|
2120
|
+
SELECT 1 AS n FROM pg_indexes
|
|
2121
|
+
WHERE schemaname = ${schema} AND indexname = ${idxName}
|
|
2122
|
+
`.execute(trx);
|
|
2123
|
+
return rows2.length > 0;
|
|
2068
2124
|
}
|
|
2125
|
+
const { rows } = await sql`
|
|
2126
|
+
SELECT 1 AS n FROM sys.indexes i
|
|
2127
|
+
JOIN sys.tables t ON i.object_id = t.object_id
|
|
2128
|
+
JOIN sys.schemas s ON t.schema_id = s.schema_id
|
|
2129
|
+
WHERE i.name = ${idxName} AND s.name = ${schema}
|
|
2130
|
+
`.execute(trx);
|
|
2131
|
+
return rows.length > 0;
|
|
2069
2132
|
}
|
|
2070
2133
|
if (dialect === "postgres") {
|
|
2071
|
-
|
|
2072
|
-
await sql`DROP INDEX IF EXISTS ${sql.ref(idx.name)}`.execute(trx);
|
|
2073
|
-
}
|
|
2134
|
+
await sql`CREATE SCHEMA IF NOT EXISTS noorm`.execute(trx);
|
|
2074
2135
|
} else {
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2136
|
+
await sql`IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = 'noorm') EXEC('CREATE SCHEMA noorm')`.execute(trx);
|
|
2137
|
+
}
|
|
2138
|
+
const fkLocations = [
|
|
2139
|
+
{ schema: src, table: "__noorm_executions__" },
|
|
2140
|
+
{ schema: "noorm", table: "__noorm_executions__" },
|
|
2141
|
+
{ schema: "noorm", table: "executions" }
|
|
2142
|
+
];
|
|
2143
|
+
for (const loc of fkLocations) {
|
|
2144
|
+
const fks = await getForeignKeys(loc.schema, loc.table);
|
|
2145
|
+
for (const fkName of fks) {
|
|
2146
|
+
await sql`ALTER TABLE ${sql.table(`${loc.schema}.${loc.table}`)} DROP CONSTRAINT ${sql.ref(fkName)}`.execute(trx);
|
|
2086
2147
|
}
|
|
2087
2148
|
}
|
|
2088
|
-
for (const
|
|
2089
|
-
if (
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2149
|
+
for (const idx of INDEXES) {
|
|
2150
|
+
if (await indexExistsIn(src, idx.name)) {
|
|
2151
|
+
if (dialect === "postgres") {
|
|
2152
|
+
await sql`DROP INDEX IF EXISTS ${sql.ref(idx.name)}`.execute(trx);
|
|
2153
|
+
} else {
|
|
2154
|
+
const tableName = PREFIXED_TABLES[idx.table];
|
|
2155
|
+
await sql`
|
|
2156
|
+
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = ${idx.name})
|
|
2157
|
+
DROP INDEX ${sql.ref(idx.name)} ON ${sql.table(tableName)}
|
|
2158
|
+
`.execute(trx);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
if (await indexExistsIn("noorm", idx.name)) {
|
|
2162
|
+
if (dialect === "postgres") {
|
|
2163
|
+
await sql`DROP INDEX IF EXISTS ${sql.ref(`noorm.${idx.name}`)}`.execute(trx);
|
|
2164
|
+
} else {
|
|
2165
|
+
const oldName = PREFIXED_TABLES[idx.table];
|
|
2166
|
+
const onOldName = await tableExistsIn("noorm", oldName);
|
|
2167
|
+
const tbl = onOldName ? `noorm.${oldName}` : `noorm.${idx.table}`;
|
|
2168
|
+
await sql`
|
|
2169
|
+
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = ${idx.name})
|
|
2170
|
+
DROP INDEX ${sql.ref(idx.name)} ON ${sql.table(tbl)}
|
|
2171
|
+
`.execute(trx);
|
|
2172
|
+
}
|
|
2093
2173
|
}
|
|
2094
2174
|
}
|
|
2095
2175
|
for (const t of TABLE_MAP) {
|
|
2176
|
+
const inNoormNew = await tableExistsIn("noorm", t.new);
|
|
2177
|
+
const inNoormOld = await tableExistsIn("noorm", t.old);
|
|
2178
|
+
const inSrc = await tableExistsIn(src, t.old);
|
|
2179
|
+
if (inNoormNew) {
|
|
2180
|
+
if (inSrc) {
|
|
2181
|
+
if (dialect === "postgres") {
|
|
2182
|
+
await sql`DROP TABLE ${sql.table(`${src}.${t.old}`)} CASCADE`.execute(trx);
|
|
2183
|
+
} else {
|
|
2184
|
+
await sql`DROP TABLE ${sql.table(`${src}.${t.old}`)}`.execute(trx);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
if (inNoormOld) {
|
|
2188
|
+
if (dialect === "postgres") {
|
|
2189
|
+
await sql`DROP TABLE ${sql.table(`noorm.${t.old}`)} CASCADE`.execute(trx);
|
|
2190
|
+
} else {
|
|
2191
|
+
await sql`DROP TABLE ${sql.table(`noorm.${t.old}`)}`.execute(trx);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
if (!inNoormOld && !inSrc) continue;
|
|
2197
|
+
if (!inNoormOld && inSrc) {
|
|
2198
|
+
if (dialect === "postgres") {
|
|
2199
|
+
await sql`ALTER TABLE ${sql.table(t.old)} SET SCHEMA noorm`.execute(trx);
|
|
2200
|
+
} else {
|
|
2201
|
+
await sql`ALTER SCHEMA noorm TRANSFER ${sql.ref(`dbo.${t.old}`)}`.execute(trx);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2096
2204
|
if (dialect === "postgres") {
|
|
2097
2205
|
await sql`ALTER TABLE ${sql.table(`noorm.${t.old}`)} RENAME TO ${sql.ref(t.new)}`.execute(trx);
|
|
2098
2206
|
} else {
|
|
2099
2207
|
await sql`EXEC sp_rename ${sql.raw(`'noorm.${t.old}'`)}, ${sql.raw(`'${t.new}'`)}`.execute(trx);
|
|
2100
2208
|
}
|
|
2209
|
+
if (inSrc && inNoormOld) {
|
|
2210
|
+
if (await tableExistsIn(src, t.old)) {
|
|
2211
|
+
if (dialect === "postgres") {
|
|
2212
|
+
await sql`DROP TABLE ${sql.table(`${src}.${t.old}`)} CASCADE`.execute(trx);
|
|
2213
|
+
} else {
|
|
2214
|
+
await sql`DROP TABLE ${sql.table(`${src}.${t.old}`)}`.execute(trx);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
const fksOnExec = await getForeignKeys("noorm", "executions");
|
|
2220
|
+
if (fksOnExec.length === 0) {
|
|
2221
|
+
await sql`
|
|
2222
|
+
ALTER TABLE ${sql.table("noorm.executions")}
|
|
2223
|
+
ADD CONSTRAINT fk_executions_change_id
|
|
2224
|
+
FOREIGN KEY (change_id) REFERENCES ${sql.table("noorm.change")}(id)
|
|
2225
|
+
ON DELETE CASCADE
|
|
2226
|
+
`.execute(trx);
|
|
2101
2227
|
}
|
|
2102
|
-
await sql`
|
|
2103
|
-
ALTER TABLE ${sql.table("noorm.executions")}
|
|
2104
|
-
ADD CONSTRAINT fk_executions_change_id
|
|
2105
|
-
FOREIGN KEY (change_id) REFERENCES ${sql.table("noorm.change")}(id)
|
|
2106
|
-
ON DELETE CASCADE
|
|
2107
|
-
`.execute(trx);
|
|
2108
2228
|
for (const idx of INDEXES) {
|
|
2109
|
-
|
|
2110
|
-
|
|
2229
|
+
if (!await indexExistsIn("noorm", idx.name)) {
|
|
2230
|
+
const cols = idx.columns.join(", ");
|
|
2231
|
+
await sql`CREATE INDEX ${sql.ref(idx.name)} ON ${sql.table(`noorm.${idx.table}`)} (${sql.raw(cols)})`.execute(trx);
|
|
2232
|
+
}
|
|
2111
2233
|
}
|
|
2112
2234
|
});
|
|
2113
2235
|
},
|
|
@@ -2170,12 +2292,7 @@ var v2 = {
|
|
|
2170
2292
|
ON DELETE CASCADE
|
|
2171
2293
|
`.execute(trx);
|
|
2172
2294
|
for (const idx of INDEXES) {
|
|
2173
|
-
const
|
|
2174
|
-
executions: "__noorm_executions__",
|
|
2175
|
-
change: "__noorm_change__",
|
|
2176
|
-
vault: "__noorm_vault__"
|
|
2177
|
-
};
|
|
2178
|
-
const tableName = prefixedTables[idx.table];
|
|
2295
|
+
const tableName = PREFIXED_TABLES[idx.table];
|
|
2179
2296
|
const cols = idx.columns.join(", ");
|
|
2180
2297
|
await sql`CREATE INDEX ${sql.ref(idx.name)} ON ${sql.table(tableName)} (${sql.raw(cols)})`.execute(trx);
|
|
2181
2298
|
}
|
|
@@ -2306,17 +2423,22 @@ async function migrateSchema(db, dialect, options) {
|
|
|
2306
2423
|
throw new VersionMismatchError("schema", status.current, status.expected);
|
|
2307
2424
|
}
|
|
2308
2425
|
if (!status.needsMigration) return;
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2426
|
+
let currentVersion = status.current;
|
|
2427
|
+
if (currentVersion === 0) {
|
|
2428
|
+
const tableFound = await tablesExist(db, dialect);
|
|
2429
|
+
if (!tableFound) {
|
|
2430
|
+
await bootstrapSchema(db, dialect);
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
currentVersion = 1;
|
|
2312
2434
|
}
|
|
2313
2435
|
const start = performance.now();
|
|
2314
2436
|
observer.emit("version:schema:migrating", {
|
|
2315
|
-
from:
|
|
2437
|
+
from: currentVersion,
|
|
2316
2438
|
to: CURRENT_VERSIONS.schema
|
|
2317
2439
|
});
|
|
2318
2440
|
const existing = await getLatestVersionRecord(db, dialect);
|
|
2319
|
-
const pendingMigrations = MIGRATIONS.filter((m) => m.version >
|
|
2441
|
+
const pendingMigrations = MIGRATIONS.filter((m) => m.version > currentVersion);
|
|
2320
2442
|
for (const migration of pendingMigrations) {
|
|
2321
2443
|
const [, err] = await attempt(() => migration.up(db, dialect));
|
|
2322
2444
|
if (err) {
|
|
@@ -2333,7 +2455,7 @@ async function migrateSchema(db, dialect, options) {
|
|
|
2333
2455
|
}).execute();
|
|
2334
2456
|
const durationMs = performance.now() - start;
|
|
2335
2457
|
observer.emit("version:schema:migrated", {
|
|
2336
|
-
from:
|
|
2458
|
+
from: currentVersion,
|
|
2337
2459
|
to: CURRENT_VERSIONS.schema,
|
|
2338
2460
|
durationMs
|
|
2339
2461
|
});
|
|
@@ -3152,11 +3274,13 @@ var ChangeHistory = class {
|
|
|
3152
3274
|
#ndb;
|
|
3153
3275
|
#tables;
|
|
3154
3276
|
#configName;
|
|
3277
|
+
#dialect;
|
|
3155
3278
|
constructor(db, configName, dialect = "sqlite") {
|
|
3156
3279
|
this.#db = db;
|
|
3157
3280
|
this.#ndb = noormDb(db, dialect);
|
|
3158
3281
|
this.#tables = getNoormTables(dialect);
|
|
3159
3282
|
this.#configName = configName;
|
|
3283
|
+
this.#dialect = dialect;
|
|
3160
3284
|
}
|
|
3161
3285
|
// ─────────────────────────────────────────────────────────
|
|
3162
3286
|
// Status Queries
|
|
@@ -3332,34 +3456,63 @@ var ChangeHistory = class {
|
|
|
3332
3456
|
* @returns The created operation's ID
|
|
3333
3457
|
*/
|
|
3334
3458
|
async createOperation(data2) {
|
|
3335
|
-
const
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
)
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
}
|
|
3348
|
-
let id = result?.id;
|
|
3349
|
-
if (id === null || id === void 0) {
|
|
3350
|
-
const [lastIdResult, lastIdErr] = await attempt(
|
|
3351
|
-
() => sql`SELECT last_insert_rowid() as id`.execute(this.#db)
|
|
3459
|
+
const insertQuery = this.#ndb.insertInto(this.#tables.change).values({
|
|
3460
|
+
name: data2.name,
|
|
3461
|
+
change_type: "change",
|
|
3462
|
+
direction: data2.direction,
|
|
3463
|
+
status: "pending",
|
|
3464
|
+
config_name: this.#configName,
|
|
3465
|
+
executed_by: data2.executedBy
|
|
3466
|
+
});
|
|
3467
|
+
let id;
|
|
3468
|
+
if (this.#dialect === "mssql") {
|
|
3469
|
+
const [result, insertErr] = await attempt(
|
|
3470
|
+
() => insertQuery.output("inserted.id as id").executeTakeFirstOrThrow()
|
|
3352
3471
|
);
|
|
3353
|
-
if (
|
|
3354
|
-
throw new Error("Failed to
|
|
3472
|
+
if (insertErr) {
|
|
3473
|
+
throw new Error("Failed to create change operation record", { cause: insertErr });
|
|
3474
|
+
}
|
|
3475
|
+
id = result?.id;
|
|
3476
|
+
} else {
|
|
3477
|
+
const [result, err] = await attempt(
|
|
3478
|
+
() => insertQuery.returning("id").executeTakeFirstOrThrow()
|
|
3479
|
+
);
|
|
3480
|
+
if (err) {
|
|
3481
|
+
throw new Error("Failed to create change operation record", { cause: err });
|
|
3482
|
+
}
|
|
3483
|
+
id = result?.id ?? void 0;
|
|
3484
|
+
if (id === null || id === void 0) {
|
|
3485
|
+
const lastIdQuery = this.#lastInsertIdQuery();
|
|
3486
|
+
if (lastIdQuery) {
|
|
3487
|
+
const [lastIdResult] = await attempt(() => lastIdQuery.execute(this.#db));
|
|
3488
|
+
id = lastIdResult?.rows?.[0]?.id;
|
|
3489
|
+
}
|
|
3355
3490
|
}
|
|
3356
|
-
id = lastIdResult.rows[0].id;
|
|
3357
3491
|
}
|
|
3358
3492
|
if (typeof id !== "number" || !Number.isFinite(id) || id <= 0) {
|
|
3359
3493
|
throw new Error(`Invalid operation ID returned: ${id}`);
|
|
3360
3494
|
}
|
|
3361
3495
|
return id;
|
|
3362
3496
|
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Get dialect-specific last-insert-id query.
|
|
3499
|
+
*
|
|
3500
|
+
* Returns null if the dialect should always use RETURNING/OUTPUT.
|
|
3501
|
+
*/
|
|
3502
|
+
#lastInsertIdQuery() {
|
|
3503
|
+
switch (this.#dialect) {
|
|
3504
|
+
case "sqlite":
|
|
3505
|
+
return sql`SELECT last_insert_rowid() as id`;
|
|
3506
|
+
case "mysql":
|
|
3507
|
+
return sql`SELECT LAST_INSERT_ID() as id`;
|
|
3508
|
+
case "mssql":
|
|
3509
|
+
return sql`SELECT SCOPE_IDENTITY() as id`;
|
|
3510
|
+
case "postgres":
|
|
3511
|
+
return sql`SELECT lastval() as id`;
|
|
3512
|
+
default:
|
|
3513
|
+
return null;
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3363
3516
|
/**
|
|
3364
3517
|
* Create pending file records for all files.
|
|
3365
3518
|
*
|
|
@@ -3437,7 +3590,7 @@ var ChangeHistory = class {
|
|
|
3437
3590
|
const [, err] = await attempt(
|
|
3438
3591
|
() => this.#ndb.updateTable(this.#tables.executions).set({
|
|
3439
3592
|
status: "skipped",
|
|
3440
|
-
skip_reason: reason
|
|
3593
|
+
skip_reason: reason.slice(0, 100)
|
|
3441
3594
|
}).where("change_id", "=", operationId).where("status", "=", "pending").execute()
|
|
3442
3595
|
);
|
|
3443
3596
|
if (err) {
|
|
@@ -3498,18 +3651,33 @@ var ChangeHistory = class {
|
|
|
3498
3651
|
* @returns The created record's ID
|
|
3499
3652
|
*/
|
|
3500
3653
|
async recordReset(executedBy, reason) {
|
|
3654
|
+
const insertQuery = this.#ndb.insertInto(this.#tables.change).values({
|
|
3655
|
+
name: "__reset__",
|
|
3656
|
+
change_type: "change",
|
|
3657
|
+
direction: "change",
|
|
3658
|
+
status: "success",
|
|
3659
|
+
config_name: this.#configName,
|
|
3660
|
+
executed_by: executedBy,
|
|
3661
|
+
error_message: reason ?? "",
|
|
3662
|
+
duration_ms: 0,
|
|
3663
|
+
checksum: ""
|
|
3664
|
+
});
|
|
3665
|
+
if (this.#dialect === "mssql") {
|
|
3666
|
+
const [result2, insertErr] = await attempt(
|
|
3667
|
+
() => insertQuery.output("inserted.id as id").executeTakeFirstOrThrow()
|
|
3668
|
+
);
|
|
3669
|
+
if (insertErr) {
|
|
3670
|
+
observer.emit("error", {
|
|
3671
|
+
source: "change",
|
|
3672
|
+
error: insertErr,
|
|
3673
|
+
context: { operation: "record-reset" }
|
|
3674
|
+
});
|
|
3675
|
+
return 0;
|
|
3676
|
+
}
|
|
3677
|
+
return result2?.id ?? 0;
|
|
3678
|
+
}
|
|
3501
3679
|
const [result, err] = await attempt(
|
|
3502
|
-
() =>
|
|
3503
|
-
name: "__reset__",
|
|
3504
|
-
change_type: "change",
|
|
3505
|
-
direction: "change",
|
|
3506
|
-
status: "success",
|
|
3507
|
-
config_name: this.#configName,
|
|
3508
|
-
executed_by: executedBy,
|
|
3509
|
-
error_message: reason ?? "",
|
|
3510
|
-
duration_ms: 0,
|
|
3511
|
-
checksum: ""
|
|
3512
|
-
}).returning("id").executeTakeFirstOrThrow()
|
|
3680
|
+
() => insertQuery.returning("id").executeTakeFirstOrThrow()
|
|
3513
3681
|
);
|
|
3514
3682
|
if (err) {
|
|
3515
3683
|
observer.emit("error", {
|
|
@@ -3723,11 +3891,13 @@ var Tracker = class {
|
|
|
3723
3891
|
#ndb;
|
|
3724
3892
|
#tables;
|
|
3725
3893
|
#configName;
|
|
3894
|
+
#dialect;
|
|
3726
3895
|
constructor(db, configName, dialect = "sqlite") {
|
|
3727
3896
|
this.#db = db;
|
|
3728
3897
|
this.#ndb = noormDb(db, dialect);
|
|
3729
3898
|
this.#tables = getNoormTables(dialect);
|
|
3730
3899
|
this.#configName = configName;
|
|
3900
|
+
this.#dialect = dialect;
|
|
3731
3901
|
}
|
|
3732
3902
|
/**
|
|
3733
3903
|
* Check if a file needs to run.
|
|
@@ -3813,34 +3983,63 @@ var Tracker = class {
|
|
|
3813
3983
|
async createOperation(data2) {
|
|
3814
3984
|
const direction = data2.direction ?? "commit";
|
|
3815
3985
|
const dbDirection = direction === "commit" ? "change" : "revert";
|
|
3816
|
-
const
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
)
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3986
|
+
const insertQuery = this.#ndb.insertInto(this.#tables.change).values({
|
|
3987
|
+
name: data2.name,
|
|
3988
|
+
change_type: data2.changeType,
|
|
3989
|
+
direction: dbDirection,
|
|
3990
|
+
status: "pending",
|
|
3991
|
+
config_name: data2.configName,
|
|
3992
|
+
executed_by: data2.executedBy
|
|
3993
|
+
});
|
|
3994
|
+
let id;
|
|
3995
|
+
if (this.#dialect === "mssql") {
|
|
3996
|
+
const [result, insertErr] = await attempt(
|
|
3997
|
+
() => insertQuery.output("inserted.id as id").executeTakeFirstOrThrow()
|
|
3998
|
+
);
|
|
3999
|
+
if (insertErr) {
|
|
4000
|
+
throw new Error("Failed to create operation record", { cause: insertErr });
|
|
4001
|
+
}
|
|
4002
|
+
id = result?.id;
|
|
4003
|
+
} else {
|
|
4004
|
+
const [result, err] = await attempt(
|
|
4005
|
+
() => insertQuery.returning("id").executeTakeFirstOrThrow()
|
|
3833
4006
|
);
|
|
3834
|
-
if (
|
|
3835
|
-
throw new Error("Failed to
|
|
4007
|
+
if (err) {
|
|
4008
|
+
throw new Error("Failed to create operation record", { cause: err });
|
|
4009
|
+
}
|
|
4010
|
+
id = result?.id ?? void 0;
|
|
4011
|
+
if (id === null || id === void 0) {
|
|
4012
|
+
const lastIdQuery = this.#lastInsertIdQuery();
|
|
4013
|
+
if (lastIdQuery) {
|
|
4014
|
+
const [lastIdResult] = await attempt(() => lastIdQuery.execute(this.#db));
|
|
4015
|
+
id = lastIdResult?.rows?.[0]?.id;
|
|
4016
|
+
}
|
|
3836
4017
|
}
|
|
3837
|
-
id = lastIdResult.rows[0].id;
|
|
3838
4018
|
}
|
|
3839
4019
|
if (typeof id !== "number" || !Number.isFinite(id) || id <= 0) {
|
|
3840
4020
|
throw new Error(`Invalid operation ID returned: ${id}`);
|
|
3841
4021
|
}
|
|
3842
4022
|
return id;
|
|
3843
4023
|
}
|
|
4024
|
+
/**
|
|
4025
|
+
* Get dialect-specific last-insert-id query.
|
|
4026
|
+
*
|
|
4027
|
+
* Returns null if the dialect should always use RETURNING/OUTPUT.
|
|
4028
|
+
*/
|
|
4029
|
+
#lastInsertIdQuery() {
|
|
4030
|
+
switch (this.#dialect) {
|
|
4031
|
+
case "sqlite":
|
|
4032
|
+
return sql`SELECT last_insert_rowid() as id`;
|
|
4033
|
+
case "mysql":
|
|
4034
|
+
return sql`SELECT LAST_INSERT_ID() as id`;
|
|
4035
|
+
case "mssql":
|
|
4036
|
+
return sql`SELECT SCOPE_IDENTITY() as id`;
|
|
4037
|
+
case "postgres":
|
|
4038
|
+
return sql`SELECT lastval() as id`;
|
|
4039
|
+
default:
|
|
4040
|
+
return null;
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
3844
4043
|
/**
|
|
3845
4044
|
* Record a file execution.
|
|
3846
4045
|
*
|
|
@@ -4003,10 +4202,11 @@ var Tracker = class {
|
|
|
4003
4202
|
* @returns Error message if skip failed, null on success
|
|
4004
4203
|
*/
|
|
4005
4204
|
async skipRemainingFiles(operationId, reason) {
|
|
4205
|
+
const truncatedReason = reason.slice(0, 100);
|
|
4006
4206
|
const [, err] = await attempt(
|
|
4007
4207
|
() => this.#ndb.updateTable(this.#tables.executions).set({
|
|
4008
4208
|
status: "skipped",
|
|
4009
|
-
skip_reason:
|
|
4209
|
+
skip_reason: truncatedReason
|
|
4010
4210
|
}).where("change_id", "=", operationId).where("status", "=", "pending").execute()
|
|
4011
4211
|
);
|
|
4012
4212
|
if (err) {
|
|
@@ -4546,7 +4746,7 @@ function getLockManager() {
|
|
|
4546
4746
|
return instance3;
|
|
4547
4747
|
}
|
|
4548
4748
|
function sleep(ms) {
|
|
4549
|
-
return new Promise((
|
|
4749
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4550
4750
|
}
|
|
4551
4751
|
|
|
4552
4752
|
// src/core/change/executor.ts
|
|
@@ -5720,7 +5920,10 @@ async function runBuild(context, sqlPath, options = {}, preFilteredFiles) {
|
|
|
5720
5920
|
error: discoverErr,
|
|
5721
5921
|
context: { sqlPath, operation: "discover-files" }
|
|
5722
5922
|
});
|
|
5723
|
-
return createFailedBatchResult(
|
|
5923
|
+
return createFailedBatchResult(
|
|
5924
|
+
`${discoverErr.message} (${sqlPath})`,
|
|
5925
|
+
performance.now() - start
|
|
5926
|
+
);
|
|
5724
5927
|
}
|
|
5725
5928
|
files = discovered;
|
|
5726
5929
|
}
|
|
@@ -5728,13 +5931,14 @@ async function runBuild(context, sqlPath, options = {}, preFilteredFiles) {
|
|
|
5728
5931
|
sqlPath,
|
|
5729
5932
|
fileCount: files.length
|
|
5730
5933
|
});
|
|
5731
|
-
const result = await executeFilesInternal(context, files, opts, "build");
|
|
5934
|
+
const result = await executeFilesInternal(context, files, opts, "build", sqlPath);
|
|
5732
5935
|
observer.emit("build:complete", {
|
|
5733
5936
|
status: result.status,
|
|
5734
5937
|
filesRun: result.filesRun,
|
|
5735
5938
|
filesSkipped: result.filesSkipped,
|
|
5736
5939
|
filesFailed: result.filesFailed,
|
|
5737
|
-
durationMs: result.durationMs
|
|
5940
|
+
durationMs: result.durationMs,
|
|
5941
|
+
error: result.error
|
|
5738
5942
|
});
|
|
5739
5943
|
return result;
|
|
5740
5944
|
}
|
|
@@ -5787,14 +5991,17 @@ async function runDir(context, dirpath, options = {}) {
|
|
|
5787
5991
|
error: discoverErr,
|
|
5788
5992
|
context: { dirpath, operation: "discover-files" }
|
|
5789
5993
|
});
|
|
5790
|
-
return createFailedBatchResult(
|
|
5994
|
+
return createFailedBatchResult(
|
|
5995
|
+
`${discoverErr.message} (${dirpath})`,
|
|
5996
|
+
performance.now() - start
|
|
5997
|
+
);
|
|
5791
5998
|
}
|
|
5792
5999
|
observer.emit("run:dir", {
|
|
5793
6000
|
dirpath,
|
|
5794
6001
|
fileCount: files.length,
|
|
5795
6002
|
configName: context.configName
|
|
5796
6003
|
});
|
|
5797
|
-
return executeFilesInternal(context, files, opts, "run");
|
|
6004
|
+
return executeFilesInternal(context, files, opts, "run", dirpath);
|
|
5798
6005
|
}
|
|
5799
6006
|
async function runFiles(context, files, options = {}) {
|
|
5800
6007
|
const opts = { ...DEFAULT_RUN_OPTIONS_INTERNAL, ...options };
|
|
@@ -5890,9 +6097,12 @@ async function executeFiles2(context, files, runOptions, execOptions) {
|
|
|
5890
6097
|
observer.emit("error", {
|
|
5891
6098
|
source: "runner:create-operation",
|
|
5892
6099
|
error: createErr,
|
|
5893
|
-
context: { operationName: execOptions.operationName }
|
|
6100
|
+
context: { operationName: execOptions.operationName, sqlPath: execOptions.sqlPath }
|
|
5894
6101
|
});
|
|
5895
|
-
return createFailedBatchResult(
|
|
6102
|
+
return createFailedBatchResult(
|
|
6103
|
+
formatErrorChain(createErr),
|
|
6104
|
+
performance.now() - start
|
|
6105
|
+
);
|
|
5896
6106
|
}
|
|
5897
6107
|
const fileRecords = [];
|
|
5898
6108
|
for (const file of files) {
|
|
@@ -5915,7 +6125,10 @@ async function executeFiles2(context, files, runOptions, execOptions) {
|
|
|
5915
6125
|
context: { operationId }
|
|
5916
6126
|
});
|
|
5917
6127
|
await tracker.finalizeOperation(operationId, "failed", 0, "", createRecordsErr);
|
|
5918
|
-
return createFailedBatchResult(
|
|
6128
|
+
return createFailedBatchResult(
|
|
6129
|
+
createRecordsErr,
|
|
6130
|
+
performance.now() - start
|
|
6131
|
+
);
|
|
5919
6132
|
}
|
|
5920
6133
|
const results = [];
|
|
5921
6134
|
let failed = false;
|
|
@@ -5936,7 +6149,7 @@ async function executeFiles2(context, files, runOptions, execOptions) {
|
|
|
5936
6149
|
failed = true;
|
|
5937
6150
|
const skipErr = await tracker.skipRemainingFiles(
|
|
5938
6151
|
operationId,
|
|
5939
|
-
`Skipped
|
|
6152
|
+
`Skipped: failure in ${path.basename(file.path)}`.slice(0, 100)
|
|
5940
6153
|
);
|
|
5941
6154
|
if (skipErr) {
|
|
5942
6155
|
observer.emit("error", {
|
|
@@ -5982,7 +6195,7 @@ async function executeFiles2(context, files, runOptions, execOptions) {
|
|
|
5982
6195
|
changeId: operationId
|
|
5983
6196
|
};
|
|
5984
6197
|
}
|
|
5985
|
-
async function executeFilesInternal(context, files, options, changeType) {
|
|
6198
|
+
async function executeFilesInternal(context, files, options, changeType, sqlPath) {
|
|
5986
6199
|
const fileInputs = files.map((f) => ({
|
|
5987
6200
|
path: f,
|
|
5988
6201
|
type: "sql"
|
|
@@ -5990,7 +6203,8 @@ async function executeFilesInternal(context, files, options, changeType) {
|
|
|
5990
6203
|
const execOptions = {
|
|
5991
6204
|
changeType,
|
|
5992
6205
|
direction: "commit",
|
|
5993
|
-
operationName: `${changeType}:${(/* @__PURE__ */ new Date()).toISOString()}
|
|
6206
|
+
operationName: `${changeType}:${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
6207
|
+
sqlPath
|
|
5994
6208
|
};
|
|
5995
6209
|
return executeFiles2(context, fileInputs, options, execOptions);
|
|
5996
6210
|
}
|
|
@@ -6060,7 +6274,7 @@ async function executeSingleFileWithUpdate(context, filepath, checksum, options,
|
|
|
6060
6274
|
filepath,
|
|
6061
6275
|
checksum: finalChecksum,
|
|
6062
6276
|
status: "failed",
|
|
6063
|
-
error: execErr
|
|
6277
|
+
error: getErrorMessage(execErr),
|
|
6064
6278
|
durationMs
|
|
6065
6279
|
};
|
|
6066
6280
|
await tracker.updateFileExecution(
|
|
@@ -6068,13 +6282,13 @@ async function executeSingleFileWithUpdate(context, filepath, checksum, options,
|
|
|
6068
6282
|
relFilepath,
|
|
6069
6283
|
"failed",
|
|
6070
6284
|
Math.round(durationMs),
|
|
6071
|
-
execErr
|
|
6285
|
+
getErrorMessage(execErr)
|
|
6072
6286
|
);
|
|
6073
6287
|
observer.emit("file:after", {
|
|
6074
6288
|
filepath,
|
|
6075
6289
|
status: "failed",
|
|
6076
6290
|
durationMs,
|
|
6077
|
-
error: execErr
|
|
6291
|
+
error: getErrorMessage(execErr)
|
|
6078
6292
|
});
|
|
6079
6293
|
return result2;
|
|
6080
6294
|
}
|
|
@@ -6184,7 +6398,7 @@ async function executeSingleFile(context, filepath, options, tracker, operationI
|
|
|
6184
6398
|
filepath,
|
|
6185
6399
|
checksum,
|
|
6186
6400
|
status: "failed",
|
|
6187
|
-
error: execErr
|
|
6401
|
+
error: getErrorMessage(execErr),
|
|
6188
6402
|
durationMs
|
|
6189
6403
|
};
|
|
6190
6404
|
await tracker.recordExecution({
|
|
@@ -6192,14 +6406,14 @@ async function executeSingleFile(context, filepath, options, tracker, operationI
|
|
|
6192
6406
|
filepath: relFilepath,
|
|
6193
6407
|
checksum,
|
|
6194
6408
|
status: "failed",
|
|
6195
|
-
errorMessage: execErr
|
|
6409
|
+
errorMessage: getErrorMessage(execErr),
|
|
6196
6410
|
durationMs: Math.round(durationMs)
|
|
6197
6411
|
});
|
|
6198
6412
|
observer.emit("file:after", {
|
|
6199
6413
|
filepath,
|
|
6200
6414
|
status: "failed",
|
|
6201
6415
|
durationMs,
|
|
6202
|
-
error: execErr
|
|
6416
|
+
error: getErrorMessage(execErr)
|
|
6203
6417
|
});
|
|
6204
6418
|
return result2;
|
|
6205
6419
|
}
|
|
@@ -6342,9 +6556,40 @@ function createFailedBatchResult(error, durationMs) {
|
|
|
6342
6556
|
filesRun: 0,
|
|
6343
6557
|
filesSkipped: 0,
|
|
6344
6558
|
filesFailed: 0,
|
|
6345
|
-
durationMs
|
|
6559
|
+
durationMs,
|
|
6560
|
+
error
|
|
6346
6561
|
};
|
|
6347
6562
|
}
|
|
6563
|
+
function formatErrorChain(err) {
|
|
6564
|
+
const parts = [err.message];
|
|
6565
|
+
let current = err.cause;
|
|
6566
|
+
while (current instanceof Error) {
|
|
6567
|
+
parts.push(current.message);
|
|
6568
|
+
current = current.cause;
|
|
6569
|
+
}
|
|
6570
|
+
return parts.join(": ");
|
|
6571
|
+
}
|
|
6572
|
+
function getErrorMessage(err) {
|
|
6573
|
+
if (err instanceof AggregateError && err.errors?.length > 0) {
|
|
6574
|
+
return err.errors.map((e) => getErrorMessage(e)).join("; ");
|
|
6575
|
+
}
|
|
6576
|
+
if (err instanceof Error && err.message) return err.message;
|
|
6577
|
+
if (typeof err === "string") return err;
|
|
6578
|
+
if (typeof err === "object" && err !== null) {
|
|
6579
|
+
const e = err;
|
|
6580
|
+
if (typeof e["message"] === "string" && e["message"]) return e["message"];
|
|
6581
|
+
if (Array.isArray(e["errors"]) && e["errors"].length > 0) {
|
|
6582
|
+
return e["errors"].map((inner) => getErrorMessage(inner)).join("; ");
|
|
6583
|
+
}
|
|
6584
|
+
if (typeof e["originalError"] === "object" && e["originalError"] !== null) {
|
|
6585
|
+
const orig = e["originalError"];
|
|
6586
|
+
if (typeof orig["message"] === "string" && orig["message"]) return orig["message"];
|
|
6587
|
+
}
|
|
6588
|
+
const str = String(err);
|
|
6589
|
+
if (str !== "[object Object]") return str;
|
|
6590
|
+
}
|
|
6591
|
+
return "Unknown error";
|
|
6592
|
+
}
|
|
6348
6593
|
|
|
6349
6594
|
// src/sdk/namespaces/run.ts
|
|
6350
6595
|
var RunNamespace = class {
|
|
@@ -9494,7 +9739,7 @@ async function copyVaultSecrets(sourceConfig, destConfig, keys, identityHash, pr
|
|
|
9494
9739
|
throw new Error(`No vault access on source config "${sourceConfig.name}"`);
|
|
9495
9740
|
}
|
|
9496
9741
|
const destStatus = await getVaultStatus(ctx.destination.db, identityHash, ctx.destination.dialect);
|
|
9497
|
-
let destVaultKey
|
|
9742
|
+
let destVaultKey;
|
|
9498
9743
|
if (!destStatus.isInitialized) {
|
|
9499
9744
|
const [newKey, initErr] = await initializeVault(
|
|
9500
9745
|
ctx.destination.db,
|
|
@@ -10681,16 +10926,16 @@ async function planTransfer(ctx, options = {}) {
|
|
|
10681
10926
|
const orderedNames = sortErr ? allTables.map((t) => t.name) : sortedNames;
|
|
10682
10927
|
const tablePlans = [];
|
|
10683
10928
|
for (const name of orderedNames) {
|
|
10684
|
-
const
|
|
10685
|
-
if (!
|
|
10929
|
+
const meta2 = tablesByName[name];
|
|
10930
|
+
if (!meta2) continue;
|
|
10686
10931
|
tablePlans.push({
|
|
10687
|
-
name:
|
|
10688
|
-
schema:
|
|
10689
|
-
rowCount:
|
|
10690
|
-
hasIdentity:
|
|
10691
|
-
identityColumn:
|
|
10692
|
-
primaryKey:
|
|
10693
|
-
columns:
|
|
10932
|
+
name: meta2.name,
|
|
10933
|
+
schema: meta2.schema,
|
|
10934
|
+
rowCount: meta2.rowCount,
|
|
10935
|
+
hasIdentity: meta2.hasIdentity,
|
|
10936
|
+
identityColumn: meta2.identityColumn,
|
|
10937
|
+
primaryKey: meta2.primaryKey,
|
|
10938
|
+
columns: meta2.columns,
|
|
10694
10939
|
dependsOn: dependencyMap.get(name) ?? []
|
|
10695
10940
|
});
|
|
10696
10941
|
}
|
|
@@ -11234,8 +11479,8 @@ async function executeTransfer(ctx, plan, options = {}) {
|
|
|
11234
11479
|
const strategy = options.onConflict ?? "fail";
|
|
11235
11480
|
const useSameServer = plan.sameServer && strategy === "fail" && !plan.crossDialect;
|
|
11236
11481
|
const useCrossDialect = plan.crossDialect && tablePlan.columnTypes;
|
|
11237
|
-
let tableResult
|
|
11238
|
-
let tableErr
|
|
11482
|
+
let tableResult;
|
|
11483
|
+
let tableErr;
|
|
11239
11484
|
if (useSameServer) {
|
|
11240
11485
|
[tableResult, tableErr] = await transferTableSameServer(
|
|
11241
11486
|
ctx,
|
|
@@ -11864,6 +12109,174 @@ var TransferNamespace = class {
|
|
|
11864
12109
|
return getTransferPlan(this.#state.config, destConfig, options);
|
|
11865
12110
|
}
|
|
11866
12111
|
};
|
|
12112
|
+
|
|
12113
|
+
// src/core/worker-bridge/pool.ts
|
|
12114
|
+
var WorkerPool = class {
|
|
12115
|
+
#workers;
|
|
12116
|
+
#nextIndex = 0;
|
|
12117
|
+
#isShutdown = false;
|
|
12118
|
+
constructor(script, options) {
|
|
12119
|
+
const size = Math.max(1, options.size);
|
|
12120
|
+
this.#workers = Array.from(
|
|
12121
|
+
{ length: size },
|
|
12122
|
+
() => new WorkerBridge(script)
|
|
12123
|
+
);
|
|
12124
|
+
}
|
|
12125
|
+
/**
|
|
12126
|
+
* Returns the number of workers in the pool.
|
|
12127
|
+
*/
|
|
12128
|
+
get size() {
|
|
12129
|
+
return this.#workers.length;
|
|
12130
|
+
}
|
|
12131
|
+
/**
|
|
12132
|
+
* Returns true if the pool has been shut down.
|
|
12133
|
+
*/
|
|
12134
|
+
get isShutdown() {
|
|
12135
|
+
return this.#isShutdown;
|
|
12136
|
+
}
|
|
12137
|
+
/**
|
|
12138
|
+
* Sends a request to the next available worker (round-robin) and awaits its response.
|
|
12139
|
+
*
|
|
12140
|
+
* Injects a correlation ID so concurrent requests on the same event type
|
|
12141
|
+
* can be matched to their originating worker.
|
|
12142
|
+
*
|
|
12143
|
+
* @example
|
|
12144
|
+
* const { values } = await pool.request('serialize', { row, columns, index: 0 })
|
|
12145
|
+
*/
|
|
12146
|
+
async request(event, data2, options) {
|
|
12147
|
+
const worker = this.#workers[this.#nextIndex];
|
|
12148
|
+
this.#nextIndex = (this.#nextIndex + 1) % this.#workers.length;
|
|
12149
|
+
return worker.request(event, data2, options);
|
|
12150
|
+
}
|
|
12151
|
+
/**
|
|
12152
|
+
* Registers a listener on all workers for the given event.
|
|
12153
|
+
*
|
|
12154
|
+
* Useful for broadcast-style events that every worker may emit.
|
|
12155
|
+
*
|
|
12156
|
+
* @example
|
|
12157
|
+
* pool.on('serialize:res', ({ data }) => console.log(data.values))
|
|
12158
|
+
*/
|
|
12159
|
+
on(event, callback, options) {
|
|
12160
|
+
for (const worker of this.#workers) {
|
|
12161
|
+
worker.on(event, callback, options);
|
|
12162
|
+
}
|
|
12163
|
+
}
|
|
12164
|
+
/**
|
|
12165
|
+
* Terminates all workers in the pool.
|
|
12166
|
+
*
|
|
12167
|
+
* Marks the pool as shut down and waits for every worker thread to exit
|
|
12168
|
+
* before resolving.
|
|
12169
|
+
*
|
|
12170
|
+
* @example
|
|
12171
|
+
* await pool.shutdown()
|
|
12172
|
+
*/
|
|
12173
|
+
async shutdown() {
|
|
12174
|
+
this.#isShutdown = true;
|
|
12175
|
+
await Promise.all(this.#workers.map((w) => w.shutdown()));
|
|
12176
|
+
}
|
|
12177
|
+
};
|
|
12178
|
+
|
|
12179
|
+
// src/core/worker-bridge/types.ts
|
|
12180
|
+
function isTransferable(data2) {
|
|
12181
|
+
return !!data2 && typeof data2 === "object" && "__transfer" in data2;
|
|
12182
|
+
}
|
|
12183
|
+
|
|
12184
|
+
// src/core/worker-bridge/bridge.ts
|
|
12185
|
+
var WorkerBridge = class extends ObserverRelay {
|
|
12186
|
+
#port;
|
|
12187
|
+
#ownsWorker;
|
|
12188
|
+
constructor(script, data2) {
|
|
12189
|
+
const isParent = !!script;
|
|
12190
|
+
super({ name: isParent ? "bridge:parent" : "bridge:worker" });
|
|
12191
|
+
if (script) {
|
|
12192
|
+
this.#port = new Worker(script, { workerData: data2 });
|
|
12193
|
+
this.#ownsWorker = true;
|
|
12194
|
+
this.#port.on("exit", (code) => {
|
|
12195
|
+
if (!this.isShutdown && code !== 0) {
|
|
12196
|
+
this.receive("worker:exit", { code, error: `Worker exited with code ${code}` }, {});
|
|
12197
|
+
}
|
|
12198
|
+
});
|
|
12199
|
+
} else {
|
|
12200
|
+
if (!parentPort) {
|
|
12201
|
+
throw new Error(
|
|
12202
|
+
"WorkerBridge: no script provided and not in a worker thread"
|
|
12203
|
+
);
|
|
12204
|
+
}
|
|
12205
|
+
this.#port = parentPort;
|
|
12206
|
+
this.#ownsWorker = false;
|
|
12207
|
+
}
|
|
12208
|
+
this.#port.on("message", (msg) => {
|
|
12209
|
+
this.receive(msg.event, msg.data, {});
|
|
12210
|
+
});
|
|
12211
|
+
}
|
|
12212
|
+
send(event, data2) {
|
|
12213
|
+
if (isTransferable(data2)) {
|
|
12214
|
+
this.#port.postMessage({ event, data: data2 }, data2.__transfer);
|
|
12215
|
+
} else {
|
|
12216
|
+
this.#port.postMessage({ event, data: data2 }, []);
|
|
12217
|
+
}
|
|
12218
|
+
}
|
|
12219
|
+
async request(event, data2, options) {
|
|
12220
|
+
const cid = randomUUID();
|
|
12221
|
+
const pending = this.once(new RegExp(`^${event}:res:${cid}$`), options);
|
|
12222
|
+
this.send(event, { ...data2, __cid: cid });
|
|
12223
|
+
const { data: { data: result } } = await pending;
|
|
12224
|
+
return result;
|
|
12225
|
+
}
|
|
12226
|
+
transfer(event, data2, transferables) {
|
|
12227
|
+
this.#port.postMessage({ event, data: data2 }, transferables);
|
|
12228
|
+
}
|
|
12229
|
+
static pool(script, options) {
|
|
12230
|
+
return new WorkerPool(script, options);
|
|
12231
|
+
}
|
|
12232
|
+
static get workerData() {
|
|
12233
|
+
return workerData;
|
|
12234
|
+
}
|
|
12235
|
+
async shutdown() {
|
|
12236
|
+
super.shutdown();
|
|
12237
|
+
if (this.#ownsWorker && "terminate" in this.#port) {
|
|
12238
|
+
await this.#port.terminate();
|
|
12239
|
+
}
|
|
12240
|
+
}
|
|
12241
|
+
};
|
|
12242
|
+
|
|
12243
|
+
// src/core/worker-bridge/order-buffer.ts
|
|
12244
|
+
var OrderBuffer = class {
|
|
12245
|
+
#buffer = /* @__PURE__ */ new Map();
|
|
12246
|
+
#nextIndex = 0;
|
|
12247
|
+
#flush;
|
|
12248
|
+
constructor(flush) {
|
|
12249
|
+
this.#flush = flush;
|
|
12250
|
+
}
|
|
12251
|
+
get nextIndex() {
|
|
12252
|
+
return this.#nextIndex;
|
|
12253
|
+
}
|
|
12254
|
+
get pending() {
|
|
12255
|
+
return this.#buffer.size;
|
|
12256
|
+
}
|
|
12257
|
+
add(index, item) {
|
|
12258
|
+
this.#buffer.set(index, item);
|
|
12259
|
+
this.#drain();
|
|
12260
|
+
}
|
|
12261
|
+
#drain() {
|
|
12262
|
+
while (this.#buffer.has(this.#nextIndex)) {
|
|
12263
|
+
const item = this.#buffer.get(this.#nextIndex);
|
|
12264
|
+
this.#buffer.delete(this.#nextIndex);
|
|
12265
|
+
this.#nextIndex++;
|
|
12266
|
+
this.#flush(item);
|
|
12267
|
+
}
|
|
12268
|
+
}
|
|
12269
|
+
};
|
|
12270
|
+
var IS_COMPILED = import.meta.url.includes("$bunfs");
|
|
12271
|
+
var meta = import.meta;
|
|
12272
|
+
var MODULE_DIR = typeof meta.dir === "string" ? meta.dir : dirname(fileURLToPath(import.meta.url));
|
|
12273
|
+
var WORKER_DIR = resolve(MODULE_DIR, "../../workers");
|
|
12274
|
+
function resolveWorker(name) {
|
|
12275
|
+
if (IS_COMPILED) {
|
|
12276
|
+
return new URL(`./workers/${name}.js`, import.meta.url);
|
|
12277
|
+
}
|
|
12278
|
+
return resolve(WORKER_DIR, `${name}.js`);
|
|
12279
|
+
}
|
|
11867
12280
|
var DtWriter = class {
|
|
11868
12281
|
#filepath;
|
|
11869
12282
|
#schema;
|
|
@@ -11967,8 +12380,8 @@ var DtWriter = class {
|
|
|
11967
12380
|
this.#passthrough.end();
|
|
11968
12381
|
await this.#pipelinePromise;
|
|
11969
12382
|
} else if (this.#stream) {
|
|
11970
|
-
await new Promise((
|
|
11971
|
-
this.#stream.end(() =>
|
|
12383
|
+
await new Promise((resolve2, reject) => {
|
|
12384
|
+
this.#stream.end(() => resolve2());
|
|
11972
12385
|
this.#stream.on("error", reject);
|
|
11973
12386
|
});
|
|
11974
12387
|
}
|
|
@@ -11985,211 +12398,26 @@ var DtWriter = class {
|
|
|
11985
12398
|
this.#stream.write(buf);
|
|
11986
12399
|
}
|
|
11987
12400
|
};
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
}
|
|
11997
|
-
function serializeValue(value, column) {
|
|
11998
|
-
if (value === null || value === void 0) {
|
|
11999
|
-
return null;
|
|
12000
|
-
}
|
|
12001
|
-
if (isEncodedType(column.type)) {
|
|
12002
|
-
return encodeValue(value, column.type);
|
|
12003
|
-
}
|
|
12004
|
-
return serializeSimple(value, column.type);
|
|
12005
|
-
}
|
|
12006
|
-
function serializeSimple(value, type) {
|
|
12007
|
-
switch (type) {
|
|
12008
|
-
case "bigint":
|
|
12009
|
-
return String(value);
|
|
12010
|
-
case "decimal":
|
|
12011
|
-
return String(value);
|
|
12012
|
-
case "bool":
|
|
12013
|
-
if (typeof value === "number") return value !== 0;
|
|
12014
|
-
if (typeof value === "string") return value === "1" || value.toLowerCase() === "true";
|
|
12015
|
-
return Boolean(value);
|
|
12016
|
-
case "timestamp": {
|
|
12017
|
-
if (value instanceof Date) return value.toISOString();
|
|
12018
|
-
return String(value);
|
|
12019
|
-
}
|
|
12020
|
-
case "date": {
|
|
12021
|
-
if (value instanceof Date) return value.toISOString().split("T")[0];
|
|
12022
|
-
return String(value);
|
|
12023
|
-
}
|
|
12024
|
-
case "uuid":
|
|
12025
|
-
return String(value);
|
|
12026
|
-
case "int":
|
|
12027
|
-
case "float":
|
|
12028
|
-
return typeof value === "string" ? Number(value) : value;
|
|
12029
|
-
case "string":
|
|
12030
|
-
default:
|
|
12031
|
-
return String(value);
|
|
12032
|
-
}
|
|
12033
|
-
}
|
|
12034
|
-
function encodeValue(value, type) {
|
|
12035
|
-
if (type === "binary") {
|
|
12036
|
-
return encodeBinary(value);
|
|
12037
|
-
}
|
|
12038
|
-
return encodeJsonLike(value);
|
|
12039
|
-
}
|
|
12040
|
-
function encodeBinary(value) {
|
|
12041
|
-
const buf = toBuffer(value);
|
|
12042
|
-
const byteLength = buf.length;
|
|
12043
|
-
if (byteLength < GZIP_THRESHOLD) {
|
|
12044
|
-
return [buf.toString("base64"), "b64"];
|
|
12045
|
-
}
|
|
12046
|
-
const compressed = gzipSync(buf);
|
|
12047
|
-
if (compressed.length < byteLength * GZIP_RATIO_THRESHOLD) {
|
|
12048
|
-
return [compressed.toString("base64"), "gz64"];
|
|
12049
|
-
}
|
|
12050
|
-
return [buf.toString("base64"), "b64"];
|
|
12051
|
-
}
|
|
12052
|
-
function encodeJsonLike(value) {
|
|
12053
|
-
const jsonStr = JSON.stringify(value);
|
|
12054
|
-
const byteLength = Buffer.byteLength(jsonStr, "utf8");
|
|
12055
|
-
if (byteLength < GZIP_THRESHOLD) {
|
|
12056
|
-
return [value, "raw"];
|
|
12057
|
-
}
|
|
12058
|
-
const buf = Buffer.from(jsonStr, "utf8");
|
|
12059
|
-
const compressed = gzipSync(buf);
|
|
12060
|
-
if (compressed.length < byteLength * GZIP_RATIO_THRESHOLD) {
|
|
12061
|
-
return [compressed.toString("base64"), "gz64"];
|
|
12062
|
-
}
|
|
12063
|
-
return [value, "raw"];
|
|
12064
|
-
}
|
|
12065
|
-
function toBuffer(value) {
|
|
12066
|
-
if (Buffer.isBuffer(value)) return value;
|
|
12067
|
-
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
12068
|
-
if (typeof value === "string") return Buffer.from(value, "base64");
|
|
12069
|
-
return Buffer.from(String(value), "utf8");
|
|
12070
|
-
}
|
|
12071
|
-
function deserializeRow(options) {
|
|
12072
|
-
const { values, columns, targetDialect, targetVersion } = options;
|
|
12073
|
-
const result = {};
|
|
12074
|
-
for (let i = 0; i < columns.length; i++) {
|
|
12075
|
-
const col = columns[i];
|
|
12076
|
-
const value = values[i];
|
|
12077
|
-
result[col.name] = deserializeValue(value, col, targetDialect, targetVersion);
|
|
12078
|
-
}
|
|
12079
|
-
return result;
|
|
12080
|
-
}
|
|
12081
|
-
function deserializeValue(value, column, targetDialect, targetVersion) {
|
|
12082
|
-
if (value === null || value === void 0) {
|
|
12083
|
-
return null;
|
|
12084
|
-
}
|
|
12085
|
-
if (isEncodedType(column.type)) {
|
|
12086
|
-
const decoded = decodeTuple(value);
|
|
12087
|
-
return convertEncodedForTarget(decoded, column.type, targetDialect, targetVersion);
|
|
12088
|
-
}
|
|
12089
|
-
return convertSimpleForTarget(value, column.type, targetDialect);
|
|
12090
|
-
}
|
|
12091
|
-
function decodeTuple(tuple) {
|
|
12092
|
-
if (!Array.isArray(tuple) || tuple.length < 2) {
|
|
12093
|
-
return tuple;
|
|
12094
|
-
}
|
|
12095
|
-
const [value, encoding] = tuple;
|
|
12096
|
-
switch (encoding) {
|
|
12097
|
-
case "raw":
|
|
12098
|
-
return value;
|
|
12099
|
-
case "b64":
|
|
12100
|
-
return Buffer.from(value, "base64");
|
|
12101
|
-
case "gz64": {
|
|
12102
|
-
const compressed = Buffer.from(value, "base64");
|
|
12103
|
-
const decompressed = gunzipSync(compressed);
|
|
12104
|
-
const str = decompressed.toString("utf8");
|
|
12105
|
-
if (/^[[{"\d]/.test(str)) {
|
|
12106
|
-
return JSON.parse(str);
|
|
12107
|
-
}
|
|
12108
|
-
return decompressed;
|
|
12109
|
-
}
|
|
12110
|
-
default:
|
|
12111
|
-
return value;
|
|
12112
|
-
}
|
|
12113
|
-
}
|
|
12114
|
-
function convertEncodedForTarget(decoded, type, targetDialect, targetVersion) {
|
|
12115
|
-
switch (type) {
|
|
12116
|
-
case "json":
|
|
12117
|
-
return convertJsonForTarget(decoded, targetDialect, targetVersion);
|
|
12118
|
-
case "text":
|
|
12119
|
-
return decoded;
|
|
12120
|
-
case "binary":
|
|
12121
|
-
return decoded;
|
|
12122
|
-
case "vector":
|
|
12123
|
-
return convertVectorForTarget(decoded, targetDialect, targetVersion);
|
|
12124
|
-
case "array":
|
|
12125
|
-
return convertArrayForTarget(decoded, targetDialect);
|
|
12126
|
-
case "custom":
|
|
12127
|
-
if (typeof decoded === "object") return JSON.stringify(decoded);
|
|
12128
|
-
return decoded;
|
|
12129
|
-
default:
|
|
12130
|
-
return decoded;
|
|
12131
|
-
}
|
|
12132
|
-
}
|
|
12133
|
-
function convertJsonForTarget(decoded, targetDialect, targetVersion) {
|
|
12134
|
-
if (targetDialect === "mssql") {
|
|
12135
|
-
const major = targetVersion?.major ?? 2022;
|
|
12136
|
-
if (major < 2025) {
|
|
12137
|
-
return typeof decoded === "string" ? decoded : JSON.stringify(decoded);
|
|
12138
|
-
}
|
|
12139
|
-
}
|
|
12140
|
-
return decoded;
|
|
12141
|
-
}
|
|
12142
|
-
function convertVectorForTarget(decoded, targetDialect, targetVersion) {
|
|
12143
|
-
const arr = Array.isArray(decoded) ? decoded : decoded;
|
|
12144
|
-
if (targetDialect === "postgres") {
|
|
12145
|
-
return Array.isArray(arr) ? `[${arr.join(",")}]` : String(arr);
|
|
12146
|
-
}
|
|
12147
|
-
if (targetDialect === "mysql") {
|
|
12148
|
-
const major = targetVersion?.major ?? 8;
|
|
12149
|
-
if (major >= 9) {
|
|
12150
|
-
return Array.isArray(arr) ? `[${arr.join(",")}]` : String(arr);
|
|
12151
|
-
}
|
|
12152
|
-
return Array.isArray(arr) ? JSON.stringify(arr) : String(arr);
|
|
12153
|
-
}
|
|
12154
|
-
return Array.isArray(arr) ? JSON.stringify(arr) : String(arr);
|
|
12155
|
-
}
|
|
12156
|
-
function convertArrayForTarget(decoded, targetDialect) {
|
|
12157
|
-
if (targetDialect === "postgres") {
|
|
12158
|
-
return decoded;
|
|
12159
|
-
}
|
|
12160
|
-
return Array.isArray(decoded) ? JSON.stringify(decoded) : String(decoded);
|
|
12401
|
+
|
|
12402
|
+
// src/core/dt/index.ts
|
|
12403
|
+
var CONNECTION_WORKER = resolveWorker("connection");
|
|
12404
|
+
var COMPUTE_WORKER = resolveWorker("compute");
|
|
12405
|
+
function createDefaultComputePool() {
|
|
12406
|
+
return WorkerBridge.pool(COMPUTE_WORKER, {
|
|
12407
|
+
size: Math.max(1, availableParallelism() - 2)
|
|
12408
|
+
});
|
|
12161
12409
|
}
|
|
12162
|
-
function
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
case "decimal":
|
|
12171
|
-
return value;
|
|
12172
|
-
case "bool":
|
|
12173
|
-
if (targetDialect === "mssql") return value ? 1 : 0;
|
|
12174
|
-
if (targetDialect === "mysql") return value ? 1 : 0;
|
|
12175
|
-
return value;
|
|
12176
|
-
case "timestamp":
|
|
12177
|
-
if (typeof value === "string") return new Date(value);
|
|
12178
|
-
return value;
|
|
12179
|
-
case "date":
|
|
12180
|
-
return value;
|
|
12181
|
-
case "uuid":
|
|
12182
|
-
return value;
|
|
12183
|
-
case "int":
|
|
12184
|
-
case "float":
|
|
12185
|
-
return value;
|
|
12186
|
-
case "string":
|
|
12187
|
-
default:
|
|
12188
|
-
return value;
|
|
12410
|
+
async function createDefaultConnectionBridge(dialect, connectionString) {
|
|
12411
|
+
const bridge = new WorkerBridge(CONNECTION_WORKER);
|
|
12412
|
+
const [, connectErr] = await attempt(
|
|
12413
|
+
() => bridge.request("connect", { dialect, connectionString })
|
|
12414
|
+
);
|
|
12415
|
+
if (connectErr) {
|
|
12416
|
+
await attempt(() => bridge.shutdown());
|
|
12417
|
+
return [null, connectErr];
|
|
12189
12418
|
}
|
|
12419
|
+
return [bridge, null];
|
|
12190
12420
|
}
|
|
12191
|
-
|
|
12192
|
-
// src/core/dt/index.ts
|
|
12193
12421
|
async function exportTable(options) {
|
|
12194
12422
|
const startTime = Date.now();
|
|
12195
12423
|
const { db, dialect, tableName, filepath, passphrase, schema: schemaName } = options;
|
|
@@ -12215,58 +12443,178 @@ async function exportTable(options) {
|
|
|
12215
12443
|
if (openErr) {
|
|
12216
12444
|
return [null, openErr];
|
|
12217
12445
|
}
|
|
12218
|
-
let
|
|
12446
|
+
let ownedConnectionBridge = null;
|
|
12447
|
+
let ownedComputePool = null;
|
|
12448
|
+
let connectionBridge = options.connectionBridge;
|
|
12449
|
+
let computePool = options.computePool;
|
|
12450
|
+
if (!connectionBridge && options.connectionString) {
|
|
12451
|
+
const [bridge, bridgeErr] = await createDefaultConnectionBridge(dialect, options.connectionString);
|
|
12452
|
+
if (bridgeErr) {
|
|
12453
|
+
return [null, bridgeErr];
|
|
12454
|
+
}
|
|
12455
|
+
connectionBridge = bridge;
|
|
12456
|
+
ownedConnectionBridge = bridge;
|
|
12457
|
+
}
|
|
12458
|
+
if (!computePool) {
|
|
12459
|
+
computePool = createDefaultComputePool();
|
|
12460
|
+
ownedComputePool = computePool;
|
|
12461
|
+
}
|
|
12462
|
+
const [result, workerErr] = await exportTableWithWorkers({
|
|
12463
|
+
writer,
|
|
12464
|
+
dtSchema,
|
|
12465
|
+
tableName,
|
|
12466
|
+
filepath,
|
|
12467
|
+
dialect,
|
|
12468
|
+
batchSize,
|
|
12469
|
+
connectionBridge,
|
|
12470
|
+
computePool,
|
|
12471
|
+
kyselyDb
|
|
12472
|
+
});
|
|
12473
|
+
if (ownedComputePool) {
|
|
12474
|
+
await attempt(() => ownedComputePool.shutdown());
|
|
12475
|
+
}
|
|
12476
|
+
if (ownedConnectionBridge) {
|
|
12477
|
+
await attempt(() => ownedConnectionBridge.shutdown());
|
|
12478
|
+
}
|
|
12479
|
+
if (workerErr) {
|
|
12480
|
+
return [null, workerErr];
|
|
12481
|
+
}
|
|
12482
|
+
const durationMs = Date.now() - startTime;
|
|
12483
|
+
observer.emit("dt:export:complete", {
|
|
12484
|
+
filepath,
|
|
12485
|
+
table: tableName,
|
|
12486
|
+
rowsWritten: result.rowsWritten,
|
|
12487
|
+
bytesWritten: result.bytesWritten,
|
|
12488
|
+
durationMs
|
|
12489
|
+
});
|
|
12490
|
+
return [result, null];
|
|
12491
|
+
}
|
|
12492
|
+
function getQuoteIdent(dialect) {
|
|
12493
|
+
if (dialect === "mssql") return (c) => `[${c}]`;
|
|
12494
|
+
if (dialect === "mysql") return (c) => `\`${c}\``;
|
|
12495
|
+
return (c) => `"${c}"`;
|
|
12496
|
+
}
|
|
12497
|
+
function buildBatchSql(dialect, tableName, columnList, orderCol, batchSize, offset) {
|
|
12498
|
+
if (dialect === "mssql") {
|
|
12499
|
+
return `SELECT ${columnList} FROM [${tableName}] ORDER BY ${orderCol} OFFSET ${offset} ROWS FETCH NEXT ${batchSize} ROWS ONLY`;
|
|
12500
|
+
}
|
|
12501
|
+
return `SELECT ${columnList} FROM "${tableName}" LIMIT ${batchSize} OFFSET ${offset}`;
|
|
12502
|
+
}
|
|
12503
|
+
async function exportTableWithWorkers(ctx) {
|
|
12504
|
+
const { writer, dtSchema, tableName, filepath, dialect, batchSize } = ctx;
|
|
12505
|
+
const { connectionBridge, computePool, kyselyDb } = ctx;
|
|
12506
|
+
const backpressureLimit = batchSize * 3;
|
|
12507
|
+
let totalRows = 0;
|
|
12508
|
+
if (connectionBridge) {
|
|
12509
|
+
const countSql = dialect === "mssql" ? `SELECT COUNT(*) AS cnt FROM [${tableName}]` : `SELECT COUNT(*) AS cnt FROM "${tableName}"`;
|
|
12510
|
+
const [countResult, countErr] = await attempt(
|
|
12511
|
+
() => connectionBridge.request("query", { sql: countSql })
|
|
12512
|
+
);
|
|
12513
|
+
if (countErr) {
|
|
12514
|
+
return [null, countErr];
|
|
12515
|
+
}
|
|
12516
|
+
const firstRow = countResult.rows[0];
|
|
12517
|
+
totalRows = Number(firstRow?.["cnt"] ?? 0);
|
|
12518
|
+
}
|
|
12219
12519
|
const columns = dtSchema.columns.map((c) => c.name);
|
|
12220
|
-
const quoteIdent4 =
|
|
12520
|
+
const quoteIdent4 = getQuoteIdent(dialect);
|
|
12221
12521
|
const columnList = columns.map(quoteIdent4).join(", ");
|
|
12522
|
+
const orderCol = quoteIdent4(columns[0]);
|
|
12523
|
+
let globalIndex = 0;
|
|
12524
|
+
let loaded = 0;
|
|
12525
|
+
let processed = 0;
|
|
12526
|
+
let saved = 0;
|
|
12527
|
+
let inFlight = 0;
|
|
12528
|
+
let pipelineError = null;
|
|
12529
|
+
const orderBuffer = new OrderBuffer((values) => {
|
|
12530
|
+
writer.writeRow(values);
|
|
12531
|
+
saved++;
|
|
12532
|
+
inFlight--;
|
|
12533
|
+
observer.emit("dt:export:saved", { table: tableName, saved, totalRows });
|
|
12534
|
+
});
|
|
12535
|
+
let offset = 0;
|
|
12222
12536
|
while (true) {
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12537
|
+
while (inFlight >= backpressureLimit) {
|
|
12538
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1));
|
|
12539
|
+
}
|
|
12540
|
+
let batchRows;
|
|
12541
|
+
if (connectionBridge) {
|
|
12542
|
+
const batchSql = buildBatchSql(dialect, tableName, columnList, orderCol, batchSize, offset);
|
|
12543
|
+
const [queryResult, queryErr] = await attempt(
|
|
12544
|
+
() => connectionBridge.request("query", { sql: batchSql })
|
|
12545
|
+
);
|
|
12546
|
+
if (queryErr) {
|
|
12547
|
+
pipelineError = queryErr;
|
|
12548
|
+
break;
|
|
12549
|
+
}
|
|
12550
|
+
if (queryResult.error) {
|
|
12551
|
+
pipelineError = new Error(queryResult.error);
|
|
12552
|
+
break;
|
|
12553
|
+
}
|
|
12554
|
+
batchRows = queryResult.rows;
|
|
12555
|
+
} else {
|
|
12556
|
+
const [rows, fetchErr] = await attempt(() => {
|
|
12557
|
+
if (dialect === "mssql") {
|
|
12558
|
+
return sql`
|
|
12559
|
+
SELECT ${sql.raw(columnList)}
|
|
12560
|
+
FROM ${sql.table(tableName)}
|
|
12561
|
+
ORDER BY ${sql.raw(orderCol)}
|
|
12562
|
+
OFFSET ${offset} ROWS
|
|
12563
|
+
FETCH NEXT ${batchSize} ROWS ONLY
|
|
12564
|
+
`.execute(kyselyDb);
|
|
12565
|
+
}
|
|
12226
12566
|
return sql`
|
|
12227
12567
|
SELECT ${sql.raw(columnList)}
|
|
12228
12568
|
FROM ${sql.table(tableName)}
|
|
12229
|
-
|
|
12230
|
-
OFFSET ${offset}
|
|
12231
|
-
FETCH NEXT ${batchSize} ROWS ONLY
|
|
12569
|
+
LIMIT ${batchSize}
|
|
12570
|
+
OFFSET ${offset}
|
|
12232
12571
|
`.execute(kyselyDb);
|
|
12572
|
+
});
|
|
12573
|
+
if (fetchErr) {
|
|
12574
|
+
pipelineError = fetchErr;
|
|
12575
|
+
break;
|
|
12233
12576
|
}
|
|
12234
|
-
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
12577
|
+
batchRows = rows.rows;
|
|
12578
|
+
}
|
|
12579
|
+
if (batchRows.length === 0) break;
|
|
12580
|
+
loaded += batchRows.length;
|
|
12581
|
+
observer.emit("dt:export:loaded", { table: tableName, loaded, totalRows });
|
|
12582
|
+
for (const row of batchRows) {
|
|
12583
|
+
const rowIndex = globalIndex++;
|
|
12584
|
+
inFlight++;
|
|
12585
|
+
computePool.request("serialize", {
|
|
12586
|
+
row,
|
|
12587
|
+
columns: dtSchema.columns,
|
|
12588
|
+
index: rowIndex
|
|
12589
|
+
}).then((result) => {
|
|
12590
|
+
processed++;
|
|
12591
|
+
observer.emit("dt:export:processed", { table: tableName, processed, totalRows });
|
|
12592
|
+
if (result.error) {
|
|
12593
|
+
pipelineError = pipelineError ?? new Error(result.error);
|
|
12594
|
+
return;
|
|
12595
|
+
}
|
|
12596
|
+
orderBuffer.add(result.index, result.values);
|
|
12597
|
+
});
|
|
12248
12598
|
}
|
|
12249
|
-
offset +=
|
|
12599
|
+
offset += batchRows.length;
|
|
12250
12600
|
observer.emit("dt:export:progress", {
|
|
12251
12601
|
filepath,
|
|
12252
12602
|
table: tableName,
|
|
12253
12603
|
rowsWritten: writer.rowsWritten,
|
|
12254
12604
|
bytesWritten: writer.bytesWritten
|
|
12255
12605
|
});
|
|
12256
|
-
if (
|
|
12606
|
+
if (batchRows.length < batchSize) break;
|
|
12607
|
+
}
|
|
12608
|
+
while (inFlight > 0) {
|
|
12609
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1));
|
|
12610
|
+
}
|
|
12611
|
+
if (pipelineError) {
|
|
12612
|
+
return [null, pipelineError];
|
|
12257
12613
|
}
|
|
12258
12614
|
const [, closeErr] = await attempt(() => writer.close());
|
|
12259
12615
|
if (closeErr) {
|
|
12260
12616
|
return [null, closeErr];
|
|
12261
12617
|
}
|
|
12262
|
-
const durationMs = Date.now() - startTime;
|
|
12263
|
-
observer.emit("dt:export:complete", {
|
|
12264
|
-
filepath,
|
|
12265
|
-
table: tableName,
|
|
12266
|
-
rowsWritten: writer.rowsWritten,
|
|
12267
|
-
bytesWritten: writer.bytesWritten,
|
|
12268
|
-
durationMs
|
|
12269
|
-
});
|
|
12270
12618
|
return [{ rowsWritten: writer.rowsWritten, bytesWritten: writer.bytesWritten }, null];
|
|
12271
12619
|
}
|
|
12272
12620
|
async function importDtFile(options) {
|
|
@@ -12326,70 +12674,158 @@ async function importDtFile(options) {
|
|
|
12326
12674
|
return [null, err];
|
|
12327
12675
|
}
|
|
12328
12676
|
}
|
|
12677
|
+
let ownedComputePool = null;
|
|
12678
|
+
let computePool = options.computePool;
|
|
12679
|
+
if (!computePool) {
|
|
12680
|
+
computePool = createDefaultComputePool();
|
|
12681
|
+
ownedComputePool = computePool;
|
|
12682
|
+
}
|
|
12683
|
+
const [result, workerErr] = await importFileWithWorkers({
|
|
12684
|
+
reader,
|
|
12685
|
+
dtSchema,
|
|
12686
|
+
tableName,
|
|
12687
|
+
filepath,
|
|
12688
|
+
dialect,
|
|
12689
|
+
version,
|
|
12690
|
+
batchSize,
|
|
12691
|
+
onConflict: options.onConflict ?? "fail",
|
|
12692
|
+
computePool,
|
|
12693
|
+
kyselyDb
|
|
12694
|
+
});
|
|
12695
|
+
if (ownedComputePool) {
|
|
12696
|
+
await attempt(() => ownedComputePool.shutdown());
|
|
12697
|
+
}
|
|
12698
|
+
if (workerErr) {
|
|
12699
|
+
reader.close();
|
|
12700
|
+
observer.emit("error", { source: "dt:import", error: workerErr, context: { filepath, table: tableName } });
|
|
12701
|
+
return [null, workerErr];
|
|
12702
|
+
}
|
|
12703
|
+
reader.close();
|
|
12704
|
+
const durationMs = Date.now() - startTime;
|
|
12705
|
+
observer.emit("dt:import:complete", {
|
|
12706
|
+
filepath,
|
|
12707
|
+
table: tableName,
|
|
12708
|
+
rowsImported: result.rowsImported,
|
|
12709
|
+
rowsSkipped: result.rowsSkipped,
|
|
12710
|
+
durationMs
|
|
12711
|
+
});
|
|
12712
|
+
return [result, null];
|
|
12713
|
+
}
|
|
12714
|
+
async function importFileWithWorkers(ctx) {
|
|
12715
|
+
const { reader, dtSchema, tableName, filepath, dialect, version, batchSize } = ctx;
|
|
12716
|
+
const { onConflict, computePool, kyselyDb } = ctx;
|
|
12329
12717
|
let rowsImported = 0;
|
|
12330
12718
|
let rowsSkipped = 0;
|
|
12331
|
-
let
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
const [batchResult, insertErr] = await insertImportBatch(
|
|
12343
|
-
kyselyDb,
|
|
12344
|
-
tableName,
|
|
12345
|
-
batch,
|
|
12346
|
-
options.onConflict ?? "fail",
|
|
12347
|
-
dialect,
|
|
12348
|
-
columnNames
|
|
12349
|
-
);
|
|
12350
|
-
if (insertErr) {
|
|
12351
|
-
reader.close();
|
|
12352
|
-
observer.emit("error", { source: "dt:import", error: insertErr, context: { filepath, table: tableName } });
|
|
12353
|
-
return [null, insertErr];
|
|
12354
|
-
}
|
|
12355
|
-
rowsImported += batchResult.inserted + batchResult.updated;
|
|
12356
|
-
rowsSkipped += batchResult.skipped;
|
|
12357
|
-
batch = [];
|
|
12358
|
-
observer.emit("dt:import:progress", {
|
|
12359
|
-
filepath,
|
|
12360
|
-
table: tableName,
|
|
12361
|
-
rowsImported,
|
|
12362
|
-
rowsSkipped
|
|
12363
|
-
});
|
|
12364
|
-
}
|
|
12365
|
-
}
|
|
12366
|
-
if (batch.length > 0) {
|
|
12367
|
-
const columnNames = dtSchema.columns.map((c) => c.name);
|
|
12719
|
+
let loaded = 0;
|
|
12720
|
+
let processed = 0;
|
|
12721
|
+
let saved = 0;
|
|
12722
|
+
let globalIndex = 0;
|
|
12723
|
+
let inFlight = 0;
|
|
12724
|
+
let pipelineError = null;
|
|
12725
|
+
let totalRows = 0;
|
|
12726
|
+
let insertBatch2 = [];
|
|
12727
|
+
const columnNames = dtSchema.columns.map((c) => c.name);
|
|
12728
|
+
const flushInsertBatch = async () => {
|
|
12729
|
+
if (insertBatch2.length === 0) return null;
|
|
12368
12730
|
const [batchResult, insertErr] = await insertImportBatch(
|
|
12369
12731
|
kyselyDb,
|
|
12370
12732
|
tableName,
|
|
12371
|
-
|
|
12372
|
-
|
|
12733
|
+
insertBatch2,
|
|
12734
|
+
onConflict,
|
|
12373
12735
|
dialect,
|
|
12374
12736
|
columnNames
|
|
12375
12737
|
);
|
|
12376
12738
|
if (insertErr) {
|
|
12377
|
-
|
|
12378
|
-
observer.emit("error", { source: "dt:import", error: insertErr, context: { filepath, table: tableName } });
|
|
12379
|
-
return [null, insertErr];
|
|
12739
|
+
return insertErr;
|
|
12380
12740
|
}
|
|
12381
12741
|
rowsImported += batchResult.inserted + batchResult.updated;
|
|
12382
12742
|
rowsSkipped += batchResult.skipped;
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12743
|
+
insertBatch2 = [];
|
|
12744
|
+
observer.emit("dt:import:progress", {
|
|
12745
|
+
filepath,
|
|
12746
|
+
table: tableName,
|
|
12747
|
+
rowsImported,
|
|
12748
|
+
rowsSkipped
|
|
12749
|
+
});
|
|
12750
|
+
return null;
|
|
12751
|
+
};
|
|
12752
|
+
const orderBuffer = new OrderBuffer((record) => {
|
|
12753
|
+
insertBatch2.push(record);
|
|
12754
|
+
saved++;
|
|
12755
|
+
inFlight--;
|
|
12756
|
+
observer.emit("dt:import:saved", { table: tableName, saved, totalRows });
|
|
12392
12757
|
});
|
|
12758
|
+
let readBatch = [];
|
|
12759
|
+
for await (const values of reader.rows()) {
|
|
12760
|
+
readBatch.push(values);
|
|
12761
|
+
loaded++;
|
|
12762
|
+
totalRows = Math.max(totalRows, loaded);
|
|
12763
|
+
if (readBatch.length >= batchSize) {
|
|
12764
|
+
observer.emit("dt:import:loaded", { table: tableName, loaded, totalRows });
|
|
12765
|
+
for (const rowValues of readBatch) {
|
|
12766
|
+
const rowIndex = globalIndex++;
|
|
12767
|
+
inFlight++;
|
|
12768
|
+
computePool.request("deserialize", {
|
|
12769
|
+
values: rowValues,
|
|
12770
|
+
columns: dtSchema.columns,
|
|
12771
|
+
targetDialect: dialect,
|
|
12772
|
+
targetVersion: version ? `${version.major}.${version.minor}` : void 0,
|
|
12773
|
+
index: rowIndex
|
|
12774
|
+
}).then((result) => {
|
|
12775
|
+
processed++;
|
|
12776
|
+
observer.emit("dt:import:processed", { table: tableName, processed, totalRows });
|
|
12777
|
+
if (result.error) {
|
|
12778
|
+
pipelineError = pipelineError ?? new Error(result.error);
|
|
12779
|
+
return;
|
|
12780
|
+
}
|
|
12781
|
+
orderBuffer.add(result.index, result.record);
|
|
12782
|
+
});
|
|
12783
|
+
}
|
|
12784
|
+
readBatch = [];
|
|
12785
|
+
while (inFlight > 0) {
|
|
12786
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1));
|
|
12787
|
+
}
|
|
12788
|
+
if (pipelineError) {
|
|
12789
|
+
return [null, pipelineError];
|
|
12790
|
+
}
|
|
12791
|
+
const flushErr = await flushInsertBatch();
|
|
12792
|
+
if (flushErr) {
|
|
12793
|
+
return [null, flushErr];
|
|
12794
|
+
}
|
|
12795
|
+
}
|
|
12796
|
+
}
|
|
12797
|
+
if (readBatch.length > 0) {
|
|
12798
|
+
observer.emit("dt:import:loaded", { table: tableName, loaded, totalRows });
|
|
12799
|
+
for (const rowValues of readBatch) {
|
|
12800
|
+
const rowIndex = globalIndex++;
|
|
12801
|
+
inFlight++;
|
|
12802
|
+
computePool.request("deserialize", {
|
|
12803
|
+
values: rowValues,
|
|
12804
|
+
columns: dtSchema.columns,
|
|
12805
|
+
targetDialect: dialect,
|
|
12806
|
+
targetVersion: version ? `${version.major}.${version.minor}` : void 0,
|
|
12807
|
+
index: rowIndex
|
|
12808
|
+
}).then((result) => {
|
|
12809
|
+
processed++;
|
|
12810
|
+
observer.emit("dt:import:processed", { table: tableName, processed, totalRows });
|
|
12811
|
+
if (result.error) {
|
|
12812
|
+
pipelineError = pipelineError ?? new Error(result.error);
|
|
12813
|
+
return;
|
|
12814
|
+
}
|
|
12815
|
+
orderBuffer.add(result.index, result.record);
|
|
12816
|
+
});
|
|
12817
|
+
}
|
|
12818
|
+
while (inFlight > 0) {
|
|
12819
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1));
|
|
12820
|
+
}
|
|
12821
|
+
if (pipelineError) {
|
|
12822
|
+
return [null, pipelineError];
|
|
12823
|
+
}
|
|
12824
|
+
const flushErr = await flushInsertBatch();
|
|
12825
|
+
if (flushErr) {
|
|
12826
|
+
return [null, flushErr];
|
|
12827
|
+
}
|
|
12828
|
+
}
|
|
12393
12829
|
return [{ rowsImported, rowsSkipped }, null];
|
|
12394
12830
|
}
|
|
12395
12831
|
async function insertImportBatch(db, table, rows, onConflict, dialect, columns) {
|