@noormdev/sdk 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-6LG4ENOJ.js +1523 -0
- package/dist/{chunk-G4B7EHL7.js → chunk-M7Q44VFJ.js} +5 -4
- package/dist/chunk-OXTXFKWJ.js +188 -0
- package/dist/{chunk-LY2F7ZFI.js → chunk-TXDGFA44.js} +3 -1518
- package/dist/{chunk-FSVO6NAJ.js → chunk-WUMCIKEQ.js} +4 -3
- package/dist/{engine-MZQ364EH.js → engine-P74UTJQO.js} +5 -4
- package/dist/index.d.ts +55 -0
- package/dist/index.js +480 -67
- package/dist/{mssql-3BQB6ACD.js → mssql-II62FO2B.js} +22 -39
- package/dist/{storage-V5I67ANZ.js → storage-FBDOY3MC.js} +5 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { createRequire } from 'module';
|
|
2
|
-
import { processFile, es_default, DtReader, isTemplate, FORMAT_VERSION, DT_EXTENSIONS, encryptWithPassphrase, ENCODED_TYPES } from './chunk-
|
|
2
|
+
import { processFile, es_default, DtReader, isTemplate, FORMAT_VERSION, DT_EXTENSIONS, encryptWithPassphrase, ENCODED_TYPES } from './chunk-M7Q44VFJ.js';
|
|
3
3
|
import './chunk-UOFJT5PY.js';
|
|
4
|
-
import { resolveLegacyAccess, checkConfigPolicy, initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey, assertVaultPolicy, assertPolicy, detectAgentHarness, guarded } from './chunk-
|
|
5
|
-
import {
|
|
6
|
-
export { observer as noormObserver } from './chunk-
|
|
4
|
+
import { resolveLegacyAccess, checkConfigPolicy, initializeVault, getVaultStatus, setVaultSecret, getVaultSecret, getAllVaultSecrets, listVaultSecretKeys, deleteVaultSecret, vaultSecretExists, getVaultKey, noormDb, getNoormTables, encryptVaultKey, assertVaultPolicy, assertPolicy, detectAgentHarness, guarded } from './chunk-WUMCIKEQ.js';
|
|
5
|
+
import { observer, getEnvConfigName, ObserverRelay, isDebug } from './chunk-6LG4ENOJ.js';
|
|
6
|
+
export { observer as noormObserver } from './chunk-6LG4ENOJ.js';
|
|
7
|
+
import { explainConnectionError, DatabaseConnectionError } from './chunk-OXTXFKWJ.js';
|
|
8
|
+
import { makeNestedConfig, gigabytes, attempt, attemptSync, clone, merge, equals, retry, runWithTimeout, isTimeoutError, wait } from './chunk-TXDGFA44.js';
|
|
7
9
|
import { external_exports, PortSchema, connectTimeoutFor, DEFAULT_PORTS } from './chunk-WE2JLJMT.js';
|
|
8
10
|
import { dist_default } from './chunk-TBRUDUBQ.js';
|
|
9
11
|
import { require_dist } from './chunk-YJBKNUTP.js';
|
|
@@ -2926,7 +2928,7 @@ async function getDialectFactory(dialect) {
|
|
|
2926
2928
|
case "mysql":
|
|
2927
2929
|
return (await import('./mysql-32B4SXWA.js')).createMysqlConnection;
|
|
2928
2930
|
case "mssql":
|
|
2929
|
-
return (await import('./mssql-
|
|
2931
|
+
return (await import('./mssql-II62FO2B.js')).createMssqlConnection;
|
|
2930
2932
|
default:
|
|
2931
2933
|
throw new Error(`Unsupported dialect: ${dialect}`);
|
|
2932
2934
|
}
|
|
@@ -2997,6 +2999,9 @@ async function openConnection(config, configName, retryOptions, signal) {
|
|
|
2997
2999
|
backoff,
|
|
2998
3000
|
jitterFactor: 0.1,
|
|
2999
3001
|
signal,
|
|
3002
|
+
// Otherwise running out of retries reads "Max retries
|
|
3003
|
+
// reached", and the server's own error is lost.
|
|
3004
|
+
throwLastError: true,
|
|
3000
3005
|
shouldRetry: (err2) => {
|
|
3001
3006
|
if (err2 instanceof OperationAbortedError) return false;
|
|
3002
3007
|
const msg = err2.message.toLowerCase();
|
|
@@ -3014,8 +3019,10 @@ async function openConnection(config, configName, retryOptions, signal) {
|
|
|
3014
3019
|
)
|
|
3015
3020
|
);
|
|
3016
3021
|
if (err) {
|
|
3017
|
-
|
|
3018
|
-
|
|
3022
|
+
const explained = explainConnectionError(err, config);
|
|
3023
|
+
const server = explained instanceof DatabaseConnectionError ? { serverCode: explained.serverCode, serverMessage: explained.serverMessage } : {};
|
|
3024
|
+
observer.emit("connection:error", { configName, error: explained.message, ...server });
|
|
3025
|
+
throw explained;
|
|
3019
3026
|
}
|
|
3020
3027
|
const manager = getConnectionManager();
|
|
3021
3028
|
const trackId = manager.track(conn, configName);
|
|
@@ -3055,15 +3062,21 @@ var SYSTEM_DATABASES = {
|
|
|
3055
3062
|
// SQLite creates the file on connect
|
|
3056
3063
|
mssql: "master"
|
|
3057
3064
|
};
|
|
3058
|
-
async function
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
}
|
|
3065
|
+
async function probeConnection(config, signal) {
|
|
3066
|
+
const [conn, err] = await attempt(
|
|
3067
|
+
() => createConnection(config, "__test__", {}, signal)
|
|
3068
|
+
);
|
|
3069
|
+
if (err) {
|
|
3070
|
+
if (err instanceof OperationAbortedError) {
|
|
3071
|
+
return { ok: false, error: err.message, aborted: true };
|
|
3072
|
+
}
|
|
3073
|
+
return { ok: false, error: err.message };
|
|
3066
3074
|
}
|
|
3075
|
+
await conn.destroy();
|
|
3076
|
+
return { ok: true };
|
|
3077
|
+
}
|
|
3078
|
+
async function testConnection(config, options = {}) {
|
|
3079
|
+
const systemDb = SYSTEM_DATABASES[config.dialect];
|
|
3067
3080
|
if (options.testServerOnly && config.dialect === "sqlite") {
|
|
3068
3081
|
const filename = config.filename ?? config.database;
|
|
3069
3082
|
if (filename !== ":memory:" && !existsSync(filename)) {
|
|
@@ -3074,17 +3087,12 @@ async function testConnection(config, options = {}) {
|
|
|
3074
3087
|
return { ok: true };
|
|
3075
3088
|
}
|
|
3076
3089
|
}
|
|
3077
|
-
const
|
|
3078
|
-
|
|
3079
|
-
)
|
|
3080
|
-
|
|
3081
|
-
if (err instanceof OperationAbortedError) {
|
|
3082
|
-
return { ok: false, error: err.message, aborted: true };
|
|
3083
|
-
}
|
|
3084
|
-
return { ok: false, error: err.message };
|
|
3090
|
+
const result = await probeConnection(config, options.signal);
|
|
3091
|
+
const targetMissing = !result.ok && !result.aborted && !!result.error?.includes("does not exist");
|
|
3092
|
+
if (options.testServerOnly && systemDb && targetMissing) {
|
|
3093
|
+
return probeConnection({ ...config, database: systemDb }, options.signal);
|
|
3085
3094
|
}
|
|
3086
|
-
|
|
3087
|
-
return { ok: true };
|
|
3095
|
+
return result;
|
|
3088
3096
|
}
|
|
3089
3097
|
function addIdColumn(builder, dialect) {
|
|
3090
3098
|
if (dialect === "postgres") {
|
|
@@ -6011,6 +6019,349 @@ function computeCombinedChecksum(checksums) {
|
|
|
6011
6019
|
const combined = sorted.join("");
|
|
6012
6020
|
return createHash("sha256").update(combined, "utf-8").digest("hex");
|
|
6013
6021
|
}
|
|
6022
|
+
var SESSION_ID_SQL = {
|
|
6023
|
+
postgres: "select pg_backend_pid() as id",
|
|
6024
|
+
mysql: "select connection_id() as id",
|
|
6025
|
+
mssql: "select @@SPID as id"
|
|
6026
|
+
};
|
|
6027
|
+
var SERVER_CANCEL = {
|
|
6028
|
+
postgres: (db, sessionId) => sql`select pg_cancel_backend(${sessionId})`.execute(db),
|
|
6029
|
+
// KILL cannot be prepared, so the id is interpolated. It comes from
|
|
6030
|
+
// connection_id() and is checked by readSessionId before it gets here,
|
|
6031
|
+
// never from user input.
|
|
6032
|
+
mysql: (db, sessionId) => sql.raw(`kill query ${sessionId}`).execute(db)
|
|
6033
|
+
};
|
|
6034
|
+
function readSessionId(rows) {
|
|
6035
|
+
const raw = rows[0]?.id;
|
|
6036
|
+
const id = Number(raw);
|
|
6037
|
+
if (!Number.isInteger(id) || id <= 0) return void 0;
|
|
6038
|
+
return id;
|
|
6039
|
+
}
|
|
6040
|
+
function percentOf(done, total) {
|
|
6041
|
+
if (done === null || total === null || total <= 0) return null;
|
|
6042
|
+
return Math.min(100, done / total * 100);
|
|
6043
|
+
}
|
|
6044
|
+
function toNumber(value) {
|
|
6045
|
+
if (value === null || value === void 0) return null;
|
|
6046
|
+
const n = Number(value);
|
|
6047
|
+
return Number.isFinite(n) ? n : null;
|
|
6048
|
+
}
|
|
6049
|
+
function toText(value) {
|
|
6050
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
6051
|
+
return String(value);
|
|
6052
|
+
}
|
|
6053
|
+
async function rowsOrEmpty(query) {
|
|
6054
|
+
const [result, err] = await attempt(query);
|
|
6055
|
+
return err ? [] : result.rows;
|
|
6056
|
+
}
|
|
6057
|
+
function toBlockers(rows) {
|
|
6058
|
+
return rows.map((row) => ({
|
|
6059
|
+
pid: Number(row.pid),
|
|
6060
|
+
query: toText(row.query),
|
|
6061
|
+
ageMs: toNumber(row.age_ms)
|
|
6062
|
+
}));
|
|
6063
|
+
}
|
|
6064
|
+
var postgresProbe = async (side, pid) => {
|
|
6065
|
+
const [activity] = await rowsOrEmpty(() => sql`
|
|
6066
|
+
select a.state, a.wait_event_type, a.wait_event,
|
|
6067
|
+
pg_blocking_pids(a.pid) as blockers,
|
|
6068
|
+
(select count(*)::int from pg_stat_activity w
|
|
6069
|
+
where w.leader_pid = a.pid and w.pid <> a.pid) as workers
|
|
6070
|
+
from pg_stat_activity a
|
|
6071
|
+
where a.pid = ${pid}
|
|
6072
|
+
`.execute(side));
|
|
6073
|
+
const blockerPids = (activity?.blockers ?? []).map(Number);
|
|
6074
|
+
const blockers = blockerPids.length === 0 ? [] : await rowsOrEmpty(() => sql`
|
|
6075
|
+
select pid, left(query, 120) as query,
|
|
6076
|
+
(extract(epoch from now() - coalesce(xact_start, query_start)) * 1000)::float8 as age_ms
|
|
6077
|
+
from pg_stat_activity
|
|
6078
|
+
where pid = any(${blockerPids}::int[])
|
|
6079
|
+
`.execute(side));
|
|
6080
|
+
const [progress] = await rowsOrEmpty(() => sql`
|
|
6081
|
+
select command as operation, phase,
|
|
6082
|
+
nullif(relid, 0)::regclass::text as relation,
|
|
6083
|
+
(case when tuples_total > 0 then tuples_done else blocks_done end)::float8 as done,
|
|
6084
|
+
(case when tuples_total > 0 then tuples_total else blocks_total end)::float8 as total
|
|
6085
|
+
from pg_stat_progress_create_index where pid = ${pid}
|
|
6086
|
+
union all
|
|
6087
|
+
select 'VACUUM', phase, nullif(relid, 0)::regclass::text,
|
|
6088
|
+
heap_blks_scanned::float8, heap_blks_total::float8
|
|
6089
|
+
from pg_stat_progress_vacuum where pid = ${pid}
|
|
6090
|
+
union all
|
|
6091
|
+
select command, phase, nullif(relid, 0)::regclass::text,
|
|
6092
|
+
heap_blks_scanned::float8, heap_blks_total::float8
|
|
6093
|
+
from pg_stat_progress_cluster where pid = ${pid}
|
|
6094
|
+
union all
|
|
6095
|
+
select command, null, nullif(relid, 0)::regclass::text,
|
|
6096
|
+
bytes_processed::float8, nullif(bytes_total, 0)::float8
|
|
6097
|
+
from pg_stat_progress_copy where pid = ${pid}
|
|
6098
|
+
union all
|
|
6099
|
+
select 'ANALYZE', phase, nullif(relid, 0)::regclass::text,
|
|
6100
|
+
sample_blks_scanned::float8, sample_blks_total::float8
|
|
6101
|
+
from pg_stat_progress_analyze where pid = ${pid}
|
|
6102
|
+
limit 1
|
|
6103
|
+
`.execute(side));
|
|
6104
|
+
const waitEvent = activity?.wait_event ? `${activity.wait_event_type ?? "Wait"}: ${activity.wait_event}` : null;
|
|
6105
|
+
return {
|
|
6106
|
+
state: toText(activity?.state),
|
|
6107
|
+
waitEvent,
|
|
6108
|
+
blockedBy: toBlockers(blockers),
|
|
6109
|
+
workers: toNumber(activity?.workers) ?? 0,
|
|
6110
|
+
progress: progress ? {
|
|
6111
|
+
operation: progress.operation,
|
|
6112
|
+
phase: toText(progress.phase),
|
|
6113
|
+
relation: toText(progress.relation),
|
|
6114
|
+
done: toNumber(progress.done),
|
|
6115
|
+
total: toNumber(progress.total),
|
|
6116
|
+
percent: percentOf(toNumber(progress.done), toNumber(progress.total))
|
|
6117
|
+
} : null
|
|
6118
|
+
};
|
|
6119
|
+
};
|
|
6120
|
+
var mssqlProbe = async (side, spid) => {
|
|
6121
|
+
const [request] = await rowsOrEmpty(() => sql`
|
|
6122
|
+
select status, wait_type, blocking_session_id, percent_complete, command
|
|
6123
|
+
from sys.dm_exec_requests
|
|
6124
|
+
where session_id = ${spid}
|
|
6125
|
+
`.execute(side));
|
|
6126
|
+
const blockerId = toNumber(request?.blocking_session_id);
|
|
6127
|
+
const blockers = !blockerId ? [] : await rowsOrEmpty(() => sql`
|
|
6128
|
+
select s.session_id as pid,
|
|
6129
|
+
coalesce(left(t.text, 120), r.command, s.status) as query,
|
|
6130
|
+
datediff_big(millisecond, coalesce(r.start_time, s.last_request_start_time), sysdatetime()) as age_ms
|
|
6131
|
+
from sys.dm_exec_sessions s
|
|
6132
|
+
left join sys.dm_exec_requests r on r.session_id = s.session_id
|
|
6133
|
+
outer apply sys.dm_exec_sql_text(r.sql_handle) t
|
|
6134
|
+
where s.session_id = ${blockerId}
|
|
6135
|
+
`.execute(side));
|
|
6136
|
+
const percent = toNumber(request?.percent_complete);
|
|
6137
|
+
return {
|
|
6138
|
+
state: toText(request?.status),
|
|
6139
|
+
waitEvent: toText(request?.wait_type),
|
|
6140
|
+
blockedBy: toBlockers(blockers),
|
|
6141
|
+
workers: 0,
|
|
6142
|
+
progress: request?.command && percent ? { operation: request.command, phase: null, relation: null, done: null, total: null, percent } : null
|
|
6143
|
+
};
|
|
6144
|
+
};
|
|
6145
|
+
var mysqlProbe = async (side, id) => {
|
|
6146
|
+
const [process2] = await rowsOrEmpty(() => sql`
|
|
6147
|
+
select state from information_schema.processlist where id = ${id}
|
|
6148
|
+
`.execute(side));
|
|
6149
|
+
const blockerIds = await rowsOrEmpty(() => sql`
|
|
6150
|
+
select blocking_pid as pid from sys.innodb_lock_waits where waiting_pid = ${id}
|
|
6151
|
+
union
|
|
6152
|
+
select blocking_pid from sys.schema_table_lock_waits where waiting_pid = ${id}
|
|
6153
|
+
`.execute(side));
|
|
6154
|
+
const ids = blockerIds.map((row) => Number(row.pid));
|
|
6155
|
+
const blockers = ids.length === 0 ? [] : await rowsOrEmpty(() => sql`
|
|
6156
|
+
select id as pid, left(coalesce(info, command), 120) as query, time * 1000 as age_ms
|
|
6157
|
+
from information_schema.processlist
|
|
6158
|
+
where id in (${sql.join(ids)})
|
|
6159
|
+
`.execute(side));
|
|
6160
|
+
const [stage] = await rowsOrEmpty(() => sql`
|
|
6161
|
+
select s.event_name, s.work_completed, s.work_estimated
|
|
6162
|
+
from performance_schema.events_stages_current s
|
|
6163
|
+
join performance_schema.threads t on t.thread_id = s.thread_id
|
|
6164
|
+
where t.processlist_id = ${id}
|
|
6165
|
+
`.execute(side));
|
|
6166
|
+
const state = toText(process2?.state);
|
|
6167
|
+
const done = toNumber(stage?.work_completed);
|
|
6168
|
+
const total = toNumber(stage?.work_estimated);
|
|
6169
|
+
return {
|
|
6170
|
+
state,
|
|
6171
|
+
waitEvent: state?.startsWith("Waiting") ? state : null,
|
|
6172
|
+
blockedBy: toBlockers(blockers),
|
|
6173
|
+
workers: 0,
|
|
6174
|
+
progress: stage ? {
|
|
6175
|
+
operation: stage.event_name.replace(/^stage\/\w+\//, ""),
|
|
6176
|
+
phase: null,
|
|
6177
|
+
relation: null,
|
|
6178
|
+
done,
|
|
6179
|
+
total,
|
|
6180
|
+
percent: percentOf(done, total)
|
|
6181
|
+
} : null
|
|
6182
|
+
};
|
|
6183
|
+
};
|
|
6184
|
+
var STATEMENT_PROBES = {
|
|
6185
|
+
postgres: postgresProbe,
|
|
6186
|
+
mssql: mssqlProbe,
|
|
6187
|
+
mysql: mysqlProbe
|
|
6188
|
+
};
|
|
6189
|
+
|
|
6190
|
+
// src/core/runner/statement-watcher.ts
|
|
6191
|
+
var DEFAULT_WATCH_DELAY_MS = 1e4;
|
|
6192
|
+
var DEFAULT_WATCH_INTERVAL_MS = 1e4;
|
|
6193
|
+
var SIDE_CONNECTION_WAIT_MS = 5e3;
|
|
6194
|
+
var StatementWatcher = class {
|
|
6195
|
+
#pool;
|
|
6196
|
+
#dialect;
|
|
6197
|
+
#signal;
|
|
6198
|
+
#delayMs;
|
|
6199
|
+
#intervalMs;
|
|
6200
|
+
#active = null;
|
|
6201
|
+
#side = null;
|
|
6202
|
+
#sideAbandoned = false;
|
|
6203
|
+
#releaseSide = null;
|
|
6204
|
+
#sideHeld = Promise.resolve();
|
|
6205
|
+
#closed = false;
|
|
6206
|
+
#onAbort = () => {
|
|
6207
|
+
const statement = this.#active;
|
|
6208
|
+
if (statement) void this.#cancel(statement);
|
|
6209
|
+
};
|
|
6210
|
+
/**
|
|
6211
|
+
* @param pool - Root connection pool the side connection is checked out
|
|
6212
|
+
* from. Never a transaction: the side connection must be a separate session.
|
|
6213
|
+
*/
|
|
6214
|
+
constructor(pool, options = {}) {
|
|
6215
|
+
this.#pool = pool;
|
|
6216
|
+
this.#dialect = options.dialect;
|
|
6217
|
+
this.#signal = options.signal;
|
|
6218
|
+
this.#delayMs = options.delayMs ?? DEFAULT_WATCH_DELAY_MS;
|
|
6219
|
+
this.#intervalMs = options.intervalMs ?? DEFAULT_WATCH_INTERVAL_MS;
|
|
6220
|
+
this.#signal?.addEventListener("abort", this.#onAbort, { once: true });
|
|
6221
|
+
}
|
|
6222
|
+
/**
|
|
6223
|
+
* Run `fn` on one pinned connection while watching it.
|
|
6224
|
+
*
|
|
6225
|
+
* A transaction is already one connection, so it is used as is; anything
|
|
6226
|
+
* else is pinned with `connection()` so the session id read up front is
|
|
6227
|
+
* the session the SQL runs on.
|
|
6228
|
+
*
|
|
6229
|
+
* @throws OperationAbortedError when the signal aborted before `fn` started
|
|
6230
|
+
*/
|
|
6231
|
+
async run(filepath, executor, fn) {
|
|
6232
|
+
if (executor.isTransaction) {
|
|
6233
|
+
return this.#watch(filepath, executor, fn);
|
|
6234
|
+
}
|
|
6235
|
+
return executor.connection().execute((conn) => this.#watch(filepath, conn, fn));
|
|
6236
|
+
}
|
|
6237
|
+
/**
|
|
6238
|
+
* Stop watching and return the side connection to the pool.
|
|
6239
|
+
*/
|
|
6240
|
+
async close() {
|
|
6241
|
+
this.#closed = true;
|
|
6242
|
+
this.#signal?.removeEventListener("abort", this.#onAbort);
|
|
6243
|
+
this.#releaseSide?.();
|
|
6244
|
+
await this.#sideHeld;
|
|
6245
|
+
}
|
|
6246
|
+
async #watch(filepath, conn, fn) {
|
|
6247
|
+
const sessionId = await this.#readSessionId(conn);
|
|
6248
|
+
const statement = {
|
|
6249
|
+
filepath,
|
|
6250
|
+
sessionId,
|
|
6251
|
+
startedAt: performance.now(),
|
|
6252
|
+
timer: void 0,
|
|
6253
|
+
finished: false,
|
|
6254
|
+
cancelling: null
|
|
6255
|
+
};
|
|
6256
|
+
if (this.#signal?.aborted) {
|
|
6257
|
+
throw new OperationAbortedError("Run cancelled before the file started");
|
|
6258
|
+
}
|
|
6259
|
+
this.#active = statement;
|
|
6260
|
+
statement.timer = setTimeout(() => void this.#report(statement), this.#delayMs);
|
|
6261
|
+
try {
|
|
6262
|
+
return await fn(conn);
|
|
6263
|
+
} finally {
|
|
6264
|
+
clearTimeout(statement.timer);
|
|
6265
|
+
this.#active = null;
|
|
6266
|
+
statement.finished = true;
|
|
6267
|
+
await statement.cancelling;
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
async #readSessionId(conn) {
|
|
6271
|
+
const query = this.#dialect ? SESSION_ID_SQL[this.#dialect] : void 0;
|
|
6272
|
+
if (!query) return null;
|
|
6273
|
+
const [result, err] = await attempt(() => sql.raw(query).execute(conn));
|
|
6274
|
+
if (err) return null;
|
|
6275
|
+
return readSessionId(result.rows) ?? null;
|
|
6276
|
+
}
|
|
6277
|
+
async #report(statement) {
|
|
6278
|
+
if (this.#active !== statement) return;
|
|
6279
|
+
const status = await this.#poll(statement);
|
|
6280
|
+
if (this.#active !== statement) return;
|
|
6281
|
+
observer.emit("file:progress", {
|
|
6282
|
+
filepath: statement.filepath,
|
|
6283
|
+
elapsedMs: performance.now() - statement.startedAt,
|
|
6284
|
+
sessionId: statement.sessionId,
|
|
6285
|
+
status
|
|
6286
|
+
});
|
|
6287
|
+
statement.timer = setTimeout(() => void this.#report(statement), this.#intervalMs);
|
|
6288
|
+
}
|
|
6289
|
+
async #poll(statement) {
|
|
6290
|
+
const probe = this.#dialect ? STATEMENT_PROBES[this.#dialect] : void 0;
|
|
6291
|
+
const sessionId = statement.sessionId;
|
|
6292
|
+
if (!probe || sessionId === null) return null;
|
|
6293
|
+
const side = await this.#sideConnection();
|
|
6294
|
+
if (!side) return null;
|
|
6295
|
+
const [status, err] = await attempt(() => probe(side, sessionId));
|
|
6296
|
+
return err ? null : status;
|
|
6297
|
+
}
|
|
6298
|
+
async #cancel(statement) {
|
|
6299
|
+
const cancel = this.#dialect ? SERVER_CANCEL[this.#dialect] : void 0;
|
|
6300
|
+
const sessionId = statement.sessionId;
|
|
6301
|
+
if (sessionId === null || !cancel) return;
|
|
6302
|
+
const side = await this.#sideConnection();
|
|
6303
|
+
if (statement.finished || this.#closed) return;
|
|
6304
|
+
if (!side) {
|
|
6305
|
+
observer.emit("error", {
|
|
6306
|
+
source: "runner:cancel",
|
|
6307
|
+
error: new Error("No free connection to send the cancel from"),
|
|
6308
|
+
context: { filepath: statement.filepath, sessionId }
|
|
6309
|
+
});
|
|
6310
|
+
return;
|
|
6311
|
+
}
|
|
6312
|
+
const sending = attempt(() => cancel(side, sessionId));
|
|
6313
|
+
statement.cancelling = sending.then(() => void 0);
|
|
6314
|
+
const [, err] = await sending;
|
|
6315
|
+
if (err) {
|
|
6316
|
+
observer.emit("error", {
|
|
6317
|
+
source: "runner:cancel",
|
|
6318
|
+
error: err,
|
|
6319
|
+
context: { filepath: statement.filepath, sessionId }
|
|
6320
|
+
});
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
async #sideConnection() {
|
|
6324
|
+
if (this.#closed || this.#sideAbandoned) return null;
|
|
6325
|
+
this.#side ??= this.#checkoutSide();
|
|
6326
|
+
const pending = this.#side;
|
|
6327
|
+
const [side] = await attempt(() => runWithTimeout(() => pending, { timeout: SIDE_CONNECTION_WAIT_MS }));
|
|
6328
|
+
if (!side && this.#side === pending) this.#sideAbandoned = true;
|
|
6329
|
+
return side ?? null;
|
|
6330
|
+
}
|
|
6331
|
+
/**
|
|
6332
|
+
* Check a connection out of the pool and hold it until `close()`.
|
|
6333
|
+
*
|
|
6334
|
+
* Kysely only lends a dedicated connection for the duration of a callback,
|
|
6335
|
+
* so the callback parks on a promise that `close()` resolves. A checkout
|
|
6336
|
+
* that arrives after the watcher gave up on it returns at once.
|
|
6337
|
+
*/
|
|
6338
|
+
#checkoutSide() {
|
|
6339
|
+
return new Promise((resolveSide) => {
|
|
6340
|
+
const released = new Promise((release) => {
|
|
6341
|
+
this.#releaseSide = release;
|
|
6342
|
+
});
|
|
6343
|
+
this.#sideHeld = attempt(() => this.#pool.connection().execute(async (side) => {
|
|
6344
|
+
if (this.#sideAbandoned || this.#closed) return;
|
|
6345
|
+
if (this.#active === null) {
|
|
6346
|
+
this.#side = null;
|
|
6347
|
+
resolveSide(null);
|
|
6348
|
+
return;
|
|
6349
|
+
}
|
|
6350
|
+
resolveSide(side);
|
|
6351
|
+
await released;
|
|
6352
|
+
})).then(([, err]) => {
|
|
6353
|
+
if (err) {
|
|
6354
|
+
resolveSide(null);
|
|
6355
|
+
observer.emit("error", {
|
|
6356
|
+
source: "runner:watch",
|
|
6357
|
+
error: err,
|
|
6358
|
+
context: { operation: "side-connection" }
|
|
6359
|
+
});
|
|
6360
|
+
}
|
|
6361
|
+
});
|
|
6362
|
+
});
|
|
6363
|
+
}
|
|
6364
|
+
};
|
|
6014
6365
|
|
|
6015
6366
|
// src/core/lock/types.ts
|
|
6016
6367
|
var DEFAULT_LOCK_OPTIONS = {
|
|
@@ -6610,6 +6961,7 @@ var ChangeRollback = class extends Error {
|
|
|
6610
6961
|
async function executeFiles(context, change, files, direction, checksum, force, history, startTime) {
|
|
6611
6962
|
const dialect = context.dialect ?? "postgres";
|
|
6612
6963
|
const expandedFiles = await expandFiles(files, context.sqlDir);
|
|
6964
|
+
const watcher = new StatementWatcher(context.db, { dialect });
|
|
6613
6965
|
if (!TRANSACTIONAL_DIALECTS.has(dialect)) {
|
|
6614
6966
|
return runFileBatch(
|
|
6615
6967
|
context,
|
|
@@ -6620,8 +6972,9 @@ async function executeFiles(context, change, files, direction, checksum, force,
|
|
|
6620
6972
|
force,
|
|
6621
6973
|
history,
|
|
6622
6974
|
context.db,
|
|
6623
|
-
startTime
|
|
6624
|
-
|
|
6975
|
+
startTime,
|
|
6976
|
+
watcher
|
|
6977
|
+
).finally(() => watcher.close());
|
|
6625
6978
|
}
|
|
6626
6979
|
const [result, err] = await attempt(
|
|
6627
6980
|
() => context.db.transaction().execute(async (trx) => {
|
|
@@ -6635,7 +6988,8 @@ async function executeFiles(context, change, files, direction, checksum, force,
|
|
|
6635
6988
|
force,
|
|
6636
6989
|
trxHistory,
|
|
6637
6990
|
trx,
|
|
6638
|
-
startTime
|
|
6991
|
+
startTime,
|
|
6992
|
+
watcher
|
|
6639
6993
|
);
|
|
6640
6994
|
if (batchResult.status !== "success") {
|
|
6641
6995
|
throw new ChangeRollback(batchResult);
|
|
@@ -6643,6 +6997,7 @@ async function executeFiles(context, change, files, direction, checksum, force,
|
|
|
6643
6997
|
return batchResult;
|
|
6644
6998
|
})
|
|
6645
6999
|
);
|
|
7000
|
+
await watcher.close();
|
|
6646
7001
|
if (err) {
|
|
6647
7002
|
if (err instanceof ChangeRollback) {
|
|
6648
7003
|
return { ...err.result, operationId: void 0 };
|
|
@@ -6651,7 +7006,7 @@ async function executeFiles(context, change, files, direction, checksum, force,
|
|
|
6651
7006
|
}
|
|
6652
7007
|
return result;
|
|
6653
7008
|
}
|
|
6654
|
-
async function runFileBatch(context, change, expandedFiles, direction, checksum, force, history, executor, startTime) {
|
|
7009
|
+
async function runFileBatch(context, change, expandedFiles, direction, checksum, force, history, executor, startTime, watcher) {
|
|
6655
7010
|
const [operationId, createErr] = await attempt(
|
|
6656
7011
|
() => history.createOperation({
|
|
6657
7012
|
name: change.name,
|
|
@@ -6775,7 +7130,9 @@ async function runFileBatch(context, change, expandedFiles, direction, checksum,
|
|
|
6775
7130
|
}
|
|
6776
7131
|
break;
|
|
6777
7132
|
}
|
|
6778
|
-
const [, execErr] = await attempt(
|
|
7133
|
+
const [, execErr] = await attempt(
|
|
7134
|
+
() => watcher.run(file.path, executor, (conn) => sql.raw(sqlContent).execute(conn))
|
|
7135
|
+
);
|
|
6779
7136
|
const durationMs = performance.now() - fileStart;
|
|
6780
7137
|
if (execErr) {
|
|
6781
7138
|
const errorMessage2 = getSqlErrorMessage(execErr);
|
|
@@ -7546,7 +7903,7 @@ async function resolveVaultKey(db, dialect) {
|
|
|
7546
7903
|
async function buildSecretsContext(stateManager, configName, db, vaultKey, dialect) {
|
|
7547
7904
|
const secrets = {};
|
|
7548
7905
|
if (db && vaultKey) {
|
|
7549
|
-
const { getAllVaultSecrets: getAllVaultSecrets2 } = await import('./storage-
|
|
7906
|
+
const { getAllVaultSecrets: getAllVaultSecrets2 } = await import('./storage-FBDOY3MC.js');
|
|
7550
7907
|
const vaultSecrets = await getAllVaultSecrets2(db, vaultKey, dialect ?? "postgres");
|
|
7551
7908
|
for (const [key, secret] of Object.entries(vaultSecrets)) {
|
|
7552
7909
|
secrets[key] = secret.value;
|
|
@@ -8350,10 +8707,11 @@ async function runFile(context, filepath, options = {}) {
|
|
|
8350
8707
|
error: createErr.message
|
|
8351
8708
|
};
|
|
8352
8709
|
}
|
|
8353
|
-
const
|
|
8710
|
+
const watcher = createWatcher(context);
|
|
8711
|
+
const result = await executeSingleFile(context, filepath, opts, tracker, operationId, watcher).finally(() => watcher.close());
|
|
8354
8712
|
await tracker.finalizeOperation(
|
|
8355
8713
|
operationId,
|
|
8356
|
-
result.status === "failed" ? "failed" : "success",
|
|
8714
|
+
result.status === "failed" || result.error ? "failed" : "success",
|
|
8357
8715
|
Math.round(result.durationMs ?? 0),
|
|
8358
8716
|
result.checksum,
|
|
8359
8717
|
result.error
|
|
@@ -8437,6 +8795,31 @@ var DEFAULT_RUN_OPTIONS_INTERNAL = {
|
|
|
8437
8795
|
preview: false,
|
|
8438
8796
|
output: null
|
|
8439
8797
|
};
|
|
8798
|
+
var RUN_CANCELLED = "Run cancelled";
|
|
8799
|
+
function createWatcher(context) {
|
|
8800
|
+
return new StatementWatcher(context.db, {
|
|
8801
|
+
dialect: context.dialect ?? "postgres",
|
|
8802
|
+
signal: context.signal
|
|
8803
|
+
});
|
|
8804
|
+
}
|
|
8805
|
+
var NOT_STARTED = /* @__PURE__ */ Symbol("not-started");
|
|
8806
|
+
async function runWatched(context, watcher, filepath, sqlContent) {
|
|
8807
|
+
const [execErrMsg, err] = await attempt(
|
|
8808
|
+
() => watcher.run(filepath, context.db, (conn) => executeSqlBody({ ...context, db: conn }, sqlContent))
|
|
8809
|
+
);
|
|
8810
|
+
if (err instanceof OperationAbortedError) return NOT_STARTED;
|
|
8811
|
+
return err ? getSqlErrorMessage(err) : execErrMsg;
|
|
8812
|
+
}
|
|
8813
|
+
async function skipRemaining(tracker, operationId, reason) {
|
|
8814
|
+
const skipErr = await tracker.skipRemainingFiles(operationId, reason.slice(0, 100));
|
|
8815
|
+
if (skipErr) {
|
|
8816
|
+
observer.emit("error", {
|
|
8817
|
+
source: "runner:skip-remaining",
|
|
8818
|
+
error: new Error(skipErr),
|
|
8819
|
+
context: { operationId }
|
|
8820
|
+
});
|
|
8821
|
+
}
|
|
8822
|
+
}
|
|
8440
8823
|
async function executeFiles2(context, files, runOptions, execOptions) {
|
|
8441
8824
|
const start = performance.now();
|
|
8442
8825
|
const opts = { ...DEFAULT_RUN_OPTIONS_INTERNAL, ...runOptions };
|
|
@@ -8524,34 +8907,48 @@ async function executeFiles2(context, files, runOptions, execOptions) {
|
|
|
8524
8907
|
}
|
|
8525
8908
|
const results = [];
|
|
8526
8909
|
let failed = false;
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8910
|
+
let cancelled = false;
|
|
8911
|
+
const watcher = createWatcher(context);
|
|
8912
|
+
try {
|
|
8913
|
+
for (let i = 0; i < files.length; i++) {
|
|
8914
|
+
const file = files[i];
|
|
8915
|
+
const fileRecord = fileRecords[i];
|
|
8916
|
+
if (context.signal?.aborted) {
|
|
8917
|
+
failed = true;
|
|
8918
|
+
cancelled = true;
|
|
8919
|
+
await skipRemaining(tracker, operationId, RUN_CANCELLED);
|
|
8920
|
+
break;
|
|
8921
|
+
}
|
|
8922
|
+
const result = await executeSingleFileWithUpdate(
|
|
8923
|
+
context,
|
|
8924
|
+
file.path,
|
|
8925
|
+
fileRecord.checksum,
|
|
8926
|
+
opts,
|
|
8927
|
+
tracker,
|
|
8543
8928
|
operationId,
|
|
8544
|
-
|
|
8929
|
+
execOptions.changeType,
|
|
8930
|
+
watcher
|
|
8545
8931
|
);
|
|
8546
|
-
if (
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8932
|
+
if (!result) {
|
|
8933
|
+
failed = true;
|
|
8934
|
+
cancelled = true;
|
|
8935
|
+
await skipRemaining(tracker, operationId, RUN_CANCELLED);
|
|
8936
|
+
break;
|
|
8937
|
+
}
|
|
8938
|
+
results.push(result);
|
|
8939
|
+
cancelled = (context.signal?.aborted ?? false) && (i < files.length - 1 || result.status === "failed");
|
|
8940
|
+
if (cancelled || result.status === "failed" && opts.abortOnError) {
|
|
8941
|
+
failed = true;
|
|
8942
|
+
await skipRemaining(
|
|
8943
|
+
tracker,
|
|
8944
|
+
operationId,
|
|
8945
|
+
cancelled ? RUN_CANCELLED : `Skipped: failure in ${path.basename(file.path)}`
|
|
8946
|
+
);
|
|
8947
|
+
break;
|
|
8552
8948
|
}
|
|
8553
|
-
break;
|
|
8554
8949
|
}
|
|
8950
|
+
} finally {
|
|
8951
|
+
await watcher.close();
|
|
8555
8952
|
}
|
|
8556
8953
|
if (!failed && results.length !== files.length) {
|
|
8557
8954
|
throw new Error(
|
|
@@ -8573,7 +8970,7 @@ async function executeFiles2(context, files, runOptions, execOptions) {
|
|
|
8573
8970
|
finalStatus,
|
|
8574
8971
|
Math.round(durationMs),
|
|
8575
8972
|
combinedChecksum,
|
|
8576
|
-
failed ? results.find((r) => r.status === "failed")?.error : void 0
|
|
8973
|
+
cancelled ? RUN_CANCELLED : failed ? results.find((r) => r.status === "failed")?.error : void 0
|
|
8577
8974
|
);
|
|
8578
8975
|
if (finalizeErr) {
|
|
8579
8976
|
observer.emit("error", {
|
|
@@ -8589,7 +8986,8 @@ async function executeFiles2(context, files, runOptions, execOptions) {
|
|
|
8589
8986
|
filesSkipped,
|
|
8590
8987
|
filesFailed,
|
|
8591
8988
|
durationMs,
|
|
8592
|
-
changeId: operationId
|
|
8989
|
+
changeId: operationId,
|
|
8990
|
+
error: cancelled ? RUN_CANCELLED : void 0
|
|
8593
8991
|
};
|
|
8594
8992
|
}
|
|
8595
8993
|
async function executeFilesInternal(context, files, options, changeType, sqlPath) {
|
|
@@ -8605,7 +9003,7 @@ async function executeFilesInternal(context, files, options, changeType, sqlPath
|
|
|
8605
9003
|
};
|
|
8606
9004
|
return executeFiles2(context, fileInputs, options, execOptions);
|
|
8607
9005
|
}
|
|
8608
|
-
async function executeSingleFileWithUpdate(context, filepath, checksum, options, tracker, operationId, changeType) {
|
|
9006
|
+
async function executeSingleFileWithUpdate(context, filepath, checksum, options, tracker, operationId, changeType, watcher) {
|
|
8609
9007
|
const start = performance.now();
|
|
8610
9008
|
const relFilepath = path.relative(context.projectRoot, filepath);
|
|
8611
9009
|
const [sqlContent, loadErr] = await attempt(() => loadAndRenderFile2(context, filepath));
|
|
@@ -8665,7 +9063,8 @@ async function executeSingleFileWithUpdate(context, filepath, checksum, options,
|
|
|
8665
9063
|
return result2;
|
|
8666
9064
|
}
|
|
8667
9065
|
}
|
|
8668
|
-
const execErrMsg = await
|
|
9066
|
+
const execErrMsg = await runWatched(context, watcher, filepath, sqlContent);
|
|
9067
|
+
if (execErrMsg === NOT_STARTED) return null;
|
|
8669
9068
|
const durationMs = performance.now() - start;
|
|
8670
9069
|
if (execErrMsg) {
|
|
8671
9070
|
const error = execErrMsg + await describePriorSuccesses(tracker, relFilepath, operationId);
|
|
@@ -8715,7 +9114,7 @@ async function executeSingleFileWithUpdate(context, filepath, checksum, options,
|
|
|
8715
9114
|
});
|
|
8716
9115
|
return result;
|
|
8717
9116
|
}
|
|
8718
|
-
async function executeSingleFile(context, filepath, options, tracker, operationId) {
|
|
9117
|
+
async function executeSingleFile(context, filepath, options, tracker, operationId, watcher) {
|
|
8719
9118
|
const start = performance.now();
|
|
8720
9119
|
const relFilepath = path.relative(context.projectRoot, filepath);
|
|
8721
9120
|
const [sqlContent, loadErr] = await attempt(() => loadAndRenderFile2(context, filepath));
|
|
@@ -8795,8 +9194,18 @@ async function executeSingleFile(context, filepath, options, tracker, operationI
|
|
|
8795
9194
|
});
|
|
8796
9195
|
return result2;
|
|
8797
9196
|
}
|
|
8798
|
-
const execErrMsg = await
|
|
9197
|
+
const execErrMsg = await runWatched(context, watcher, filepath, sqlContent);
|
|
8799
9198
|
const durationMs = performance.now() - start;
|
|
9199
|
+
if (execErrMsg === NOT_STARTED) {
|
|
9200
|
+
await tracker.recordExecution({
|
|
9201
|
+
changeId: operationId,
|
|
9202
|
+
filepath: relFilepath,
|
|
9203
|
+
checksum,
|
|
9204
|
+
status: "skipped",
|
|
9205
|
+
skipReason: RUN_CANCELLED
|
|
9206
|
+
});
|
|
9207
|
+
return { filepath, checksum, status: "skipped", error: RUN_CANCELLED };
|
|
9208
|
+
}
|
|
8800
9209
|
if (execErrMsg) {
|
|
8801
9210
|
const error = execErrMsg + await describePriorSuccesses(tracker, relFilepath, operationId);
|
|
8802
9211
|
const result2 = {
|
|
@@ -9227,7 +9636,8 @@ var postgresExploreOperations = {
|
|
|
9227
9636
|
SELECT
|
|
9228
9637
|
p.proname,
|
|
9229
9638
|
n.nspname,
|
|
9230
|
-
p.pronargs::text as param_count
|
|
9639
|
+
p.pronargs::text as param_count,
|
|
9640
|
+
pg_get_function_identity_arguments(p.oid) as identity_args
|
|
9231
9641
|
FROM pg_proc p
|
|
9232
9642
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
9233
9643
|
WHERE n.nspname ${schemaFilter(schema)}
|
|
@@ -9242,7 +9652,8 @@ var postgresExploreOperations = {
|
|
|
9242
9652
|
return result.rows.map((row) => ({
|
|
9243
9653
|
name: row.proname,
|
|
9244
9654
|
schema: row.nspname,
|
|
9245
|
-
parameterCount: parseInt(row.param_count, 10)
|
|
9655
|
+
parameterCount: parseInt(row.param_count, 10),
|
|
9656
|
+
signature: row.identity_args
|
|
9246
9657
|
}));
|
|
9247
9658
|
},
|
|
9248
9659
|
async listFunctions(db, schema) {
|
|
@@ -9251,7 +9662,8 @@ var postgresExploreOperations = {
|
|
|
9251
9662
|
p.proname,
|
|
9252
9663
|
n.nspname,
|
|
9253
9664
|
p.pronargs::text as param_count,
|
|
9254
|
-
pg_get_function_result(p.oid) as return_type
|
|
9665
|
+
pg_get_function_result(p.oid) as return_type,
|
|
9666
|
+
pg_get_function_identity_arguments(p.oid) as identity_args
|
|
9255
9667
|
FROM pg_proc p
|
|
9256
9668
|
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
9257
9669
|
WHERE n.nspname ${schemaFilter(schema)}
|
|
@@ -9267,7 +9679,8 @@ var postgresExploreOperations = {
|
|
|
9267
9679
|
name: row.proname,
|
|
9268
9680
|
schema: row.nspname,
|
|
9269
9681
|
parameterCount: parseInt(row.param_count, 10),
|
|
9270
|
-
returnType: row.return_type
|
|
9682
|
+
returnType: row.return_type,
|
|
9683
|
+
signature: row.identity_args
|
|
9271
9684
|
}));
|
|
9272
9685
|
},
|
|
9273
9686
|
async listTypes(db, schema) {
|