@noormdev/sdk 1.0.0-alpha.3 → 1.0.0-alpha.31

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.
Files changed (46) hide show
  1. package/dist/chunk-5AYPF5D5.js +943 -0
  2. package/dist/chunk-5AYPF5D5.js.map +1 -0
  3. package/dist/chunk-D2XW2UOP.js +42 -0
  4. package/dist/chunk-D2XW2UOP.js.map +1 -0
  5. package/dist/chunk-DE3NJ22A.js +308 -0
  6. package/dist/chunk-DE3NJ22A.js.map +1 -0
  7. package/dist/chunk-DLQES4PM.js +3136 -0
  8. package/dist/chunk-DLQES4PM.js.map +1 -0
  9. package/dist/chunk-TBRUDUBQ.js +1099 -0
  10. package/dist/chunk-TBRUDUBQ.js.map +1 -0
  11. package/dist/chunk-UOFJT5PY.js +1499 -0
  12. package/dist/chunk-UOFJT5PY.js.map +1 -0
  13. package/dist/chunk-YJBKNUTP.js +7233 -0
  14. package/dist/chunk-YJBKNUTP.js.map +1 -0
  15. package/dist/csv-T5P3IN4K.js +7 -0
  16. package/dist/csv-T5P3IN4K.js.map +1 -0
  17. package/dist/engine-GMQXUPNB.js +11 -0
  18. package/dist/{engine-B4JH5RQJ.js.map → engine-GMQXUPNB.js.map} +1 -1
  19. package/dist/index.d.ts +3316 -1006
  20. package/dist/index.js +26662 -6922
  21. package/dist/index.js.map +1 -1
  22. package/dist/json5-LNT3RGJ7.js +7 -0
  23. package/dist/json5-LNT3RGJ7.js.map +1 -0
  24. package/dist/mssql-3NNVV77C.js +121 -0
  25. package/dist/mssql-3NNVV77C.js.map +1 -0
  26. package/dist/{mysql-C2DR4TF7.js → mysql-UPMQUBLE.js} +5 -3
  27. package/dist/mysql-UPMQUBLE.js.map +1 -0
  28. package/dist/{postgres-SIDJBSIT.js → postgres-CPVTCR7W.js} +5 -3
  29. package/dist/postgres-CPVTCR7W.js.map +1 -0
  30. package/dist/{sqlite-5VZTBN5J.js → sqlite-F3ZSSDUM.js} +5 -3
  31. package/dist/sqlite-F3ZSSDUM.js.map +1 -0
  32. package/dist/sqlite-bun-LMHUMGQI.js +60 -0
  33. package/dist/sqlite-bun-LMHUMGQI.js.map +1 -0
  34. package/dist/storage-WQURRPCA.js +8 -0
  35. package/dist/storage-WQURRPCA.js.map +1 -0
  36. package/dist/yaml-XI7RQ2D5.js +7 -0
  37. package/dist/yaml-XI7RQ2D5.js.map +1 -0
  38. package/package.json +8 -4
  39. package/dist/chunk-MERTHCAJ.js +0 -299
  40. package/dist/chunk-MERTHCAJ.js.map +0 -1
  41. package/dist/engine-B4JH5RQJ.js +0 -3
  42. package/dist/mssql-T4M7OGEC.js +0 -46
  43. package/dist/mssql-T4M7OGEC.js.map +0 -1
  44. package/dist/mysql-C2DR4TF7.js.map +0 -1
  45. package/dist/postgres-SIDJBSIT.js.map +0 -1
  46. package/dist/sqlite-5VZTBN5J.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
- import { Kysely } from 'kysely';
3
+ import { Generated, Kysely, Transaction } from 'kysely';
4
4
 
5
5
  /**
6
6
  * Supported database dialects.
@@ -45,6 +45,31 @@ export interface ConnectionConfig {
45
45
  key?: string;
46
46
  };
47
47
  }
48
+ /**
49
+ * Result of creating a connection.
50
+ */
51
+ export interface ConnectionResult {
52
+ db: Kysely<unknown>;
53
+ dialect: Dialect;
54
+ destroy: () => Promise<void>;
55
+ }
56
+ /**
57
+ * Logger Types
58
+ *
59
+ * Type definitions for the noorm logging system.
60
+ * The logger captures observer events and streams them
61
+ * to a log file with configurable verbosity.
62
+ */
63
+ /**
64
+ * Log verbosity levels.
65
+ *
66
+ * - silent: No logging
67
+ * - error: Errors only
68
+ * - warn: Errors + warnings
69
+ * - info: Errors + warnings + info (default)
70
+ * - verbose: All events including debug
71
+ */
72
+ export type LogLevel = "silent" | "error" | "warn" | "info" | "verbose";
48
73
  /**
49
74
  * Full configuration object.
50
75
  *
@@ -63,10 +88,6 @@ export interface ConnectionConfig {
63
88
  * user: 'postgres',
64
89
  * password: 'postgres',
65
90
  * },
66
- * paths: {
67
- * sql: './sql',
68
- * changes: './changes',
69
- * },
70
91
  * }
71
92
  * ```
72
93
  */
@@ -76,188 +97,865 @@ export interface Config {
76
97
  isTest: boolean;
77
98
  protected: boolean;
78
99
  connection: ConnectionConfig;
79
- paths: {
80
- sql: string;
81
- changes: string;
82
- };
83
100
  identity?: string;
84
101
  }
85
- /**
86
- * Settings Types
87
- *
88
- * Settings define project-wide build behavior and stage configuration.
89
- * Unlike encrypted configs (credentials), settings are version controlled
90
- * and shared across the team via .noorm/settings.yml
91
- */
92
- /**
93
- * Secret type hints for CLI input handling.
94
- *
95
- * - string: Plain text input
96
- * - password: Masked input, no echo
97
- * - api_key: Masked input, validated format
98
- * - connection_string: Validated as URI
99
- */
100
- export type SecretType = "string" | "password" | "api_key" | "connection_string";
101
- /**
102
- * Required secret definition for a stage.
103
- *
104
- * @example
105
- * ```yaml
106
- * secrets:
107
- * - key: DB_PASSWORD
108
- * type: password
109
- * description: Database password
110
- * required: true
111
- * ```
112
- */
113
- export interface StageSecret {
114
- /** Secret key name (e.g., DB_PASSWORD) */
115
- key: string;
116
- /** Type hint for CLI input handling */
117
- type: SecretType;
118
- /** Human-readable description shown in CLI prompts */
119
- description?: string;
120
- /** Whether the secret is required (default: true) */
121
- required?: boolean;
122
- }
123
- /**
124
- * Stage defaults that can be overridden when creating a config.
125
- *
126
- * These provide initial values. Some are enforceable constraints:
127
- * - protected: true - Cannot be overridden to false
128
- * - isTest: true - Cannot be overridden to false
129
- * - dialect - Cannot be changed after creation
130
- */
131
- export interface StageDefaults {
132
- dialect?: "postgres" | "mysql" | "sqlite" | "mssql";
133
- host?: string;
134
- port?: number;
135
- database?: string;
136
- user?: string;
137
- password?: string;
138
- ssl?: boolean;
139
- isTest?: boolean;
140
- protected?: boolean;
102
+ export type Events<Shape> = keyof Shape;
103
+ declare namespace ObserverEngine {
104
+ interface EventCallback<Shape> {
105
+ (data: Shape, info?: {
106
+ event: string;
107
+ listener: Function;
108
+ } | undefined): void;
109
+ }
110
+ type RgxEmitData<Shape> = {
111
+ event: Events<Shape>;
112
+ data: Shape[Events<Shape>];
113
+ };
114
+ type Cleanup = () => void;
115
+ type Component<Ev extends Record<string, any>> = {
116
+ on: ObserverEngine<Ev>["on"];
117
+ once: ObserverEngine<Ev>["once"];
118
+ emit: ObserverEngine<Ev>["emit"];
119
+ off: ObserverEngine<Ev>["off"];
120
+ };
121
+ type Child<C, Ev extends Record<string, any>> = C & Component<Ev> & {
122
+ cleanup: Cleanup;
123
+ clear: Cleanup;
124
+ };
125
+ type Instance<Ev extends Record<string, any>> = Component<Ev> & {
126
+ observe: ObserverEngine<Ev>["observe"];
127
+ $observer: ObserverEngine<Ev>;
128
+ };
129
+ type FuncName = "on" | "once" | "off" | "emit" | "cleanup";
130
+ type SpyAction<Ev extends Record<string, any>> = {
131
+ event: keyof Ev | RegExp | "*";
132
+ listener?: Function | null | undefined;
133
+ data?: unknown | undefined;
134
+ fn: FuncName;
135
+ context: ObserverEngine<any>;
136
+ };
137
+ interface Spy<Ev extends Record<string, any>> {
138
+ (action: SpyAction<Ev>): void;
139
+ }
140
+ interface EmitValidator<Ev extends Record<string, any>> {
141
+ (event: keyof Ev, data: Ev[keyof Ev], context: ObserverEngine<Ev>): void;
142
+ (event: RegExp, data: unknown, context: ObserverEngine<Ev>): void;
143
+ (event: "*", data: unknown, context: ObserverEngine<Ev>): void;
144
+ }
145
+ type Options<Ev extends Record<string, any>> = {
146
+ name?: string | undefined;
147
+ spy?: Spy<Ev> | undefined;
148
+ emitValidator?: EmitValidator<Ev> | undefined;
149
+ signal?: AbortSignal | undefined;
150
+ };
151
+ type ListenerOptions = {
152
+ signal?: AbortSignal | undefined;
153
+ };
154
+ type ObserveOptions = {
155
+ signal?: AbortSignal | undefined;
156
+ };
157
+ type TransferOptions<Ev extends Record<string, any>> = {
158
+ filter?: (Events<Ev> | RegExp)[];
159
+ exclude?: (Events<Ev> | RegExp)[];
160
+ };
141
161
  }
142
162
  /**
143
- * Stage definition - a preconfigured config template.
163
+ * Generic function type for type-safe function signatures.
144
164
  *
145
- * Stages are team-defined config templates with defaults, constraints,
146
- * and required secrets. They're managed via version control.
165
+ * Enables consistent typing across utility functions and higher-order functions
166
+ * where function parameters need to be strongly typed.
147
167
  *
148
168
  * @example
149
- * ```yaml
150
- * stages:
151
- * prod:
152
- * description: Production database
153
- * locked: true
154
- * defaults:
155
- * dialect: postgres
156
- * protected: true
157
- * secrets:
158
- * - key: DB_PASSWORD
159
- * type: password
160
- * ```
161
- */
162
- export interface Stage {
163
- /** Human-readable description shown in CLI */
164
- description?: string;
165
- /** If true, configs linked to this stage cannot be deleted */
166
- locked?: boolean;
167
- /** Default values when creating config from this stage */
168
- defaults?: StageDefaults;
169
- /** Required secrets that must be set before config is usable */
170
- secrets?: StageSecret[];
171
- }
172
- /**
173
- * Conditions for rule matching.
174
- *
175
- * All conditions in a rule are AND'd together.
176
- * A rule matches when ALL specified conditions are true.
169
+ * function memoize<A extends any[], R>(fn: Func<A, R>): Func<A, R>
170
+ * function debounce<A extends any[]>(fn: Func<A, void>, ms: number): Func<A, void>
177
171
  */
178
- export interface RuleMatch {
179
- /** Config name (exact match) */
180
- name?: string;
181
- /** Protected config flag */
182
- protected?: boolean;
183
- /** Test database flag */
184
- isTest?: boolean;
185
- /** Connection type */
186
- type?: "local" | "remote";
187
- }
172
+ export type Func<A extends any[] = any[], R = any> = (...args: A) => R;
188
173
  /**
189
- * Stage-based rule for conditional include/exclude.
174
+ * Represents a value that can be either synchronous or asynchronous.
190
175
  *
191
- * Rules control which files/folders are included or excluded
192
- * based on the active config's properties.
176
+ * Critical for utility functions that need to handle both sync and async
177
+ * operations uniformly, enabling flexible API design.
193
178
  *
194
179
  * @example
195
- * ```yaml
196
- * rules:
197
- * - match:
198
- * isTest: true
199
- * include:
200
- * - sql/seeds
201
- * - match:
202
- * protected: true
203
- * exclude:
204
- * - sql/dangerous
205
- * ```
180
+ * function processData<T>(processor: () => MaybePromise<T>): Promise<T>
181
+ * function transform<T, U>(value: T, transformer: (val: T) => MaybePromise<U>): Promise<U>
206
182
  */
207
- export interface Rule {
208
- /** User-friendly description (e.g., "test seeds", "prod config") */
209
- description?: string;
210
- /** Conditions that must all match */
211
- match: RuleMatch;
212
- /** Folders to include when rule matches */
213
- include?: string[];
214
- /** Folders to exclude when rule matches */
215
- exclude?: string[];
183
+ export type MaybePromise<T> = T | Promise<T>;
184
+ export interface EventGeneratorOptions {
185
+ signal?: AbortSignal | undefined;
216
186
  }
217
- /**
218
- * Build configuration - controls file inclusion and execution order.
219
- */
220
- export interface BuildConfig {
187
+ declare class EventGenerator<S extends Record<string, any>, E extends Events<S> | RegExp = Events<S>> {
188
+ cleanup: ObserverEngine.Cleanup;
189
+ next: () => Promise<EventData<S, E>>;
190
+ constructor(observer: ObserverEngine<S>, event: E | RegExp, options?: EventGeneratorOptions);
191
+ [Symbol.asyncIterator](): AsyncGenerator<Awaited<EventData<S, E>>, undefined, unknown>;
192
+ get lastValue(): E extends Events<S> ? S[E] : E extends RegExp ? ObserverEngine.RgxEmitData<S> : S[Events<S>];
193
+ get done(): boolean;
194
+ emit(data?: (E extends Events<S> ? S[E] : S[Events<S>])): void;
195
+ }
196
+ declare class ObserverEngine<Shape extends Record<string, any> = Record<string, any>> {
197
+ constructor(options?: ObserverEngine.Options<Shape>);
198
+ name: string;
221
199
  /**
222
- * Folders to include, executed in this order.
223
- * First listed = first executed.
200
+ * Returns facts about the the internal state of the observable instance.
224
201
  */
225
- include?: string[];
226
- /** Folders to exclude from all builds */
227
- exclude?: string[];
228
- }
229
- /**
230
- * Path configuration - override default locations.
231
- */
232
- export interface PathConfig {
233
- /** Path to SQL files (relative to project root) */
234
- sql?: string;
235
- /** Path to change files (relative to project root) */
236
- changes?: string;
237
- }
238
- /**
239
- * Strict mode configuration.
240
- *
241
- * When enabled, requires certain stages to exist before operations can run.
242
- */
243
- export interface StrictConfig {
244
- /** Enable strict mode */
245
- enabled?: boolean;
246
- /** Required stage names that must have configs */
247
- stages?: string[];
248
- }
249
- /**
250
- * Logging configuration.
251
- */
252
- export interface LoggingConfig {
253
- /** Enable file logging */
254
- enabled?: boolean;
255
- /** Minimum level to capture: silent, error, warn, info, verbose */
256
- level?: "silent" | "error" | "warn" | "info" | "verbose";
257
- /** Log file path (relative to project root) */
258
- file?: string;
259
- /** Rotate log when size exceeded (e.g., '10mb') */
260
- maxSize?: string;
202
+ $facts(): {
203
+ listeners: (keyof Shape)[];
204
+ rgxListeners: string[];
205
+ listenerCounts: Record<string, number>;
206
+ hasSpy: boolean;
207
+ };
208
+ /**
209
+ * The internals of the observable instance.
210
+ *
211
+ * NOTE: Do not use this to try to meddle with the
212
+ * internals of the observable instance. This is for
213
+ * debugging purposes only.
214
+ */
215
+ $internals(): {
216
+ listenerMap: Map<keyof Shape, Set<Func>>;
217
+ rgxListenerMap: Map<string, Set<Func>>;
218
+ internalListener: EventTarget;
219
+ name: string;
220
+ spy: ObserverEngine.Spy<Shape> | undefined;
221
+ };
222
+ /**
223
+ * Returns if the observable instance has the given event
224
+ */
225
+ $has(event: Events<Shape>): boolean;
226
+ /**
227
+ * Returns if the observable instance has a regex event
228
+ */
229
+ $has(event: RegExp): boolean;
230
+ /**
231
+ * Returns if the observable instance has the given event
232
+ */
233
+ $has(event: string): boolean;
234
+ /**
235
+ * Enables or disables debugging for the observable instance.
236
+ * Works in conjunction with your spy function. Provides a
237
+ * stack trace of events that are triggered, listened to, and
238
+ * cleaned up.
239
+ *
240
+ * @param on Whether to enable or disable debugging
241
+ */
242
+ debug(on?: boolean): void;
243
+ /**
244
+ * Sets the spy function for the observable instance.
245
+ * @param spy The spy function to set
246
+ * @param force Whether to force the spy function to be set even if one is already set
247
+ */
248
+ spy(spy: ObserverEngine.Spy<Shape>, force?: boolean): void;
249
+ /**
250
+ * Observes given component as an extension of this observable instance.
251
+ * @param component Component to wrap events around
252
+ * @param options Optional configuration including signal for cleanup
253
+ *
254
+ * @example
255
+ *
256
+ * const obs = new ObserverEngine();
257
+ *
258
+ * const modal = {};
259
+ *
260
+ * obs.observe(modal);
261
+ *
262
+ * modal.on('modal-open', () => {});
263
+ *
264
+ * obs.trigger('modal-open'); // opens modal
265
+ * modal.trigger('modal-open'); // opens modal
266
+ *
267
+ * modal.cleanup(); // clears all event listeners
268
+ */
269
+ observe<C>(component: C, options?: ObserverEngine.ObserveOptions): ObserverEngine.Child<C, Shape>;
270
+ /**
271
+ * Returns an event generator that will listen for the specified event
272
+ *
273
+ * @example
274
+ *
275
+ * const obs = new ObserverEngine();
276
+ *
277
+ * const something = obs.on('something');
278
+ * const data = await something.next(); // waits for next event
279
+ * something.emit('special'); // emits data to listeners
280
+ *
281
+ * something.cleanup(); // stops listening for events
282
+ */
283
+ on<E extends Events<Shape>>(event: E, options?: ObserverEngine.ListenerOptions): EventGenerator<Shape, E>;
284
+ on<E extends string>(event: E, options?: ObserverEngine.ListenerOptions): EventGenerator<Record<E, any>>;
285
+ /**
286
+ * Listens for the specified event and executes the given callback
287
+ *
288
+ * @example
289
+ *
290
+ * const obs = new ObserverEngine();
291
+ *
292
+ * obs.on('something', (data) => {
293
+ * console.log(data);
294
+ * });
295
+ */
296
+ on<E extends Events<Shape>>(event: E, listener: ObserverEngine.EventCallback<Shape[E]>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
297
+ on<E extends string>(event: E, listener: ObserverEngine.EventCallback<Record<E, any>>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
298
+ /**
299
+ * Returns an event generator that will listen for all events matching the regex
300
+ *
301
+ * @example
302
+ *
303
+ * const obs = new ObserverEngine();
304
+ *
305
+ * const onEvent = obs.on(/some/);
306
+ * const { event, data } = await onEvent.next(); // waits for next event
307
+ * onEvent.emit('something'); // emits data to listeners
308
+ *
309
+ * onEvent.cleanup(); // stops listening for events
310
+ */
311
+ on(event: RegExp, options?: ObserverEngine.ListenerOptions): EventGenerator<Shape, RegExp>;
312
+ /**
313
+ * Listens for all events matching the regex and executes the given callback
314
+ *
315
+ * @example
316
+ *
317
+ * const obs = new ObserverEngine();
318
+ *
319
+ * obs.on(/some/, ({ event, data }) => {
320
+ * console.log(event, data);
321
+ * });
322
+ */
323
+ on(event: RegExp, listener: ObserverEngine.EventCallback<ObserverEngine.RgxEmitData<Shape>>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
324
+ /**
325
+ * Returns an event promise that resolves when
326
+ * the specified event is emitted
327
+ */
328
+ once<E extends Events<Shape>>(event: E, options?: ObserverEngine.ListenerOptions): EventPromise<Shape[E]>;
329
+ /**
330
+ * Returns an event promise that resolves when
331
+ * the specified event is emitted. This overload
332
+ * is untyped and can be used to listen for any
333
+ * event that is emitted.
334
+ */
335
+ once<E extends string>(event: E, options?: ObserverEngine.ListenerOptions): EventPromise<Record<E, any>>;
336
+ /**
337
+ * Executes a callback once when the specified
338
+ * event is emitted
339
+ */
340
+ once<E extends Events<Shape>>(event: E, listener: ObserverEngine.EventCallback<Shape[E]>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
341
+ /**
342
+ * Executes a callback once when the specified
343
+ * event is emitted. This overload is untyped
344
+ * and can be used to listen for any event that
345
+ * is emitted.
346
+ */
347
+ once<E extends string>(event: E, listener: ObserverEngine.EventCallback<Record<E, any>>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
348
+ /**
349
+ * Returns an event promise that resolves when
350
+ * any events matching the regex are emitted
351
+ */
352
+ once(event: RegExp, options?: ObserverEngine.ListenerOptions): EventPromise<ObserverEngine.RgxEmitData<Shape>>;
353
+ /**
354
+ * Executes a callback once when any events
355
+ * matching the regex are emitted
356
+ */
357
+ once(event: RegExp, listener: ObserverEngine.EventCallback<ObserverEngine.RgxEmitData<Shape>>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
358
+ /**
359
+ * Stop listening for an event
360
+ * @param event
361
+ * @param listener
362
+ */
363
+ off(event: Events<Shape> | RegExp | string, listener?: Function): void;
364
+ /** Emits an event */
365
+ emit<E extends Events<Shape> | RegExp | string>(event: E, data?: E extends Events<Shape> ? Shape[E] : E extends string ? Record<E, any>[E] : unknown): void;
366
+ clear(): void;
367
+ queue<E extends Events<Shape> | RegExp>(event: E, process: (data: EventData<Shape, E>) => MaybePromise<any>, options: QueueOpts): EventQueue<Shape, E>;
368
+ /**
369
+ * Transfers all matching listeners from source to target, removing
370
+ * them from the source. Target's existing listeners are preserved.
371
+ *
372
+ * Useful when cloning an engine (e.g. FetchEngine) and carrying
373
+ * over observability listeners to the new instance.
374
+ *
375
+ * @example
376
+ *
377
+ * const old = new ObserverEngine();
378
+ * const next = new ObserverEngine();
379
+ *
380
+ * old.on('ready', handler);
381
+ *
382
+ * ObserverEngine.transfer(old, next);
383
+ * // `next` now owns the 'ready' listener; `old` has none
384
+ *
385
+ * @example
386
+ *
387
+ * // Transfer only specific events
388
+ * ObserverEngine.transfer(old, next, {
389
+ * filter: ['ready', /^user\./]
390
+ * });
391
+ */
392
+ static transfer<Shape extends Record<string, any>>(source: ObserverEngine<Shape>, target: ObserverEngine<Shape>, options?: ObserverEngine.TransferOptions<Shape>): void;
393
+ /**
394
+ * Copies all matching listeners from source to target without
395
+ * removing them from the source. Target's existing listeners
396
+ * are preserved.
397
+ *
398
+ * Useful when forking an engine so both instances react to the
399
+ * same events independently.
400
+ *
401
+ * @example
402
+ *
403
+ * const primary = new ObserverEngine();
404
+ * const mirror = new ObserverEngine();
405
+ *
406
+ * primary.on('update', handler);
407
+ *
408
+ * ObserverEngine.copy(primary, mirror);
409
+ * // Both engines now have the 'update' listener
410
+ *
411
+ * @example
412
+ *
413
+ * // Copy everything except internal events
414
+ * ObserverEngine.copy(primary, mirror, {
415
+ * exclude: [/^__/]
416
+ * });
417
+ */
418
+ static copy<Shape extends Record<string, any>>(source: ObserverEngine<Shape>, target: ObserverEngine<Shape>, options?: ObserverEngine.TransferOptions<Shape>): void;
419
+ }
420
+ export type EventData<S, E extends Events<S> | RegExp = Events<S>> = (E extends Events<S> ? S[E] : E extends RegExp ? ObserverEngine.RgxEmitData<S> : S[Events<S>]);
421
+ declare class EventPromise<T> extends Promise<T> {
422
+ cleanup?: () => void;
423
+ reject?: (err: Error | string) => void;
424
+ resolve?: (value: T) => void;
425
+ }
426
+ declare class InternalQueueEvent<T = unknown> {
427
+ readonly data: T;
428
+ constructor(data: T);
429
+ }
430
+ declare enum QueueRejectionReason {
431
+ full = "Queue is full",
432
+ notRunning = "Queue is not running"
433
+ }
434
+ export type QueueEventData<S, E extends Events<S> | RegExp = Events<S>> = {
435
+ data?: EventData<S, E>;
436
+ _taskId?: string;
437
+ priority?: number;
438
+ reason?: QueueRejectionReason;
439
+ startedAt?: number;
440
+ rateLimited?: boolean;
441
+ elapsed?: number;
442
+ error?: Error;
443
+ force?: boolean;
444
+ pending?: number;
445
+ flushed?: number;
446
+ drained?: number;
447
+ count?: number;
448
+ };
449
+ export type QueueEvents<S extends Record<string, any>, E extends Events<S> | RegExp = Events<S>> = {
450
+ added: QueueEventData<S, E>;
451
+ start: void;
452
+ started: void;
453
+ stopped: void;
454
+ processing: QueueEventData<S, E> & {
455
+ startedAt: number;
456
+ rateLimited: boolean;
457
+ };
458
+ success: QueueEventData<S, E> & {
459
+ startedAt: number;
460
+ elapsed: number;
461
+ rateLimited: boolean;
462
+ };
463
+ error: QueueEventData<S, E> & {
464
+ error: Error;
465
+ rateLimited: boolean;
466
+ };
467
+ timeout: QueueEventData<S, E> & {
468
+ error: Error;
469
+ rateLimited: boolean;
470
+ };
471
+ rejected: QueueEventData<S, E> & {
472
+ reason: QueueRejectionReason;
473
+ };
474
+ "rate-limited": QueueEventData<S, E> & {
475
+ rateLimited: boolean;
476
+ };
477
+ empty: void;
478
+ idle: void;
479
+ drain: {
480
+ pending: number;
481
+ };
482
+ drained: {
483
+ pending?: number;
484
+ drained?: number;
485
+ };
486
+ flush: {
487
+ pending: number;
488
+ };
489
+ flushed: {
490
+ flushed: number;
491
+ };
492
+ paused: void;
493
+ resumed: void;
494
+ cleanup: void;
495
+ purged: {
496
+ count: number;
497
+ };
498
+ shutdown: {
499
+ force: boolean;
500
+ pending?: number;
501
+ };
502
+ };
503
+ declare enum QueueState {
504
+ running = "running",
505
+ paused = "paused",
506
+ stopped = "stopped",
507
+ draining = "draining"
508
+ }
509
+ /**
510
+ * The options for the queue
511
+ */
512
+ export interface QueueOpts {
513
+ /**
514
+ * The name of the queue
515
+ */
516
+ name: string;
517
+ /**
518
+ * The type of queue to use
519
+ *
520
+ * @default 'fifo'
521
+ */
522
+ type?: "fifo" | "lifo";
523
+ /**
524
+ * The concurrency of the queue
525
+ *
526
+ * @default 1
527
+ */
528
+ concurrency?: number;
529
+ /**
530
+ * The poll interval in milliseconds before the queue will
531
+ * check for new items after idle.
532
+ *
533
+ * @default 100
534
+ */
535
+ pollIntervalMs?: number;
536
+ /**
537
+ * The jitter percentage to displace
538
+ * the next process time. This stops all concurrent
539
+ * processes from happening at the same time.
540
+ *
541
+ * @range [0, 1]
542
+ * @default 1
543
+ */
544
+ jitterFactor?: number;
545
+ /**
546
+ * The interval in milliseconds before picking up the next item
547
+ *
548
+ * @note If the interval is 0, the queue will not wait between items
549
+ *
550
+ * @default 0
551
+ */
552
+ processIntervalMs?: number;
553
+ /**
554
+ * The timeout in milliseconds before the task is considered timed out
555
+ *
556
+ * @note If the timeout is 0, the task will not be considered timed out
557
+ *
558
+ * @default 0
559
+ */
560
+ taskTimeoutMs?: number;
561
+ /**
562
+ * The maximum size of the queue
563
+ *
564
+ * @default 999_999_999
565
+ */
566
+ maxQueueSize?: number;
567
+ /**
568
+ * The rate limit of the queue in items per window
569
+ *
570
+ * @default 999_999_999
571
+ */
572
+ rateLimitCapacity?: number;
573
+ /**
574
+ * The rate limit window in milliseconds
575
+ *
576
+ * @default 1000
577
+ */
578
+ rateLimitIntervalMs?: number;
579
+ /**
580
+ * Automatically start the queue
581
+ *
582
+ * @default true
583
+ */
584
+ autoStart?: boolean;
585
+ /**
586
+ * Whether to enable debug mode. Can be set to 'info' or 'verbose' to
587
+ * get more detailed output.
588
+ *
589
+ * @default false
590
+ */
591
+ debug?: boolean | "info" | "verbose";
592
+ }
593
+ declare class EventQueue<S extends Record<string, any>, E extends Events<S> | RegExp = Events<S>> {
594
+ private opts;
595
+ constructor(opts: QueueOpts & {
596
+ event: E | RegExp;
597
+ process: (data: EventData<S, E>) => Promise<void>;
598
+ observer?: ObserverEngine<S>;
599
+ });
600
+ /**
601
+ * Start the queue if it is not already started
602
+ */
603
+ start(): Promise<void> | undefined;
604
+ /**
605
+ * Stop the queue
606
+ */
607
+ stop(): void;
608
+ /**
609
+ * Pause the queue
610
+ */
611
+ pause(): Promise<void> | undefined;
612
+ /**
613
+ * Resume the queue
614
+ */
615
+ resume(): Promise<void> | undefined;
616
+ /**
617
+ * Empty and process all items in the queue and
618
+ * then stop the queue. Returns the number of items
619
+ * processed.
620
+ */
621
+ shutdown(force?: boolean): Promise<number>;
622
+ /**
623
+ * Executes `limit` items in the queue
624
+ * and then emits a `flush` event. Returns the number
625
+ * of items processed.
626
+ */
627
+ flush(limit?: number): Promise<number>;
628
+ /**
629
+ * Clears the queue and emits a `purge` event. Returns
630
+ * the number of items purged.
631
+ */
632
+ purge(): number;
633
+ /**
634
+ * Emit an event to the observer, which then gets
635
+ * picked up by the queue and processed.
636
+ *
637
+ * @param data The data to add to the queue
638
+ * @param priority The priority of the data
639
+ *
640
+ * @note If priority is provided, it will be used to determine the order of the data in the queue.
641
+ * The higher the priority, the sooner the data will be processed.
642
+ *
643
+ * @note If priority is not provided, the data will be added to the queue with a priority of 0.
644
+ */
645
+ add(data: EventData<S, E>, priority?: number): boolean;
646
+ debug(on?: boolean | "info" | "verbose"): void;
647
+ on<K extends keyof QueueEvents<S, E>>(event: K, listener: ((payload: InternalQueueEvent<QueueEvents<S, E>[K]>) => void)): ObserverEngine.Cleanup;
648
+ on<K extends keyof QueueEvents<S, E>>(event: K): EventGenerator<QueueEvents<S, E>, K>;
649
+ once<K extends keyof QueueEvents<S, E>>(event: K): EventPromise<InternalQueueEvent<QueueEvents<S, E>[K]>>;
650
+ once<K extends keyof QueueEvents<S, E>>(event: K, listener: ((payload: InternalQueueEvent<QueueEvents<S, E>[K]>) => void)): ObserverEngine.Cleanup;
651
+ off<K extends keyof QueueEvents<S, E>>(event: K, listener?: Function): void;
652
+ /**
653
+ * The name of the queue
654
+ */
655
+ get name(): string;
656
+ /**
657
+ * Whether the queue is waiting for the
658
+ * the
659
+ */
660
+ get isWaiting(): boolean;
661
+ /**
662
+ * Whether the queue is idle
663
+ */
664
+ get isIdle(): boolean;
665
+ /**
666
+ * Whether the queue is running
667
+ */
668
+ get isRunning(): boolean;
669
+ /**
670
+ * Whether the queue is paused
671
+ */
672
+ get isPaused(): boolean;
673
+ /**
674
+ * Whether the queue is stopped
675
+ */
676
+ get isStopped(): boolean;
677
+ /**
678
+ * Whether the queue is draining
679
+ */
680
+ get isDraining(): boolean;
681
+ /**
682
+ * The current state of the queue
683
+ */
684
+ get state(): QueueState;
685
+ /**
686
+ * The number of items in the queue
687
+ */
688
+ get pending(): number;
689
+ /**
690
+ * The stats of the queue
691
+ */
692
+ get stats(): {
693
+ processed: number;
694
+ processing: number;
695
+ avgProcessingTime: number;
696
+ success: number;
697
+ error: number;
698
+ rejected: number;
699
+ };
700
+ get snapshot(): {
701
+ name: string;
702
+ state: QueueState;
703
+ pending: number;
704
+ stats: {
705
+ processed: number;
706
+ processing: number;
707
+ avgProcessingTime: number;
708
+ success: number;
709
+ error: number;
710
+ rejected: number;
711
+ };
712
+ rateLimiter: {
713
+ currentTokens: number;
714
+ capacity: number;
715
+ refillIntervalMs: number;
716
+ totalRequests: number;
717
+ rejectedRequests: number;
718
+ successfulRequests: number;
719
+ rejectionRate: number;
720
+ totalWaitTime: number;
721
+ waitCount: number;
722
+ averageWaitTime: number;
723
+ uptime: number;
724
+ createdAt: number;
725
+ };
726
+ activeRunners: number;
727
+ runningNodes: Set<`${number}-${number}`>;
728
+ isIdle: boolean;
729
+ isWaiting: boolean;
730
+ isRunning: boolean;
731
+ isPaused: boolean;
732
+ isStopped: boolean;
733
+ isDraining: boolean;
734
+ isEmpty: boolean;
735
+ };
736
+ }
737
+ /**
738
+ * Simple types stored as native JSON values. No encoding tuple needed.
739
+ */
740
+ export type SimpleType = "string" | "int" | "bigint" | "float" | "decimal" | "bool" | "timestamp" | "date" | "uuid";
741
+ /**
742
+ * Encoded types that always use a `[value, encoding]` tuple in .dt files.
743
+ */
744
+ export type EncodedType = "json" | "binary" | "vector" | "array" | "text" | "custom";
745
+ /**
746
+ * All universal types recognized by the .dt format.
747
+ */
748
+ export type UniversalType = SimpleType | EncodedType;
749
+ /**
750
+ * Column definition within a .dt schema.
751
+ *
752
+ * @example
753
+ * ```typescript
754
+ * const col: DtColumn = {
755
+ * name: 'embedding',
756
+ * type: 'vector',
757
+ * sourceType: 'vector(1536)',
758
+ * nullable: false,
759
+ * };
760
+ * ```
761
+ */
762
+ export interface DtColumn {
763
+ /** Column name as it appears in the source database. */
764
+ name: string;
765
+ /** Universal type classification. */
766
+ type: UniversalType;
767
+ /** Original database-specific type (e.g., `jsonb`, `vector(1536)`). */
768
+ sourceType?: string;
769
+ /** Whether NULL values are permitted. Default: true. */
770
+ nullable?: boolean;
771
+ }
772
+ /**
773
+ * Schema validation result.
774
+ */
775
+ export interface SchemaValidationResult {
776
+ /** Whether the schema is compatible with the target. */
777
+ valid: boolean;
778
+ /** Blocking errors that prevent transfer. */
779
+ errors: string[];
780
+ /** Non-blocking warnings about potential issues. */
781
+ warnings: string[];
782
+ }
783
+ /**
784
+ * Settings Types
785
+ *
786
+ * Settings define project-wide build behavior and stage configuration.
787
+ * Unlike encrypted configs (credentials), settings are version controlled
788
+ * and shared across the team via .noorm/settings.yml
789
+ */
790
+ /**
791
+ * Secret type hints for CLI input handling.
792
+ *
793
+ * - string: Plain text input
794
+ * - password: Masked input, no echo
795
+ * - api_key: Masked input, validated format
796
+ * - connection_string: Validated as URI
797
+ */
798
+ export type SecretType = "string" | "password" | "api_key" | "connection_string";
799
+ /**
800
+ * Required secret definition for a stage.
801
+ *
802
+ * @example
803
+ * ```yaml
804
+ * secrets:
805
+ * - key: DB_PASSWORD
806
+ * type: password
807
+ * description: Database password
808
+ * required: true
809
+ * ```
810
+ */
811
+ export interface StageSecret {
812
+ /** Secret key name (e.g., DB_PASSWORD) */
813
+ key: string;
814
+ /** Type hint for CLI input handling */
815
+ type: SecretType;
816
+ /** Human-readable description shown in CLI prompts */
817
+ description?: string;
818
+ /** Whether the secret is required (default: true) */
819
+ required?: boolean;
820
+ }
821
+ /**
822
+ * Stage defaults that can be overridden when creating a config.
823
+ *
824
+ * These provide initial values. Some are enforceable constraints:
825
+ * - protected: true - Cannot be overridden to false
826
+ * - isTest: true - Cannot be overridden to false
827
+ * - dialect - Cannot be changed after creation
828
+ */
829
+ export interface StageDefaults {
830
+ dialect?: "postgres" | "mysql" | "sqlite" | "mssql";
831
+ host?: string;
832
+ port?: number;
833
+ database?: string;
834
+ user?: string;
835
+ password?: string;
836
+ ssl?: boolean;
837
+ isTest?: boolean;
838
+ protected?: boolean;
839
+ }
840
+ /**
841
+ * Stage definition - a preconfigured config template.
842
+ *
843
+ * Stages are team-defined config templates with defaults, constraints,
844
+ * and required secrets. They're managed via version control.
845
+ *
846
+ * @example
847
+ * ```yaml
848
+ * stages:
849
+ * prod:
850
+ * description: Production database
851
+ * locked: true
852
+ * defaults:
853
+ * dialect: postgres
854
+ * protected: true
855
+ * secrets:
856
+ * - key: DB_PASSWORD
857
+ * type: password
858
+ * ```
859
+ */
860
+ export interface Stage {
861
+ /** Human-readable description shown in CLI */
862
+ description?: string;
863
+ /** If true, configs linked to this stage cannot be deleted */
864
+ locked?: boolean;
865
+ /** Default values when creating config from this stage */
866
+ defaults?: StageDefaults;
867
+ /** Required secrets that must be set before config is usable */
868
+ secrets?: StageSecret[];
869
+ }
870
+ /**
871
+ * Conditions for rule matching.
872
+ *
873
+ * All conditions in a rule are AND'd together.
874
+ * A rule matches when ALL specified conditions are true.
875
+ */
876
+ export interface RuleMatch {
877
+ /** Config name (exact match) */
878
+ name?: string;
879
+ /** Protected config flag */
880
+ protected?: boolean;
881
+ /** Test database flag */
882
+ isTest?: boolean;
883
+ /** Connection type */
884
+ type?: "local" | "remote";
885
+ }
886
+ /**
887
+ * Stage-based rule for conditional include/exclude.
888
+ *
889
+ * Rules control which files/folders are included or excluded
890
+ * based on the active config's properties.
891
+ *
892
+ * @example
893
+ * ```yaml
894
+ * rules:
895
+ * - match:
896
+ * isTest: true
897
+ * include:
898
+ * - sql/seeds
899
+ * - match:
900
+ * protected: true
901
+ * exclude:
902
+ * - sql/dangerous
903
+ * ```
904
+ */
905
+ export interface Rule {
906
+ /** User-friendly description (e.g., "test seeds", "prod config") */
907
+ description?: string;
908
+ /** Conditions that must all match */
909
+ match: RuleMatch;
910
+ /** Folders to include when rule matches */
911
+ include?: string[];
912
+ /** Folders to exclude when rule matches */
913
+ exclude?: string[];
914
+ }
915
+ /**
916
+ * Build configuration - controls file inclusion and execution order.
917
+ */
918
+ export interface BuildConfig {
919
+ /**
920
+ * Folders to include, executed in this order.
921
+ * First listed = first executed.
922
+ */
923
+ include?: string[];
924
+ /** Folders to exclude from all builds */
925
+ exclude?: string[];
926
+ }
927
+ /**
928
+ * Path configuration - override default locations.
929
+ */
930
+ export interface PathConfig {
931
+ /** Path to SQL files (relative to project root) */
932
+ sql?: string;
933
+ /** Path to change files (relative to project root) */
934
+ changes?: string;
935
+ }
936
+ /**
937
+ * Strict mode configuration.
938
+ *
939
+ * When enabled, requires certain stages to exist before operations can run.
940
+ */
941
+ export interface StrictConfig {
942
+ /** Enable strict mode */
943
+ enabled?: boolean;
944
+ /** Required stage names that must have configs */
945
+ stages?: string[];
946
+ }
947
+ /**
948
+ * Logging configuration.
949
+ */
950
+ export interface LoggingConfig {
951
+ /** Enable file logging */
952
+ enabled?: boolean;
953
+ /** Minimum level to capture: silent, error, warn, info, verbose */
954
+ level?: "silent" | "error" | "warn" | "info" | "verbose";
955
+ /** Log file path (relative to project root) */
956
+ file?: string;
957
+ /** Rotate log when size exceeded (e.g., '10mb') */
958
+ maxSize?: string;
261
959
  /** Number of rotated files to keep */
262
960
  maxFiles?: number;
263
961
  }
@@ -371,44 +1069,33 @@ export interface SettingsEvents {
371
1069
  stageName?: string;
372
1070
  };
373
1071
  /** Secret removed (universal or stage-scoped) */
374
- "settings:secret-removed": {
375
- key: string;
376
- scope: "universal" | "stage";
377
- stageName?: string;
378
- };
379
- }
380
- /**
381
- * Identity types.
382
- *
383
- * Two types of identity in noorm:
384
- * 1. Audit Identity - Simple name/email for tracking "who executed this"
385
- * 2. Cryptographic Identity - Full keypair system for config sharing and team discovery
386
- */
387
- /**
388
- * Source of audit identity resolution.
389
- */
390
- export type IdentitySource = "state" | "config" | "env" | "git" | "system";
1072
+ "settings:secret-removed": {
1073
+ key: string;
1074
+ scope: "universal" | "stage";
1075
+ stageName?: string;
1076
+ };
1077
+ }
391
1078
  /**
392
- * Resolved audit identity.
393
- *
394
- * Used for tracking "who" executed a change or SQL file.
1079
+ * Version tracking table.
395
1080
  *
396
- * @example
397
- * ```typescript
398
- * const identity: Identity = {
399
- * name: 'John Doe',
400
- * email: 'john@example.com',
401
- * source: 'git',
402
- * }
403
- * ```
1081
+ * Tracks noorm CLI version and all internal schema versions for migrations.
1082
+ * See: plan/datamodel.md#__noorm_version__
404
1083
  */
405
- export interface Identity {
406
- /** User's name */
407
- name: string;
408
- /** User's email (optional) */
409
- email?: string;
410
- /** How the identity was resolved */
411
- source: IdentitySource;
1084
+ export interface NoormVersionTable {
1085
+ /** Primary key */
1086
+ id: Generated<number>;
1087
+ /** CLI semver (e.g., "1.2.3") */
1088
+ cli_version: string;
1089
+ /** Database tracking tables version */
1090
+ noorm_version: number;
1091
+ /** State file (state.enc) schema version */
1092
+ state_version: number;
1093
+ /** Settings file (settings.yml) schema version */
1094
+ settings_version: number;
1095
+ /** First installation timestamp */
1096
+ installed_at: Generated<Date>;
1097
+ /** Last upgrade timestamp */
1098
+ upgraded_at: Generated<Date>;
412
1099
  }
413
1100
  /**
414
1101
  * Operation status values.
@@ -420,221 +1107,225 @@ export interface Identity {
420
1107
  * - stale: Operation's schema objects were torn down (needs re-run)
421
1108
  */
422
1109
  export type OperationStatus = "pending" | "success" | "failed" | "reverted" | "stale";
1110
+ /**
1111
+ * Change type values.
1112
+ */
1113
+ export type ChangeType = "build" | "run" | "change";
423
1114
  /**
424
1115
  * Direction values.
425
1116
  */
426
1117
  export type Direction = "change" | "revert";
427
1118
  /**
428
- * File execution status values.
1119
+ * Change tracking table.
1120
+ *
1121
+ * Tracks all operation batches—changes, builds, and ad-hoc runs.
1122
+ * See: plan/datamodel.md#__noorm_change__
429
1123
  */
430
- export type ExecutionStatus = "pending" | "success" | "failed" | "skipped";
1124
+ export interface NoormChangeTable {
1125
+ /** Primary key */
1126
+ id: Generated<number>;
1127
+ /** Operation identifier */
1128
+ name: string;
1129
+ /** 'build', 'run', or 'change' */
1130
+ change_type: ChangeType;
1131
+ /** 'change' or 'revert' */
1132
+ direction: Direction;
1133
+ /** SHA-256 of sorted file checksums */
1134
+ checksum: Generated<string>;
1135
+ /** When executed */
1136
+ executed_at: Generated<Date>;
1137
+ /** Identity string */
1138
+ executed_by: Generated<string>;
1139
+ /** Which config was used */
1140
+ config_name: Generated<string>;
1141
+ /** noorm version */
1142
+ cli_version: Generated<string>;
1143
+ /** 'pending', 'success', 'failed', 'reverted' */
1144
+ status: OperationStatus;
1145
+ /** Error details (empty = no error) */
1146
+ error_message: Generated<string>;
1147
+ /** Execution time (0 = never ran) */
1148
+ duration_ms: Generated<number>;
1149
+ }
431
1150
  /**
432
- * Overview counts for all categories.
1151
+ * File execution status values.
433
1152
  */
434
- export interface ExploreOverview {
435
- tables: number;
436
- views: number;
437
- procedures: number;
438
- functions: number;
439
- types: number;
440
- indexes: number;
441
- foreignKeys: number;
442
- triggers: number;
443
- locks: number;
444
- connections: number;
445
- }
1153
+ export type ExecutionStatus = "pending" | "success" | "failed" | "skipped";
446
1154
  /**
447
- * Table summary for list display.
1155
+ * File type values.
448
1156
  */
449
- export interface TableSummary {
450
- name: string;
451
- schema?: string;
452
- columnCount: number;
453
- rowCountEstimate?: number;
454
- }
1157
+ export type FileType = "sql" | "txt";
455
1158
  /**
456
- * Index summary for list display.
1159
+ * Executions tracking table.
1160
+ *
1161
+ * Tracks individual file executions within an operation.
1162
+ * See: plan/datamodel.md#__noorm_executions__
457
1163
  */
458
- export interface IndexSummary {
459
- name: string;
460
- schema?: string;
461
- tableName: string;
462
- tableSchema?: string;
463
- columns: string[];
464
- isUnique: boolean;
465
- isPrimary: boolean;
1164
+ export interface NoormExecutionsTable {
1165
+ /** Primary key */
1166
+ id: Generated<number>;
1167
+ /** Parent operation (FK to __noorm_change__) */
1168
+ change_id: number;
1169
+ /** File that was executed */
1170
+ filepath: string;
1171
+ /** 'sql' or 'txt' */
1172
+ file_type: FileType;
1173
+ /** SHA-256 of file contents */
1174
+ checksum: Generated<string>;
1175
+ /** noorm version */
1176
+ cli_version: Generated<string>;
1177
+ /** 'pending', 'success', 'failed', 'skipped' */
1178
+ status: ExecutionStatus;
1179
+ /** Error details (empty = no error) */
1180
+ error_message: Generated<string>;
1181
+ /** 'unchanged', 'already-run', 'change failed' */
1182
+ skip_reason: Generated<string>;
1183
+ /** Execution time (0 = never ran) */
1184
+ duration_ms: Generated<number>;
466
1185
  }
467
1186
  /**
468
- * Foreign key summary for list display.
1187
+ * Lock table.
1188
+ *
1189
+ * Prevents concurrent operations on the same database.
1190
+ * See: plan/datamodel.md#__noorm_lock__
469
1191
  */
470
- export interface ForeignKeySummary {
471
- name: string;
472
- schema?: string;
473
- tableName: string;
474
- tableSchema?: string;
475
- columns: string[];
476
- referencedTable: string;
477
- referencedSchema?: string;
478
- referencedColumns: string[];
479
- onDelete?: string;
480
- onUpdate?: string;
1192
+ export interface NoormLockTable {
1193
+ /** Primary key */
1194
+ id: Generated<number>;
1195
+ /** Lock scope (config name) */
1196
+ config_name: string;
1197
+ /** Identity of holder */
1198
+ locked_by: string;
1199
+ /** When acquired */
1200
+ locked_at: Generated<Date>;
1201
+ /** Auto-expiry time */
1202
+ expires_at: Date;
1203
+ /** Lock reason (empty = none) */
1204
+ reason: Generated<string>;
481
1205
  }
482
1206
  /**
483
- * Column detail for tables/views.
1207
+ * Identities table.
1208
+ *
1209
+ * Stores user identities for team discovery.
1210
+ * Auto-populated on first connect when identity is set up.
1211
+ * See: plan/datamodel.md#__noorm_identities__
484
1212
  */
485
- export interface ColumnDetail {
1213
+ export interface NoormIdentitiesTable {
1214
+ /** Primary key */
1215
+ id: Generated<number>;
1216
+ /** SHA-256(email + name + machine + os) */
1217
+ identity_hash: string;
1218
+ /** User email */
1219
+ email: string;
1220
+ /** Display name */
486
1221
  name: string;
487
- dataType: string;
488
- isNullable: boolean;
489
- defaultValue?: string;
490
- isPrimaryKey: boolean;
491
- ordinalPosition: number;
1222
+ /** Machine hostname */
1223
+ machine: string;
1224
+ /** OS platform and version */
1225
+ os: string;
1226
+ /** X25519 public key (hex) */
1227
+ public_key: string;
1228
+ /** First registration */
1229
+ registered_at: Generated<Date>;
1230
+ /** Last activity */
1231
+ last_seen_at: Generated<Date>;
1232
+ /** Vault key encrypted with user's public key (nullable for users without vault access) */
1233
+ encrypted_vault_key: string | null;
492
1234
  }
493
1235
  /**
494
- * Full table detail.
1236
+ * Vault secrets table.
1237
+ *
1238
+ * Stores encrypted secrets shared across the team.
1239
+ * Secrets are encrypted with a vault key that is distributed
1240
+ * to team members via their public keys.
495
1241
  */
496
- export interface TableDetail {
497
- name: string;
498
- schema?: string;
499
- columns: ColumnDetail[];
500
- indexes: IndexSummary[];
501
- foreignKeys: ForeignKeySummary[];
502
- rowCountEstimate?: number;
1242
+ export interface NoormVaultTable {
1243
+ /** Primary key */
1244
+ id: Generated<number>;
1245
+ /** Secret key name (e.g., 'API_KEY', 'DB_PASSWORD') */
1246
+ secret_key: string;
1247
+ /** AES-256-GCM encrypted value (JSON: {iv, authTag, ciphertext}) */
1248
+ encrypted_value: string;
1249
+ /** Identity who set this secret */
1250
+ set_by: string;
1251
+ /** When created */
1252
+ created_at: Generated<Date>;
1253
+ /** When last updated */
1254
+ updated_at: Generated<Date>;
503
1255
  }
504
1256
  /**
505
- * Result of a truncate operation.
1257
+ * Schema-qualified database interface for pg/mssql.
1258
+ *
1259
+ * Used with db.withSchema('noorm') — table names have no prefix.
506
1260
  */
507
- export interface TruncateResult {
508
- /** Tables that were truncated */
509
- truncated: string[];
510
- /** Tables that were preserved */
511
- preserved: string[];
512
- /** SQL statements executed (or would execute in dry-run) */
513
- statements: string[];
514
- /** Duration in milliseconds */
515
- durationMs: number;
1261
+ export interface NoormSchemaDb {
1262
+ version: NoormVersionTable;
1263
+ change: NoormChangeTable;
1264
+ executions: NoormExecutionsTable;
1265
+ lock: NoormLockTable;
1266
+ identities: NoormIdentitiesTable;
1267
+ vault: NoormVaultTable;
516
1268
  }
517
1269
  /**
518
- * Result of a teardown operation.
1270
+ * Prefixed database interface for sqlite/mysql.
1271
+ *
1272
+ * Used directly — table names have __noorm_ prefix.
519
1273
  */
520
- export interface TeardownResult {
521
- /** Objects dropped by category */
522
- dropped: {
523
- tables: string[];
524
- views: string[];
525
- functions: string[];
526
- procedures: string[];
527
- types: string[];
528
- foreignKeys: string[];
529
- };
530
- /** Objects preserved */
531
- preserved: string[];
532
- /** SQL statements executed (or would execute in dry-run) */
533
- statements: string[];
534
- /** Duration in milliseconds */
535
- durationMs: number;
536
- /** Post-script execution result (if any) */
537
- postScriptResult?: {
538
- executed: boolean;
539
- error?: string;
540
- };
541
- /** Number of changes marked as stale (if configName provided) */
542
- staleCount?: number;
543
- /** ID of the reset record created (if configName provided) */
544
- resetRecordId?: number;
1274
+ export interface NoormPrefixDb {
1275
+ __noorm_version__: NoormVersionTable;
1276
+ __noorm_change__: NoormChangeTable;
1277
+ __noorm_executions__: NoormExecutionsTable;
1278
+ __noorm_lock__: NoormLockTable;
1279
+ __noorm_identities__: NoormIdentitiesTable;
1280
+ __noorm_vault__: NoormVaultTable;
545
1281
  }
546
1282
  /**
547
- * Options for running SQL files.
1283
+ * Combined database interface for all noorm tracking tables.
1284
+ *
1285
+ * Intersection of schema-qualified (pg/mssql) and prefixed (sqlite/mysql)
1286
+ * interfaces. Both key sets are valid — use getNoormTables(dialect) to get
1287
+ * the correct keys for your dialect.
548
1288
  *
549
1289
  * @example
550
1290
  * ```typescript
551
- * const options: RunOptions = {
552
- * force: true, // Re-run even if unchanged
553
- * abortOnError: false // Continue on failures
554
- * }
1291
+ * const tables = getNoormTables(dialect);
1292
+ * const ndb = noormDb(db, dialect);
1293
+ * await ndb.selectFrom(tables.change).selectAll().execute();
555
1294
  * ```
556
1295
  */
557
- export interface RunOptions {
558
- /** Re-run files even if unchanged. Default: false */
559
- force?: boolean;
560
- /** Number of files to run in parallel. Default: 1 (sequential for DDL safety) */
561
- concurrency?: number;
562
- /** Stop execution on first failure. Default: true */
563
- abortOnError?: boolean;
564
- /** Report what would run without executing. Default: false */
565
- dryRun?: boolean;
566
- /** Output rendered SQL without executing. Default: false */
567
- preview?: boolean;
568
- /** Write preview output to file instead of stdout. Default: null */
569
- output?: string | null;
570
- }
571
- /**
572
- * Why a file was skipped.
573
- */
574
- export type SkipReason = "unchanged" | "already-run";
1296
+ export type NoormDatabase = NoormSchemaDb & NoormPrefixDb;
575
1297
  /**
576
- * Result of executing a single file.
1298
+ * Identity types.
577
1299
  *
578
- * @example
579
- * ```typescript
580
- * const result: FileResult = {
581
- * filepath: '/project/sql/001.sql',
582
- * checksum: 'abc123...',
583
- * status: 'success',
584
- * durationMs: 42,
585
- * }
586
- * ```
1300
+ * Two types of identity in noorm:
1301
+ * 1. Audit Identity - Simple name/email for tracking "who executed this"
1302
+ * 2. Cryptographic Identity - Full keypair system for config sharing and team discovery
587
1303
  */
588
- export interface FileResult {
589
- /** Absolute path to the file */
590
- filepath: string;
591
- /** SHA-256 hash of file contents */
592
- checksum: string;
593
- /** Execution status */
594
- status: ExecutionStatus;
595
- /** Why the file was skipped (only when status is 'skipped') */
596
- skipReason?: SkipReason;
597
- /** Execution time in milliseconds */
598
- durationMs?: number;
599
- /** Error message if failed */
600
- error?: string;
601
- /** Rendered SQL (only in preview mode) */
602
- renderedSql?: string;
603
- }
604
1304
  /**
605
- * Overall status of a batch operation.
1305
+ * Source of audit identity resolution.
606
1306
  */
607
- export type BatchStatus = "success" | "failed" | "partial";
1307
+ export type IdentitySource = "state" | "config" | "env" | "git" | "system";
608
1308
  /**
609
- * Result of a batch operation (build, dir).
1309
+ * Resolved audit identity.
1310
+ *
1311
+ * Used for tracking "who" executed a change or SQL file.
610
1312
  *
611
1313
  * @example
612
1314
  * ```typescript
613
- * const result: BatchResult = {
614
- * status: 'success',
615
- * files: [...],
616
- * filesRun: 5,
617
- * filesSkipped: 2,
618
- * filesFailed: 0,
619
- * durationMs: 1234,
1315
+ * const identity: Identity = {
1316
+ * name: 'John Doe',
1317
+ * email: 'john@example.com',
1318
+ * source: 'git',
620
1319
  * }
621
1320
  * ```
622
1321
  */
623
- export interface BatchResult {
624
- /** Overall status */
625
- status: BatchStatus;
626
- /** Results for each file */
627
- files: FileResult[];
628
- /** Number of files executed */
629
- filesRun: number;
630
- /** Number of files skipped */
631
- filesSkipped: number;
632
- /** Number of files that failed */
633
- filesFailed: number;
634
- /** Total execution time in milliseconds */
635
- durationMs: number;
636
- /** Change ID in tracking table */
637
- changeId?: number;
1322
+ export interface Identity {
1323
+ /** User's name */
1324
+ name: string;
1325
+ /** User's email (optional) */
1326
+ email?: string;
1327
+ /** How the identity was resolved */
1328
+ source: IdentitySource;
638
1329
  }
639
1330
  /**
640
1331
  * File extension type in changes.
@@ -669,6 +1360,40 @@ export interface ChangeFile {
669
1360
  /** Why the file was skipped */
670
1361
  skipReason?: string;
671
1362
  }
1363
+ /**
1364
+ * A change parsed from disk.
1365
+ *
1366
+ * Contains the folder structure with change and revert files.
1367
+ *
1368
+ * @example
1369
+ * ```typescript
1370
+ * const change: Change = {
1371
+ * name: '2024-01-15-add-email-verification',
1372
+ * path: '/project/changes/2024-01-15-add-email-verification',
1373
+ * date: new Date('2024-01-15'),
1374
+ * description: 'add-email-verification',
1375
+ * changeFiles: [...],
1376
+ * revertFiles: [...],
1377
+ * hasChangelog: true,
1378
+ * }
1379
+ * ```
1380
+ */
1381
+ export interface Change {
1382
+ /** Folder name (e.g., "2024-01-15-add-email-verification") */
1383
+ name: string;
1384
+ /** Absolute path to change folder */
1385
+ path: string;
1386
+ /** Date parsed from name (null if no date prefix) */
1387
+ date: Date | null;
1388
+ /** Human-readable description from name */
1389
+ description: string;
1390
+ /** Files in change/ folder */
1391
+ changeFiles: ChangeFile[];
1392
+ /** Files in revert/ folder */
1393
+ revertFiles: ChangeFile[];
1394
+ /** Whether changelog.md exists */
1395
+ hasChangelog: boolean;
1396
+ }
672
1397
  /**
673
1398
  * A change with merged disk and database information.
674
1399
  *
@@ -745,6 +1470,50 @@ export interface ChangeOptions {
745
1470
  /** Write preview output to file. Default: null */
746
1471
  output?: string | null;
747
1472
  }
1473
+ /**
1474
+ * Options for batch operations (next, ff, rewind).
1475
+ */
1476
+ export interface BatchChangeOptions extends ChangeOptions {
1477
+ /** Stop on first failure. Default: true */
1478
+ abortOnError?: boolean;
1479
+ }
1480
+ /**
1481
+ * Context required to execute changes.
1482
+ *
1483
+ * @example
1484
+ * ```typescript
1485
+ * const context: ChangeContext = {
1486
+ * db,
1487
+ * configName: 'dev',
1488
+ * identity: { name: 'Alice', email: 'alice@example.com' },
1489
+ * projectRoot: '/project',
1490
+ * changesDir: '/project/changes',
1491
+ * sqlDir: '/project/sql',
1492
+ * }
1493
+ * ```
1494
+ */
1495
+ export interface ChangeContext {
1496
+ /** Kysely database connection */
1497
+ db: Kysely<NoormDatabase>;
1498
+ /** Name of the active config */
1499
+ configName: string;
1500
+ /** User identity for tracking */
1501
+ identity: Identity;
1502
+ /** Project root for template resolution */
1503
+ projectRoot: string;
1504
+ /** Directory containing changes */
1505
+ changesDir: string;
1506
+ /** Schema directory for resolving .txt references */
1507
+ sqlDir: string;
1508
+ /** Config object for template context */
1509
+ config?: Record<string, unknown>;
1510
+ /** Secrets for template context */
1511
+ secrets?: Record<string, string>;
1512
+ /** Global secrets for template context */
1513
+ globalSecrets?: Record<string, string>;
1514
+ /** Database dialect for lock formatting. Default: 'postgres' */
1515
+ dialect?: "postgres" | "mysql" | "sqlite" | "mssql";
1516
+ }
748
1517
  /**
749
1518
  * Result of executing a single change.
750
1519
  *
@@ -835,169 +1604,285 @@ export interface ChangeHistoryRecord {
835
1604
  checksum: string;
836
1605
  }
837
1606
  /**
838
- * Lock manager types.
839
- *
840
- * Defines the shape of locks and options for acquiring them.
841
- * Used to prevent concurrent operations on the same database.
1607
+ * File execution record from history.
1608
+ */
1609
+ export interface FileHistoryRecord {
1610
+ /** Record ID */
1611
+ id: number;
1612
+ /** Parent change ID */
1613
+ changeId: number;
1614
+ /** File path */
1615
+ filepath: string;
1616
+ /** File type */
1617
+ fileType: ChangeFileType;
1618
+ /** File checksum */
1619
+ checksum: string;
1620
+ /** Execution status */
1621
+ status: ExecutionStatus;
1622
+ /** Skip reason if skipped */
1623
+ skipReason: string | null;
1624
+ /** Error message if failed */
1625
+ errorMessage: string | null;
1626
+ /** Duration in milliseconds */
1627
+ durationMs: number;
1628
+ }
1629
+ /**
1630
+ * Options for creating a new change.
1631
+ */
1632
+ export interface CreateChangeOptions {
1633
+ /** Description for the change name */
1634
+ description: string;
1635
+ /** Optional date (defaults to today) */
1636
+ date?: Date;
1637
+ }
1638
+ /**
1639
+ * Options for adding a file to a change.
1640
+ */
1641
+ export interface AddFileOptions {
1642
+ /** Descriptive name for the file */
1643
+ name: string;
1644
+ /** File type */
1645
+ type: ChangeFileType;
1646
+ /** For 'txt' type, the paths to include */
1647
+ paths?: string[];
1648
+ /** Initial content (optional) */
1649
+ content?: string;
1650
+ }
1651
+ /**
1652
+ * Error when change structure is invalid.
1653
+ */
1654
+ export declare class ChangeValidationError extends Error {
1655
+ readonly changeName: string;
1656
+ readonly issue: string;
1657
+ readonly name: "ChangeValidationError";
1658
+ constructor(changeName: string, issue: string);
1659
+ }
1660
+ /**
1661
+ * Error when change is not found.
1662
+ */
1663
+ export declare class ChangeNotFoundError extends Error {
1664
+ readonly changeName: string;
1665
+ readonly name: "ChangeNotFoundError";
1666
+ constructor(changeName: string);
1667
+ }
1668
+ /**
1669
+ * Error when change is already applied.
1670
+ */
1671
+ export declare class ChangeAlreadyAppliedError extends Error {
1672
+ readonly changeName: string;
1673
+ readonly appliedAt: Date;
1674
+ readonly name: "ChangeAlreadyAppliedError";
1675
+ constructor(changeName: string, appliedAt: Date);
1676
+ }
1677
+ /**
1678
+ * Error when trying to revert an unapplied change.
1679
+ */
1680
+ export declare class ChangeNotAppliedError extends Error {
1681
+ readonly changeName: string;
1682
+ readonly name: "ChangeNotAppliedError";
1683
+ constructor(changeName: string);
1684
+ }
1685
+ /**
1686
+ * Error when change is orphaned (in DB but not on disk).
1687
+ */
1688
+ export declare class ChangeOrphanedError extends Error {
1689
+ readonly changeName: string;
1690
+ readonly name: "ChangeOrphanedError";
1691
+ constructor(changeName: string);
1692
+ }
1693
+ /**
1694
+ * Error when .txt manifest has invalid references.
1695
+ */
1696
+ export declare class ManifestReferenceError extends Error {
1697
+ readonly manifestPath: string;
1698
+ readonly missingPath: string;
1699
+ readonly name: "ManifestReferenceError";
1700
+ constructor(manifestPath: string, missingPath: string);
1701
+ }
1702
+ /**
1703
+ * Options for running SQL files.
842
1704
  *
843
- * WHY: Concurrent DDL operations can corrupt database state.
844
- * Locks ensure only one process modifies the schema at a time.
1705
+ * @example
1706
+ * ```typescript
1707
+ * const options: RunOptions = {
1708
+ * force: true, // Re-run even if unchanged
1709
+ * abortOnError: false // Continue on failures
1710
+ * }
1711
+ * ```
845
1712
  */
1713
+ export interface RunOptions {
1714
+ /** Re-run files even if unchanged. Default: false */
1715
+ force?: boolean;
1716
+ /** Number of files to run in parallel. Default: 1 (sequential for DDL safety) */
1717
+ concurrency?: number;
1718
+ /** Stop execution on first failure. Default: true */
1719
+ abortOnError?: boolean;
1720
+ /** Report what would run without executing. Default: false */
1721
+ dryRun?: boolean;
1722
+ /** Output rendered SQL without executing. Default: false */
1723
+ preview?: boolean;
1724
+ /** Write preview output to file instead of stdout. Default: null */
1725
+ output?: string | null;
1726
+ }
846
1727
  /**
847
- * Lock state returned after acquisition.
1728
+ * Why a file was skipped.
1729
+ */
1730
+ export type SkipReason = "unchanged" | "already-run";
1731
+ /**
1732
+ * Result of executing a single file.
848
1733
  *
849
1734
  * @example
850
1735
  * ```typescript
851
- * const lock = await lockManager.acquire('dev', 'alice@example.com')
852
- * console.log(lock.lockedBy) // 'alice@example.com'
853
- * console.log(lock.expiresAt) // Date object
1736
+ * const result: FileResult = {
1737
+ * filepath: '/project/sql/001.sql',
1738
+ * checksum: 'abc123...',
1739
+ * status: 'success',
1740
+ * durationMs: 42,
1741
+ * }
854
1742
  * ```
855
1743
  */
856
- interface Lock$1 {
857
- /** Identity string of holder */
858
- lockedBy: string;
859
- /** When acquired */
860
- lockedAt: Date;
861
- /** Auto-expiry time */
862
- expiresAt: Date;
863
- /** Optional reason for acquiring */
864
- reason?: string;
1744
+ export interface FileResult {
1745
+ /** Absolute path to the file */
1746
+ filepath: string;
1747
+ /** SHA-256 hash of file contents */
1748
+ checksum: string;
1749
+ /** Execution status */
1750
+ status: ExecutionStatus;
1751
+ /** Why the file was skipped (only when status is 'skipped') */
1752
+ skipReason?: SkipReason;
1753
+ /** Execution time in milliseconds */
1754
+ durationMs?: number;
1755
+ /** Error message if failed */
1756
+ error?: string;
1757
+ /** Rendered SQL (only in preview mode) */
1758
+ renderedSql?: string;
865
1759
  }
866
1760
  /**
867
- * Options for acquiring a lock.
1761
+ * Overall status of a batch operation.
1762
+ */
1763
+ export type BatchStatus = "success" | "failed" | "partial";
1764
+ /**
1765
+ * Result of a batch operation (build, dir).
868
1766
  *
869
1767
  * @example
870
1768
  * ```typescript
871
- * // Wait up to 30s for lock, polling every 500ms
872
- * const options: LockOptions = {
873
- * timeout: 60_000, // Lock expires after 60s
874
- * wait: true, // Block until available
875
- * waitTimeout: 30_000, // Max wait time
876
- * pollInterval: 500, // Check every 500ms
877
- * reason: 'Running migrations',
1769
+ * const result: BatchResult = {
1770
+ * status: 'success',
1771
+ * files: [...],
1772
+ * filesRun: 5,
1773
+ * filesSkipped: 2,
1774
+ * filesFailed: 0,
1775
+ * durationMs: 1234,
878
1776
  * }
879
1777
  * ```
880
1778
  */
881
- interface LockOptions$1 {
1779
+ export interface BatchResult {
1780
+ /** Overall status */
1781
+ status: BatchStatus;
1782
+ /** Results for each file */
1783
+ files: FileResult[];
1784
+ /** Number of files executed */
1785
+ filesRun: number;
1786
+ /** Number of files skipped */
1787
+ filesSkipped: number;
1788
+ /** Number of files that failed */
1789
+ filesFailed: number;
1790
+ /** Total execution time in milliseconds */
1791
+ durationMs: number;
1792
+ /** Change ID in tracking table */
1793
+ changeId?: number;
1794
+ /** Error message when batch fails before file execution (e.g. tracking setup failure) */
1795
+ error?: string;
1796
+ }
1797
+ declare class ChangeManager {
1798
+ #private;
1799
+ constructor(context: ChangeContext);
882
1800
  /**
883
- * Database dialect for date formatting.
1801
+ * List all changes with status.
884
1802
  *
885
- * SQLite stores dates as ISO strings, other dialects can use Date objects.
886
- * @default 'postgres'
1803
+ * Merges changes from disk with status from database.
1804
+ * Includes orphaned changes (in DB but not on disk).
1805
+ *
1806
+ * @returns Array of changes with merged status
887
1807
  */
888
- dialect?: "postgres" | "mysql" | "sqlite" | "mssql";
1808
+ list(): Promise<ChangeListItem[]>;
889
1809
  /**
890
- * Lock duration in milliseconds.
1810
+ * Run a specific change by name.
891
1811
  *
892
- * After this time, the lock expires and can be claimed by others.
893
- * @default 300_000 (5 minutes)
1812
+ * @param name - Change name
1813
+ * @param options - Execution options
1814
+ * @returns Execution result
894
1815
  */
895
- timeout?: number;
1816
+ run(name: string, options?: ChangeOptions): Promise<ChangeResult>;
896
1817
  /**
897
- * Block until lock is available?
1818
+ * Run a change object directly.
1819
+ */
1820
+ runChange(change: Change, options?: ChangeOptions): Promise<ChangeResult>;
1821
+ /**
1822
+ * Revert a specific change by name.
898
1823
  *
899
- * If true, will poll until the lock is acquired or waitTimeout is reached.
900
- * @default false
1824
+ * @param name - Change name
1825
+ * @param options - Execution options
1826
+ * @returns Execution result
901
1827
  */
902
- wait?: boolean;
1828
+ revert(name: string, options?: ChangeOptions): Promise<ChangeResult>;
903
1829
  /**
904
- * Max time to wait for lock in milliseconds.
1830
+ * Revert a change object directly.
1831
+ */
1832
+ revertChange(change: Change, options?: ChangeOptions): Promise<ChangeResult>;
1833
+ /**
1834
+ * Run next N pending changes.
905
1835
  *
906
- * Only used if wait is true.
907
- * @default 30_000 (30 seconds)
1836
+ * @param count - Number of changes to run (default: 1)
1837
+ * @param options - Execution options
1838
+ * @returns Batch result
908
1839
  */
909
- waitTimeout?: number;
1840
+ next(count?: number, options?: BatchChangeOptions): Promise<BatchChangeResult>;
910
1841
  /**
911
- * How often to poll when waiting, in milliseconds.
1842
+ * Fast-forward: run all pending changes.
912
1843
  *
913
- * Only used if wait is true.
914
- * @default 1_000 (1 second)
1844
+ * @param options - Execution options
1845
+ * @returns Batch result
915
1846
  */
916
- pollInterval?: number;
1847
+ ff(options?: BatchChangeOptions): Promise<BatchChangeResult>;
917
1848
  /**
918
- * Optional reason for acquiring the lock.
1849
+ * Rewind: revert changes in reverse order.
919
1850
  *
920
- * Shown to users who are blocked by this lock.
1851
+ * @param target - Number of changes to revert, or name to revert to
1852
+ * @param options - Execution options
1853
+ * @returns Batch result
921
1854
  */
922
- reason?: string;
923
- }
924
- /**
925
- * Result of a lock status check.
926
- */
927
- export interface LockStatus {
928
- /** Whether the lock is currently held */
929
- isLocked: boolean;
930
- /** Lock details if held, null if not */
931
- lock: Lock$1 | null;
932
- }
933
- /**
934
- * Lock-related errors.
935
- *
936
- * WHY: Specific error types allow callers to handle lock failures
937
- * differently from other errors (e.g., prompt user to wait vs retry).
938
- */
939
- /**
940
- * Error when lock cannot be acquired.
941
- *
942
- * Thrown when another process holds the lock and wait=false,
943
- * or when waitTimeout is exceeded.
944
- *
945
- * @example
946
- * ```typescript
947
- * const [lock, err] = await attempt(() => lockManager.acquire(config, identity))
948
- * if (err instanceof LockAcquireError) {
949
- * console.log(`Blocked by ${err.holder} since ${err.heldSince}`)
950
- * }
951
- * ```
952
- */
953
- export declare class LockAcquireError extends Error {
954
- readonly configName: string;
955
- readonly holder: string;
956
- readonly heldSince: Date;
957
- readonly expiresAt: Date;
958
- readonly reason?: string | undefined;
959
- readonly name: "LockAcquireError";
960
- constructor(configName: string, holder: string, heldSince: Date, expiresAt: Date, reason?: string | undefined);
961
- }
962
- /**
963
- * Error when lock expires during operation.
964
- *
965
- * Thrown when validating lock before a critical operation
966
- * and discovering it has expired.
967
- *
968
- * @example
969
- * ```typescript
970
- * // Before committing transaction
971
- * const [, err] = await attempt(() => lockManager.validate(configName, identity))
972
- * if (err instanceof LockExpiredError) {
973
- * await transaction.rollback()
974
- * throw new Error('Lock expired, operation aborted')
975
- * }
976
- * ```
977
- */
978
- export declare class LockExpiredError extends Error {
979
- readonly configName: string;
980
- readonly identity: string;
981
- readonly expiredAt: Date;
982
- readonly name: "LockExpiredError";
983
- constructor(configName: string, identity: string, expiredAt: Date);
984
- }
985
- /**
986
- * Result of processing a SQL file.
987
- */
988
- interface ProcessResult {
1855
+ rewind(target: number | string, options?: BatchChangeOptions): Promise<BatchChangeResult>;
989
1856
  /**
990
- * The SQL content (rendered if template, raw if .sql).
1857
+ * Get execution history.
1858
+ *
1859
+ * @param name - Optional change name (all if not provided)
1860
+ * @param limit - Max records to return
1861
+ * @returns Array of history records
1862
+ */
1863
+ getHistory(name?: string, limit?: number): Promise<ChangeHistoryRecord[]>;
1864
+ /**
1865
+ * Get file execution history for an operation.
1866
+ */
1867
+ getFileHistory(operationId: number): Promise<FileHistoryRecord[]>;
1868
+ /**
1869
+ * Remove a change from disk, database, or both.
1870
+ *
1871
+ * @param name - Change name
1872
+ * @param options - What to remove
991
1873
  */
992
- sql: string;
1874
+ remove(name: string, options: {
1875
+ disk?: boolean;
1876
+ db?: boolean;
1877
+ }): Promise<void>;
993
1878
  /**
994
- * Whether the file was a template.
1879
+ * Get a specific change by name.
995
1880
  */
996
- isTemplate: boolean;
1881
+ get(name: string): Promise<ChangeListItem | null>;
997
1882
  /**
998
- * Render duration in milliseconds (only for templates).
1883
+ * Load a change from disk.
999
1884
  */
1000
- durationMs?: number;
1885
+ load(name: string): Promise<Change>;
1001
1886
  }
1002
1887
  /**
1003
1888
  * SDK Types.
@@ -1018,12 +1903,6 @@ interface ProcessResult {
1018
1903
  * requireTest: true,
1019
1904
  * })
1020
1905
  *
1021
- * // Allow destructive ops on protected config
1022
- * const ctx = await createContext({
1023
- * config: 'staging',
1024
- * allowProtected: true,
1025
- * })
1026
- *
1027
1906
  * // Env-only mode (CI/CD) - no stored config needed
1028
1907
  * // Requires NOORM_CONNECTION_DIALECT and NOORM_CONNECTION_DATABASE
1029
1908
  * const ctx = await createContext()
@@ -1047,629 +1926,1437 @@ export interface CreateContextOptions {
1047
1926
  projectRoot?: string;
1048
1927
  /** Refuse if config.isTest !== true. Default: false */
1049
1928
  requireTest?: boolean;
1050
- /** Allow destructive ops on protected configs. Default: false */
1051
- allowProtected?: boolean;
1052
1929
  /** Stage name for stage defaults (from settings.yml) */
1053
1930
  stage?: string;
1054
1931
  }
1055
1932
  /**
1056
- * Result of an execute() call.
1933
+ * Options for build operations.
1057
1934
  */
1058
- export interface ExecuteResult {
1059
- /** Number of rows affected (if available) */
1060
- rowsAffected?: number;
1935
+ export interface BuildOptions {
1936
+ /** Skip checksum checks, rebuild everything. Default: false */
1937
+ force?: boolean;
1061
1938
  }
1062
1939
  /**
1063
- * Transaction context for use within transaction callbacks.
1940
+ * Options for exporting a table to a .dt file.
1941
+ *
1942
+ * Connection details (db, dialect) come from the context automatically.
1064
1943
  *
1065
1944
  * @example
1066
1945
  * ```typescript
1067
- * await ctx.transaction(async (tx) => {
1068
- * const [user] = await tx.query('SELECT * FROM users WHERE id = $1', [id])
1069
- * await tx.execute('UPDATE users SET login_count = login_count + 1 WHERE id = $1', [id])
1070
- * return user
1071
- * })
1946
+ * const [result, err] = await ctx.exportTable('users', './exports/users.dtz', {
1947
+ * passphrase: 'secret',
1948
+ * batchSize: 5000,
1949
+ * });
1072
1950
  * ```
1073
1951
  */
1074
- export interface TransactionContext {
1075
- /**
1076
- * Execute a SELECT query within the transaction.
1077
- */
1078
- query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
1079
- /**
1080
- * Execute an INSERT/UPDATE/DELETE within the transaction.
1081
- */
1082
- execute(sql: string, params?: unknown[]): Promise<ExecuteResult>;
1952
+ export interface ExportOptions {
1953
+ /** Passphrase for .dtzx encryption. */
1954
+ passphrase?: string;
1955
+ /** Schema/namespace (e.g., 'public' for PostgreSQL). */
1956
+ schema?: string;
1957
+ /** Rows per batch. Default: 1000. */
1958
+ batchSize?: number;
1083
1959
  }
1084
1960
  /**
1085
- * Options for build operations.
1961
+ * Options for importing a .dt file into the database.
1962
+ *
1963
+ * Connection details (db, dialect) come from the context automatically.
1964
+ *
1965
+ * @example
1966
+ * ```typescript
1967
+ * const [result, err] = await ctx.importFile('./exports/users.dtz', {
1968
+ * onConflict: 'skip',
1969
+ * truncate: true,
1970
+ * });
1971
+ * ```
1086
1972
  */
1087
- export interface BuildOptions {
1088
- /** Skip checksum checks, rebuild everything. Default: false */
1089
- force?: boolean;
1090
- }
1091
- export type Events<Shape> = keyof Shape;
1092
- declare namespace ObserverEngine {
1093
- interface EventCallback<Shape> {
1094
- (data: Shape, info?: {
1095
- event: string;
1096
- listener: Function;
1097
- } | undefined): void;
1098
- }
1099
- type RgxEmitData<Shape> = {
1100
- event: Events<Shape>;
1101
- data: Shape[Events<Shape>];
1102
- };
1103
- type Cleanup = () => void;
1104
- type Component<Ev extends Record<string, any>> = {
1105
- on: ObserverEngine<Ev>["on"];
1106
- once: ObserverEngine<Ev>["once"];
1107
- emit: ObserverEngine<Ev>["emit"];
1108
- off: ObserverEngine<Ev>["off"];
1109
- };
1110
- type Child<C, Ev extends Record<string, any>> = C & Component<Ev> & {
1111
- cleanup: Cleanup;
1112
- clear: Cleanup;
1113
- };
1114
- type Instance<Ev extends Record<string, any>> = Component<Ev> & {
1115
- observe: ObserverEngine<Ev>["observe"];
1116
- $observer: ObserverEngine<Ev>;
1117
- };
1118
- type FuncName = "on" | "once" | "off" | "emit" | "cleanup";
1119
- type SpyAction<Ev extends Record<string, any>> = {
1120
- event: keyof Ev | RegExp | "*";
1121
- listener?: Function | null | undefined;
1122
- data?: unknown | undefined;
1123
- fn: FuncName;
1124
- context: ObserverEngine<Ev>;
1125
- };
1126
- interface Spy<Ev extends Record<string, any>> {
1127
- (action: SpyAction<Ev>): void;
1128
- }
1129
- interface EmitValidator<Ev extends Record<string, any>> {
1130
- (event: keyof Ev, data: Ev[keyof Ev], context: ObserverEngine<Ev>): void;
1131
- (event: RegExp, data: unknown, context: ObserverEngine<Ev>): void;
1132
- (event: "*", data: unknown, context: ObserverEngine<Ev>): void;
1133
- }
1134
- type Options<Ev extends Record<string, any>> = {
1135
- name?: string | undefined;
1136
- spy?: Spy<Ev> | undefined;
1137
- emitValidator?: EmitValidator<Ev> | undefined;
1138
- signal?: AbortSignal | undefined;
1139
- };
1140
- type ListenerOptions = {
1141
- signal?: AbortSignal | undefined;
1142
- };
1143
- type ObserveOptions = {
1144
- signal?: AbortSignal | undefined;
1145
- };
1973
+ export interface ImportOptions {
1974
+ /** Passphrase for .dtzx decryption. */
1975
+ passphrase?: string;
1976
+ /** Rows per batch. Default: 1000. */
1977
+ batchSize?: number;
1978
+ /** Conflict strategy. Default: 'fail'. */
1979
+ onConflict?: "fail" | "skip" | "update" | "replace";
1980
+ /** Truncate target table before import. Default: false. */
1981
+ truncate?: boolean;
1146
1982
  }
1147
1983
  /**
1148
- * Generic function type for type-safe function signatures.
1984
+ * Extract the args type from a proc/func/tvf entry.
1149
1985
  *
1150
- * Enables consistent typing across utility functions and higher-order functions
1151
- * where function parameters need to be strongly typed.
1986
+ * Supports tuple form `[Args, Return]` and plain `void`.
1987
+ * Non-tuple entries pass through as-is for backward compat.
1152
1988
  *
1153
1989
  * @example
1154
- * function memoize<A extends any[], R>(fn: Func<A, R>): Func<A, R>
1155
- * function debounce<A extends any[]>(fn: Func<A, void>, ms: number): Func<A, void>
1990
+ * ```typescript
1991
+ * ExtractArgs<[{ id: number }, User]> // { id: number }
1992
+ * ExtractArgs<void> // void
1993
+ * ```
1156
1994
  */
1157
- export type Func<A extends any[] = any[], R = any> = (...args: A) => R;
1995
+ export type ExtractArgs<E> = E extends [
1996
+ infer A,
1997
+ unknown
1998
+ ] ? A : E;
1158
1999
  /**
1159
- * Represents a value that can be either synchronous or asynchronous.
2000
+ * Extract the return type from a proc/func/tvf entry.
1160
2001
  *
1161
- * Critical for utility functions that need to handle both sync and async
1162
- * operations uniformly, enabling flexible API design.
2002
+ * Returns the second element of a `[Args, Return]` tuple.
2003
+ * Falls back to `unknown` for `void` or non-tuple entries.
1163
2004
  *
1164
2005
  * @example
1165
- * function processData<T>(processor: () => MaybePromise<T>): Promise<T>
1166
- * function transform<T, U>(value: T, transformer: (val: T) => MaybePromise<U>): Promise<U>
2006
+ * ```typescript
2007
+ * ExtractReturn<[{ id: number }, User]> // User
2008
+ * ExtractReturn<void> // unknown
2009
+ * ```
1167
2010
  */
1168
- export type MaybePromise<T> = T | Promise<T>;
1169
- export interface EventGeneratorOptions {
1170
- signal?: AbortSignal | undefined;
1171
- }
1172
- declare class EventGenerator<S extends Record<string, any>, E extends Events<S> | RegExp = Events<S>> {
1173
- #private;
1174
- cleanup: ObserverEngine.Cleanup;
1175
- next: () => Promise<EventData<S, E>>;
1176
- constructor(observer: ObserverEngine<S>, event: E | RegExp, options?: EventGeneratorOptions);
1177
- [Symbol.asyncIterator](): AsyncGenerator<Awaited<EventData<S, E>>, undefined, unknown>;
1178
- get lastValue(): E extends Events<S> ? S[E] : E extends RegExp ? ObserverEngine.RgxEmitData<S> : S[Events<S>];
1179
- get done(): boolean;
1180
- emit(data?: (E extends Events<S> ? S[E] : S[Events<S>])): void;
2011
+ export type ExtractReturn<E> = E extends [
2012
+ unknown,
2013
+ infer R
2014
+ ] ? R : unknown;
2015
+ export interface ContextState {
2016
+ connection: ConnectionResult | null;
2017
+ config: Config;
2018
+ settings: Settings;
2019
+ identity: Identity;
2020
+ options: CreateContextOptions;
2021
+ projectRoot: string;
2022
+ changeManager: ChangeManager | null;
1181
2023
  }
1182
- declare class ObserverEngine<Shape extends Record<string, any> = Record<string, any>> {
2024
+ export declare class ChangesNamespace {
1183
2025
  #private;
1184
- constructor(options?: ObserverEngine.Options<Shape>);
1185
- name: string;
2026
+ constructor(state: ContextState);
1186
2027
  /**
1187
- * Returns facts about the the internal state of the observable instance.
2028
+ * Create a new change directory with change/ and revert/ folders.
2029
+ *
2030
+ * @example
2031
+ * ```typescript
2032
+ * const change = await ctx.noorm.changes.create({ description: 'add-user-roles' })
2033
+ * ```
1188
2034
  */
1189
- $facts(): {
1190
- listeners: (keyof Shape)[];
1191
- rgxListeners: string[];
1192
- listenerCounts: Record<string, number>;
1193
- hasSpy: boolean;
1194
- };
2035
+ create(options: CreateChangeOptions): Promise<Change>;
1195
2036
  /**
1196
- * The internals of the observable instance.
2037
+ * Add a file to a change.
1197
2038
  *
1198
- * NOTE: Do not use this to try to meddle with the
1199
- * internals of the observable instance. This is for
1200
- * debugging purposes only.
2039
+ * @example
2040
+ * ```typescript
2041
+ * const updated = await ctx.noorm.changes.addFile(change, 'change', {
2042
+ * name: 'create-table',
2043
+ * type: 'sql',
2044
+ * })
2045
+ * ```
1201
2046
  */
1202
- $internals(): {
1203
- listenerMap: Map<keyof Shape, Set<Func>>;
1204
- rgxListenerMap: Map<string, Set<Func>>;
1205
- internalListener: EventTarget;
1206
- name: string;
1207
- spy: ObserverEngine.Spy<Shape> | undefined;
1208
- };
2047
+ addFile(change: Change, folder: "change" | "revert", options: AddFileOptions): Promise<Change>;
1209
2048
  /**
1210
- * Returns if the observable instance has the given event
2049
+ * Remove a file from a change.
2050
+ *
2051
+ * @example
2052
+ * ```typescript
2053
+ * await ctx.noorm.changes.removeFile(change, 'change', '001_create-table.sql')
2054
+ * ```
1211
2055
  */
1212
- $has(event: Events<Shape>): boolean;
2056
+ removeFile(change: Change, folder: "change" | "revert", filename: string): Promise<Change>;
1213
2057
  /**
1214
- * Returns if the observable instance has a regex event
2058
+ * Rename a file in a change.
2059
+ *
2060
+ * @example
2061
+ * ```typescript
2062
+ * await ctx.noorm.changes.renameFile(change, 'change', '001_old.sql', 'new-name')
2063
+ * ```
1215
2064
  */
1216
- $has(event: RegExp): boolean;
2065
+ renameFile(change: Change, folder: "change" | "revert", oldFilename: string, newDescription: string): Promise<Change>;
1217
2066
  /**
1218
- * Returns if the observable instance has the given event
2067
+ * Reorder files in a change folder.
2068
+ *
2069
+ * @example
2070
+ * ```typescript
2071
+ * await ctx.noorm.changes.reorderFiles(change, 'change', ['002_b.sql', '001_a.sql'])
2072
+ * ```
1219
2073
  */
1220
- $has(event: string): boolean;
2074
+ reorderFiles(change: Change, folder: "change" | "revert", newOrder: string[]): Promise<Change>;
1221
2075
  /**
1222
- * Enables or disables debugging for the observable instance.
1223
- * Works in conjunction with your spy function. Provides a
1224
- * stack trace of events that are triggered, listened to, and
1225
- * cleaned up.
2076
+ * Delete a change directory from disk.
1226
2077
  *
1227
- * @param on Whether to enable or disable debugging
2078
+ * @example
2079
+ * ```typescript
2080
+ * await ctx.noorm.changes.delete(change)
2081
+ * ```
1228
2082
  */
1229
- debug(on?: boolean): void;
2083
+ delete(change: Change): Promise<void>;
1230
2084
  /**
1231
- * Sets the spy function for the observable instance.
1232
- * @param spy The spy function to set
1233
- * @param force Whether to force the spy function to be set even if one is already set
2085
+ * Discover all changes on disk.
2086
+ *
2087
+ * @example
2088
+ * ```typescript
2089
+ * const changes = await ctx.noorm.changes.discover()
2090
+ * ```
1234
2091
  */
1235
- spy(spy: ObserverEngine.Spy<Shape>, force?: boolean): void;
2092
+ discover(): Promise<Change[]>;
1236
2093
  /**
1237
- * Observes given component as an extension of this observable instance.
1238
- * @param component Component to wrap events around
1239
- * @param options Optional configuration including signal for cleanup
2094
+ * Parse a single change from disk by name.
1240
2095
  *
1241
2096
  * @example
2097
+ * ```typescript
2098
+ * const change = await ctx.noorm.changes.parse('2024-01-15-add-users')
2099
+ * ```
2100
+ */
2101
+ parse(name: string): Promise<Change>;
2102
+ /**
2103
+ * Validate a change's structure.
1242
2104
  *
1243
- * const obs = new ObserverEngine();
1244
- *
1245
- * const modal = {};
1246
- *
1247
- * obs.observe(modal);
1248
- *
1249
- * modal.on('modal-open', () => {});
1250
- *
1251
- * obs.trigger('modal-open'); // opens modal
1252
- * modal.trigger('modal-open'); // opens modal
2105
+ * @throws ChangeValidationError if invalid
1253
2106
  *
1254
- * modal.cleanup(); // clears all event listeners
2107
+ * @example
2108
+ * ```typescript
2109
+ * ctx.noorm.changes.validate(change)
2110
+ * ```
1255
2111
  */
1256
- observe<C>(component: C, options?: ObserverEngine.ObserveOptions): ObserverEngine.Child<C, Shape>;
2112
+ validate(change: Change): void;
1257
2113
  /**
1258
- * Returns an event generator that will listen for the specified event
2114
+ * Apply a specific change.
1259
2115
  *
1260
2116
  * @example
1261
- *
1262
- * const obs = new ObserverEngine();
1263
- *
1264
- * const something = obs.on('something');
1265
- * const data = await something.next(); // waits for next event
1266
- * something.emit('special'); // emits data to listeners
1267
- *
1268
- * something.cleanup(); // stops listening for events
2117
+ * ```typescript
2118
+ * const result = await ctx.noorm.changes.apply('2024-01-15-add-users')
2119
+ * ```
1269
2120
  */
1270
- on<E extends Events<Shape>>(event: E, options?: ObserverEngine.ListenerOptions): EventGenerator<Shape, E>;
1271
- on<E extends string>(event: E, options?: ObserverEngine.ListenerOptions): EventGenerator<Record<E, any>>;
2121
+ apply(name: string, options?: ChangeOptions): Promise<ChangeResult>;
1272
2122
  /**
1273
- * Listens for the specified event and executes the given callback
2123
+ * Revert a specific change.
1274
2124
  *
1275
2125
  * @example
2126
+ * ```typescript
2127
+ * const result = await ctx.noorm.changes.revert('2024-01-15-add-users')
2128
+ * ```
2129
+ */
2130
+ revert(name: string, options?: ChangeOptions): Promise<ChangeResult>;
2131
+ /**
2132
+ * Apply all pending changes.
1276
2133
  *
1277
- * const obs = new ObserverEngine();
1278
- *
1279
- * obs.on('something', (data) => {
1280
- * console.log(data);
1281
- * });
2134
+ * @example
2135
+ * ```typescript
2136
+ * const result = await ctx.noorm.changes.ff()
2137
+ * ```
1282
2138
  */
1283
- on<E extends Events<Shape>>(event: E, listener: ObserverEngine.EventCallback<Shape[E]>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
1284
- on<E extends string>(event: E, listener: ObserverEngine.EventCallback<Record<E, any>>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
2139
+ ff(): Promise<BatchChangeResult>;
1285
2140
  /**
1286
- * Returns an event generator that will listen for all events matching the regex
2141
+ * Apply the next N pending changes (default 1).
1287
2142
  *
1288
2143
  * @example
2144
+ * ```typescript
2145
+ * const result = await ctx.noorm.changes.next()
2146
+ * const result = await ctx.noorm.changes.next(3)
2147
+ * ```
2148
+ */
2149
+ next(count?: number): Promise<BatchChangeResult>;
2150
+ /**
2151
+ * Get status of all changes (merged disk + DB).
1289
2152
  *
1290
- * const obs = new ObserverEngine();
2153
+ * @example
2154
+ * ```typescript
2155
+ * const all = await ctx.noorm.changes.status()
2156
+ * ```
2157
+ */
2158
+ status(): Promise<ChangeListItem[]>;
2159
+ /**
2160
+ * Get only pending (unapplied or reverted) changes.
1291
2161
  *
1292
- * const onEvent = obs.on(/some/);
1293
- * const { event, data } = await onEvent.next(); // waits for next event
1294
- * onEvent.emit('something'); // emits data to listeners
2162
+ * @example
2163
+ * ```typescript
2164
+ * const pending = await ctx.noorm.changes.pending()
2165
+ * ```
2166
+ */
2167
+ pending(): Promise<ChangeListItem[]>;
2168
+ /**
2169
+ * Get execution history.
1295
2170
  *
1296
- * onEvent.cleanup(); // stops listening for events
2171
+ * @example
2172
+ * ```typescript
2173
+ * const history = await ctx.noorm.changes.history(10)
2174
+ * ```
1297
2175
  */
1298
- on(event: RegExp, options?: ObserverEngine.ListenerOptions): EventGenerator<Shape, RegExp>;
2176
+ history(limit?: number): Promise<ChangeHistoryRecord[]>;
1299
2177
  /**
1300
- * Listens for all events matching the regex and executes the given callback
2178
+ * Get execution history for a specific change by name.
2179
+ *
2180
+ * Returns all operation records for the given change, most recent first.
1301
2181
  *
1302
2182
  * @example
2183
+ * ```typescript
2184
+ * const records = await ctx.noorm.changes.historyForChange('2024-01-15-add-users')
2185
+ * ```
2186
+ */
2187
+ historyForChange(name: string, limit?: number): Promise<ChangeHistoryRecord[]>;
2188
+ /**
2189
+ * Rewind applied changes in reverse order back to (and including) the named change.
1303
2190
  *
1304
- * const obs = new ObserverEngine();
2191
+ * When a string is passed, reverts until and including that change.
2192
+ * When a number is passed, reverts that many recent changes.
1305
2193
  *
1306
- * obs.on(/some/, ({ event, data }) => {
1307
- * console.log(event, data);
1308
- * });
2194
+ * @example
2195
+ * ```typescript
2196
+ * const result = await ctx.noorm.changes.rewind('2024-01-15-add-users')
2197
+ * const result = await ctx.noorm.changes.rewind(3)
2198
+ * ```
1309
2199
  */
1310
- on(event: RegExp, listener: ObserverEngine.EventCallback<ObserverEngine.RgxEmitData<Shape>>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
2200
+ rewind(target: number | string): Promise<BatchChangeResult>;
1311
2201
  /**
1312
- * Returns an event promise that resolves when
1313
- * the specified event is emitted
2202
+ * Get per-file execution records for a specific operation ID.
2203
+ *
2204
+ * Use the `id` from a `ChangeHistoryRecord` to drill into individual
2205
+ * file-level results for that operation.
2206
+ *
2207
+ * @example
2208
+ * ```typescript
2209
+ * const records = await ctx.noorm.changes.historyForChange('2024-01-15-add-users')
2210
+ * const files = await ctx.noorm.changes.getFileHistory(records[0].id)
2211
+ * ```
1314
2212
  */
1315
- once<E extends Events<Shape>>(event: E, options?: ObserverEngine.ListenerOptions): EventPromise<Shape[E]>;
2213
+ getFileHistory(operationId: number): Promise<FileHistoryRecord[]>;
2214
+ }
2215
+ export declare class RunNamespace {
2216
+ #private;
2217
+ constructor(state: ContextState);
1316
2218
  /**
1317
- * Returns an event promise that resolves when
1318
- * the specified event is emitted. This overload
1319
- * is untyped and can be used to listen for any
1320
- * event that is emitted.
2219
+ * Discover SQL files in a directory.
2220
+ *
2221
+ * @example
2222
+ * ```typescript
2223
+ * const files = await ctx.noorm.run.discover('sql/')
2224
+ * ```
1321
2225
  */
1322
- once<E extends string>(event: E, options?: ObserverEngine.ListenerOptions): EventPromise<Record<E, any>>;
2226
+ discover(dirpath?: string): Promise<string[]>;
1323
2227
  /**
1324
- * Executes a callback once when the specified
1325
- * event is emitted
2228
+ * Preview SQL files — render templates without executing.
2229
+ *
2230
+ * @example
2231
+ * ```typescript
2232
+ * const results = await ctx.noorm.run.preview(['sql/001.sql', 'sql/002.sql'])
2233
+ * ```
1326
2234
  */
1327
- once<E extends Events<Shape>>(event: E, listener: ObserverEngine.EventCallback<Shape[E]>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
2235
+ preview(filepaths: string[], output?: string | null): Promise<FileResult[]>;
1328
2236
  /**
1329
- * Executes a callback once when the specified
1330
- * event is emitted. This overload is untyped
1331
- * and can be used to listen for any event that
1332
- * is emitted.
2237
+ * Execute a single SQL file.
2238
+ *
2239
+ * @example
2240
+ * ```typescript
2241
+ * await ctx.noorm.run.file('seeds/test-data.sql')
2242
+ * ```
1333
2243
  */
1334
- once<E extends string>(event: E, listener: ObserverEngine.EventCallback<Record<E, any>>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
2244
+ file(filepath: string, options?: RunOptions): Promise<FileResult>;
1335
2245
  /**
1336
- * Returns an event promise that resolves when
1337
- * any events matching the regex are emitted
2246
+ * Execute multiple SQL files sequentially.
2247
+ *
2248
+ * @example
2249
+ * ```typescript
2250
+ * await ctx.noorm.run.files(['functions/utils.sql', 'triggers/audit.sql'])
2251
+ * ```
1338
2252
  */
1339
- once(event: RegExp, options?: ObserverEngine.ListenerOptions): EventPromise<ObserverEngine.RgxEmitData<Shape>>;
2253
+ files(filepaths: string[], options?: RunOptions): Promise<BatchResult>;
1340
2254
  /**
1341
- * Executes a callback once when any events
1342
- * matching the regex are emitted
2255
+ * Execute all SQL files in a directory.
2256
+ *
2257
+ * @example
2258
+ * ```typescript
2259
+ * await ctx.noorm.run.dir('seeds/')
2260
+ * ```
1343
2261
  */
1344
- once(event: RegExp, listener: ObserverEngine.EventCallback<ObserverEngine.RgxEmitData<Shape>>, options?: ObserverEngine.ListenerOptions): ObserverEngine.Cleanup;
2262
+ dir(dirpath: string, options?: RunOptions): Promise<BatchResult>;
1345
2263
  /**
1346
- * Stop listening for an event
1347
- * @param event
1348
- * @param listener
2264
+ * Execute all SQL files in the schema directory.
2265
+ *
2266
+ * @example
2267
+ * ```typescript
2268
+ * await ctx.noorm.run.build({ force: true })
2269
+ * ```
1349
2270
  */
1350
- off(event: Events<Shape> | RegExp | string, listener?: Function): void;
1351
- /** Emits an event */
1352
- emit<E extends Events<Shape> | RegExp | string>(event: E, data?: E extends Events<Shape> ? Shape[E] : E extends string ? Record<E, any>[E] : unknown): void;
1353
- clear(): void;
1354
- queue<E extends Events<Shape> | RegExp>(event: E, process: (data: EventData<Shape, E>) => MaybePromise<any>, options: QueueOpts): EventQueue<Shape, E>;
2271
+ build(options?: BuildOptions): Promise<BatchResult>;
1355
2272
  }
1356
- export type EventData<S, E extends Events<S> | RegExp = Events<S>> = (E extends Events<S> ? S[E] : E extends RegExp ? ObserverEngine.RgxEmitData<S> : S[Events<S>]);
1357
- declare class EventPromise<T> extends Promise<T> {
1358
- cleanup?: () => void;
1359
- reject?: (err: Error | string) => void;
1360
- resolve?: (value: T) => void;
2273
+ /**
2274
+ * Overview counts for all categories.
2275
+ */
2276
+ export interface ExploreOverview {
2277
+ tables: number;
2278
+ views: number;
2279
+ procedures: number;
2280
+ functions: number;
2281
+ types: number;
2282
+ indexes: number;
2283
+ foreignKeys: number;
2284
+ triggers: number;
2285
+ locks: number;
2286
+ connections: number;
1361
2287
  }
1362
- declare class InternalQueueEvent<T = unknown> {
1363
- readonly data: T;
1364
- constructor(data: T);
2288
+ /**
2289
+ * Table summary for list display.
2290
+ */
2291
+ export interface TableSummary {
2292
+ name: string;
2293
+ schema?: string;
2294
+ columnCount: number;
2295
+ rowCountEstimate?: number;
1365
2296
  }
1366
- declare enum QueueRejectionReason {
1367
- full = "Queue is full",
1368
- notRunning = "Queue is not running"
2297
+ /**
2298
+ * View summary for list display.
2299
+ */
2300
+ export interface ViewSummary {
2301
+ name: string;
2302
+ schema?: string;
2303
+ columnCount: number;
2304
+ isUpdatable: boolean;
1369
2305
  }
1370
- export type QueueEventData<S, E extends Events<S> | RegExp = Events<S>> = {
1371
- data?: EventData<S, E>;
1372
- _taskId?: string;
1373
- priority?: number;
1374
- reason?: QueueRejectionReason;
1375
- startedAt?: number;
1376
- rateLimited?: boolean;
1377
- elapsed?: number;
1378
- error?: Error;
1379
- force?: boolean;
1380
- pending?: number;
1381
- flushed?: number;
1382
- drained?: number;
1383
- count?: number;
1384
- };
1385
- export type QueueEvents<S extends Record<string, any>, E extends Events<S> | RegExp = Events<S>> = {
1386
- added: QueueEventData<S, E>;
1387
- start: void;
1388
- started: void;
1389
- stopped: void;
1390
- processing: QueueEventData<S, E> & {
1391
- startedAt: number;
1392
- rateLimited: boolean;
1393
- };
1394
- success: QueueEventData<S, E> & {
1395
- startedAt: number;
1396
- elapsed: number;
1397
- rateLimited: boolean;
1398
- };
1399
- error: QueueEventData<S, E> & {
1400
- error: Error;
1401
- rateLimited: boolean;
1402
- };
1403
- timeout: QueueEventData<S, E> & {
1404
- error: Error;
1405
- rateLimited: boolean;
1406
- };
1407
- rejected: QueueEventData<S, E> & {
1408
- reason: QueueRejectionReason;
1409
- };
1410
- "rate-limited": QueueEventData<S, E> & {
1411
- rateLimited: boolean;
1412
- };
1413
- empty: void;
1414
- idle: void;
1415
- drain: {
1416
- pending: number;
1417
- };
1418
- drained: {
1419
- pending?: number;
1420
- drained?: number;
1421
- };
1422
- flush: {
1423
- pending: number;
1424
- };
1425
- flushed: {
1426
- flushed: number;
1427
- };
1428
- paused: void;
1429
- resumed: void;
1430
- cleanup: void;
1431
- purged: {
1432
- count: number;
2306
+ /**
2307
+ * Stored procedure summary for list display.
2308
+ */
2309
+ export interface ProcedureSummary {
2310
+ name: string;
2311
+ schema?: string;
2312
+ parameterCount: number;
2313
+ }
2314
+ /**
2315
+ * Function summary for list display.
2316
+ */
2317
+ export interface FunctionSummary {
2318
+ name: string;
2319
+ schema?: string;
2320
+ parameterCount: number;
2321
+ returnType: string;
2322
+ }
2323
+ /**
2324
+ * Type/Enum summary for list display.
2325
+ */
2326
+ export interface TypeSummary {
2327
+ name: string;
2328
+ schema?: string;
2329
+ kind: "enum" | "composite" | "domain" | "other";
2330
+ valueCount?: number;
2331
+ }
2332
+ /**
2333
+ * Index summary for list display.
2334
+ */
2335
+ export interface IndexSummary {
2336
+ name: string;
2337
+ schema?: string;
2338
+ tableName: string;
2339
+ tableSchema?: string;
2340
+ columns: string[];
2341
+ isUnique: boolean;
2342
+ isPrimary: boolean;
2343
+ }
2344
+ /**
2345
+ * Foreign key summary for list display.
2346
+ */
2347
+ export interface ForeignKeySummary {
2348
+ name: string;
2349
+ schema?: string;
2350
+ tableName: string;
2351
+ tableSchema?: string;
2352
+ columns: string[];
2353
+ referencedTable: string;
2354
+ referencedSchema?: string;
2355
+ referencedColumns: string[];
2356
+ onDelete?: string;
2357
+ onUpdate?: string;
2358
+ }
2359
+ /**
2360
+ * Column detail for tables/views.
2361
+ */
2362
+ export interface ColumnDetail {
2363
+ name: string;
2364
+ dataType: string;
2365
+ isNullable: boolean;
2366
+ defaultValue?: string;
2367
+ isPrimaryKey: boolean;
2368
+ ordinalPosition: number;
2369
+ }
2370
+ /**
2371
+ * Parameter detail for procedures/functions.
2372
+ */
2373
+ export interface ParameterDetail {
2374
+ name: string;
2375
+ dataType: string;
2376
+ mode: "IN" | "OUT" | "INOUT";
2377
+ defaultValue?: string;
2378
+ ordinalPosition: number;
2379
+ }
2380
+ /**
2381
+ * Full table detail.
2382
+ */
2383
+ export interface TableDetail {
2384
+ name: string;
2385
+ schema?: string;
2386
+ columns: ColumnDetail[];
2387
+ indexes: IndexSummary[];
2388
+ foreignKeys: ForeignKeySummary[];
2389
+ rowCountEstimate?: number;
2390
+ }
2391
+ /**
2392
+ * Full view detail.
2393
+ */
2394
+ export interface ViewDetail {
2395
+ name: string;
2396
+ schema?: string;
2397
+ columns: ColumnDetail[];
2398
+ definition?: string;
2399
+ isUpdatable: boolean;
2400
+ }
2401
+ /**
2402
+ * Full procedure detail.
2403
+ */
2404
+ export interface ProcedureDetail {
2405
+ name: string;
2406
+ schema?: string;
2407
+ parameters: ParameterDetail[];
2408
+ definition?: string;
2409
+ }
2410
+ /**
2411
+ * Full function detail.
2412
+ */
2413
+ export interface FunctionDetail {
2414
+ name: string;
2415
+ schema?: string;
2416
+ parameters: ParameterDetail[];
2417
+ returnType: string;
2418
+ definition?: string;
2419
+ language?: string;
2420
+ }
2421
+ /**
2422
+ * Full type detail.
2423
+ */
2424
+ export interface TypeDetail {
2425
+ name: string;
2426
+ schema?: string;
2427
+ kind: "enum" | "composite" | "domain" | "other";
2428
+ values?: string[];
2429
+ attributes?: ColumnDetail[];
2430
+ baseType?: string;
2431
+ definition?: string;
2432
+ }
2433
+ /**
2434
+ * Teardown Types
2435
+ *
2436
+ * Types for database reset and teardown operations.
2437
+ * Supports data wipe (truncate) and schema teardown (drop).
2438
+ */
2439
+ /**
2440
+ * Options for truncating table data.
2441
+ *
2442
+ * @example
2443
+ * ```typescript
2444
+ * // Truncate all tables except preserved ones from settings
2445
+ * await truncateData(db, 'postgres', {
2446
+ * preserve: ['AppSettings', 'UserRoles'],
2447
+ * restartIdentity: true,
2448
+ * })
2449
+ *
2450
+ * // Truncate only specific tables
2451
+ * await truncateData(db, 'postgres', {
2452
+ * only: ['users', 'posts'],
2453
+ * })
2454
+ * ```
2455
+ */
2456
+ export interface TruncateOptions {
2457
+ /** Tables to preserve (won't be truncated) */
2458
+ preserve?: string[];
2459
+ /** If set, only truncate these tables (inverse of preserve) */
2460
+ only?: string[];
2461
+ /** Restart identity/auto-increment sequences (default: true) */
2462
+ restartIdentity?: boolean;
2463
+ /** Dry run - return SQL without executing */
2464
+ dryRun?: boolean;
2465
+ }
2466
+ /**
2467
+ * Result of a truncate operation.
2468
+ */
2469
+ export interface TruncateResult {
2470
+ /** Tables that were truncated */
2471
+ truncated: string[];
2472
+ /** Tables that were preserved */
2473
+ preserved: string[];
2474
+ /** SQL statements executed (or would execute in dry-run) */
2475
+ statements: string[];
2476
+ /** Duration in milliseconds */
2477
+ durationMs: number;
2478
+ }
2479
+ /**
2480
+ * Result of a teardown operation.
2481
+ */
2482
+ export interface TeardownResult {
2483
+ /** Objects dropped by category */
2484
+ dropped: {
2485
+ tables: string[];
2486
+ views: string[];
2487
+ functions: string[];
2488
+ procedures: string[];
2489
+ types: string[];
2490
+ foreignKeys: string[];
1433
2491
  };
1434
- shutdown: {
1435
- force: boolean;
1436
- pending?: number;
2492
+ /** Objects preserved */
2493
+ preserved: string[];
2494
+ /** SQL statements executed (or would execute in dry-run) */
2495
+ statements: string[];
2496
+ /** Duration in milliseconds */
2497
+ durationMs: number;
2498
+ /** Post-script execution result (if any) */
2499
+ postScriptResult?: {
2500
+ executed: boolean;
2501
+ error?: string;
1437
2502
  };
1438
- };
1439
- declare enum QueueState {
1440
- running = "running",
1441
- paused = "paused",
1442
- stopped = "stopped",
1443
- draining = "draining"
2503
+ /** Number of changes marked as stale (if configName provided) */
2504
+ staleCount?: number;
2505
+ /** ID of the reset record created (if configName provided) */
2506
+ resetRecordId?: number;
1444
2507
  }
1445
2508
  /**
1446
- * The options for the queue
2509
+ * Preview of what would be affected by teardown.
1447
2510
  */
1448
- export interface QueueOpts {
2511
+ export interface TeardownPreview {
2512
+ /** Objects that would be dropped */
2513
+ toDrop: {
2514
+ tables: string[];
2515
+ views: string[];
2516
+ functions: string[];
2517
+ procedures: string[];
2518
+ types: string[];
2519
+ foreignKeys: string[];
2520
+ };
2521
+ /** Objects that would be preserved */
2522
+ toPreserve: string[];
2523
+ /** SQL statements that would execute */
2524
+ statements: string[];
2525
+ }
2526
+ export declare class DbNamespace {
2527
+ #private;
2528
+ constructor(state: ContextState);
1449
2529
  /**
1450
- * The name of the queue
2530
+ * List all tables in the database.
2531
+ *
2532
+ * @example
2533
+ * ```typescript
2534
+ * const tables = await ctx.noorm.db.listTables()
2535
+ * ```
1451
2536
  */
1452
- name: string;
2537
+ listTables(): Promise<TableSummary[]>;
1453
2538
  /**
1454
- * The type of queue to use
2539
+ * Get detailed information about a table.
1455
2540
  *
1456
- * @default 'fifo'
2541
+ * @example
2542
+ * ```typescript
2543
+ * const detail = await ctx.noorm.db.describeTable('users')
2544
+ * ```
1457
2545
  */
1458
- type?: "fifo" | "lifo";
2546
+ describeTable(name: string, schema?: string): Promise<TableDetail | null>;
1459
2547
  /**
1460
- * The concurrency of the queue
2548
+ * List all views in the database.
1461
2549
  *
1462
- * @default 1
2550
+ * @example
2551
+ * ```typescript
2552
+ * const views = await ctx.noorm.db.listViews()
2553
+ * ```
1463
2554
  */
1464
- concurrency?: number;
2555
+ listViews(): Promise<ViewSummary[]>;
1465
2556
  /**
1466
- * The poll interval in milliseconds before the queue will
1467
- * check for new items after idle.
2557
+ * Get detailed information about a view.
1468
2558
  *
1469
- * @default 100
2559
+ * @example
2560
+ * ```typescript
2561
+ * const detail = await ctx.noorm.db.describeView('active_users')
2562
+ * ```
1470
2563
  */
1471
- pollIntervalMs?: number;
2564
+ describeView(name: string, schema?: string): Promise<ViewDetail | null>;
1472
2565
  /**
1473
- * The jitter percentage to displace
1474
- * the next process time. This stops all concurrent
1475
- * processes from happening at the same time.
2566
+ * List all stored procedures in the database.
1476
2567
  *
1477
- * @range [0, 1]
1478
- * @default 1
2568
+ * @example
2569
+ * ```typescript
2570
+ * const procs = await ctx.noorm.db.listProcedures()
2571
+ * ```
1479
2572
  */
1480
- jitterFactor?: number;
2573
+ listProcedures(): Promise<ProcedureSummary[]>;
1481
2574
  /**
1482
- * The interval in milliseconds before picking up the next item
2575
+ * Get detailed information about a stored procedure.
1483
2576
  *
1484
- * @note If the interval is 0, the queue will not wait between items
2577
+ * @example
2578
+ * ```typescript
2579
+ * const detail = await ctx.noorm.db.describeProcedure('sp_update_user')
2580
+ * ```
2581
+ */
2582
+ describeProcedure(name: string, schema?: string): Promise<ProcedureDetail | null>;
2583
+ /**
2584
+ * List all functions in the database.
1485
2585
  *
1486
- * @default 0
2586
+ * @example
2587
+ * ```typescript
2588
+ * const fns = await ctx.noorm.db.listFunctions()
2589
+ * ```
1487
2590
  */
1488
- processIntervalMs?: number;
2591
+ listFunctions(): Promise<FunctionSummary[]>;
1489
2592
  /**
1490
- * The timeout in milliseconds before the task is considered timed out
2593
+ * Get detailed information about a function.
1491
2594
  *
1492
- * @note If the timeout is 0, the task will not be considered timed out
2595
+ * @example
2596
+ * ```typescript
2597
+ * const detail = await ctx.noorm.db.describeFunction('fn_get_user')
2598
+ * ```
2599
+ */
2600
+ describeFunction(name: string, schema?: string): Promise<FunctionDetail | null>;
2601
+ /**
2602
+ * List all custom types in the database.
1493
2603
  *
1494
- * @default 0
2604
+ * @example
2605
+ * ```typescript
2606
+ * const types = await ctx.noorm.db.listTypes()
2607
+ * ```
1495
2608
  */
1496
- taskTimeoutMs?: number;
2609
+ listTypes(): Promise<TypeSummary[]>;
2610
+ /**
2611
+ * Get detailed information about a custom type.
2612
+ *
2613
+ * @example
2614
+ * ```typescript
2615
+ * const detail = await ctx.noorm.db.describeType('user_status')
2616
+ * ```
2617
+ */
2618
+ describeType(name: string, schema?: string): Promise<TypeDetail | null>;
2619
+ /**
2620
+ * List all indexes in the database.
2621
+ *
2622
+ * @example
2623
+ * ```typescript
2624
+ * const indexes = await ctx.noorm.db.listIndexes()
2625
+ * ```
2626
+ */
2627
+ listIndexes(): Promise<IndexSummary[]>;
2628
+ /**
2629
+ * List all foreign keys in the database.
2630
+ *
2631
+ * @example
2632
+ * ```typescript
2633
+ * const fks = await ctx.noorm.db.listForeignKeys()
2634
+ * ```
2635
+ */
2636
+ listForeignKeys(): Promise<ForeignKeySummary[]>;
2637
+ /**
2638
+ * Get database overview with counts of all object types.
2639
+ *
2640
+ * @example
2641
+ * ```typescript
2642
+ * const overview = await ctx.noorm.db.overview()
2643
+ * ```
2644
+ */
2645
+ overview(): Promise<ExploreOverview>;
2646
+ /**
2647
+ * Preview what teardown would drop without executing.
2648
+ *
2649
+ * @example
2650
+ * ```typescript
2651
+ * const preview = await ctx.noorm.db.previewTeardown()
2652
+ * ```
2653
+ */
2654
+ previewTeardown(): Promise<TeardownPreview>;
2655
+ /**
2656
+ * Wipe all data, keeping the schema intact.
2657
+ *
2658
+ * User-provided preserve/only options take priority.
2659
+ * Falls back to settings.teardown.preserveTables from settings.yml.
2660
+ *
2661
+ * @example
2662
+ * ```typescript
2663
+ * // Uses preserve list from settings.yml automatically
2664
+ * const result = await ctx.noorm.db.truncate()
2665
+ *
2666
+ * // Override with explicit preserve list
2667
+ * const result = await ctx.noorm.db.truncate({ preserve: ['seeds'] })
2668
+ *
2669
+ * // Truncate only specific tables
2670
+ * const result = await ctx.noorm.db.truncate({ only: ['users', 'posts'] })
2671
+ * ```
2672
+ */
2673
+ truncate(options?: TruncateOptions): Promise<TruncateResult>;
2674
+ /**
2675
+ * Drop all database objects except noorm tracking tables.
2676
+ *
2677
+ * @example
2678
+ * ```typescript
2679
+ * const result = await ctx.noorm.db.teardown()
2680
+ * ```
2681
+ */
2682
+ teardown(): Promise<TeardownResult>;
2683
+ /**
2684
+ * Full rebuild: teardown + build.
2685
+ *
2686
+ * @example
2687
+ * ```typescript
2688
+ * await ctx.noorm.db.reset()
2689
+ * ```
2690
+ */
2691
+ reset(): Promise<void>;
2692
+ /** @internal Used by NoormOps to wire up reset -> build. */
2693
+ set _buildFn(fn: (opts?: BuildOptions) => Promise<unknown>);
2694
+ }
2695
+ /**
2696
+ * Lock manager types.
2697
+ *
2698
+ * Defines the shape of locks and options for acquiring them.
2699
+ * Used to prevent concurrent operations on the same database.
2700
+ *
2701
+ * WHY: Concurrent DDL operations can corrupt database state.
2702
+ * Locks ensure only one process modifies the schema at a time.
2703
+ */
2704
+ /**
2705
+ * Lock state returned after acquisition.
2706
+ *
2707
+ * @example
2708
+ * ```typescript
2709
+ * const lock = await lockManager.acquire('dev', 'alice@example.com')
2710
+ * console.log(lock.lockedBy) // 'alice@example.com'
2711
+ * console.log(lock.expiresAt) // Date object
2712
+ * ```
2713
+ */
2714
+ interface Lock$1 {
2715
+ /** Identity string of holder */
2716
+ lockedBy: string;
2717
+ /** When acquired */
2718
+ lockedAt: Date;
2719
+ /** Auto-expiry time */
2720
+ expiresAt: Date;
2721
+ /** Optional reason for acquiring */
2722
+ reason?: string;
2723
+ }
2724
+ /**
2725
+ * Options for acquiring a lock.
2726
+ *
2727
+ * @example
2728
+ * ```typescript
2729
+ * // Wait up to 30s for lock, polling every 500ms
2730
+ * const options: LockOptions = {
2731
+ * timeout: 60_000, // Lock expires after 60s
2732
+ * wait: true, // Block until available
2733
+ * waitTimeout: 30_000, // Max wait time
2734
+ * pollInterval: 500, // Check every 500ms
2735
+ * reason: 'Running migrations',
2736
+ * }
2737
+ * ```
2738
+ */
2739
+ interface LockOptions$1 {
2740
+ /**
2741
+ * Database dialect for date formatting.
2742
+ *
2743
+ * SQLite stores dates as ISO strings, other dialects can use Date objects.
2744
+ * @default 'postgres'
2745
+ */
2746
+ dialect?: "postgres" | "mysql" | "sqlite" | "mssql";
2747
+ /**
2748
+ * Lock duration in milliseconds.
2749
+ *
2750
+ * After this time, the lock expires and can be claimed by others.
2751
+ * @default 300_000 (5 minutes)
2752
+ */
2753
+ timeout?: number;
2754
+ /**
2755
+ * Block until lock is available?
2756
+ *
2757
+ * If true, will poll until the lock is acquired or waitTimeout is reached.
2758
+ * @default false
2759
+ */
2760
+ wait?: boolean;
2761
+ /**
2762
+ * Max time to wait for lock in milliseconds.
2763
+ *
2764
+ * Only used if wait is true.
2765
+ * @default 30_000 (30 seconds)
2766
+ */
2767
+ waitTimeout?: number;
2768
+ /**
2769
+ * How often to poll when waiting, in milliseconds.
2770
+ *
2771
+ * Only used if wait is true.
2772
+ * @default 1_000 (1 second)
2773
+ */
2774
+ pollInterval?: number;
2775
+ /**
2776
+ * Optional reason for acquiring the lock.
2777
+ *
2778
+ * Shown to users who are blocked by this lock.
2779
+ */
2780
+ reason?: string;
2781
+ }
2782
+ /**
2783
+ * Result of a lock status check.
2784
+ */
2785
+ export interface LockStatus {
2786
+ /** Whether the lock is currently held */
2787
+ isLocked: boolean;
2788
+ /** Lock details if held, null if not */
2789
+ lock: Lock$1 | null;
2790
+ }
2791
+ /**
2792
+ * Lock-related errors.
2793
+ *
2794
+ * WHY: Specific error types allow callers to handle lock failures
2795
+ * differently from other errors (e.g., prompt user to wait vs retry).
2796
+ */
2797
+ /**
2798
+ * Error when lock cannot be acquired.
2799
+ *
2800
+ * Thrown when another process holds the lock and wait=false,
2801
+ * or when waitTimeout is exceeded.
2802
+ *
2803
+ * @example
2804
+ * ```typescript
2805
+ * const [lock, err] = await attempt(() => lockManager.acquire(config, identity))
2806
+ * if (err instanceof LockAcquireError) {
2807
+ * console.log(`Blocked by ${err.holder} since ${err.heldSince}`)
2808
+ * }
2809
+ * ```
2810
+ */
2811
+ export declare class LockAcquireError extends Error {
2812
+ readonly configName: string;
2813
+ readonly holder: string;
2814
+ readonly heldSince: Date;
2815
+ readonly expiresAt: Date;
2816
+ readonly reason?: string | undefined;
2817
+ readonly name: "LockAcquireError";
2818
+ constructor(configName: string, holder: string, heldSince: Date, expiresAt: Date, reason?: string | undefined);
2819
+ }
2820
+ /**
2821
+ * Error when lock expires during operation.
2822
+ *
2823
+ * Thrown when validating lock before a critical operation
2824
+ * and discovering it has expired.
2825
+ *
2826
+ * @example
2827
+ * ```typescript
2828
+ * // Before committing transaction
2829
+ * const [, err] = await attempt(() => lockManager.validate(configName, identity))
2830
+ * if (err instanceof LockExpiredError) {
2831
+ * await transaction.rollback()
2832
+ * throw new Error('Lock expired, operation aborted')
2833
+ * }
2834
+ * ```
2835
+ */
2836
+ export declare class LockExpiredError extends Error {
2837
+ readonly configName: string;
2838
+ readonly identity: string;
2839
+ readonly expiredAt: Date;
2840
+ readonly name: "LockExpiredError";
2841
+ constructor(configName: string, identity: string, expiredAt: Date);
2842
+ }
2843
+ export declare class LockNamespace {
2844
+ #private;
2845
+ constructor(state: ContextState);
1497
2846
  /**
1498
- * The maximum size of the queue
2847
+ * Acquire a database lock.
1499
2848
  *
1500
- * @default 999_999_999
2849
+ * @example
2850
+ * ```typescript
2851
+ * const lock = await ctx.noorm.lock.acquire({ timeout: 60000 })
2852
+ * ```
1501
2853
  */
1502
- maxQueueSize?: number;
2854
+ acquire(options?: LockOptions$1): Promise<Lock$1>;
1503
2855
  /**
1504
- * The rate limit of the queue in items per window
2856
+ * Release the current lock.
1505
2857
  *
1506
- * @default 999_999_999
2858
+ * @example
2859
+ * ```typescript
2860
+ * await ctx.noorm.lock.release()
2861
+ * ```
1507
2862
  */
1508
- rateLimitCapacity?: number;
2863
+ release(): Promise<void>;
1509
2864
  /**
1510
- * The rate limit window in milliseconds
2865
+ * Get current lock status.
1511
2866
  *
1512
- * @default 1000
2867
+ * @example
2868
+ * ```typescript
2869
+ * const status = await ctx.noorm.lock.status()
2870
+ * ```
1513
2871
  */
1514
- rateLimitIntervalMs?: number;
2872
+ status(): Promise<LockStatus>;
1515
2873
  /**
1516
- * Automatically start the queue
2874
+ * Execute an operation with automatic lock acquisition and release.
1517
2875
  *
1518
- * @default true
2876
+ * @example
2877
+ * ```typescript
2878
+ * await ctx.noorm.lock.withLock(async () => {
2879
+ * await ctx.noorm.run.build()
2880
+ * })
2881
+ * ```
1519
2882
  */
1520
- autoStart?: boolean;
2883
+ withLock<T>(fn: () => Promise<T>, options?: LockOptions$1): Promise<T>;
1521
2884
  /**
1522
- * Whether to enable debug mode. Can be set to 'info' or 'verbose' to
1523
- * get more detailed output.
2885
+ * Force release any database lock regardless of ownership.
1524
2886
  *
1525
- * @default false
2887
+ * @example
2888
+ * ```typescript
2889
+ * await ctx.noorm.lock.forceRelease()
2890
+ * ```
1526
2891
  */
1527
- debug?: boolean | "info" | "verbose";
2892
+ forceRelease(): Promise<boolean>;
1528
2893
  }
1529
- declare class EventQueue<S extends Record<string, any>, E extends Events<S> | RegExp = Events<S>> {
2894
+ /**
2895
+ * Vault secret entry (decrypted).
2896
+ */
2897
+ export interface VaultSecret {
2898
+ /** Secret key name */
2899
+ key: string;
2900
+ /** Decrypted value */
2901
+ value: string;
2902
+ /** Identity who set this secret */
2903
+ setBy: string;
2904
+ /** When created */
2905
+ createdAt: Date;
2906
+ /** When last updated */
2907
+ updatedAt: Date;
2908
+ }
2909
+ /**
2910
+ * Result of vault copy operation.
2911
+ */
2912
+ export interface VaultCopyResult {
2913
+ /** Keys that were successfully copied */
2914
+ copied: string[];
2915
+ /** Keys that were skipped (already exist and force not set) */
2916
+ skipped: string[];
2917
+ /** Keys that failed with errors */
2918
+ errors: Array<{
2919
+ key: string;
2920
+ error: string;
2921
+ }>;
2922
+ }
2923
+ /**
2924
+ * Result of vault propagation.
2925
+ */
2926
+ export interface VaultPropagationResult {
2927
+ /** Identity hashes that received the vault key */
2928
+ propagatedTo: string[];
2929
+ /** Count of users who already had access */
2930
+ alreadyHadAccess: number;
2931
+ }
2932
+ /**
2933
+ * Vault status for the current user.
2934
+ */
2935
+ export interface VaultStatus {
2936
+ /** Whether the vault is initialized (at least one user has the vault key) */
2937
+ isInitialized: boolean;
2938
+ /** Whether the current user has access to the vault */
2939
+ hasAccess: boolean;
2940
+ /** Number of secrets in the vault */
2941
+ secretCount: number;
2942
+ /** Number of users with vault access */
2943
+ usersWithAccess: number;
2944
+ /** Number of users without vault access */
2945
+ usersWithoutAccess: number;
2946
+ }
2947
+ /**
2948
+ * Vault event types.
2949
+ *
2950
+ * All events emitted by the vault module. These are merged into
2951
+ * NoormEvents via the VaultEvents interface.
2952
+ */
2953
+ /**
2954
+ * Vault events for observer integration.
2955
+ *
2956
+ * Events follow the naming convention `vault:{operation}`.
2957
+ */
2958
+ export interface VaultEvents {
2959
+ /** Vault was initialized for the first time */
2960
+ "vault:initialized": {
2961
+ identityHash: string;
2962
+ };
2963
+ /** New vault secret was created */
2964
+ "vault:secret:created": {
2965
+ key: string;
2966
+ setBy: string;
2967
+ };
2968
+ /** Existing vault secret was updated */
2969
+ "vault:secret:updated": {
2970
+ key: string;
2971
+ setBy: string;
2972
+ };
2973
+ /** Vault secret was deleted */
2974
+ "vault:secret:deleted": {
2975
+ key: string;
2976
+ };
2977
+ /** Vault key was propagated to a user */
2978
+ "vault:propagated": {
2979
+ toIdentityHash: string;
2980
+ toEmail: string;
2981
+ };
2982
+ /** Vault copy operation started */
2983
+ "vault:copy:starting": {
2984
+ source: string;
2985
+ destination: string;
2986
+ keys: "all" | number;
2987
+ };
2988
+ /** Vault copy operation completed */
2989
+ "vault:copy:completed": {
2990
+ source: string;
2991
+ destination: string;
2992
+ copied: number;
2993
+ skipped: number;
2994
+ errors: number;
2995
+ };
2996
+ }
2997
+ /**
2998
+ * Options for vault copy operation.
2999
+ */
3000
+ export interface VaultCopyOptions {
3001
+ /** Overwrite existing secrets in destination (default: false) */
3002
+ force?: boolean;
3003
+ }
3004
+ export declare class VaultNamespace {
1530
3005
  #private;
1531
- private opts;
1532
- constructor(opts: QueueOpts & {
1533
- event: E | RegExp;
1534
- process: (data: EventData<S, E>) => Promise<void>;
1535
- observer?: ObserverEngine<S>;
1536
- });
3006
+ constructor(state: ContextState);
1537
3007
  /**
1538
- * Start the queue if it is not already started
3008
+ * Initialize the vault for this database.
3009
+ *
3010
+ * @example
3011
+ * ```typescript
3012
+ * const [vaultKey, err] = await ctx.noorm.vault.init()
3013
+ * ```
1539
3014
  */
1540
- start(): Promise<void> | undefined;
3015
+ init(): Promise<[
3016
+ Buffer | null,
3017
+ Error | null
3018
+ ]>;
1541
3019
  /**
1542
- * Stop the queue
3020
+ * Get vault status.
3021
+ *
3022
+ * @example
3023
+ * ```typescript
3024
+ * const status = await ctx.noorm.vault.status()
3025
+ * ```
1543
3026
  */
1544
- stop(): void;
3027
+ status(): Promise<VaultStatus>;
1545
3028
  /**
1546
- * Pause the queue
3029
+ * Set a vault secret.
3030
+ *
3031
+ * @example
3032
+ * ```typescript
3033
+ * const [, err] = await ctx.noorm.vault.set('API_KEY', 'sk-live-...', privateKey)
3034
+ * ```
1547
3035
  */
1548
- pause(): Promise<void> | undefined;
3036
+ set(key: string, value: string, privateKey: string): Promise<[
3037
+ void,
3038
+ Error | null
3039
+ ]>;
1549
3040
  /**
1550
- * Resume the queue
3041
+ * Get a vault secret by key.
3042
+ *
3043
+ * @example
3044
+ * ```typescript
3045
+ * const value = await ctx.noorm.vault.get('API_KEY', privateKey)
3046
+ * ```
1551
3047
  */
1552
- resume(): Promise<void> | undefined;
3048
+ get(key: string, privateKey: string): Promise<string | null>;
1553
3049
  /**
1554
- * Empty and process all items in the queue and
1555
- * then stop the queue. Returns the number of items
1556
- * processed.
3050
+ * Get all vault secrets.
3051
+ *
3052
+ * @example
3053
+ * ```typescript
3054
+ * const all = await ctx.noorm.vault.getAll(privateKey)
3055
+ * ```
1557
3056
  */
1558
- shutdown(force?: boolean): Promise<number>;
3057
+ getAll(privateKey: string): Promise<Record<string, VaultSecret>>;
1559
3058
  /**
1560
- * Executes `limit` items in the queue
1561
- * and then emits a `flush` event. Returns the number
1562
- * of items processed.
3059
+ * List all vault secret keys (without decrypting values).
3060
+ *
3061
+ * @example
3062
+ * ```typescript
3063
+ * const keys = await ctx.noorm.vault.list()
3064
+ * ```
1563
3065
  */
1564
- flush(limit?: number): Promise<number>;
3066
+ list(): Promise<string[]>;
1565
3067
  /**
1566
- * Clears the queue and emits a `purge` event. Returns
1567
- * the number of items purged.
3068
+ * Delete a vault secret.
3069
+ *
3070
+ * @example
3071
+ * ```typescript
3072
+ * const [deleted, err] = await ctx.noorm.vault.delete('OLD_KEY')
3073
+ * ```
1568
3074
  */
1569
- purge(): number;
3075
+ delete(key: string): Promise<[
3076
+ boolean,
3077
+ Error | null
3078
+ ]>;
1570
3079
  /**
1571
- * Emit an event to the observer, which then gets
1572
- * picked up by the queue and processed.
1573
- *
1574
- * @param data The data to add to the queue
1575
- * @param priority The priority of the data
1576
- *
1577
- * @note If priority is provided, it will be used to determine the order of the data in the queue.
1578
- * The higher the priority, the sooner the data will be processed.
3080
+ * Check if a vault secret exists.
1579
3081
  *
1580
- * @note If priority is not provided, the data will be added to the queue with a priority of 0.
3082
+ * @example
3083
+ * ```typescript
3084
+ * const exists = await ctx.noorm.vault.exists('API_KEY')
3085
+ * ```
1581
3086
  */
1582
- add(data: EventData<S, E>, priority?: number): boolean;
1583
- debug(on?: boolean | "info" | "verbose"): void;
1584
- on<K extends keyof QueueEvents<S, E>>(event: K, listener: ((payload: InternalQueueEvent<QueueEvents<S, E>[K]>) => void)): ObserverEngine.Cleanup;
1585
- on<K extends keyof QueueEvents<S, E>>(event: K): EventGenerator<QueueEvents<S, E>, K>;
1586
- once<K extends keyof QueueEvents<S, E>>(event: K): EventPromise<InternalQueueEvent<QueueEvents<S, E>[K]>>;
1587
- once<K extends keyof QueueEvents<S, E>>(event: K, listener: ((payload: InternalQueueEvent<QueueEvents<S, E>[K]>) => void)): ObserverEngine.Cleanup;
1588
- off<K extends keyof QueueEvents<S, E>>(event: K, listener?: Function): void;
3087
+ exists(key: string): Promise<boolean>;
1589
3088
  /**
1590
- * The name of the queue
3089
+ * Propagate vault key to all users without access.
3090
+ *
3091
+ * @example
3092
+ * ```typescript
3093
+ * const result = await ctx.noorm.vault.propagate(privateKey)
3094
+ * ```
1591
3095
  */
1592
- get name(): string;
3096
+ propagate(privateKey: string): Promise<VaultPropagationResult>;
1593
3097
  /**
1594
- * Whether the queue is waiting for the
1595
- * the
3098
+ * Copy vault secrets to another config's database.
3099
+ *
3100
+ * @example
3101
+ * ```typescript
3102
+ * const [result, err] = await ctx.noorm.vault.copy(destConfig, ['API_KEY'], privateKey)
3103
+ * ```
1596
3104
  */
1597
- get isWaiting(): boolean;
3105
+ copy(destConfig: Config, keys: string[] | "all", privateKey: string, options?: VaultCopyOptions): Promise<[
3106
+ VaultCopyResult | null,
3107
+ Error | null
3108
+ ]>;
3109
+ }
3110
+ export declare class SecretsNamespace {
3111
+ #private;
3112
+ constructor(state: ContextState);
1598
3113
  /**
1599
- * Whether the queue is idle
3114
+ * Get a config-scoped secret.
3115
+ *
3116
+ * @example
3117
+ * ```typescript
3118
+ * const apiKey = ctx.noorm.secrets.get('API_KEY')
3119
+ * ```
1600
3120
  */
1601
- get isIdle(): boolean;
3121
+ get(key: string): string | undefined;
3122
+ }
3123
+ /**
3124
+ * Result of processing a SQL file.
3125
+ */
3126
+ interface ProcessResult {
1602
3127
  /**
1603
- * Whether the queue is running
3128
+ * The SQL content (rendered if template, raw if .sql).
1604
3129
  */
1605
- get isRunning(): boolean;
3130
+ sql: string;
1606
3131
  /**
1607
- * Whether the queue is paused
3132
+ * Whether the file was a template.
1608
3133
  */
1609
- get isPaused(): boolean;
3134
+ isTemplate: boolean;
1610
3135
  /**
1611
- * Whether the queue is stopped
3136
+ * Render duration in milliseconds (only for templates).
1612
3137
  */
1613
- get isStopped(): boolean;
3138
+ durationMs?: number;
3139
+ }
3140
+ export declare class TemplatesNamespace {
3141
+ #private;
3142
+ constructor(state: ContextState);
1614
3143
  /**
1615
- * Whether the queue is draining
3144
+ * Render a template file without executing.
3145
+ *
3146
+ * @example
3147
+ * ```typescript
3148
+ * const result = await ctx.noorm.templates.render('sql/001_users.sql.tmpl')
3149
+ * ```
1616
3150
  */
1617
- get isDraining(): boolean;
3151
+ render(filepath: string): Promise<ProcessResult>;
3152
+ }
3153
+ /**
3154
+ * Strategy for handling primary key conflicts during transfer.
3155
+ *
3156
+ * - `fail`: Abort on first conflict (default)
3157
+ * - `skip`: Skip conflicting rows, continue transfer
3158
+ * - `update`: Update existing rows with source data
3159
+ * - `replace`: Delete and re-insert conflicting rows
3160
+ */
3161
+ export type ConflictStrategy = "fail" | "skip" | "update" | "replace";
3162
+ /**
3163
+ * Options for data transfer operations.
3164
+ *
3165
+ * @example
3166
+ * ```typescript
3167
+ * const options: TransferOptions = {
3168
+ * tables: ['users', 'posts'],
3169
+ * onConflict: 'skip',
3170
+ * batchSize: 5000,
3171
+ * truncateFirst: false,
3172
+ * };
3173
+ * ```
3174
+ */
3175
+ export interface TransferOptions {
3176
+ /** Tables to transfer. Empty = all user tables. */
3177
+ tables?: string[];
3178
+ /** How to handle primary key conflicts. Default: 'fail' */
3179
+ onConflict?: ConflictStrategy;
3180
+ /** Rows per batch for cross-server transfers. Default: 1000 */
3181
+ batchSize?: number;
3182
+ /** Disable foreign key checks during transfer. Default: true */
3183
+ disableForeignKeys?: boolean;
3184
+ /** Preserve identity/auto-increment values. Default: true */
3185
+ preserveIdentity?: boolean;
3186
+ /** Truncate destination tables before transfer. Default: false */
3187
+ truncateFirst?: boolean;
3188
+ /** Validate only, don't execute. Default: false */
3189
+ dryRun?: boolean;
3190
+ /** Export to .dt file instead of DB insert. */
3191
+ exportPath?: string;
3192
+ /** Passphrase for .dtzx export encryption. */
3193
+ passphrase?: string;
3194
+ }
3195
+ /**
3196
+ * Plan for a single table transfer.
3197
+ */
3198
+ export interface TransferTablePlan {
3199
+ /** Table name */
3200
+ name: string;
3201
+ /** Table schema (if applicable) */
3202
+ schema?: string;
3203
+ /** Estimated row count */
3204
+ rowCount: number;
3205
+ /** Whether table has identity/auto-increment column */
3206
+ hasIdentity: boolean;
3207
+ /** Identity column name if present */
3208
+ identityColumn?: string;
3209
+ /** Primary key columns */
3210
+ primaryKey: string[];
3211
+ /** All column names */
3212
+ columns: string[];
3213
+ /** Tables this table depends on (FK references) */
3214
+ dependsOn: string[];
3215
+ /** Column type definitions for cross-dialect transfers. */
3216
+ columnTypes?: DtColumn[];
3217
+ }
3218
+ /**
3219
+ * Complete transfer plan.
3220
+ *
3221
+ * Contains ordered list of tables and transfer metadata.
3222
+ */
3223
+ export interface TransferPlan {
3224
+ /** Tables in dependency order (parents before children) */
3225
+ tables: TransferTablePlan[];
3226
+ /** Whether source and destination are on same server */
3227
+ sameServer: boolean;
3228
+ /** Total estimated rows across all tables */
3229
+ estimatedRows: number;
3230
+ /** Warnings about potential issues */
3231
+ warnings: string[];
3232
+ /** Whether this is a cross-dialect transfer. */
3233
+ crossDialect: boolean;
3234
+ /** Source database dialect. */
3235
+ sourceDialect: Dialect;
3236
+ /** Destination database dialect. */
3237
+ destinationDialect: Dialect;
3238
+ }
3239
+ /**
3240
+ * Result for a single table transfer.
3241
+ */
3242
+ export interface TransferTableResult {
3243
+ /** Table name */
3244
+ table: string;
3245
+ /** Transfer status */
3246
+ status: "success" | "skipped" | "failed";
3247
+ /** Rows successfully transferred */
3248
+ rowsTransferred: number;
3249
+ /** Rows skipped due to conflicts */
3250
+ rowsSkipped: number;
3251
+ /** Duration in milliseconds */
3252
+ durationMs: number;
3253
+ /** Error message if failed */
3254
+ error?: string;
3255
+ }
3256
+ /**
3257
+ * Complete transfer result.
3258
+ */
3259
+ export interface TransferResult {
3260
+ /** Overall status */
3261
+ status: "success" | "partial" | "failed";
3262
+ /** Results per table */
3263
+ tables: TransferTableResult[];
3264
+ /** Total rows transferred */
3265
+ totalRows: number;
3266
+ /** Total duration in milliseconds */
3267
+ durationMs: number;
3268
+ }
3269
+ export declare class TransferNamespace {
3270
+ #private;
3271
+ constructor(state: ContextState);
1618
3272
  /**
1619
- * The current state of the queue
3273
+ * Transfer data to a destination database.
3274
+ *
3275
+ * @example
3276
+ * ```typescript
3277
+ * const [result, err] = await ctx.noorm.transfer.to(destConfig, {
3278
+ * tables: ['users', 'posts'],
3279
+ * onConflict: 'skip',
3280
+ * })
3281
+ * ```
1620
3282
  */
1621
- get state(): QueueState;
3283
+ to(destConfig: Config, options?: TransferOptions): Promise<[
3284
+ TransferResult | null,
3285
+ Error | null
3286
+ ]>;
1622
3287
  /**
1623
- * The number of items in the queue
3288
+ * Generate a transfer plan without executing.
3289
+ *
3290
+ * @example
3291
+ * ```typescript
3292
+ * const [plan, err] = await ctx.noorm.transfer.plan(destConfig)
3293
+ * ```
1624
3294
  */
1625
- get pending(): number;
3295
+ plan(destConfig: Config, options?: TransferOptions): Promise<[
3296
+ TransferPlan | null,
3297
+ Error | null
3298
+ ]>;
3299
+ }
3300
+ export declare class DtNamespace {
3301
+ #private;
3302
+ constructor(state: ContextState);
1626
3303
  /**
1627
- * The stats of the queue
3304
+ * Export a table to a .dt file.
3305
+ *
3306
+ * @example
3307
+ * ```typescript
3308
+ * const [result, err] = await ctx.noorm.dt.exportTable('users', './exports/users.dtz')
3309
+ * ```
1628
3310
  */
1629
- get stats(): {
1630
- processed: number;
1631
- processing: number;
1632
- avgProcessingTime: number;
1633
- success: number;
1634
- error: number;
1635
- rejected: number;
1636
- };
1637
- get snapshot(): {
1638
- name: string;
1639
- state: QueueState;
1640
- pending: number;
1641
- stats: {
1642
- processed: number;
1643
- processing: number;
1644
- avgProcessingTime: number;
1645
- success: number;
1646
- error: number;
1647
- rejected: number;
1648
- };
1649
- rateLimiter: {
1650
- currentTokens: number;
1651
- capacity: number;
1652
- refillIntervalMs: number;
1653
- totalRequests: number;
1654
- rejectedRequests: number;
1655
- successfulRequests: number;
1656
- rejectionRate: number;
1657
- totalWaitTime: number;
1658
- waitCount: number;
1659
- averageWaitTime: number;
1660
- uptime: number;
1661
- createdAt: number;
1662
- };
1663
- activeRunners: number;
1664
- runningNodes: Set<`${number}-${number}`>;
1665
- isIdle: boolean;
1666
- isWaiting: boolean;
1667
- isRunning: boolean;
1668
- isPaused: boolean;
1669
- isStopped: boolean;
1670
- isDraining: boolean;
1671
- isEmpty: boolean;
1672
- };
3311
+ exportTable(tableName: string, filepath: string, options?: ExportOptions): Promise<[
3312
+ {
3313
+ rowsWritten: number;
3314
+ bytesWritten: number;
3315
+ } | null,
3316
+ Error | null
3317
+ ]>;
3318
+ /**
3319
+ * Import a .dt file into the connected database.
3320
+ *
3321
+ * @example
3322
+ * ```typescript
3323
+ * const [result, err] = await ctx.noorm.dt.importFile('./exports/users.dtz', {
3324
+ * onConflict: 'skip',
3325
+ * })
3326
+ * ```
3327
+ */
3328
+ importFile(filepath: string, options?: ImportOptions): Promise<[
3329
+ {
3330
+ rowsImported: number;
3331
+ rowsSkipped: number;
3332
+ } | null,
3333
+ Error | null
3334
+ ]>;
3335
+ }
3336
+ export declare class UtilsNamespace {
3337
+ #private;
3338
+ constructor(state: ContextState);
3339
+ /**
3340
+ * Compute SHA-256 checksum for a file.
3341
+ *
3342
+ * @example
3343
+ * ```typescript
3344
+ * const checksum = await ctx.noorm.utils.checksum('sql/001_users.sql')
3345
+ * ```
3346
+ */
3347
+ checksum(filepath: string): Promise<string>;
3348
+ /**
3349
+ * Tests if the connection can be established.
3350
+ *
3351
+ * @example
3352
+ * ```typescript
3353
+ * const result = await ctx.noorm.utils.testConnection()
3354
+ * ```
3355
+ */
3356
+ testConnection(): Promise<{
3357
+ ok: boolean;
3358
+ error?: string;
3359
+ }>;
1673
3360
  }
1674
3361
  /**
1675
3362
  * Shutdown phases in order of execution.
@@ -1689,6 +3376,292 @@ export type ShutdownReason = "signal" | "user" | "error" | "programmatic";
1689
3376
  * Application mode.
1690
3377
  */
1691
3378
  export type AppMode = "tui" | "headless";
3379
+ /**
3380
+ * Signal types we handle.
3381
+ */
3382
+ export type Signal = "SIGINT" | "SIGTERM" | "SIGHUP";
3383
+ /**
3384
+ * Type definitions for auto-update feature.
3385
+ *
3386
+ * Includes global settings structure and update check/result types.
3387
+ */
3388
+ /**
3389
+ * Dismissable alert preference.
3390
+ *
3391
+ * Controls how alerts are handled after user makes a choice:
3392
+ * - 'ask': Show the alert and ask user each time (default)
3393
+ * - 'always': Always perform the action without asking
3394
+ * - 'never': Never perform the action, auto-deny
3395
+ */
3396
+ export type DismissablePreference = "ask" | "always" | "never";
3397
+ /**
3398
+ * Global settings stored in ~/.noorm/settings.yml
3399
+ *
3400
+ * These are user-level settings, separate from project settings.
3401
+ * Used for preferences that apply across all projects.
3402
+ */
3403
+ export interface GlobalSettings {
3404
+ /** Check for updates on TUI launch (default: true) */
3405
+ checkUpdates: boolean;
3406
+ /** Auto-install updates without prompting (default: false) */
3407
+ autoUpdate: boolean;
3408
+ /** Last check timestamp (ISO string) */
3409
+ lastCheck?: string;
3410
+ /** Cached latest version from last check */
3411
+ cachedVersion?: string;
3412
+ /**
3413
+ * Dismissable alert preferences.
3414
+ *
3415
+ * Stores user decisions for alerts that can be permanently dismissed.
3416
+ * Keys are alert identifiers (e.g., 'majorVersionUpdate').
3417
+ */
3418
+ dismissable?: Record<string, DismissablePreference>;
3419
+ }
3420
+ /**
3421
+ * Events emitted by the update module.
3422
+ *
3423
+ * Used to integrate with the observer event system.
3424
+ */
3425
+ export interface UpdateEvents {
3426
+ "update:checking": Record<string, never>;
3427
+ "update:available": {
3428
+ currentVersion: string;
3429
+ latestVersion: string;
3430
+ };
3431
+ "update:major-available": {
3432
+ currentVersion: string;
3433
+ latestVersion: string;
3434
+ };
3435
+ "update:not-available": {
3436
+ currentVersion: string;
3437
+ };
3438
+ "update:check-failed": {
3439
+ error: string;
3440
+ };
3441
+ "update:installing": {
3442
+ version: string;
3443
+ };
3444
+ "update:complete": {
3445
+ previousVersion: string;
3446
+ newVersion: string;
3447
+ };
3448
+ "update:failed": {
3449
+ version: string;
3450
+ error: string;
3451
+ };
3452
+ "update:skipped": {
3453
+ version: string;
3454
+ reason: "user-denied" | "user-deferred";
3455
+ };
3456
+ "global-settings:loaded": {
3457
+ settings: GlobalSettings;
3458
+ };
3459
+ "global-settings:updated": {
3460
+ key: string;
3461
+ value: unknown;
3462
+ };
3463
+ }
3464
+ /**
3465
+ * Transfer event types for observer integration.
3466
+ *
3467
+ * Events emitted during data transfer operations for CLI progress display.
3468
+ */
3469
+ /**
3470
+ * Events emitted by the transfer module.
3471
+ *
3472
+ * Subscribe to these events to track transfer progress in the CLI.
3473
+ */
3474
+ export interface TransferEvents {
3475
+ /** Emitted when transfer planning starts */
3476
+ "transfer:planning": {
3477
+ source: string;
3478
+ destination: string;
3479
+ };
3480
+ /** Emitted when transfer plan is ready */
3481
+ "transfer:plan:ready": {
3482
+ sameServer: boolean;
3483
+ tableCount: number;
3484
+ estimatedRows: number;
3485
+ warnings: string[];
3486
+ };
3487
+ /** Emitted when transfer execution starts */
3488
+ "transfer:starting": {
3489
+ tableCount: number;
3490
+ sameServer: boolean;
3491
+ };
3492
+ /** Emitted before transferring each table */
3493
+ "transfer:table:before": {
3494
+ table: string;
3495
+ index: number;
3496
+ total: number;
3497
+ rowCount: number;
3498
+ };
3499
+ /** Emitted during table transfer with progress */
3500
+ "transfer:table:progress": {
3501
+ table: string;
3502
+ rowsTransferred: number;
3503
+ rowsTotal: number;
3504
+ rowsSkipped: number;
3505
+ };
3506
+ /** Emitted after transferring each table */
3507
+ "transfer:table:after": {
3508
+ table: string;
3509
+ status: "success" | "skipped" | "failed";
3510
+ rowsTransferred: number;
3511
+ rowsSkipped: number;
3512
+ durationMs: number;
3513
+ error?: string;
3514
+ };
3515
+ /** Emitted when transfer completes */
3516
+ "transfer:complete": {
3517
+ status: "success" | "partial" | "failed";
3518
+ totalRows: number;
3519
+ tableCount: number;
3520
+ durationMs: number;
3521
+ };
3522
+ }
3523
+ /**
3524
+ * Events emitted by the dt module.
3525
+ *
3526
+ * Subscribe to these events to track .dt operation progress in the CLI.
3527
+ */
3528
+ export interface DtEvents {
3529
+ /** Export begins for a table. */
3530
+ "dt:export:start": {
3531
+ filepath: string;
3532
+ table: string;
3533
+ columnCount: number;
3534
+ };
3535
+ /** After each batch flush during export. */
3536
+ "dt:export:progress": {
3537
+ filepath: string;
3538
+ table: string;
3539
+ rowsWritten: number;
3540
+ bytesWritten: number;
3541
+ };
3542
+ /** Export finished for a table. */
3543
+ "dt:export:complete": {
3544
+ filepath: string;
3545
+ table: string;
3546
+ rowsWritten: number;
3547
+ bytesWritten: number;
3548
+ durationMs: number;
3549
+ };
3550
+ /** Import begins for a file. */
3551
+ "dt:import:start": {
3552
+ filepath: string;
3553
+ sourceDialect: string;
3554
+ sourceVersion: string;
3555
+ table: string;
3556
+ };
3557
+ /** Schema parsed and validated during import. */
3558
+ "dt:import:schema": {
3559
+ filepath: string;
3560
+ table: string;
3561
+ columns: number;
3562
+ validation: SchemaValidationResult;
3563
+ };
3564
+ /** After each batch insert during import. */
3565
+ "dt:import:progress": {
3566
+ filepath: string;
3567
+ table: string;
3568
+ rowsImported: number;
3569
+ rowsSkipped: number;
3570
+ };
3571
+ /** Import finished for a file. */
3572
+ "dt:import:complete": {
3573
+ filepath: string;
3574
+ table: string;
3575
+ rowsImported: number;
3576
+ rowsSkipped: number;
3577
+ durationMs: number;
3578
+ };
3579
+ /** Cross-dialect stream begins. */
3580
+ "dt:stream:start": {
3581
+ table: string;
3582
+ sourceDialect: string;
3583
+ targetDialect: string;
3584
+ };
3585
+ /** After each batch conversion during streaming. */
3586
+ "dt:stream:progress": {
3587
+ table: string;
3588
+ rowsConverted: number;
3589
+ };
3590
+ /** Cross-dialect stream finished. */
3591
+ "dt:stream:complete": {
3592
+ table: string;
3593
+ rowsConverted: number;
3594
+ durationMs: number;
3595
+ };
3596
+ /** Schema validation completed. */
3597
+ "dt:validate:result": {
3598
+ table: string;
3599
+ valid: boolean;
3600
+ errors: string[];
3601
+ warnings: string[];
3602
+ };
3603
+ /** A batch of rows arrived from the Connection Worker. */
3604
+ "dt:export:loaded": {
3605
+ table: string;
3606
+ loaded: number;
3607
+ totalRows: number;
3608
+ };
3609
+ /** A compute worker finished serializing a row. */
3610
+ "dt:export:processed": {
3611
+ table: string;
3612
+ processed: number;
3613
+ totalRows: number;
3614
+ };
3615
+ /** Order buffer flushed consecutive rows to DtWriter. */
3616
+ "dt:export:saved": {
3617
+ table: string;
3618
+ saved: number;
3619
+ totalRows: number;
3620
+ };
3621
+ /** A batch of lines read from file by DtReader. */
3622
+ "dt:import:loaded": {
3623
+ table: string;
3624
+ loaded: number;
3625
+ totalRows: number;
3626
+ };
3627
+ /** A compute worker finished deserializing a row. */
3628
+ "dt:import:processed": {
3629
+ table: string;
3630
+ processed: number;
3631
+ totalRows: number;
3632
+ };
3633
+ /** Deserialized rows inserted into the database. */
3634
+ "dt:import:saved": {
3635
+ table: string;
3636
+ saved: number;
3637
+ totalRows: number;
3638
+ };
3639
+ /** Modify operation begins. */
3640
+ "dt:modify:start": {
3641
+ inputPath: string;
3642
+ outputPath: string;
3643
+ recipeLength: number;
3644
+ };
3645
+ /** After each row processed during modify. */
3646
+ "dt:modify:progress": {
3647
+ rowsRead: number;
3648
+ rowsWritten: number;
3649
+ rowsFiltered: number;
3650
+ };
3651
+ /** Modify operation finished. */
3652
+ "dt:modify:complete": {
3653
+ result: {
3654
+ rowsRead: number;
3655
+ rowsWritten: number;
3656
+ rowsFiltered: number;
3657
+ columnsDropped: number;
3658
+ columnsAdded: number;
3659
+ columnsRenamed: number;
3660
+ outputPath: string;
3661
+ durationMs: number;
3662
+ };
3663
+ };
3664
+ }
1692
3665
  /**
1693
3666
  * All events emitted by noorm core modules.
1694
3667
  *
@@ -1706,9 +3679,17 @@ export type AppMode = "tui" | "headless";
1706
3679
  * - `identity:*` - Identity resolution
1707
3680
  * - `connection:*` - Database connections
1708
3681
  * - `settings:*` - Settings lifecycle and mutations
3682
+ * - `vault:*` - Vault operations
3683
+ * - `transfer:*` - Data transfer operations
3684
+ * - `dt:*` - .dt format export/import/stream operations
3685
+ * - `version:*` - Version checking and migration
3686
+ * - `logger:*` - Logger lifecycle
3687
+ * - `teardown:*` - Database teardown/truncate operations
3688
+ * - `sql-terminal:*` - SQL terminal query execution
3689
+ * - `init:*` - Project initialization
1709
3690
  * - `error` - Catch-all errors
1710
3691
  */
1711
- export interface NoormEvents extends SettingsEvents {
3692
+ export interface NoormEvents extends SettingsEvents, UpdateEvents, VaultEvents, TransferEvents, DtEvents {
1712
3693
  "file:before": {
1713
3694
  filepath: string;
1714
3695
  checksum: string;
@@ -1726,7 +3707,9 @@ export interface NoormEvents extends SettingsEvents {
1726
3707
  };
1727
3708
  "file:dry-run": {
1728
3709
  filepath: string;
1729
- outputPath: string;
3710
+ status: "success" | "failed";
3711
+ outputPath?: string;
3712
+ error?: string;
1730
3713
  };
1731
3714
  "change:created": {
1732
3715
  name: string;
@@ -1763,6 +3746,7 @@ export interface NoormEvents extends SettingsEvents {
1763
3746
  filesSkipped: number;
1764
3747
  filesFailed: number;
1765
3748
  durationMs: number;
3749
+ error?: string;
1766
3750
  };
1767
3751
  "run:file": {
1768
3752
  filepath: string;
@@ -1773,6 +3757,10 @@ export interface NoormEvents extends SettingsEvents {
1773
3757
  fileCount: number;
1774
3758
  configName: string;
1775
3759
  };
3760
+ "run:files": {
3761
+ fileCount: number;
3762
+ configName: string;
3763
+ };
1776
3764
  "lock:acquiring": {
1777
3765
  configName: string;
1778
3766
  identity: string;
@@ -1860,6 +3848,25 @@ export interface NoormEvents extends SettingsEvents {
1860
3848
  configName: string;
1861
3849
  tables: string[];
1862
3850
  };
3851
+ "db:dual:connecting": {
3852
+ source: string;
3853
+ destination: string;
3854
+ };
3855
+ "db:dual:connected": {
3856
+ source: string;
3857
+ destination: string;
3858
+ };
3859
+ "db:dual:disconnecting": {
3860
+ source: string;
3861
+ destination: string;
3862
+ };
3863
+ "db:dual:disconnected": {
3864
+ source: string;
3865
+ destination: string;
3866
+ };
3867
+ "db:dual:cleanup-warning": {
3868
+ errors: string[];
3869
+ };
1863
3870
  "template:render": {
1864
3871
  filepath: string;
1865
3872
  durationMs: number;
@@ -1893,6 +3900,7 @@ export interface NoormEvents extends SettingsEvents {
1893
3900
  registered: boolean;
1894
3901
  knownUsersCount: number;
1895
3902
  };
3903
+ "identity:not-found": void;
1896
3904
  "connection:open": {
1897
3905
  configName: string;
1898
3906
  dialect: string;
@@ -1928,6 +3936,13 @@ export interface NoormEvents extends SettingsEvents {
1928
3936
  error: Error;
1929
3937
  type?: "exception" | "rejection";
1930
3938
  };
3939
+ "app:signal": {
3940
+ signal: Signal;
3941
+ };
3942
+ "app:exception": {
3943
+ error: Error;
3944
+ type: "exception" | "rejection";
3945
+ };
1931
3946
  "router:navigated": {
1932
3947
  from: string;
1933
3948
  to: string;
@@ -1937,6 +3952,84 @@ export interface NoormEvents extends SettingsEvents {
1937
3952
  popped: string;
1938
3953
  to: string;
1939
3954
  };
3955
+ "init:complete": {
3956
+ projectRoot: string;
3957
+ hasIdentity: boolean;
3958
+ };
3959
+ "version:schema:checking": {
3960
+ current: number;
3961
+ };
3962
+ "version:schema:migrating": {
3963
+ from: number;
3964
+ to: number;
3965
+ };
3966
+ "version:schema:migrated": {
3967
+ from: number;
3968
+ to: number;
3969
+ durationMs: number;
3970
+ };
3971
+ "version:state:migrating": {
3972
+ from: number;
3973
+ to: number;
3974
+ };
3975
+ "version:state:migrated": {
3976
+ from: number;
3977
+ to: number;
3978
+ };
3979
+ "version:settings:migrating": {
3980
+ from: number;
3981
+ to: number;
3982
+ };
3983
+ "version:settings:migrated": {
3984
+ from: number;
3985
+ to: number;
3986
+ };
3987
+ "version:mismatch": {
3988
+ layer: "schema" | "state" | "settings";
3989
+ current: number;
3990
+ expected: number;
3991
+ };
3992
+ "logger:started": {
3993
+ file: string;
3994
+ level: LogLevel;
3995
+ };
3996
+ "logger:rotated": {
3997
+ oldFile: string;
3998
+ newFile: string;
3999
+ };
4000
+ "teardown:start": {
4001
+ type: "truncate" | "schema";
4002
+ };
4003
+ "teardown:progress": {
4004
+ category: string;
4005
+ object: string | null;
4006
+ action: string;
4007
+ };
4008
+ "teardown:error": {
4009
+ error: Error;
4010
+ object: string | null;
4011
+ };
4012
+ "teardown:complete": {
4013
+ result: TruncateResult | TeardownResult;
4014
+ };
4015
+ "sql-terminal:execute:before": {
4016
+ query: string;
4017
+ configName: string;
4018
+ };
4019
+ "sql-terminal:execute:after": {
4020
+ query: string;
4021
+ configName: string;
4022
+ success: true;
4023
+ durationMs: number;
4024
+ rowCount: number;
4025
+ rowsAffected: number | undefined;
4026
+ } | {
4027
+ query: string;
4028
+ configName: string;
4029
+ success: false;
4030
+ durationMs: number;
4031
+ error: string;
4032
+ };
1940
4033
  error: {
1941
4034
  source: string;
1942
4035
  error: Error;
@@ -1944,67 +4037,290 @@ export interface NoormEvents extends SettingsEvents {
1944
4037
  };
1945
4038
  }
1946
4039
  export type NoormEventNames = Events<NoormEvents>;
4040
+ /**
4041
+ * Noorm-specific operations accessed via ctx.noorm.
4042
+ *
4043
+ * Domain-aligned sub-namespaces mirror the TUI home screen:
4044
+ * changes, run, db, lock, vault, secrets, templates, transfer, dt, utils.
4045
+ *
4046
+ * @example
4047
+ * ```typescript
4048
+ * const ctx = await createContext({ config: 'dev' })
4049
+ * await ctx.connect()
4050
+ *
4051
+ * await ctx.noorm.run.build()
4052
+ * await ctx.noorm.changes.ff()
4053
+ * const tables = await ctx.noorm.db.listTables()
4054
+ * ```
4055
+ */
4056
+ export declare class NoormOps {
4057
+ #private;
4058
+ constructor(state: ContextState);
4059
+ get config(): Config;
4060
+ get settings(): Settings;
4061
+ get identity(): Identity;
4062
+ get observer(): ObserverEngine<NoormEvents>;
4063
+ get changes(): ChangesNamespace;
4064
+ get run(): RunNamespace;
4065
+ get db(): DbNamespace;
4066
+ get lock(): LockNamespace;
4067
+ get vault(): VaultNamespace;
4068
+ get secrets(): SecretsNamespace;
4069
+ get templates(): TemplatesNamespace;
4070
+ get transfer(): TransferNamespace;
4071
+ get dt(): DtNamespace;
4072
+ get utils(): UtilsNamespace;
4073
+ }
4074
+ /**
4075
+ * Scoped query interface bound to a dedicated impersonated connection.
4076
+ *
4077
+ * All queries route through a single pool connection running under
4078
+ * the impersonated principal. Mirrors Context's query surface without
4079
+ * lifecycle or noorm ops.
4080
+ */
4081
+ export interface ImpersonatedScope<DB = unknown, Procs = object, Funcs = object, Tvfs = object> {
4082
+ kysely: Kysely<DB>;
4083
+ proc: <N extends keyof Procs & string = keyof Procs & string, T = ExtractReturn<Procs[N]>>(name: N, ...args: ExtractArgs<Procs[N]> extends void ? [
4084
+ ] : [
4085
+ params: ExtractArgs<Procs[N]>
4086
+ ]) => Promise<T[]>;
4087
+ func: <N extends keyof Funcs & string = keyof Funcs & string, T = ExtractReturn<Funcs[N]>>(name: N, ...args: ExtractArgs<Funcs[N]> extends void ? [
4088
+ column: string
4089
+ ] : [
4090
+ params: ExtractArgs<Funcs[N]>,
4091
+ column: string
4092
+ ]) => Promise<T>;
4093
+ tvf: <N extends keyof Tvfs & string = keyof Tvfs & string, T = ExtractReturn<Tvfs[N]>>(name: N, ...args: ExtractArgs<Tvfs[N]> extends void ? [
4094
+ ] : [
4095
+ params: ExtractArgs<Tvfs[N]>
4096
+ ]) => Promise<T[]>;
4097
+ transaction: <T>(fn: (trx: Transaction<DB>) => Promise<T>) => Promise<T>;
4098
+ revert: () => Promise<void>;
4099
+ }
4100
+ /**
4101
+ * Thrown when impersonation fails.
4102
+ *
4103
+ * Covers unsupported dialects and invalid usernames.
4104
+ */
4105
+ export declare class ImpersonationError extends Error {
4106
+ constructor(message: string);
4107
+ }
1947
4108
  /**
1948
4109
  * SDK Context implementation.
1949
4110
  *
1950
- * Provides programmatic access to all noorm operations.
4111
+ * Provides SQL-focused programmatic access to the database.
4112
+ * Noorm management operations are accessible via ctx.noorm.
1951
4113
  *
1952
4114
  * @example
1953
4115
  * ```typescript
1954
4116
  * const ctx = await createContext({ config: 'dev' })
1955
4117
  * await ctx.connect()
1956
4118
  *
1957
- * // Run queries
1958
- * const users = await ctx.query('SELECT * FROM users')
4119
+ * // Top-level — SQL focused
4120
+ * const users = await ctx.kysely
4121
+ * .selectFrom('users')
4122
+ * .selectAll()
4123
+ * .execute()
4124
+ *
4125
+ * // Noorm operations — under namespace
4126
+ * await ctx.noorm.run.build()
4127
+ * await ctx.noorm.changes.ff()
1959
4128
  *
1960
- * // Clean disconnect
1961
4129
  * await ctx.disconnect()
1962
4130
  * ```
1963
4131
  */
1964
- export declare class Context<DB = unknown> {
4132
+ export declare class Context<DB = unknown, Procs = object, Funcs = object, Tvfs = object> {
1965
4133
  #private;
1966
4134
  constructor(config: Config, settings: Settings, identity: Identity, options: CreateContextOptions, projectRoot: string);
1967
- get config(): Config;
1968
- get settings(): Settings;
1969
- get identity(): Identity;
1970
4135
  get dialect(): Dialect;
1971
4136
  get connected(): boolean;
1972
- get observer(): ObserverEngine<NoormEvents>;
1973
4137
  get kysely(): Kysely<DB>;
4138
+ /**
4139
+ * Noorm management operations.
4140
+ *
4141
+ * Lazily instantiated on first access. Returns the same instance
4142
+ * on repeated access (singleton per Context).
4143
+ *
4144
+ * @example
4145
+ * ```typescript
4146
+ * await ctx.noorm.run.build()
4147
+ * await ctx.noorm.changes.ff()
4148
+ * const tables = await ctx.noorm.db.listTables()
4149
+ * ```
4150
+ */
4151
+ get noorm(): NoormOps;
1974
4152
  connect(): Promise<void>;
1975
4153
  disconnect(): Promise<void>;
1976
- query<T = Record<string, unknown>>(sqlStr: string, _params?: unknown[]): Promise<T[]>;
1977
- execute(sqlStr: string, _params?: unknown[]): Promise<ExecuteResult>;
1978
- transaction<T>(fn: (tx: TransactionContext) => Promise<T>): Promise<T>;
1979
- listTables(): Promise<TableSummary[]>;
1980
- describeTable(name: string, schema?: string): Promise<TableDetail | null>;
1981
- overview(): Promise<ExploreOverview>;
1982
- truncate(): Promise<TruncateResult>;
1983
- teardown(): Promise<TeardownResult>;
1984
- build(options?: BuildOptions): Promise<BatchResult>;
1985
- reset(): Promise<void>;
1986
- runFile(filepath: string, options?: RunOptions): Promise<FileResult>;
1987
- runFiles(filepaths: string[], options?: RunOptions): Promise<BatchResult>;
1988
- runDir(dirpath: string, options?: RunOptions): Promise<BatchResult>;
1989
- applyChange(name: string, options?: ChangeOptions): Promise<ChangeResult>;
1990
- revertChange(name: string, options?: ChangeOptions): Promise<ChangeResult>;
1991
- fastForward(): Promise<BatchChangeResult>;
1992
- getChangeStatus(): Promise<ChangeListItem[]>;
1993
- getPendingChanges(): Promise<ChangeListItem[]>;
1994
- getSecret(key: string): string | undefined;
1995
- acquireLock(options?: LockOptions$1): Promise<Lock$1>;
1996
- releaseLock(): Promise<void>;
1997
- getLockStatus(): Promise<LockStatus>;
1998
- withLock<T>(fn: () => Promise<T>, options?: LockOptions$1): Promise<T>;
1999
- forceReleaseLock(): Promise<boolean>;
2000
- renderTemplate(filepath: string): Promise<ProcessResult>;
2001
- getHistory(limit?: number): Promise<ChangeHistoryRecord[]>;
2002
- computeChecksum(filepath: string): Promise<string>;
2003
- testConnection(): Promise<{
2004
- ok: boolean;
2005
- error?: string;
2006
- }>;
4154
+ /**
4155
+ * Execute operations within a database transaction.
4156
+ *
4157
+ * The callback receives a full Kysely Transaction instance with
4158
+ * query builder, `sql` template literal, and all Kysely features.
4159
+ *
4160
+ * @example
4161
+ * ```typescript
4162
+ * await ctx.transaction(async (trx) => {
4163
+ * await trx
4164
+ * .updateTable('accounts')
4165
+ * .set({ balance: sql`balance - ${amount}` })
4166
+ * .where('id', '=', fromId)
4167
+ * .execute();
4168
+ * await trx
4169
+ * .updateTable('accounts')
4170
+ * .set({ balance: sql`balance + ${amount}` })
4171
+ * .where('id', '=', toId)
4172
+ * .execute();
4173
+ * });
4174
+ * ```
4175
+ */
4176
+ transaction<T>(fn: (trx: Transaction<DB>) => Promise<T>): Promise<T>;
4177
+ /**
4178
+ * Call a stored procedure and return the result set.
4179
+ *
4180
+ * Generates dialect-specific SQL: EXEC (MSSQL), CALL (PG/MySQL).
4181
+ * Named params use dialect-appropriate syntax; MySQL falls back
4182
+ * to positional. SQLite throws — no procedure support.
4183
+ *
4184
+ * @example
4185
+ * ```typescript
4186
+ * // Return type inferred from tuple definition
4187
+ * const users = await ctx.proc('get_users', { department_id: 1 });
4188
+ *
4189
+ * // Explicit return type override
4190
+ * const users = await ctx.proc<'get_users', SpecialUser>('get_users', { department_id: 1 });
4191
+ *
4192
+ * // Positional params
4193
+ * await ctx.proc('simple_proc', [42, 'hello']);
4194
+ *
4195
+ * // No params
4196
+ * await ctx.proc('refresh_cache');
4197
+ * ```
4198
+ */
4199
+ proc<N extends keyof Procs & string = keyof Procs & string, T = ExtractReturn<Procs[N]>>(name: N, ...args: ExtractArgs<Procs[N]> extends void ? [
4200
+ ] : [
4201
+ params: ExtractArgs<Procs[N]>
4202
+ ]): Promise<T[]>;
4203
+ /**
4204
+ * Call a database function and return the scalar result.
4205
+ *
4206
+ * Generates SELECT name(...) AS column. Named params only on PG;
4207
+ * other dialects fall back to positional. SQLite throws.
4208
+ *
4209
+ * @example
4210
+ * ```typescript
4211
+ * // Return type inferred from tuple definition
4212
+ * const result = await ctx.func('calc_total', { order_id: 42 }, 'total');
4213
+ *
4214
+ * // Explicit return type override
4215
+ * const result = await ctx.func<'calc_total', { total: number }>('calc_total', { order_id: 42 }, 'total');
4216
+ *
4217
+ * // No params — just column alias
4218
+ * const ver = await ctx.func('get_version', 'v');
4219
+ * ```
4220
+ */
4221
+ func<N extends keyof Funcs & string = keyof Funcs & string, T = ExtractReturn<Funcs[N]>>(name: N, ...args: ExtractArgs<Funcs[N]> extends void ? [
4222
+ column: string
4223
+ ] : [
4224
+ params: ExtractArgs<Funcs[N]>,
4225
+ column: string
4226
+ ]): Promise<T>;
4227
+ /**
4228
+ * Call a table-valued function and return the result set.
4229
+ *
4230
+ * Generates SELECT * FROM name(...) with dialect-specific parameter
4231
+ * syntax. Returns multiple rows, unlike func() which returns a scalar.
4232
+ * Only supported on MSSQL and PostgreSQL — MySQL and SQLite throw.
4233
+ *
4234
+ * @example
4235
+ * ```typescript
4236
+ * // Return type inferred from tuple definition
4237
+ * const sessions = await ctx.tvf('validate_session', { session_key: key });
4238
+ *
4239
+ * // Explicit return type override
4240
+ * const sessions = await ctx.tvf<'validate_session', SpecialSession>('validate_session', { session_key: key });
4241
+ *
4242
+ * // No params
4243
+ * const items = await ctx.tvf('get_active_items');
4244
+ * ```
4245
+ */
4246
+ tvf<N extends keyof Tvfs & string = keyof Tvfs & string, T = ExtractReturn<Tvfs[N]>>(name: N, ...args: ExtractArgs<Tvfs[N]> extends void ? [
4247
+ ] : [
4248
+ params: ExtractArgs<Tvfs[N]>
4249
+ ]): Promise<T[]>;
4250
+ /**
4251
+ * Execute queries as a specific database principal.
4252
+ *
4253
+ * Borrows a dedicated connection from the pool, switches identity
4254
+ * via dialect-specific SQL, and provides a scoped query interface.
4255
+ * Two modes: callback (auto-reverts) and explicit (caller reverts).
4256
+ *
4257
+ * @example
4258
+ * ```typescript
4259
+ * // Callback mode — auto-reverts, even on throw
4260
+ * const result = await ctx.impersonate('username', async (scope) => {
4261
+ * return scope.kysely.selectFrom('users').selectAll().execute();
4262
+ * });
4263
+ *
4264
+ * // Explicit mode — caller owns lifecycle
4265
+ * const scope = await ctx.impersonate('username');
4266
+ * const users = await scope.kysely.selectFrom('users').selectAll().execute();
4267
+ * await scope.revert();
4268
+ * ```
4269
+ */
4270
+ impersonate<T>(username: string, fn: (scope: ImpersonatedScope<DB, Procs, Funcs, Tvfs>) => Promise<T>): Promise<T>;
4271
+ impersonate(username: string): Promise<ImpersonatedScope<DB, Procs, Funcs, Tvfs>>;
4272
+ }
4273
+ /**
4274
+ * A TVP marker that wraps row data with the required table type name.
4275
+ *
4276
+ * Use the `tvp()` helper to create instances. Column names are
4277
+ * inferred from the object keys of the first row.
4278
+ *
4279
+ * @example
4280
+ * ```typescript
4281
+ * interface CheckoutItem { Type: string; ReferenceNo: number; Qty: number }
4282
+ *
4283
+ * interface MyProcs {
4284
+ * 'Checkout_trx': [{ Party: number; Items: TvpValue<CheckoutItem> }, void];
4285
+ * }
4286
+ * ```
4287
+ */
4288
+ export interface TvpValue<T extends Record<string, unknown> = Record<string, unknown>> {
4289
+ readonly __noorm_tvp: true;
4290
+ readonly typeName: string;
4291
+ readonly rows: ReadonlyArray<T>;
2007
4292
  }
4293
+ /**
4294
+ * Create a Table-Valued Parameter for MSSQL stored procedure calls.
4295
+ *
4296
+ * Wraps an array of row objects as a TVP with the specified SQL Server
4297
+ * table type name. Column names are inferred from the first row's keys.
4298
+ *
4299
+ * @param typeName - SQL Server table type (e.g., 'CheckoutItems' or 'dbo.CheckoutItems')
4300
+ * @param rows - Array of row objects with consistent keys
4301
+ *
4302
+ * @example
4303
+ * ```typescript
4304
+ * interface CheckoutItem { Type: number; ReferenceNo: number; Qty: number }
4305
+ *
4306
+ * // Row type is inferred — returns TvpValue<CheckoutItem>
4307
+ * const items: CheckoutItem[] = [
4308
+ * { Type: 1, ReferenceNo: 100, Qty: 5 },
4309
+ * { Type: 2, ReferenceNo: 200, Qty: 3 },
4310
+ * ];
4311
+ *
4312
+ * // Named params — type-checked against TvpValue<CheckoutItem>
4313
+ * await ctx.proc('Checkout_trx', {
4314
+ * Party: 1,
4315
+ * PaymentMethod: 2,
4316
+ * Items: tvp('CheckoutItems', items),
4317
+ * });
4318
+ *
4319
+ * // Positional params
4320
+ * await ctx.proc('Checkout_trx', [1, 2, tvp('CheckoutItems', items)]);
4321
+ * ```
4322
+ */
4323
+ export declare function tvp<T extends Record<string, unknown>>(typeName: string, rows: T[]): TvpValue<T>;
2008
4324
  /**
2009
4325
  * Error thrown when requireTest is enabled but config.isTest is false.
2010
4326
  *
@@ -2026,8 +4342,8 @@ export declare class RequireTestError extends Error {
2026
4342
  *
2027
4343
  * @example
2028
4344
  * ```typescript
2029
- * // If config.protected is true and allowProtected is false
2030
- * await ctx.truncate() // Throws ProtectedConfigError
4345
+ * // config.protected is true — all destructive ops are blocked
4346
+ * await ctx.noorm.db.truncate() // Throws ProtectedConfigError
2031
4347
  * ```
2032
4348
  */
2033
4349
  export declare class ProtectedConfigError extends Error {
@@ -2061,18 +4377,12 @@ export declare class ProtectedConfigError extends Error {
2061
4377
  * requireTest: true,
2062
4378
  * })
2063
4379
  *
2064
- * // Allow destructive ops on protected config
2065
- * const ctx = await createContext({
2066
- * config: 'staging',
2067
- * allowProtected: true,
2068
- * })
2069
- *
2070
4380
  * // Env-only mode (CI/CD) - no stored config needed
2071
4381
  * // Requires NOORM_CONNECTION_DIALECT and NOORM_CONNECTION_DATABASE
2072
4382
  * const ctx = await createContext()
2073
4383
  * ```
2074
4384
  */
2075
- export declare function createContext<DB = unknown>(options?: CreateContextOptions): Promise<Context<DB>>;
4385
+ export declare function createContext<DB = unknown, Procs = object, Funcs = object, Tvfs = object>(options?: CreateContextOptions): Promise<Context<DB, Procs, Funcs, Tvfs>>;
2076
4386
 
2077
4387
  export {
2078
4388
  Lock$1 as Lock,