@noormdev/sdk 1.0.0-alpha.2 → 1.0.0-alpha.20

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