pipework 0.279.0 → 0.281.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/CHANGELOG.md +8 -0
- package/REFERENCE.md +1 -0
- package/dist/async/jobs/claims.d.ts +15 -1
- package/dist/async/jobs/claims.d.ts.map +1 -1
- package/dist/async/jobs/claims.js.map +1 -1
- package/dist/async/jobs/index.d.ts +1 -1
- package/dist/async/jobs/index.d.ts.map +1 -1
- package/dist/async/jobs/queue.d.ts +28 -8
- package/dist/async/jobs/queue.d.ts.map +1 -1
- package/dist/async/jobs/queue.js +71 -9
- package/dist/async/jobs/queue.js.map +1 -1
- package/dist/async/jobs/serialize-error.d.ts +2 -0
- package/dist/async/jobs/serialize-error.d.ts.map +1 -1
- package/dist/async/jobs/serialize-error.js +5 -4
- package/dist/async/jobs/serialize-error.js.map +1 -1
- package/dist/cli/commands/migrate.d.ts +4 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +17 -2
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/data/migrate/apply.d.ts +13 -0
- package/dist/data/migrate/apply.d.ts.map +1 -1
- package/dist/data/migrate/apply.js +28 -3
- package/dist/data/migrate/apply.js.map +1 -1
- package/dist/data/migrate/run.d.ts +12 -1
- package/dist/data/migrate/run.d.ts.map +1 -1
- package/dist/data/migrate/run.js +8 -1
- package/dist/data/migrate/run.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/async/jobs/claims.ts +11 -1
- package/src/async/jobs/index.ts +1 -1
- package/src/async/jobs/queue.ts +100 -15
- package/src/async/jobs/serialize-error.ts +5 -4
- package/src/cli/commands/migrate.ts +18 -3
- package/src/data/migrate/apply.ts +38 -3
- package/src/data/migrate/run.ts +22 -3
- package/src/index.ts +1 -1
|
@@ -18,7 +18,8 @@ const DROPPED_KEYS = new Set(['params', 'parameters', 'args', 'types'])
|
|
|
18
18
|
const MAX_QUERY_CHARS = 2_000
|
|
19
19
|
const MAX_STRING_CHARS = 4_000
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
/** Cuts `text` to `max` characters, ending `…[truncated N chars]` when it was longer. */
|
|
22
|
+
export function cutText(text: string, max: number): string {
|
|
22
23
|
return text.length <= max ? text : `${text.slice(0, max)}…[truncated ${String(text.length - max)} chars]`
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -33,13 +34,13 @@ function serialize(value: unknown, seen: Set<Error>): unknown {
|
|
|
33
34
|
const stack = typeof value.stack === 'string' && paramsAt !== -1 ? value.stack.replace(value.message, message) : value.stack
|
|
34
35
|
const out: Record<string, unknown> = {
|
|
35
36
|
name: value.name,
|
|
36
|
-
message:
|
|
37
|
-
stack: typeof stack === 'string' ?
|
|
37
|
+
message: cutText(message, MAX_STRING_CHARS),
|
|
38
|
+
stack: typeof stack === 'string' ? cutText(stack, MAX_STRING_CHARS) : stack,
|
|
38
39
|
}
|
|
39
40
|
for (const key of Object.keys(value)) {
|
|
40
41
|
if (key === 'name' || key === 'message' || key === 'stack' || key === 'cause' || DROPPED_KEYS.has(key)) continue
|
|
41
42
|
const v = (value as unknown as Record<string, unknown>)[key]
|
|
42
|
-
out[key] = typeof v === 'string' ?
|
|
43
|
+
out[key] = typeof v === 'string' ? cutText(v, key === 'query' ? MAX_QUERY_CHARS : MAX_STRING_CHARS) : v
|
|
43
44
|
}
|
|
44
45
|
if (value.cause !== undefined) {
|
|
45
46
|
out['cause'] = serialize(value.cause, seen)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { info, success, error as logError, warn } from '../output.js'
|
|
2
|
-
import type { MigrateResult } from '../../data/migrate/run.js'
|
|
1
|
+
import { info, success, error as logError, warn, yellow, cyan } from '../output.js'
|
|
2
|
+
import type { MigrateResult, MigrateNotice } from '../../data/migrate/run.js'
|
|
3
3
|
import { frameworkTablesChanged } from '../../data/migrate/provision-framework.js'
|
|
4
4
|
|
|
5
5
|
export interface MigrateCommandOptions {
|
|
@@ -44,7 +44,7 @@ export async function migrate(options: MigrateCommandOptions): Promise<void> {
|
|
|
44
44
|
warn('Running migrations against PRODUCTION database.')
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const migrateOptions = {}
|
|
47
|
+
const migrateOptions = { onNotice: printNotice }
|
|
48
48
|
|
|
49
49
|
try {
|
|
50
50
|
if (options.db !== undefined) {
|
|
@@ -130,3 +130,18 @@ function reportFrameworkTables(result: MigrateResult): void {
|
|
|
130
130
|
`${f.partitionsCreated.length} day partitions created`,
|
|
131
131
|
)
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
/** One notice or warning a migration statement raised, indented under its database. Issue #970. */
|
|
135
|
+
export function formatNotice(n: MigrateNotice): string {
|
|
136
|
+
const label = n.severity === 'NOTICE' || n.severity === 'INFO' || n.severity === 'LOG' || n.severity === 'DEBUG'
|
|
137
|
+
? cyan(n.severity)
|
|
138
|
+
: yellow(n.severity)
|
|
139
|
+
let out = ` ${label} ${n.tag} (statement ${n.statement}/${n.statements}): ${n.message}\n`
|
|
140
|
+
if (n.detail !== undefined) out += ` DETAIL: ${n.detail}\n`
|
|
141
|
+
if (n.hint !== undefined) out += ` HINT: ${n.hint}\n`
|
|
142
|
+
return out
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function printNotice(n: MigrateNotice): void {
|
|
146
|
+
process.stderr.write(formatNotice(n))
|
|
147
|
+
}
|
|
@@ -7,6 +7,14 @@ import { listMigrations } from './history.js'
|
|
|
7
7
|
|
|
8
8
|
const NO_TRANSACTION_PRAGMA = '-- pipework:no-transaction'
|
|
9
9
|
|
|
10
|
+
/** A NOTICE or WARNING (or other non-error message) a migration statement raised. */
|
|
11
|
+
export interface MigrationNotice {
|
|
12
|
+
severity: string
|
|
13
|
+
message: string
|
|
14
|
+
detail?: string
|
|
15
|
+
hint?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
10
18
|
export interface ApplyOptions {
|
|
11
19
|
migrationsFolder: string
|
|
12
20
|
migrationsTable: string
|
|
@@ -44,6 +52,12 @@ export interface ApplyOptions {
|
|
|
44
52
|
* Issue #378.
|
|
45
53
|
*/
|
|
46
54
|
onResume?: (tag: string, fromStatement: number, total: number) => void
|
|
55
|
+
/**
|
|
56
|
+
* Called for each notice or warning a migration file's own statement raises,
|
|
57
|
+
* with the file's tag, the 1-based statement and the file's statement count.
|
|
58
|
+
* The runner's own bookkeeping statements never reach it. Issue #970.
|
|
59
|
+
*/
|
|
60
|
+
onNotice?: (tag: string, statement: number, statements: number, notice: MigrationNotice) => void
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
export interface Migration {
|
|
@@ -60,7 +74,28 @@ export async function applyMigrations(options: ApplyOptions): Promise<number> {
|
|
|
60
74
|
const migrations = readMigrations(options)
|
|
61
75
|
if (migrations.length === 0) return 0
|
|
62
76
|
|
|
63
|
-
|
|
77
|
+
// Set only while a statement from a migration file runs; the runner's own
|
|
78
|
+
// bookkeeping raises "already exists, skipping" notices that are not the
|
|
79
|
+
// migration's and are dropped.
|
|
80
|
+
let current: { tag: string; statement: number; statements: number } | undefined
|
|
81
|
+
const client = postgres(options.connectionUrl, {
|
|
82
|
+
max: 1,
|
|
83
|
+
onnotice: (n) => {
|
|
84
|
+
if (current === undefined || options.onNotice === undefined) return
|
|
85
|
+
const notice: MigrationNotice = { severity: String(n['severity']), message: String(n['message']) }
|
|
86
|
+
if (typeof n['detail'] === 'string') notice.detail = n['detail']
|
|
87
|
+
if (typeof n['hint'] === 'string') notice.hint = n['hint']
|
|
88
|
+
options.onNotice(current.tag, current.statement, current.statements, notice)
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
const inStatement = async <T>(tag: string, statement: number, statements: number, run: () => Promise<T>): Promise<T> => {
|
|
92
|
+
current = { tag, statement, statements }
|
|
93
|
+
try {
|
|
94
|
+
return await run()
|
|
95
|
+
} finally {
|
|
96
|
+
current = undefined
|
|
97
|
+
}
|
|
98
|
+
}
|
|
64
99
|
|
|
65
100
|
try {
|
|
66
101
|
const table = quoteIdent(options.migrationsTable)
|
|
@@ -158,7 +193,7 @@ export async function applyMigrations(options: ApplyOptions): Promise<number> {
|
|
|
158
193
|
if (from > 0) options.onResume?.(migration.tag, from + 1, migration.statements.length)
|
|
159
194
|
|
|
160
195
|
for (let i = from; i < migration.statements.length; i++) {
|
|
161
|
-
await client.unsafe(migration.statements[i]!)
|
|
196
|
+
await inStatement(migration.tag, i + 1, migration.statements.length, () => client.unsafe(migration.statements[i]!))
|
|
162
197
|
// Recorded AFTER the statement it describes: marking first would let
|
|
163
198
|
// a death between the mark and the statement skip work silently,
|
|
164
199
|
// which is worse than replaying one statement. The window that
|
|
@@ -179,7 +214,7 @@ export async function applyMigrations(options: ApplyOptions): Promise<number> {
|
|
|
179
214
|
} else {
|
|
180
215
|
await client.begin(async (tx) => {
|
|
181
216
|
for (const [i, stmt] of migration.statements.entries()) {
|
|
182
|
-
await tx.unsafe(stmt)
|
|
217
|
+
await inStatement(migration.tag, i + 1, migration.statements.length, () => tx.unsafe(stmt))
|
|
183
218
|
options.onStatement?.(migration.tag, i + 1, migration.statements.length, applied + 1, migrations.length)
|
|
184
219
|
}
|
|
185
220
|
await tx.unsafe(
|
package/src/data/migrate/run.ts
CHANGED
|
@@ -5,14 +5,17 @@ import { quoteIdentifier } from '../db/identifiers.js'
|
|
|
5
5
|
import { OWNER_LABEL_MIGRATE } from '../db/owner-labels.js'
|
|
6
6
|
import { checkMigrations, type MigrationCheckResult } from './check.js'
|
|
7
7
|
import { readAppliedState } from './applied.js'
|
|
8
|
-
import { applyMigrations } from './apply.js'
|
|
8
|
+
import { applyMigrations, type MigrationNotice } from './apply.js'
|
|
9
9
|
import { provisionFrameworkTables, type FrameworkTablesResult } from './provision-framework.js'
|
|
10
10
|
|
|
11
11
|
// Deliberately empty of hazard switches. Authorization for a hazardous
|
|
12
12
|
// migration is declared in that migration's own file (see pragma.ts) — an
|
|
13
13
|
// option here would be an invocation-level approval, which is exactly the
|
|
14
14
|
// thing that gets written into a deploy workflow once and never revisited.
|
|
15
|
-
export interface MigrateOptions {
|
|
15
|
+
export interface MigrateOptions {
|
|
16
|
+
/** Called as each notice or warning a migration statement raises arrives. Issue #970. */
|
|
17
|
+
onNotice?: (notice: MigrateNotice) => void
|
|
18
|
+
}
|
|
16
19
|
|
|
17
20
|
export interface MigrateResult {
|
|
18
21
|
database: string
|
|
@@ -21,6 +24,8 @@ export interface MigrateResult {
|
|
|
21
24
|
safetyCheck: MigrationCheckResult
|
|
22
25
|
/** No-transaction files picked up half applied and finished. Issue #378. */
|
|
23
26
|
resumed: ResumedMigration[]
|
|
27
|
+
/** Notices and warnings raised by migration statements, in arrival order. Issue #970. */
|
|
28
|
+
notices: MigrateNotice[]
|
|
24
29
|
/** Trace/audit tables, columns and day partitions this run provisioned. */
|
|
25
30
|
frameworkTables: FrameworkTablesResult
|
|
26
31
|
}
|
|
@@ -32,6 +37,13 @@ export interface ResumedMigration {
|
|
|
32
37
|
totalStatements: number
|
|
33
38
|
}
|
|
34
39
|
|
|
40
|
+
export interface MigrateNotice extends MigrationNotice {
|
|
41
|
+
tag: string
|
|
42
|
+
/** 1-based statement within the file. */
|
|
43
|
+
statement: number
|
|
44
|
+
statements: number
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
export class UnsafeMigrationError extends Error {
|
|
36
48
|
override readonly name = 'UnsafeMigrationError'
|
|
37
49
|
constructor(
|
|
@@ -54,7 +66,7 @@ export async function migrateAll(instance: Manifold, options: MigrateOptions = {
|
|
|
54
66
|
return results
|
|
55
67
|
}
|
|
56
68
|
|
|
57
|
-
export async function migrateOne(instance: Manifold, dbConfig: ResolvedDatabase,
|
|
69
|
+
export async function migrateOne(instance: Manifold, dbConfig: ResolvedDatabase, options: MigrateOptions = {}): Promise<MigrateResult> {
|
|
58
70
|
const conn = instance.pool.getOrCreate(dbConfig, 'owner', OWNER_LABEL_MIGRATE)
|
|
59
71
|
|
|
60
72
|
// Scope the safety gate to pending migrations only — hazards in already-applied
|
|
@@ -87,11 +99,17 @@ export async function migrateOne(instance: Manifold, dbConfig: ResolvedDatabase,
|
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
const resumed: ResumedMigration[] = []
|
|
102
|
+
const notices: MigrateNotice[] = []
|
|
90
103
|
const migrationsRun = await applyMigrations({
|
|
91
104
|
migrationsFolder: dbConfig.migrations!,
|
|
92
105
|
migrationsTable: dbConfig.migrationTable,
|
|
93
106
|
connectionUrl: dbConfig.url,
|
|
94
107
|
onResume: (tag, fromStatement, totalStatements) => resumed.push({ tag, fromStatement, totalStatements }),
|
|
108
|
+
onNotice: (tag, statement, statements, notice) => {
|
|
109
|
+
const full: MigrateNotice = { tag, statement, statements, ...notice }
|
|
110
|
+
notices.push(full)
|
|
111
|
+
options.onNotice?.(full)
|
|
112
|
+
},
|
|
95
113
|
})
|
|
96
114
|
|
|
97
115
|
// Trace and audit storage rides the default database; their partitioned
|
|
@@ -115,6 +133,7 @@ export async function migrateOne(instance: Manifold, dbConfig: ResolvedDatabase,
|
|
|
115
133
|
extensions: extensionsCreated,
|
|
116
134
|
safetyCheck,
|
|
117
135
|
resumed,
|
|
136
|
+
notices,
|
|
118
137
|
frameworkTables,
|
|
119
138
|
}
|
|
120
139
|
}
|
package/src/index.ts
CHANGED
|
@@ -83,7 +83,7 @@ export type { AuditChainHead, AuditChainBreak, AuditChainBreakReason, AuditVerif
|
|
|
83
83
|
export { jobs } from './async/jobs/namespace.js'
|
|
84
84
|
export { JobAbortedError, JobLaterError, JobPermanentError } from './async/jobs/index.js'
|
|
85
85
|
export { serializeError } from './async/jobs/serialize-error.js'
|
|
86
|
-
export type { ExecuteJobOptions, Queue, QueueConfig, EnqueueOptions, ClaimedJob, JobClaim, JobClaimReason, ReapedJob, FailedAttempt, QueueTotals, QueueTypeTotals, JobResult, BackoffConfig,
|
|
86
|
+
export type { ExecuteJobOptions, Queue, QueueConfig, EnqueueOptions, ClaimedJob, JobClaim, JobClaimReason, ReapedJob, FailedAttempt, FailOptions, QueueTotals, QueueTypeTotals, JobResult, BackoffConfig,
|
|
87
87
|
WaitOptions, WaitResult, ListenClient, ListenHandle, CronSchedule, ScheduleSpec,
|
|
88
88
|
ScheduleInfo, SchedulerOptions } from './async/jobs/index.js'
|
|
89
89
|
|