@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/index.js CHANGED
@@ -1,19 +1,22 @@
1
- import { processFile, DtReader, isTemplate, FORMAT_VERSION, DT_EXTENSIONS, encryptWithPassphrase, ENCODED_TYPES, GZIP_THRESHOLD, GZIP_RATIO_THRESHOLD } from './chunk-BTXUEWYQ.js';
2
- import { initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey } from './chunk-LJDHQEWW.js';
3
- import { observer } from './chunk-4HW4CS2C.js';
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 { createGzip, gunzipSync, gzipSync } from 'zlib';
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
- new Promise((resolve) => setTimeout(resolve, CLOSE_TIMEOUT))
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-7VB7EHRX.js')).createMssqlConnection;
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
- if (dialect === "postgres") {
2045
- await sql`CREATE SCHEMA IF NOT EXISTS noorm`.execute(trx);
2046
- } else {
2047
- await sql`IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = 'noorm') EXEC('CREATE SCHEMA noorm')`.execute(trx);
2048
- }
2049
- if (dialect === "postgres") {
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 constraint_name
2052
- FROM information_schema.table_constraints
2053
- WHERE table_name = '__noorm_executions__'
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
- for (const row of rows) {
2057
- await sql`ALTER TABLE __noorm_executions__ DROP CONSTRAINT ${sql.ref(row.constraint_name)}`.execute(trx);
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 AS 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
- WHERE t.name = '__noorm_executions__'
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
- for (const row of rows) {
2067
- await sql`ALTER TABLE __noorm_executions__ DROP CONSTRAINT ${sql.ref(row.fk_name)}`.execute(trx);
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
- for (const idx of INDEXES) {
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
- const prefixedTables = {
2076
- executions: "__noorm_executions__",
2077
- change: "__noorm_change__",
2078
- vault: "__noorm_vault__"
2079
- };
2080
- for (const idx of INDEXES) {
2081
- const tableName = prefixedTables[idx.table];
2082
- await sql`
2083
- IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = ${idx.name})
2084
- DROP INDEX ${sql.ref(idx.name)} ON ${sql.table(tableName)}
2085
- `.execute(trx);
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 t of TABLE_MAP) {
2089
- if (dialect === "postgres") {
2090
- await sql`ALTER TABLE ${sql.table(t.old)} SET SCHEMA noorm`.execute(trx);
2091
- } else {
2092
- await sql`ALTER SCHEMA noorm TRANSFER ${sql.ref(`dbo.${t.old}`)}`.execute(trx);
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
- const cols = idx.columns.join(", ");
2110
- await sql`CREATE INDEX ${sql.ref(idx.name)} ON ${sql.table(`noorm.${idx.table}`)} (${sql.raw(cols)})`.execute(trx);
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 prefixedTables = {
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
- if (status.current === 0) {
2310
- await bootstrapSchema(db, dialect);
2311
- return;
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: status.current,
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 > status.current);
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: status.current,
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 [result, err] = await attempt(
3336
- () => this.#ndb.insertInto(this.#tables.change).values({
3337
- name: data2.name,
3338
- change_type: "change",
3339
- direction: data2.direction,
3340
- status: "pending",
3341
- config_name: this.#configName,
3342
- executed_by: data2.executedBy
3343
- }).returning("id").executeTakeFirstOrThrow()
3344
- );
3345
- if (err) {
3346
- throw new Error("Failed to create change operation record", { cause: err });
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 (lastIdErr || !lastIdResult?.rows?.[0]?.id) {
3354
- throw new Error("Failed to retrieve last insert row id");
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
- () => this.#ndb.insertInto(this.#tables.change).values({
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 [result, err] = await attempt(
3817
- () => this.#ndb.insertInto(this.#tables.change).values({
3818
- name: data2.name,
3819
- change_type: data2.changeType,
3820
- direction: dbDirection,
3821
- status: "pending",
3822
- config_name: data2.configName,
3823
- executed_by: data2.executedBy
3824
- }).returning("id").executeTakeFirstOrThrow()
3825
- );
3826
- if (err) {
3827
- throw new Error("Failed to create operation record", { cause: err });
3828
- }
3829
- let id = result?.id;
3830
- if (id === null || id === void 0) {
3831
- const [lastIdResult, lastIdErr] = await attempt(
3832
- () => sql`SELECT last_insert_rowid() as id`.execute(this.#db)
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 (lastIdErr || !lastIdResult?.rows?.[0]?.id) {
3835
- throw new Error("Failed to retrieve last insert row id");
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: 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((resolve) => setTimeout(resolve, ms));
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(discoverErr.message, performance.now() - start);
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(discoverErr.message, performance.now() - start);
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(createErr.message, performance.now() - start);
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(createRecordsErr, performance.now() - start);
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 due to failure in ${file.path}`
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.message,
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.message
6285
+ getErrorMessage(execErr)
6072
6286
  );
6073
6287
  observer.emit("file:after", {
6074
6288
  filepath,
6075
6289
  status: "failed",
6076
6290
  durationMs,
6077
- error: execErr.message
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.message,
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.message,
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.message
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 = null;
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 meta = tablesByName[name];
10685
- if (!meta) continue;
10929
+ const meta2 = tablesByName[name];
10930
+ if (!meta2) continue;
10686
10931
  tablePlans.push({
10687
- name: meta.name,
10688
- schema: meta.schema,
10689
- rowCount: meta.rowCount,
10690
- hasIdentity: meta.hasIdentity,
10691
- identityColumn: meta.identityColumn,
10692
- primaryKey: meta.primaryKey,
10693
- columns: meta.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 = null;
11238
- let tableErr = null;
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((resolve, reject) => {
11971
- this.#stream.end(() => resolve());
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
- function serializeRow(options) {
11989
- const { row, columns } = options;
11990
- const result = [];
11991
- for (const col of columns) {
11992
- const value = row[col.name];
11993
- result.push(serializeValue(value, col));
11994
- }
11995
- return result;
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 convertSimpleForTarget(value, type, targetDialect) {
12163
- switch (type) {
12164
- case "bigint":
12165
- if (typeof value === "string") {
12166
- const num = Number(value);
12167
- if (Number.isSafeInteger(num)) return num;
12168
- }
12169
- return value;
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 offset = 0;
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 = dialect === "mssql" ? (c) => `[${c}]` : dialect === "mysql" ? (c) => `\`${c}\`` : (c) => `"${c}"`;
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
- const [rows, fetchErr] = await attempt(() => {
12224
- if (dialect === "mssql") {
12225
- const orderCol = quoteIdent4(columns[0]);
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
- ORDER BY ${sql.raw(orderCol)}
12230
- OFFSET ${offset} ROWS
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
- return sql`
12235
- SELECT ${sql.raw(columnList)}
12236
- FROM ${sql.table(tableName)}
12237
- LIMIT ${batchSize}
12238
- OFFSET ${offset}
12239
- `.execute(kyselyDb);
12240
- });
12241
- if (fetchErr) {
12242
- return [null, fetchErr];
12243
- }
12244
- if (rows.rows.length === 0) break;
12245
- for (const row of rows.rows) {
12246
- const values = serializeRow({ row, columns: dtSchema.columns });
12247
- writer.writeRow(values);
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 += rows.rows.length;
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 (rows.rows.length < batchSize) break;
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 batch = [];
12332
- for await (const values of reader.rows()) {
12333
- const row = deserializeRow({
12334
- values,
12335
- columns: dtSchema.columns,
12336
- targetDialect: dialect,
12337
- targetVersion: version
12338
- });
12339
- batch.push(row);
12340
- if (batch.length >= batchSize) {
12341
- const columnNames = dtSchema.columns.map((c) => c.name);
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
- batch,
12372
- options.onConflict ?? "fail",
12733
+ insertBatch2,
12734
+ onConflict,
12373
12735
  dialect,
12374
12736
  columnNames
12375
12737
  );
12376
12738
  if (insertErr) {
12377
- reader.close();
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
- reader.close();
12385
- const durationMs = Date.now() - startTime;
12386
- observer.emit("dt:import:complete", {
12387
- filepath,
12388
- table: tableName,
12389
- rowsImported,
12390
- rowsSkipped,
12391
- durationMs
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) {