@noormdev/sdk 1.0.0-alpha.12 → 1.0.0-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4HW4CS2C.js +11 -0
- package/dist/chunk-4HW4CS2C.js.map +1 -0
- package/dist/{chunk-GR57BRV4.js → chunk-BTXUEWYQ.js} +5 -11
- package/dist/chunk-BTXUEWYQ.js.map +1 -0
- package/dist/chunk-ZRZ7AECF.js +268 -0
- package/dist/chunk-ZRZ7AECF.js.map +1 -0
- package/dist/engine-5K7FA66A.js +4 -0
- package/dist/{engine-DGLYELPZ.js.map → engine-5K7FA66A.js.map} +1 -1
- package/dist/index.d.ts +1159 -754
- package/dist/index.js +8925 -7997
- package/dist/index.js.map +1 -1
- package/dist/sqlite-bun-AV7LHRJK.js +58 -0
- package/dist/sqlite-bun-AV7LHRJK.js.map +1 -0
- package/dist/storage-FJP4ED7H.js +4 -0
- package/dist/storage-FJP4ED7H.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-GR57BRV4.js.map +0 -1
- package/dist/engine-DGLYELPZ.js +0 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ObserverEngine } from '@logosdx/observer';
|
|
2
|
+
|
|
3
|
+
// src/core/observer.ts
|
|
4
|
+
var observer = new ObserverEngine({
|
|
5
|
+
name: "noorm",
|
|
6
|
+
spy: process.env["NOORM_DEBUG"] ? (action) => console.error(`[noorm:${action.fn}] ${String(action.event)}`) : void 0
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export { observer };
|
|
10
|
+
//# sourceMappingURL=chunk-4HW4CS2C.js.map
|
|
11
|
+
//# sourceMappingURL=chunk-4HW4CS2C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/observer.ts"],"names":[],"mappings":";;;AAoMO,IAAM,QAAA,GAAW,IAAI,cAAA,CAA4B;AAAA,EACpD,IAAA,EAAM,OAAA;AAAA,EACN,KAAK,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAA,GACxB,CAAC,WAAW,OAAA,CAAQ,KAAA,CAAM,CAAA,OAAA,EAAU,MAAA,CAAO,EAAE,CAAA,EAAA,EAAK,MAAA,CAAO,OAAO,KAAK,CAAC,EAAE,CAAA,GACxE;AACV,CAAC","file":"chunk-4HW4CS2C.js","sourcesContent":["/**\n * Central event system for noorm.\n *\n * Core modules emit events, CLI subscribes. This creates a clean separation\n * between business logic and UI concerns.\n *\n * @example\n * ```typescript\n * // In core module - emit events at key points\n * observer.emit('file:before', { filepath, checksum, configName })\n *\n * // In CLI - subscribe to events\n * const cleanup = observer.on('file:after', (data) => updateProgress(data))\n *\n * // Pattern matching for multiple events\n * observer.on(/^file:/, ({ event, data }) => logFileEvent(event, data))\n * ```\n */\nimport { ObserverEngine, type Events } from '@logosdx/observer';\n\nimport type { SettingsEvents } from './settings/index.js';\nimport type { AppMode, ShutdownReason, ShutdownPhase, PhaseStatus } from './lifecycle/types.js';\nimport type { UpdateEvents } from './update/types.js';\nimport type { VaultEvents } from './vault/events.js';\nimport type { TransferEvents } from './transfer/events.js';\nimport type { DtEvents } from './dt/events.js';\n\n/**\n * All events emitted by noorm core modules.\n *\n * Events are namespaced by module:\n * - `file:*` - Individual SQL file execution\n * - `build:*` - Schema build operations\n * - `run:*` - Ad-hoc file/dir execution\n * - `change:*` - Change execution\n * - `lock:*` - Lock acquisition/release\n * - `state:*` - State load/persist\n * - `config:*` - Config CRUD\n * - `secret:*` - Secret CRUD\n * - `db:*` - Database lifecycle\n * - `template:*` - Template rendering\n * - `identity:*` - Identity resolution\n * - `connection:*` - Database connections\n * - `settings:*` - Settings lifecycle and mutations\n * - `vault:*` - Vault operations\n * - `transfer:*` - Data transfer operations\n * - `dt:*` - .dt format export/import/stream operations\n * - `error` - Catch-all errors\n */\nexport interface NoormEvents extends SettingsEvents, UpdateEvents, VaultEvents, TransferEvents, DtEvents {\n // File execution\n 'file:before': { filepath: string; checksum: string; configName: string };\n 'file:after': {\n filepath: string;\n status: 'success' | 'failed';\n durationMs: number;\n error?: string;\n };\n 'file:skip': { filepath: string; reason: 'unchanged' | 'already-run' };\n 'file:dry-run': {\n filepath: string;\n status: 'success' | 'failed';\n outputPath?: string;\n error?: string;\n };\n\n // Change lifecycle\n 'change:created': { name: string; path: string };\n 'change:start': { name: string; direction: 'change' | 'revert'; files: string[] };\n 'change:file': { change: string; filepath: string; index: number; total: number };\n 'change:complete': {\n name: string;\n direction: 'change' | 'revert';\n status: 'success' | 'failed';\n durationMs: number;\n };\n 'change:skip': { name: string; reason: string };\n\n // Build/Run\n 'build:start': { sqlPath: string; fileCount: number };\n 'build:complete': {\n status: 'success' | 'failed' | 'partial';\n filesRun: number;\n filesSkipped: number;\n filesFailed: number;\n durationMs: number;\n };\n 'run:file': { filepath: string; configName: string };\n 'run:dir': { dirpath: string; fileCount: number; configName: string };\n\n // Lock\n 'lock:acquiring': { configName: string; identity: string };\n 'lock:acquired': { configName: string; identity: string; expiresAt: Date };\n 'lock:released': { configName: string; identity: string };\n 'lock:blocked': { configName: string; holder: string; heldSince: Date };\n 'lock:expired': { configName: string; previousHolder: string };\n\n // State\n 'state:loaded': { configCount: number; activeConfig: string | null; version: string };\n 'state:persisted': { configCount: number };\n 'state:migrated': { from: string; to: string };\n\n // Config\n 'config:created': { name: string };\n 'config:updated': { name: string; fields: string[] };\n 'config:deleted': { name: string };\n 'config:activated': { name: string; previous: string | null };\n\n // Secrets (config-scoped)\n 'secret:set': { configName: string; key: string };\n 'secret:deleted': { configName: string; key: string };\n\n // Global secrets (app-level)\n 'global-secret:set': { key: string };\n 'global-secret:deleted': { key: string };\n\n // Known users\n 'known-user:added': { email: string; source: string };\n\n // DB lifecycle\n 'db:creating': { configName: string; database: string };\n 'db:created': { configName: string; database: string; durationMs: number };\n 'db:destroying': { configName: string; database: string };\n 'db:destroyed': { configName: string; database: string };\n 'db:bootstrap': { configName: string; tables: string[] };\n\n // Template\n 'template:render': { filepath: string; durationMs: number };\n 'template:load': { filepath: string; format: string };\n 'template:helpers': { filepath: string; count: number };\n\n // Identity (audit)\n 'identity:resolved': {\n name: string;\n email?: string;\n source: 'state' | 'git' | 'system' | 'config' | 'env';\n };\n\n // Identity (cryptographic)\n 'identity:created': { identityHash: string; name: string; email: string; machine: string };\n 'identity:registered': { identityHash: string; name: string; email: string };\n 'identity:synced': { configName: string; registered: boolean; knownUsersCount: number };\n 'identity:not-found': void;\n\n // Connection\n 'connection:open': { configName: string; dialect: string };\n 'connection:close': { configName: string };\n 'connection:error': { configName: string; error: string };\n\n // App lifecycle\n 'app:starting': { mode: AppMode };\n 'app:ready': { mode: AppMode; startedAt: Date };\n 'app:shutdown': { reason: ShutdownReason; exitCode: number };\n 'app:shutdown:phase': {\n phase: ShutdownPhase;\n status: PhaseStatus;\n durationMs?: number;\n error?: Error;\n };\n 'app:exit': { code: number };\n 'app:fatal': { error: Error; type?: 'exception' | 'rejection' };\n\n // Router\n 'router:navigated': { from: string; to: string; params: Record<string, string | number | boolean | undefined> };\n 'router:popped': { popped: string; to: string };\n\n // Errors\n error: { source: string; error: Error; context?: Record<string, unknown> };\n}\n\nexport type NoormEventNames = Events<NoormEvents>;\nexport type NoormEventCallback<E extends NoormEventNames> = ObserverEngine.EventCallback<\n NoormEvents[E]\n>;\n\n/**\n * Global observer instance for noorm.\n *\n * Enable debug mode with `NOORM_DEBUG=1` to see all events as they occur.\n *\n * @example\n * ```typescript\n * import { observer } from './observer'\n *\n * // Emit an event\n * observer.emit('file:before', { filepath, checksum, configName })\n *\n * // Subscribe to an event\n * const cleanup = observer.on('file:after', (data) => {\n * console.log(`File ${data.filepath}: ${data.status}`)\n * })\n *\n * // Clean up when done\n * cleanup()\n * ```\n */\nexport const observer = new ObserverEngine<NoormEvents>({\n name: 'noorm',\n spy: process.env['NOORM_DEBUG']\n ? (action) => console.error(`[noorm:${action.fn}] ${String(action.event)}`)\n : undefined,\n});\n\n// ? Not against this, but why are we re-exporting? convenience\nexport type { ObserverEngine };\n\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { observer } from './chunk-4HW4CS2C.js';
|
|
1
2
|
import { readFile, readdir, stat } from 'fs/promises';
|
|
2
3
|
import { Eta } from 'eta';
|
|
3
|
-
import { ObserverEngine } from '@logosdx/observer';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { attempt } from '@logosdx/utils';
|
|
6
6
|
import { pathToFileURL } from 'url';
|
|
@@ -14,12 +14,6 @@ import { PassThrough } from 'stream';
|
|
|
14
14
|
import { randomBytes, pbkdf2Sync, createCipheriv, createDecipheriv } from 'crypto';
|
|
15
15
|
import v from 'voca';
|
|
16
16
|
|
|
17
|
-
// src/core/template/engine.ts
|
|
18
|
-
var observer = new ObserverEngine({
|
|
19
|
-
name: "noorm",
|
|
20
|
-
spy: process.env["NOORM_DEBUG"] ? (action) => console.error(`[noorm:${action.fn}] ${String(action.event)}`) : void 0
|
|
21
|
-
});
|
|
22
|
-
|
|
23
17
|
// src/core/template/types.ts
|
|
24
18
|
var TEMPLATE_EXTENSION = ".tmpl";
|
|
25
19
|
var HELPER_FILENAME = "$helpers";
|
|
@@ -420,7 +414,7 @@ function createIncludeHelper(templateDir, projectRoot, options) {
|
|
|
420
414
|
throw new Error(`Include path escapes project root: ${includePath}`);
|
|
421
415
|
}
|
|
422
416
|
if (resolved.endsWith(".tmpl")) {
|
|
423
|
-
const { processFile: processFile2 } = await import('./engine-
|
|
417
|
+
const { processFile: processFile2 } = await import('./engine-5K7FA66A.js');
|
|
424
418
|
const result = await processFile2(resolved, options);
|
|
425
419
|
return result.sql;
|
|
426
420
|
}
|
|
@@ -485,6 +479,6 @@ async function processFiles(filepaths, options = {}) {
|
|
|
485
479
|
return results;
|
|
486
480
|
}
|
|
487
481
|
|
|
488
|
-
export { DT_EXTENSIONS, DtReader, ENCODED_TYPES, FORMAT_VERSION, GZIP_RATIO_THRESHOLD, GZIP_THRESHOLD, encryptWithPassphrase, eta, isTemplate,
|
|
489
|
-
//# sourceMappingURL=chunk-
|
|
490
|
-
//# sourceMappingURL=chunk-
|
|
482
|
+
export { DT_EXTENSIONS, DtReader, ENCODED_TYPES, FORMAT_VERSION, GZIP_RATIO_THRESHOLD, GZIP_THRESHOLD, encryptWithPassphrase, eta, isTemplate, processFile, processFiles, renderTemplate };
|
|
483
|
+
//# sourceMappingURL=chunk-BTXUEWYQ.js.map
|
|
484
|
+
//# sourceMappingURL=chunk-BTXUEWYQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/template/types.ts","../../../src/core/template/loaders/js.ts","../../../src/core/template/helpers.ts","../../../src/core/template/loaders/json5.ts","../../../src/core/template/loaders/yaml.ts","../../../src/core/template/loaders/csv.ts","../../../src/core/template/loaders/sql.ts","../../../src/core/dt/constants.ts","../../../src/core/dt/crypto.ts","../../../src/core/dt/reader.ts","../../../src/core/template/loaders/dt.ts","../../../src/core/template/loaders/index.ts","../../../src/core/template/utils.ts","../../../src/core/template/context.ts","../../../src/core/template/engine.ts"],"names":["exports","JSON5","readFile","parse","path","attempt","processFile"],"mappings":";;;;;;;;;;;;;;;;;AA2MO,IAAM,kBAAA,GAAqB,OAAA;AAK3B,IAAM,eAAA,GAAkB,UAAA;AAKxB,IAAM,iBAAA,GAAoB,CAAC,KAAA,EAAO,KAAA,EAAO,MAAM,CAAA;AC9LtD,eAAsB,OAAO,QAAA,EAAoC;AAG7D,EAAA,MAAM,GAAA,GAAM,aAAA,CAAc,QAAQ,CAAA,CAAE,IAAA;AAGpC,EAAA,MAAM,mBAAmB,CAAA,EAAG,GAAG,CAAA,GAAA,EAAM,IAAA,CAAK,KAAK,CAAA,CAAA;AAE/C,EAAA,MAAM,GAAA,GAAM,MAAM,OAAO,gBAAA,CAAA;AAGzB,EAAA,OAAO,GAAA,CAAI,OAAA,KAAY,MAAA,GAAY,GAAA,CAAI,OAAA,GAAU,GAAA;AAErD;;;ACGA,eAAsB,eAAA,CAAgB,SAAiB,WAAA,EAAwC;AAE3F,EAAA,MAAM,cAAwB,EAAC;AAC/B,EAAA,IAAI,UAAA,GAAa,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AACrC,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,WAAW,CAAA;AAGrC,EAAA,OAAO,UAAA,CAAW,UAAA,CAAW,IAAI,CAAA,EAAG;AAEhC,IAAA,MAAM,UAAA,GAAa,MAAM,eAAA,CAAgB,UAAU,CAAA;AAEnD,IAAA,IAAI,UAAA,EAAY;AAGZ,MAAA,WAAA,CAAY,QAAQ,UAAU,CAAA;AAAA,IAElC;AAGA,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,OAAA,CAAQ,UAAU,CAAA;AAGzC,IAAA,IAAI,cAAc,UAAA,EAAY;AAE1B,MAAA;AAAA,IAEJ;AAEA,IAAA,UAAA,GAAa,SAAA;AAAA,EAEjB;AAEA,EAAA,OAAO,WAAA;AAEX;AAUA,eAAe,gBAAgB,GAAA,EAAqC;AAEhE,EAAA,KAAA,MAAW,OAAO,iBAAA,EAAmB;AAEjC,IAAA,MAAM,QAAA,GAAW,KAAK,IAAA,CAAK,GAAA,EAAK,GAAG,eAAe,CAAA,EAAG,GAAG,CAAA,CAAE,CAAA;AAC1D,IAAA,MAAM,CAAC,KAAK,CAAA,GAAI,MAAM,QAAQ,MAAM,IAAA,CAAK,QAAQ,CAAC,CAAA;AAElD,IAAA,IAAI,KAAA,EAAO,QAAO,EAAG;AAEjB,MAAA,OAAO,QAAA;AAAA,IAEX;AAAA,EAEJ;AAEA,EAAA,OAAO,IAAA;AAEX;AAoBA,eAAsB,WAAA,CAClB,SACA,WAAA,EACgC;AAEhC,EAAA,MAAM,WAAA,GAAc,MAAM,eAAA,CAAgB,OAAA,EAAS,WAAW,CAAA;AAC9D,EAAA,MAAM,SAAkC,EAAC;AAEzC,EAAA,KAAA,MAAW,YAAY,WAAA,EAAa;AAEhC,IAAA,MAAM,CAAC,KAAK,GAAG,CAAA,GAAI,MAAM,OAAA,CAAQ,MAAM,MAAA,CAAO,QAAQ,CAAC,CAAA;AAEvD,IAAA,IAAI,GAAA,EAAK;AAEL,MAAA,QAAA,CAAS,KAAK,OAAA,EAAS;AAAA,QACnB,MAAA,EAAQ,UAAA;AAAA,QACR,KAAA,EAAO,GAAA;AAAA,QACP,OAAA,EAAS,EAAE,QAAA,EAAU,SAAA,EAAW,cAAA;AAAe,OAClD,CAAA;AACD,MAAA;AAAA,IAEJ;AAGA,IAAA,IAAI,GAAA,IAAO,OAAO,GAAA,KAAQ,QAAA,EAAU;AAEhC,MAAA,MAAMA,SAAA,GAAU,GAAA;AAChB,MAAA,MAAM,WAAA,GAAc,MAAA,CAAO,IAAA,CAAKA,SAAO,CAAA,CAAE,MAAA;AAEzC,MAAA,MAAA,CAAO,MAAA,CAAO,QAAQA,SAAO,CAAA;AAE7B,MAAA,QAAA,CAAS,KAAK,kBAAA,EAAoB;AAAA,QAC9B,QAAA;AAAA,QACA,KAAA,EAAO;AAAA,OACV,CAAA;AAAA,IAEL;AAAA,EAEJ;AAEA,EAAA,OAAO,MAAA;AAEX;ACxIA,eAAsB,UAAU,QAAA,EAAoC;AAEhE,EAAA,MAAM,OAAA,GAAU,MAAM,QAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAEhD,EAAA,OAAOC,MAAA,CAAM,MAAM,OAAO,CAAA;AAE9B;ACXA,eAAsB,SAAS,QAAA,EAAoC;AAE/D,EAAA,MAAM,OAAA,GAAU,MAAMC,QAAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAEhD,EAAA,OAAOC,QAAM,OAAO,CAAA;AAExB;ACJA,eAAsB,QAAQ,QAAA,EAAqD;AAE/E,EAAA,MAAM,OAAA,GAAU,MAAMD,QAAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAEhD,EAAA,OAAOC,MAAM,OAAA,EAAS;AAAA,IAClB,OAAA,EAAS,IAAA;AAAA,IACT,gBAAA,EAAkB,IAAA;AAAA,IAClB,IAAA,EAAM;AAAA,GACT,CAAA;AAEL;ACbA,eAAsB,QAAQ,QAAA,EAAmC;AAE7D,EAAA,OAAOD,QAAAA,CAAS,UAAU,OAAO,CAAA;AAErC;;;ACdO,IAAM,cAAA,GAAiB;AAQvB,IAAM,cAAA,GAAiB;AAQvB,IAAM,oBAAA,GAAuB;AAwB7B,IAAM,aAAA,GAAgD;AAAA,EACzD,IAAA,EAAM,IAAA;AAAA,EACN,MAAA,EAAQ,IAAA;AAAA,EACR,MAAA,EAAQ,IAAA;AAAA,EACR,KAAA,EAAO,IAAA;AAAA,EACP,IAAA,EAAM,IAAA;AAAA,EACN,MAAA,EAAQ;AACZ;AAKO,IAAM,aAAA,GAAgB;AAAA;AAAA,EAEzB,GAAA,EAAK,KAAA;AAAA;AAAA,EAEL,UAAA,EAAY,MAAA;AAAA;AAAA,EAEZ,SAAA,EAAW;AACf;AC9CA,IAAM,SAAA,GAAY,aAAA;AAClB,IAAM,SAAA,GAAY,EAAA;AAClB,IAAM,WAAA,GAAc,EAAA;AACpB,IAAM,eAAA,GAAkB,EAAA;AACxB,IAAM,UAAA,GAAa,EAAA;AACnB,IAAM,iBAAA,GAAoB,GAAA;AAC1B,IAAM,aAAA,GAAgB,QAAA;AAkBf,SAAS,qBAAA,CAAsB,MAAc,UAAA,EAAwC;AAExF,EAAA,MAAM,IAAA,GAAO,YAAY,WAAW,CAAA;AACpC,EAAA,MAAM,EAAA,GAAK,YAAY,SAAS,CAAA;AAEhC,EAAA,MAAM,MAAM,UAAA,CAAW,UAAA,EAAY,IAAA,EAAM,iBAAA,EAAmB,YAAY,aAAa,CAAA;AAErF,EAAA,MAAM,MAAA,GAAS,cAAA,CAAe,SAAA,EAAW,GAAA,EAAK,EAAA,EAAI;AAAA,IAC9C,aAAA,EAAe;AAAA,GAClB,CAAA;AAED,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,MAAA,CAAO,CAAC,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA,EAAG,MAAA,CAAO,KAAA,EAAO,CAAC,CAAA;AACtE,EAAA,MAAM,OAAA,GAAU,OAAO,UAAA,EAAW;AAElC,EAAA,OAAO;AAAA,IACH,IAAA,EAAM,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAA;AAAA,IAC5B,EAAA,EAAI,EAAA,CAAG,QAAA,CAAS,QAAQ,CAAA;AAAA,IACxB,OAAA,EAAS,OAAA,CAAQ,QAAA,CAAS,QAAQ,CAAA;AAAA,IAClC,UAAA,EAAY,UAAA,CAAW,QAAA,CAAS,QAAQ;AAAA,GAC5C;AAEJ;AAmBO,SAAS,qBAAA,CAAsB,SAA6B,UAAA,EAA4B;AAE3F,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,MAAM,QAAQ,CAAA;AAC/C,EAAA,MAAM,EAAA,GAAK,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,IAAI,QAAQ,CAAA;AAC3C,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,SAAS,QAAQ,CAAA;AACrD,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,YAAY,QAAQ,CAAA;AAE3D,EAAA,MAAM,MAAM,UAAA,CAAW,UAAA,EAAY,IAAA,EAAM,iBAAA,EAAmB,YAAY,aAAa,CAAA;AAErF,EAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,SAAA,EAAW,GAAA,EAAK,EAAA,EAAI;AAAA,IAClD,aAAA,EAAe;AAAA,GAClB,CAAA;AAED,EAAA,QAAA,CAAS,WAAW,OAAO,CAAA;AAE3B,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,MAAA,CAAO,CAAC,QAAA,CAAS,MAAA,CAAO,UAAU,CAAA,EAAG,QAAA,CAAS,KAAA,EAAO,CAAC,CAAA;AAE/E,EAAA,OAAO,SAAA;AAEX;;;AC/DO,IAAM,WAAN,MAAe;AAAA,EAElB,SAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA,GAA2B,IAAA;AAAA,EAC3B,WAAA,GAAiE,IAAA;AAAA,EACjE,YAAA,GAAgC,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhC,YAAY,OAAA,EAA0B;AAElC,IAAA,IAAA,CAAK,YAAY,OAAA,CAAQ,QAAA;AACzB,IAAA,IAAA,CAAK,cAAc,OAAA,CAAQ,UAAA;AAC3B,IAAA,IAAA,CAAK,aAAaE,IAAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,QAAQ,EAAE,WAAA,EAAY;AAAA,EAEjE;AAAA;AAAA,EAGA,IAAI,MAAA,GAA0B;AAE1B,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EAEhB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,IAAA,GAAsB;AAExB,IAAA,MAAM,QAAA,GAAW,KAAK,qBAAA,EAAsB;AAC5C,IAAA,IAAA,CAAK,YAAA,GAAe,QAAA;AAGpB,IAAA,MAAM,KAAK,eAAA,CAAgB,EAAE,OAAO,QAAA,EAAU,SAAA,EAAW,UAAU,CAAA;AACnE,IAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,MAAA,CAAO,aAAa,CAAA,EAAE;AACvC,IAAA,MAAM,SAAA,GAAY,MAAM,KAAA,CAAM,IAAA,EAAK;AAEnC,IAAA,IAAI,SAAA,CAAU,IAAA,IAAQ,CAAC,SAAA,CAAU,KAAA,EAAO;AAEpC,MAAA,MAAM,IAAI,MAAM,sCAAiC,CAAA;AAAA,IAErD;AAEA,IAAA,IAAA,CAAK,OAAA,GAAUH,MAAAA,CAAM,KAAA,CAAM,SAAA,CAAU,KAAK,CAAA;AAG1C,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,CAAA,KAAM,CAAA,EAAG;AAEtB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gCAAA,EAAmC,IAAA,CAAK,OAAA,CAAQ,CAAC,CAAA,CAAE,CAAA;AAAA,IAEvE;AAGA,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,kBAAA,CAAmB,KAAK,CAAA;AAAA,EAEpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,OAAO,IAAA,GAAmD;AAEtD,IAAA,IAAI,CAAC,KAAK,WAAA,EAAa;AAEnB,MAAA,MAAM,IAAI,MAAM,4CAAuC,CAAA;AAAA,IAE3D;AAEA,IAAA,OAAO,IAAA,CAAK,WAAA;AAAA,EAEhB;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAc;AAEV,IAAA,IAAI,KAAK,YAAA,EAAc;AAEnB,MAAA,IAAA,CAAK,aAAa,OAAA,EAAQ;AAC1B,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AAAA,IAExB;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAA,GAAkC;AAE9B,IAAA,IAAI,IAAA,CAAK,UAAA,KAAe,aAAA,CAAc,SAAA,EAAW;AAG7C,MAAA,IAAI,CAAC,KAAK,WAAA,EAAa;AAEnB,QAAA,MAAM,IAAI,MAAM,qCAAqC,CAAA;AAAA,MAEzD;AAEA,MAAA,MAAM,SAAA,GAAY,YAAA,CAAa,IAAA,CAAK,SAAA,EAAW,MAAM,CAAA;AACrD,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA;AACpC,MAAA,MAAM,UAAA,GAAa,qBAAA,CAAsB,OAAA,EAAS,IAAA,CAAK,WAAW,CAAA;AAClE,MAAA,MAAM,GAAA,GAAM,WAAW,UAAU,CAAA;AAEjC,MAAA,MAAM,MAAA,GAAS,IAAI,WAAA,EAAY;AAC/B,MAAA,MAAA,CAAO,IAAI,GAAG,CAAA;AAEd,MAAA,OAAO,MAAA;AAAA,IAEX;AAEA,IAAA,IAAI,IAAA,CAAK,UAAA,KAAe,aAAA,CAAc,UAAA,EAAY;AAG9C,MAAA,MAAM,UAAA,GAAa,gBAAA,CAAiB,IAAA,CAAK,SAAS,CAAA;AAClD,MAAA,MAAM,SAAS,YAAA,EAAa;AAE5B,MAAA,UAAA,CAAW,KAAK,MAAM,CAAA;AAEtB,MAAA,OAAO,MAAA;AAAA,IAEX;AAGA,IAAA,OAAO,iBAAiB,IAAA,CAAK,SAAA,EAAW,EAAE,QAAA,EAAU,QAAQ,CAAA;AAAA,EAEhE;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,mBACH,KAAA,EAC0C;AAE1C,IAAA,WAAA,MAAiB,IAAA,IAAQ,EAAE,CAAC,MAAA,CAAO,aAAa,GAAG,MAAM,OAAM,EAAG;AAE9D,MAAA,MAAM,OAAA,GAAU,KAAK,IAAA,EAAK;AAE1B,MAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AAE1B,MAAA,MAAMA,MAAAA,CAAM,MAAM,OAAO,CAAA;AAAA,IAE7B;AAAA,EAEJ;AAEJ;;;AC7KA,eAAsB,OAAO,QAAA,EAAsD;AAE/E,EAAA,MAAM,MAAA,GAAS,IAAI,QAAA,CAAS,EAAE,UAAU,CAAA;AAExC,EAAA,MAAM,OAAO,IAAA,EAAK;AAElB,EAAA,MAAM,SAAS,MAAA,CAAO,MAAA;AAEtB,EAAA,IAAI,CAAC,MAAA,EAAQ;AAET,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gCAAA,EAAmC,QAAQ,CAAA,CAAE,CAAA;AAAA,EAEjE;AAEA,EAAA,MAAM,cAAc,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA;AACpD,EAAA,MAAM,OAAkC,EAAC;AAEzC,EAAA,WAAA,MAAiB,MAAA,IAAU,MAAA,CAAO,IAAA,EAAK,EAAG;AAEtC,IAAA,MAAM,MAA+B,EAAC;AAEtC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,WAAA,CAAY,QAAQ,CAAA,EAAA,EAAK;AAEzC,MAAA,GAAA,CAAI,YAAY,CAAC,CAAE,CAAA,GAAI,MAAA,CAAO,CAAC,CAAA,IAAK,IAAA;AAAA,IAExC;AAEA,IAAA,IAAA,CAAK,KAAK,GAAG,CAAA;AAAA,EAEjB;AAEA,EAAA,MAAA,CAAO,KAAA,EAAM;AAEb,EAAA,OAAO,IAAA;AAEX;;;ACzCA,IAAM,OAAA,GAA0B;AAAA,EAC5B,OAAA,EAAS,SAAA;AAAA,EACT,QAAA,EAAU,SAAA;AAAA,EACV,OAAA,EAAS,QAAA;AAAA,EACT,MAAA,EAAQ,QAAA;AAAA,EACR,MAAA,EAAQ,OAAA;AAAA,EACR,KAAA,EAAO,MAAA;AAAA,EACP,MAAA,EAAQ,MAAA;AAAA,EACR,KAAA,EAAO,MAAA;AAAA,EACP,MAAA,EAAQ,OAAA;AAAA,EACR,KAAA,EAAO,MAAA;AAAA,EACP,MAAA,EAAQ;AAAA;AAEZ,CAAA;AAQO,SAAS,UAAU,GAAA,EAAsB;AAE5C,EAAA,OAAO,GAAA,IAAO,OAAA;AAElB;AAQO,SAAS,UAAU,GAAA,EAAiC;AAEvD,EAAA,OAAO,QAAQ,GAAG,CAAA;AAEtB;AAkBA,eAAsB,aAAa,QAAA,EAAoC;AAEnE,EAAA,MAAM,GAAA,GAAMG,IAAAA,CAAK,OAAA,CAAQ,QAAQ,EAAE,WAAA,EAAY;AAC/C,EAAA,MAAM,MAAA,GAAS,UAAU,GAAG,CAAA;AAE5B,EAAA,IAAI,CAAC,MAAA,EAAQ;AAET,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oCAAA,EAAuC,GAAG,CAAA,CAAE,CAAA;AAAA,EAEhE;AAEA,EAAA,OAAO,OAAO,QAAQ,CAAA;AAE1B;AC7DO,SAAS,aAAa,QAAA,EAA0B;AAGnD,EAAA,MAAM,GAAA,GAAMA,IAAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA;AACjC,EAAA,MAAM,IAAA,GAAOA,IAAAA,CAAK,QAAA,CAAS,QAAA,EAAU,GAAG,CAAA;AAGxC,EAAA,OAAO,CAAA,CAAE,UAAU,IAAI,CAAA;AAE3B;AAiBO,SAAS,UAAU,KAAA,EAAuB;AAE7C,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,IAAA,EAAM,IAAI,CAAA;AAEnC;AAkBO,SAAS,SAAS,KAAA,EAAiD;AAEtE,EAAA,IAAI,UAAU,IAAA,EAAM;AAEhB,IAAA,OAAO,MAAA;AAAA,EAEX;AAEA,EAAA,OAAO,CAAA,CAAA,EAAI,SAAA,CAAU,MAAA,CAAO,KAAK,CAAC,CAAC,CAAA,CAAA,CAAA;AAEvC;AAcO,SAAS,YAAA,GAAuB;AAEnC,EAAA,OAAO,OAAO,UAAA,EAAW;AAE7B;AAYO,SAAS,MAAA,GAAiB;AAE7B,EAAA,OAAA,iBAAO,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAElC;;;ACvFA,eAAsB,YAAA,CAClB,YAAA,EACA,OAAA,GAAyB,EAAC,EACF;AAExB,EAAA,MAAM,WAAA,GAAcA,IAAAA,CAAK,OAAA,CAAQ,YAAY,CAAA;AAC7C,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,WAAA,IAAe,OAAA,CAAQ,GAAA,EAAI;AAGvD,EAAA,MAAM,OAAA,GAAU,MAAM,WAAA,CAAY,WAAA,EAAa,WAAW,CAAA;AAG1D,EAAA,MAAM,SAAA,GAAY,MAAM,kBAAA,CAAmB,WAAW,CAAA;AAGtD,EAAA,MAAM,iBAAiB,QAAA,IAAY,SAAA;AAGnC,EAAA,MAAM,GAAA,GAAuB;AAAA;AAAA,IAEzB,GAAG,OAAA;AAAA;AAAA,IAGH,GAAG,SAAA;AAAA;AAAA,IAGH,GAAI,cAAA,GAAiB,KAAK,EAAE,MAAA,EAAQ,QAAQ,MAAA,EAAO;AAAA;AAAA,IAGnD,OAAA,EAAS,OAAA,CAAQ,OAAA,IAAW,EAAC;AAAA,IAC7B,aAAA,EAAe,OAAA,CAAQ,aAAA,IAAiB,EAAC;AAAA;AAAA,IAGzC,KAAK,OAAA,CAAQ,GAAA;AAAA;AAAA,IAGb,OAAA,EAAS,mBAAA,CAAoB,WAAA,EAAa,WAAA,EAAa,OAAO,CAAA;AAAA,IAC9D,MAAA,EAAQ,SAAA;AAAA,IACR,KAAA,EAAO,QAAA;AAAA,IACP,IAAA,EAAM,CAAC,KAAA,KAAmB,IAAA,CAAK,UAAU,KAAK,CAAA;AAAA,IAC9C,GAAA,EAAK,MAAA;AAAA,IACL,IAAA,EAAM;AAAA,GACV;AAEA,EAAA,OAAO,GAAA;AAEX;AAWA,eAAe,mBAAmB,GAAA,EAA+C;AAE7E,EAAA,MAAM,OAAgC,EAAC;AAEvC,EAAA,MAAM,CAAC,OAAA,EAAS,OAAO,CAAA,GAAI,MAAMC,OAAAA,CAAQ,MAAM,OAAA,CAAQ,GAAA,EAAK,EAAE,aAAA,EAAe,IAAA,EAAM,CAAC,CAAA;AAEpF,EAAA,IAAI,OAAA,IAAW,CAAC,OAAA,EAAS;AAErB,IAAA,QAAA,CAAS,KAAK,OAAA,EAAS;AAAA,MACnB,MAAA,EAAQ,UAAA;AAAA,MACR,KAAA,EAAO,OAAA,IAAW,IAAI,KAAA,CAAM,0BAA0B,CAAA;AAAA,MACtD,OAAA,EAAS,EAAE,GAAA,EAAK,SAAA,EAAW,iBAAA;AAAkB,KAChD,CAAA;AAED,IAAA,OAAO,IAAA;AAAA,EAEX;AAEA,EAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAGzB,IAAA,IAAI,CAAC,KAAA,CAAM,MAAA,EAAO,EAAG;AAEjB,MAAA;AAAA,IAEJ;AAGA,IAAA,IAAI,KAAA,CAAM,IAAA,CAAK,UAAA,CAAW,eAAe,CAAA,EAAG;AAExC,MAAA;AAAA,IAEJ;AAGA,IAAA,IAAI,KAAA,CAAM,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA,EAAG;AAE9B,MAAA;AAAA,IAEJ;AAEA,IAAA,MAAM,MAAMD,IAAAA,CAAK,OAAA,CAAQ,KAAA,CAAM,IAAI,EAAE,WAAA,EAAY;AAGjD,IAAA,IAAI,CAAC,SAAA,CAAU,GAAG,CAAA,EAAG;AAEjB,MAAA;AAAA,IAEJ;AAGA,IAAA,IAAI,QAAQ,MAAA,EAAQ;AAEhB,MAAA;AAAA,IAEJ;AAEA,IAAA,MAAM,QAAA,GAAWA,IAAAA,CAAK,IAAA,CAAK,GAAA,EAAK,MAAM,IAAI,CAAA;AAC1C,IAAA,MAAM,GAAA,GAAM,YAAA,CAAa,KAAA,CAAM,IAAI,CAAA;AAEnC,IAAA,MAAM,CAAC,QAAQ,OAAO,CAAA,GAAI,MAAMC,OAAAA,CAAQ,MAAM,YAAA,CAAa,QAAQ,CAAC,CAAA;AAEpE,IAAA,IAAI,OAAA,EAAS;AAET,MAAA,QAAA,CAAS,KAAK,OAAA,EAAS;AAAA,QACnB,MAAA,EAAQ,UAAA;AAAA,QACR,KAAA,EAAO,OAAA;AAAA,QACP,OAAA,EAAS,EAAE,QAAA,EAAU,SAAA,EAAW,gBAAA;AAAiB,OACpD,CAAA;AACD,MAAA;AAAA,IAEJ;AAEA,IAAA,IAAA,CAAK,GAAG,CAAA,GAAI,MAAA;AAEZ,IAAA,QAAA,CAAS,KAAK,eAAA,EAAiB;AAAA,MAC3B,QAAA;AAAA,MACA,MAAA,EAAQ;AAAA,KACX,CAAA;AAAA,EAEL;AAEA,EAAA,OAAO,IAAA;AAEX;AAcA,SAAS,mBAAA,CACL,WAAA,EACA,WAAA,EACA,OAAA,EACwC;AAExC,EAAA,OAAO,OAAO,WAAA,KAAyC;AAGnD,IAAA,MAAM,QAAA,GAAWD,IAAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,WAAW,CAAA;AAGtD,IAAA,IAAI,CAAC,QAAA,CAAS,UAAA,CAAW,WAAW,CAAA,EAAG;AAEnC,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mCAAA,EAAsC,WAAW,CAAA,CAAE,CAAA;AAAA,IAEvE;AAGA,IAAA,IAAI,QAAA,CAAS,QAAA,CAAS,OAAO,CAAA,EAAG;AAG5B,MAAA,MAAM,EAAE,WAAA,EAAAE,YAAAA,EAAY,GAAI,MAAM,OAAO,sBAAa,CAAA;AAClD,MAAA,MAAM,MAAA,GAAS,MAAMA,YAAAA,CAAY,QAAA,EAAU,OAAO,CAAA;AAElD,MAAA,OAAO,MAAA,CAAO,GAAA;AAAA,IAElB;AAGA,IAAA,MAAM,CAAC,SAAS,GAAG,CAAA,GAAI,MAAMD,OAAAA,CAAQ,MAAM,YAAA,CAAa,QAAQ,CAAC,CAAA;AAEjE,IAAA,IAAI,GAAA,EAAK;AAEL,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,WAAW,CAAA,GAAA,EAAM,GAAA,CAAI,OAAO,CAAA,CAAE,CAAA;AAAA,IAExE;AAEA,IAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAE7B,MAAA,OAAO,OAAA;AAAA,IAEX;AAGA,IAAA,OAAO,IAAA,CAAK,UAAU,OAAO,CAAA;AAAA,EAEjC,CAAA;AAEJ;;;AC5MA,IAAM,GAAA,GAAM,IAAI,GAAA,CAAI;AAAA;AAAA,EAEhB,IAAA,EAAM,CAAC,IAAA,EAAM,IAAI,CAAA;AAAA;AAAA,EAGjB,OAAA,EAAS,GAAA;AAAA;AAAA,EAGT,UAAA,EAAY,KAAA;AAAA;AAAA,EAGZ,OAAA,EAAS,KAAA;AAAA;AAAA,EAGT,KAAA,EAAO;AACX,CAAC;AAQM,SAAS,WAAW,QAAA,EAA2B;AAElD,EAAA,OAAO,QAAA,CAAS,SAAS,kBAAkB,CAAA;AAE/C;AAiBA,eAAsB,cAAA,CAAe,UAAkB,OAAA,EAA2C;AAE9F,EAAA,OAAO,GAAA,CAAI,iBAAA,CAAkB,QAAA,EAAU,OAAO,CAAA;AAElD;AAyBA,eAAsB,WAAA,CAClB,QAAA,EACA,OAAA,GAAyB,EAAC,EACJ;AAGtB,EAAA,MAAM,OAAA,GAAU,MAAMH,QAAAA,CAAS,QAAA,EAAU,OAAO,CAAA;AAGhD,EAAA,IAAI,CAAC,UAAA,CAAW,QAAQ,CAAA,EAAG;AAEvB,IAAA,OAAO;AAAA,MACH,GAAA,EAAK,OAAA;AAAA,MACL,UAAA,EAAY;AAAA,KAChB;AAAA,EAEJ;AAGA,EAAA,MAAM,KAAA,GAAQ,YAAY,GAAA,EAAI;AAE9B,EAAA,MAAM,OAAA,GAAU,MAAM,YAAA,CAAa,QAAA,EAAU,OAAO,CAAA;AACpD,EAAA,MAAM,GAAA,GAAM,MAAM,cAAA,CAAe,OAAA,EAAS,OAAO,CAAA;AAEjD,EAAA,MAAM,UAAA,GAAa,WAAA,CAAY,GAAA,EAAI,GAAI,KAAA;AAEvC,EAAA,QAAA,CAAS,KAAK,iBAAA,EAAmB;AAAA,IAC7B,QAAA;AAAA,IACA;AAAA,GACH,CAAA;AAED,EAAA,OAAO;AAAA,IACH,GAAA;AAAA,IACA,UAAA,EAAY,IAAA;AAAA,IACZ;AAAA,GACJ;AAEJ;AASA,eAAsB,YAAA,CAClB,SAAA,EACA,OAAA,GAAyB,EAAC,EACF;AAExB,EAAA,MAAM,UAA2B,EAAC;AAElC,EAAA,KAAA,MAAW,YAAY,SAAA,EAAW;AAE9B,IAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,QAAA,EAAU,OAAO,CAAA;AAClD,IAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AAAA,EAEvB;AAEA,EAAA,OAAO,OAAA;AAEX","file":"chunk-BTXUEWYQ.js","sourcesContent":["/**\n * Template engine types.\n *\n * Defines the context object ($) available in templates, loader interfaces,\n * and configuration options for the template engine.\n */\n\n/**\n * Built-in helper functions available on the template context.\n */\nexport interface BuiltInHelpers {\n /**\n * Include another SQL file.\n * Path is resolved relative to the template's directory.\n *\n * @example\n * ```sql\n * {%~ await $.include('lib/uuid_function.sql') %}\n * ```\n */\n include: (path: string) => Promise<string>;\n\n /**\n * SQL-escape a string value.\n * Escapes single quotes by doubling them.\n *\n * @example\n * ```sql\n * WHERE name = '{%~ $.escape(userName) %}'\n * ```\n */\n escape: (value: string) => string;\n\n /**\n * SQL-escape and wrap in single quotes.\n *\n * @example\n * ```sql\n * INSERT INTO users (name) VALUES ({%~ $.quote(userName) %});\n * ```\n */\n quote: (value: string | number | boolean | null) => string;\n\n /**\n * JSON stringify a value.\n *\n * @example\n * ```sql\n * INSERT INTO config (data) VALUES ('{%~ $.json(configObject) %}');\n * ```\n */\n json: (value: unknown) => string;\n\n /**\n * Current ISO timestamp.\n *\n * @example\n * ```sql\n * INSERT INTO logs (created_at) VALUES ('{%~ $.now() %}');\n * ```\n */\n now: () => string;\n\n /**\n * Generate a UUID v4.\n *\n * @example\n * ```sql\n * INSERT INTO users (id) VALUES ('{%~ $.uuid() %}');\n * ```\n */\n uuid: () => string;\n}\n\n/**\n * Template context object ($) available in templates.\n *\n * Contains auto-loaded data, inherited helpers, secrets, and built-in functions.\n */\nexport interface TemplateContext extends BuiltInHelpers {\n /**\n * Active configuration object.\n * Only available if no `config.*` file exists in the template directory.\n */\n config?: Record<string, unknown>;\n\n /**\n * Decrypted secrets for the active config.\n */\n secrets: Record<string, string>;\n\n /**\n * Decrypted global secrets (shared across configs).\n */\n globalSecrets: Record<string, string>;\n\n /**\n * Environment variables.\n */\n env: Record<string, string | undefined>;\n\n /**\n * Auto-loaded data files and inherited helpers.\n * Keys are camelCased filenames (e.g., `$.users`, `$.seedData`).\n */\n [key: string]: unknown;\n}\n\n/**\n * Result from a data loader.\n */\nexport interface LoaderResult {\n /**\n * The parsed data.\n */\n data: unknown;\n\n /**\n * Original file path.\n */\n filepath: string;\n\n /**\n * File extension (e.g., '.json5', '.yml').\n */\n format: string;\n}\n\n/**\n * A data file loader function.\n */\nexport type Loader = (filepath: string) => Promise<unknown>;\n\n/**\n * Registry of loaders by file extension.\n */\nexport type LoaderRegistry = Record<string, Loader>;\n\n/**\n * Options for rendering a template.\n */\nexport interface RenderOptions {\n /**\n * Active configuration to include in context.\n */\n config?: Record<string, unknown>;\n\n /**\n * Secrets for the active config.\n */\n secrets?: Record<string, string>;\n\n /**\n * Global secrets shared across configs.\n */\n globalSecrets?: Record<string, string>;\n\n /**\n * Project root directory.\n * Used to determine where to stop walking up for helpers.\n * Defaults to process.cwd().\n */\n projectRoot?: string;\n}\n\n/**\n * Result of processing a SQL file.\n */\nexport interface ProcessResult {\n /**\n * The SQL content (rendered if template, raw if .sql).\n */\n sql: string;\n\n /**\n * Whether the file was a template.\n */\n isTemplate: boolean;\n\n /**\n * Render duration in milliseconds (only for templates).\n */\n durationMs?: number;\n}\n\n/**\n * Supported data file extensions.\n */\nexport const DATA_EXTENSIONS = [\n '.json',\n '.json5',\n '.yaml',\n '.yml',\n '.csv',\n '.js',\n '.mjs',\n '.ts',\n '.sql',\n] as const;\n\n/**\n * Template file extension.\n */\nexport const TEMPLATE_EXTENSION = '.tmpl';\n\n/**\n * Helper file name pattern.\n */\nexport const HELPER_FILENAME = '$helpers';\n\n/**\n * Supported helper file extensions.\n */\nexport const HELPER_EXTENSIONS = ['.ts', '.js', '.mjs'] as const;\n","/**\n * JavaScript/TypeScript module loader.\n *\n * Loads .js, .mjs, and .ts files via dynamic import.\n * Returns module.default if available, otherwise the entire module.\n *\n * @example\n * ```typescript\n * const data = await loadJs('/path/to/helpers.ts')\n * ```\n */\nimport { pathToFileURL } from 'node:url';\n\n/**\n * Load a JavaScript or TypeScript module.\n *\n * Uses dynamic import to load the module. If the module has a default\n * export, returns that. Otherwise returns the entire module object.\n *\n * @param filepath - Absolute path to the JS/TS file\n * @returns The module's default export or the entire module\n * @throws If file cannot be imported\n */\nexport async function loadJs(filepath: string): Promise<unknown> {\n\n // Convert to file URL for cross-platform compatibility\n const url = pathToFileURL(filepath).href;\n\n // Add cache-busting query param to avoid stale imports\n const urlWithCacheBust = `${url}?t=${Date.now()}`;\n\n const mod = await import(urlWithCacheBust);\n\n // Return default export if available, otherwise the whole module\n return mod.default !== undefined ? mod.default : mod;\n\n}\n","/**\n * Helper file tree walker.\n *\n * Walks up the directory tree from a template's location to the project root,\n * collecting and merging $helpers.ts files. Child helpers override parent helpers.\n *\n * @example\n * ```typescript\n * import { loadHelpers } from './helpers'\n *\n * // Given structure:\n * // sql/\n * // ├── $helpers.ts ← loaded first (base)\n * // └── users/\n * // ├── $helpers.ts ← loaded second (overrides)\n * // └── 001_create.sql.tmpl\n *\n * const helpers = await loadHelpers('/project/sql/users', '/project')\n * // helpers contains merged exports from both files\n * ```\n */\nimport path from 'node:path';\nimport { stat } from 'node:fs/promises';\n\nimport { attempt } from '@logosdx/utils';\n\nimport { observer } from '../observer.js';\nimport { HELPER_FILENAME, HELPER_EXTENSIONS } from './types.js';\nimport { loadJs } from './loaders/js.js';\n\n/**\n * Find all helper files from a directory up to the project root.\n *\n * Returns paths in order from root to leaf (so child can override parent).\n *\n * @param fromDir - Starting directory (template's directory)\n * @param projectRoot - Project root directory (stop walking here)\n * @returns Array of helper file paths, ordered root to leaf\n */\nexport async function findHelperFiles(fromDir: string, projectRoot: string): Promise<string[]> {\n\n const helperPaths: string[] = [];\n let currentDir = path.resolve(fromDir);\n const root = path.resolve(projectRoot);\n\n // Walk up until we reach or pass the project root\n while (currentDir.startsWith(root)) {\n\n const helperPath = await findHelperInDir(currentDir);\n\n if (helperPath) {\n\n // Prepend so we get root-to-leaf order\n helperPaths.unshift(helperPath);\n\n }\n\n // Move up one directory\n const parentDir = path.dirname(currentDir);\n\n // Stop if we've reached the root or can't go higher\n if (parentDir === currentDir) {\n\n break;\n\n }\n\n currentDir = parentDir;\n\n }\n\n return helperPaths;\n\n}\n\n/**\n * Find a helper file in a directory.\n *\n * Checks for $helpers.ts, $helpers.js, $helpers.mjs in order.\n *\n * @param dir - Directory to search\n * @returns Path to helper file if found, null otherwise\n */\nasync function findHelperInDir(dir: string): Promise<string | null> {\n\n for (const ext of HELPER_EXTENSIONS) {\n\n const filepath = path.join(dir, `${HELPER_FILENAME}${ext}`);\n const [stats] = await attempt(() => stat(filepath));\n\n if (stats?.isFile()) {\n\n return filepath;\n\n }\n\n }\n\n return null;\n\n}\n\n/**\n * Load and merge helper files from a directory tree.\n *\n * Walks up from the template directory to the project root, loading each\n * $helpers.ts file found. Later helpers (closer to template) override\n * earlier helpers (closer to root).\n *\n * @param fromDir - Template's directory\n * @param projectRoot - Project root directory\n * @returns Merged helper exports\n *\n * @example\n * ```typescript\n * const helpers = await loadHelpers('/project/sql/users', '/project')\n * // helpers.padId() from sql/users/$helpers.ts\n * // helpers.formatDate() from sql/$helpers.ts\n * ```\n */\nexport async function loadHelpers(\n fromDir: string,\n projectRoot: string,\n): Promise<Record<string, unknown>> {\n\n const helperPaths = await findHelperFiles(fromDir, projectRoot);\n const merged: Record<string, unknown> = {};\n\n for (const filepath of helperPaths) {\n\n const [mod, err] = await attempt(() => loadJs(filepath));\n\n if (err) {\n\n observer.emit('error', {\n source: 'template',\n error: err,\n context: { filepath, operation: 'load-helpers' },\n });\n continue;\n\n }\n\n // Merge exports (later files override earlier)\n if (mod && typeof mod === 'object') {\n\n const exports = mod as Record<string, unknown>;\n const exportCount = Object.keys(exports).length;\n\n Object.assign(merged, exports);\n\n observer.emit('template:helpers', {\n filepath,\n count: exportCount,\n });\n\n }\n\n }\n\n return merged;\n\n}\n","/**\n * JSON5 data loader.\n *\n * Loads .json and .json5 files using the JSON5 parser, which supports:\n * - Comments (single-line and block)\n * - Trailing commas\n * - Unquoted keys\n * - Single-quoted strings\n * - Multi-line strings\n *\n * @example\n * ```typescript\n * const data = await loadJson5('/path/to/config.json5')\n * ```\n */\nimport { readFile } from 'node:fs/promises';\n\nimport JSON5 from 'json5';\n\n/**\n * Load and parse a JSON5 file.\n *\n * @param filepath - Absolute path to the JSON5 file\n * @returns Parsed JSON5 data\n * @throws If file cannot be read or parsed\n */\nexport async function loadJson5(filepath: string): Promise<unknown> {\n\n const content = await readFile(filepath, 'utf-8');\n\n return JSON5.parse(content);\n\n}\n","/**\n * YAML data loader.\n *\n * Loads .yaml and .yml files using the yaml parser.\n *\n * @example\n * ```typescript\n * const data = await loadYaml('/path/to/config.yml')\n * ```\n */\nimport { readFile } from 'node:fs/promises';\n\nimport { parse } from 'yaml';\n\n/**\n * Load and parse a YAML file.\n *\n * @param filepath - Absolute path to the YAML file\n * @returns Parsed YAML data\n * @throws If file cannot be read or parsed\n */\nexport async function loadYaml(filepath: string): Promise<unknown> {\n\n const content = await readFile(filepath, 'utf-8');\n\n return parse(content);\n\n}\n","/**\n * CSV data loader.\n *\n * Loads .csv files using csv-parse with headers.\n * Returns an array of objects where keys are column headers.\n *\n * @example\n * ```typescript\n * const data = await loadCsv('/path/to/users.csv')\n * // → [{ name: 'Alice', email: 'alice@example.com' }, ...]\n * ```\n */\nimport { readFile } from 'node:fs/promises';\n\nimport { parse } from 'csv-parse/sync';\n\n/**\n * Load and parse a CSV file.\n *\n * @param filepath - Absolute path to the CSV file\n * @returns Array of row objects with header keys\n * @throws If file cannot be read or parsed\n */\nexport async function loadCsv(filepath: string): Promise<Record<string, string>[]> {\n\n const content = await readFile(filepath, 'utf-8');\n\n return parse(content, {\n columns: true,\n skip_empty_lines: true,\n trim: true,\n });\n\n}\n","/**\n * SQL file loader.\n *\n * Loads .sql files as raw text strings.\n * Used for including SQL fragments in templates.\n *\n * @example\n * ```typescript\n * const sql = await loadSql('/path/to/fragment.sql')\n * ```\n */\nimport { readFile } from 'node:fs/promises';\n\n/**\n * Load a SQL file as text.\n *\n * @param filepath - Absolute path to the SQL file\n * @returns The SQL file content as a string\n * @throws If file cannot be read\n */\nexport async function loadSql(filepath: string): Promise<string> {\n\n return readFile(filepath, 'utf-8');\n\n}\n","/**\n * Constants for the .dt format.\n *\n * Defines thresholds for smart compression decisions and type classification sets.\n */\nimport type { EncodedType, SimpleType } from './types.js';\n\n/**\n * Current .dt format version.\n */\nexport const FORMAT_VERSION = 1;\n\n/**\n * Minimum byte size before considering gzip compression.\n *\n * Values smaller than this threshold remain uncompressed for readability.\n * 128 bytes is the sweet spot: below this, gzip overhead exceeds savings.\n */\nexport const GZIP_THRESHOLD = 128;\n\n/**\n * Maximum gzip-to-raw ratio to accept compression.\n *\n * Gzip must save at least 15% (ratio < 0.85) to be worth the overhead.\n * Incompressible data (random bytes, already-compressed content) stays raw.\n */\nexport const GZIP_RATIO_THRESHOLD = 0.85;\n\n/**\n * Simple types stored as native JSON values.\n *\n * Uses boolean hash for O(1) lookups.\n */\nexport const SIMPLE_TYPES: { [key in SimpleType]: true } = {\n string: true,\n int: true,\n bigint: true,\n float: true,\n decimal: true,\n bool: true,\n timestamp: true,\n date: true,\n uuid: true,\n};\n\n/**\n * Encoded types that use `[value, encoding]` tuples.\n *\n * Uses boolean hash for O(1) lookups.\n */\nexport const ENCODED_TYPES: { [key in EncodedType]: true } = {\n json: true,\n binary: true,\n vector: true,\n array: true,\n text: true,\n custom: true,\n};\n\n/**\n * Valid .dt file extensions.\n */\nexport const DT_EXTENSIONS = {\n /** Raw .dt file (uncompressed JSON5 lines). */\n RAW: '.dt',\n /** Gzip-compressed .dt file. */\n COMPRESSED: '.dtz',\n /** Gzip-compressed and AES-256-GCM encrypted .dt file. */\n ENCRYPTED: '.dtzx',\n} as const;\n\n/**\n * Supported database dialects for .dt operations.\n */\nexport const DT_SUPPORTED_DIALECTS = ['postgres', 'mysql', 'mssql'] as const;\n","/**\n * Passphrase-based encryption for .dtzx files.\n *\n * Self-contained AES-256-GCM encryption using PBKDF2 key derivation.\n * Independent of the identity system — uses a user-provided passphrase.\n *\n * @example\n * ```typescript\n * import { encryptWithPassphrase, decryptWithPassphrase } from './crypto.js';\n *\n * const payload = encryptWithPassphrase(data, 'my-secret');\n * const decrypted = decryptWithPassphrase(payload, 'my-secret');\n * ```\n */\nimport {\n createCipheriv,\n createDecipheriv,\n randomBytes,\n pbkdf2Sync,\n} from 'node:crypto';\n\nimport type { DtEncryptedPayload } from './types.js';\n\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 16;\nconst SALT_LENGTH = 32;\nconst AUTH_TAG_LENGTH = 16;\nconst KEY_LENGTH = 32;\nconst PBKDF2_ITERATIONS = 100_000;\nconst PBKDF2_DIGEST = 'sha256';\n\n/**\n * Encrypt data with a passphrase using AES-256-GCM.\n *\n * Derives a key from the passphrase using PBKDF2 with a random salt.\n * Each call generates a new salt and IV for unique ciphertexts.\n *\n * @param data - Data to encrypt\n * @param passphrase - User-provided encryption passphrase\n * @returns Encrypted payload with salt, IV, authTag, and ciphertext\n *\n * @example\n * ```typescript\n * const compressed = gzipSync(fileContent);\n * const payload = encryptWithPassphrase(compressed, 'my-passphrase');\n * ```\n */\nexport function encryptWithPassphrase(data: Buffer, passphrase: string): DtEncryptedPayload {\n\n const salt = randomBytes(SALT_LENGTH);\n const iv = randomBytes(IV_LENGTH);\n\n const key = pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, PBKDF2_DIGEST);\n\n const cipher = createCipheriv(ALGORITHM, key, iv, {\n authTagLength: AUTH_TAG_LENGTH,\n });\n\n const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n return {\n salt: salt.toString('base64'),\n iv: iv.toString('base64'),\n authTag: authTag.toString('base64'),\n ciphertext: ciphertext.toString('base64'),\n };\n\n}\n\n/**\n * Decrypt a payload using a passphrase.\n *\n * Derives the same key using PBKDF2 with the stored salt,\n * then decrypts and verifies with AES-256-GCM.\n *\n * @param payload - Encrypted payload from encryptWithPassphrase\n * @param passphrase - Same passphrase used for encryption\n * @returns Decrypted data buffer\n * @throws If passphrase is wrong or data is tampered\n *\n * @example\n * ```typescript\n * const decrypted = decryptWithPassphrase(payload, 'my-passphrase');\n * const content = gunzipSync(decrypted);\n * ```\n */\nexport function decryptWithPassphrase(payload: DtEncryptedPayload, passphrase: string): Buffer {\n\n const salt = Buffer.from(payload.salt, 'base64');\n const iv = Buffer.from(payload.iv, 'base64');\n const authTag = Buffer.from(payload.authTag, 'base64');\n const ciphertext = Buffer.from(payload.ciphertext, 'base64');\n\n const key = pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, PBKDF2_DIGEST);\n\n const decipher = createDecipheriv(ALGORITHM, key, iv, {\n authTagLength: AUTH_TAG_LENGTH,\n });\n\n decipher.setAuthTag(authTag);\n\n const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);\n\n return decrypted;\n\n}\n","/**\n * Streaming .dt file reader.\n *\n * Reads .dt files with extension-based input handling:\n * - `.dt`: raw text readline\n * - `.dtz`: gunzip then readline\n * - `.dtzx`: decrypt then gunzip then readline\n *\n * @example\n * ```typescript\n * import { DtReader } from './reader.js';\n *\n * const reader = new DtReader({ filepath: './data/users.dtz' });\n * await reader.open();\n *\n * const schema = reader.schema;\n *\n * for await (const values of reader.rows()) {\n * // values is DtValue[]\n * }\n *\n * reader.close();\n * ```\n */\nimport { createReadStream, readFileSync } from 'node:fs';\nimport { createGunzip } from 'node:zlib';\nimport { gunzipSync } from 'node:zlib';\nimport { createInterface } from 'node:readline';\nimport { PassThrough } from 'node:stream';\nimport path from 'node:path';\nimport JSON5 from 'json5';\n\nimport type { Readable } from 'node:stream';\nimport type { DtSchema, DtValue, DtReaderOptions } from './types.js';\nimport { DT_EXTENSIONS } from './constants.js';\nimport { decryptWithPassphrase } from './crypto.js';\n\n/**\n * Streaming .dt file reader.\n *\n * Handles raw, compressed, and encrypted input based on file extension.\n * Provides schema access and async iteration over rows.\n */\nexport class DtReader {\n\n #filepath: string;\n #passphrase?: string;\n #extension: string;\n #schema: DtSchema | null = null;\n #lineReader: AsyncGenerator<DtValue[], void, undefined> | null = null;\n #inputStream: Readable | null = null;\n\n /**\n * Create a new DtReader.\n *\n * @param options - File path and optional passphrase for .dtzx\n */\n constructor(options: DtReaderOptions) {\n\n this.#filepath = options.filepath;\n this.#passphrase = options.passphrase;\n this.#extension = path.extname(options.filepath).toLowerCase();\n\n }\n\n /** The parsed schema from line 1. Available after open(). */\n get schema(): DtSchema | null {\n\n return this.#schema;\n\n }\n\n /**\n * Open the file and parse the schema header.\n *\n * Must be called before iterating rows.\n */\n async open(): Promise<void> {\n\n const readable = this.#createReadableStream();\n this.#inputStream = readable;\n\n // Read first line as schema\n const rl = createInterface({ input: readable, crlfDelay: Infinity });\n const lines = rl[Symbol.asyncIterator]();\n const firstLine = await lines.next();\n\n if (firstLine.done || !firstLine.value) {\n\n throw new Error('Empty .dt file — no schema line');\n\n }\n\n this.#schema = JSON5.parse(firstLine.value) as DtSchema;\n\n // Validate version\n if (this.#schema.v !== 1) {\n\n throw new Error(`Unsupported .dt format version: ${this.#schema.v}`);\n\n }\n\n // Store iterator for rows\n this.#lineReader = this.#createRowIterator(lines);\n\n }\n\n /**\n * Async generator yielding parsed row value arrays.\n *\n * Each yield is a `DtValue[]` in column order matching the schema.\n *\n * @example\n * ```typescript\n * for await (const values of reader.rows()) {\n * console.log(values); // [1, \"alice@example.com\", true, \"2024-01-15T10:30:00Z\"]\n * }\n * ```\n */\n async *rows(): AsyncGenerator<DtValue[], void, undefined> {\n\n if (!this.#lineReader) {\n\n throw new Error('Reader not opened — call open() first');\n\n }\n\n yield* this.#lineReader;\n\n }\n\n /**\n * Close the reader and release resources.\n */\n close(): void {\n\n if (this.#inputStream) {\n\n this.#inputStream.destroy();\n this.#inputStream = null;\n\n }\n\n }\n\n /**\n * Create the readable stream based on file extension.\n */\n #createReadableStream(): Readable {\n\n if (this.#extension === DT_EXTENSIONS.ENCRYPTED) {\n\n // .dtzx: read all, decrypt, gunzip, create readable from buffer\n if (!this.#passphrase) {\n\n throw new Error('Passphrase required for .dtzx files');\n\n }\n\n const encrypted = readFileSync(this.#filepath, 'utf8');\n const payload = JSON.parse(encrypted);\n const compressed = decryptWithPassphrase(payload, this.#passphrase);\n const raw = gunzipSync(compressed);\n\n const stream = new PassThrough();\n stream.end(raw);\n\n return stream;\n\n }\n\n if (this.#extension === DT_EXTENSIONS.COMPRESSED) {\n\n // .dtz: pipe through gunzip\n const fileStream = createReadStream(this.#filepath);\n const gunzip = createGunzip();\n\n fileStream.pipe(gunzip);\n\n return gunzip;\n\n }\n\n // .dt: raw file stream\n return createReadStream(this.#filepath, { encoding: 'utf8' });\n\n }\n\n /**\n * Create async generator from remaining readline lines.\n */\n async *#createRowIterator(\n lines: AsyncIterableIterator<string>,\n ): AsyncGenerator<DtValue[], void, undefined> {\n\n for await (const line of { [Symbol.asyncIterator]: () => lines }) {\n\n const trimmed = line.trim();\n\n if (trimmed.length === 0) continue;\n\n yield JSON5.parse(trimmed) as DtValue[];\n\n }\n\n }\n\n}\n","/**\n * .dt file loader for template seed data.\n *\n * Loads .dt and .dtz files as arrays of row objects.\n * Does NOT support .dtzx — no way to provide a decryption\n * key in the template context.\n *\n * @example\n * ```typescript\n * import { loadDt } from './dt.js';\n *\n * const rows = await loadDt('/path/to/seed.dt');\n * // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]\n * ```\n */\nimport { DtReader } from '../../dt/reader.js';\n\n/**\n * Load a .dt or .dtz file as an array of row objects.\n *\n * Reads the schema to get column names, then maps each row's\n * positional values to named fields.\n *\n * @param filepath - Path to .dt or .dtz file\n * @returns Array of row objects with column names as keys\n * @throws If the file cannot be read or parsed\n *\n * @example\n * ```typescript\n * // Given a .dt file with columns [id, name, active]:\n * const data = await loadDt('./seeds/users.dt');\n * // [{ id: 1, name: 'Alice', active: true }, ...]\n * ```\n */\nexport async function loadDt(filepath: string): Promise<Record<string, unknown>[]> {\n\n const reader = new DtReader({ filepath });\n\n await reader.open();\n\n const schema = reader.schema;\n\n if (!schema) {\n\n throw new Error(`Failed to read .dt schema from: ${filepath}`);\n\n }\n\n const columnNames = schema.columns.map((c) => c.name);\n const rows: Record<string, unknown>[] = [];\n\n for await (const values of reader.rows()) {\n\n const row: Record<string, unknown> = {};\n\n for (let i = 0; i < columnNames.length; i++) {\n\n row[columnNames[i]!] = values[i] ?? null;\n\n }\n\n rows.push(row);\n\n }\n\n reader.close();\n\n return rows;\n\n}\n","/**\n * Loader registry for data files.\n *\n * Maps file extensions to their respective loader functions.\n * Provides a unified interface for loading any supported data format.\n *\n * @example\n * ```typescript\n * import { loadDataFile, getLoader, hasLoader } from './loaders'\n *\n * if (hasLoader('.json5')) {\n * const data = await loadDataFile('/path/to/config.json5')\n * }\n * ```\n */\nimport path from 'node:path';\n\nimport type { Loader, LoaderRegistry } from '../types.js';\nimport { loadJson5 } from './json5.js';\nimport { loadYaml } from './yaml.js';\nimport { loadCsv } from './csv.js';\nimport { loadJs } from './js.js';\nimport { loadSql } from './sql.js';\nimport { loadDt } from './dt.js';\n\n/**\n * Registry of loaders by file extension.\n */\nconst loaders: LoaderRegistry = {\n '.json': loadJson5,\n '.json5': loadJson5,\n '.yaml': loadYaml,\n '.yml': loadYaml,\n '.csv': loadCsv,\n '.js': loadJs,\n '.mjs': loadJs,\n '.ts': loadJs,\n '.sql': loadSql,\n '.dt': loadDt,\n '.dtz': loadDt,\n // NO .dtzx — can't provide passphrase in template context\n};\n\n/**\n * Check if a loader exists for the given extension.\n *\n * @param ext - File extension (e.g., '.json5')\n * @returns True if a loader is registered for this extension\n */\nexport function hasLoader(ext: string): boolean {\n\n return ext in loaders;\n\n}\n\n/**\n * Get the loader function for a file extension.\n *\n * @param ext - File extension (e.g., '.json5')\n * @returns The loader function, or undefined if not found\n */\nexport function getLoader(ext: string): Loader | undefined {\n\n return loaders[ext];\n\n}\n\n/**\n * Load a data file using the appropriate loader.\n *\n * Determines the loader from the file extension and loads the file.\n *\n * @param filepath - Absolute path to the data file\n * @returns The loaded and parsed data\n * @throws If no loader exists for the file extension\n * @throws If the file cannot be loaded or parsed\n *\n * @example\n * ```typescript\n * const users = await loadDataFile('/path/to/users.json5')\n * const config = await loadDataFile('/path/to/config.yml')\n * ```\n */\nexport async function loadDataFile(filepath: string): Promise<unknown> {\n\n const ext = path.extname(filepath).toLowerCase();\n const loader = getLoader(ext);\n\n if (!loader) {\n\n throw new Error(`No loader registered for extension: ${ext}`);\n\n }\n\n return loader(filepath);\n\n}\n\n/**\n * Get all supported data file extensions.\n *\n * @returns Array of supported extensions (e.g., ['.json', '.json5', '.yml', ...])\n */\nexport function getSupportedExtensions(): string[] {\n\n return Object.keys(loaders);\n\n}\n\n// Re-export individual loaders for direct use\nexport { loadJson5 } from './json5.js';\nexport { loadYaml } from './yaml.js';\nexport { loadCsv } from './csv.js';\nexport { loadJs } from './js.js';\nexport { loadSql } from './sql.js';\nexport { loadDt } from './dt.js';\n","/**\n * String transformation utilities using Voca.\n *\n * Provides consistent string transformations for converting filenames\n * to context property names.\n *\n * @example\n * ```typescript\n * import { toContextKey } from './utils'\n *\n * toContextKey('my-config.json5') // → 'myConfig'\n * toContextKey('seed_data.yml') // → 'seedData'\n * toContextKey('API_KEYS.json') // → 'apiKeys'\n * ```\n */\nimport v from 'voca';\nimport path from 'node:path';\n\n/**\n * Convert a filename to a camelCase context key.\n *\n * Strips the file extension and converts the base name to camelCase.\n * Handles kebab-case, snake_case, and SCREAMING_CASE.\n *\n * @param filename - The filename to convert (e.g., 'my-config.json5')\n * @returns The camelCase key (e.g., 'myConfig')\n *\n * @example\n * ```typescript\n * toContextKey('my-config.json5') // → 'myConfig'\n * toContextKey('seed_data.yml') // → 'seedData'\n * toContextKey('API_KEYS.json') // → 'apiKeys'\n * toContextKey('users.csv') // → 'users'\n * ```\n */\nexport function toContextKey(filename: string): string {\n\n // Get basename without extension\n const ext = path.extname(filename);\n const base = path.basename(filename, ext);\n\n // Convert to camelCase\n return v.camelCase(base);\n\n}\n\n/**\n * SQL-escape a string value.\n *\n * Escapes single quotes by doubling them, which is the standard\n * SQL escape sequence for string literals.\n *\n * @param value - The string to escape\n * @returns The escaped string (without surrounding quotes)\n *\n * @example\n * ```typescript\n * sqlEscape(\"O'Brien\") // → \"O''Brien\"\n * sqlEscape(\"normal\") // → \"normal\"\n * ```\n */\nexport function sqlEscape(value: string): string {\n\n return value.replace(/'/g, \"''\");\n\n}\n\n/**\n * SQL-escape and wrap in single quotes.\n *\n * Handles null values and various types appropriately.\n *\n * @param value - The value to quote\n * @returns The quoted SQL literal\n *\n * @example\n * ```typescript\n * sqlQuote(\"O'Brien\") // → \"'O''Brien'\"\n * sqlQuote(42) // → \"'42'\"\n * sqlQuote(null) // → \"NULL\"\n * sqlQuote(true) // → \"'true'\"\n * ```\n */\nexport function sqlQuote(value: string | number | boolean | null): string {\n\n if (value === null) {\n\n return 'NULL';\n\n }\n\n return `'${sqlEscape(String(value))}'`;\n\n}\n\n/**\n * Generate a UUID v4.\n *\n * Uses crypto.randomUUID() for secure random generation.\n *\n * @returns A UUID v4 string\n *\n * @example\n * ```typescript\n * generateUuid() // → \"550e8400-e29b-41d4-a716-446655440000\"\n * ```\n */\nexport function generateUuid(): string {\n\n return crypto.randomUUID();\n\n}\n\n/**\n * Get current ISO timestamp.\n *\n * @returns ISO 8601 timestamp string\n *\n * @example\n * ```typescript\n * isoNow() // → \"2024-01-15T10:30:00.000Z\"\n * ```\n */\nexport function isoNow(): string {\n\n return new Date().toISOString();\n\n}\n","/**\n * Template context builder.\n *\n * Builds the $ context object available in templates by:\n * 1. Loading inherited helpers from $helpers.ts files\n * 2. Auto-loading data files from the template's directory\n * 3. Adding config, secrets, env, and built-in helpers\n *\n * @example\n * ```typescript\n * import { buildContext } from './context'\n *\n * const ctx = await buildContext('/project/sql/users/001_create.sql.tmpl', {\n * projectRoot: '/project',\n * config: activeConfig,\n * secrets: { API_KEY: '...' },\n * })\n *\n * // ctx now has: $.padId, $.users, $.config, $.secrets, $.quote, etc.\n * ```\n */\nimport path from 'node:path';\nimport { readdir } from 'node:fs/promises';\n\nimport { attempt } from '@logosdx/utils';\n\nimport { observer } from '../observer.js';\nimport type { TemplateContext, RenderOptions } from './types.js';\nimport { HELPER_FILENAME } from './types.js';\nimport { loadHelpers } from './helpers.js';\nimport { loadDataFile, hasLoader } from './loaders/index.js';\nimport { toContextKey, sqlEscape, sqlQuote, generateUuid, isoNow } from './utils.js';\n\n/**\n * Build the template context ($) for a template file.\n *\n * @param templatePath - Absolute path to the template file\n * @param options - Render options (config, secrets, projectRoot)\n * @returns The complete template context\n */\nexport async function buildContext(\n templatePath: string,\n options: RenderOptions = {},\n): Promise<TemplateContext> {\n\n const templateDir = path.dirname(templatePath);\n const projectRoot = options.projectRoot ?? process.cwd();\n\n // 1. Load inherited helpers\n const helpers = await loadHelpers(templateDir, projectRoot);\n\n // 2. Auto-load data files from template directory\n const dataFiles = await loadDataFilesInDir(templateDir);\n\n // 3. Check if data files include a config file\n const hasLocalConfig = 'config' in dataFiles;\n\n // 4. Build context with all components\n const ctx: TemplateContext = {\n // Inherited helpers (can be overridden by data files with same name)\n ...helpers,\n\n // Auto-loaded data files\n ...dataFiles,\n\n // Config (only if no local config.* file)\n ...(hasLocalConfig ? {} : { config: options.config }),\n\n // Secrets\n secrets: options.secrets ?? {},\n globalSecrets: options.globalSecrets ?? {},\n\n // Environment\n env: process.env as Record<string, string | undefined>,\n\n // Built-in helpers\n include: createIncludeHelper(templateDir, projectRoot, options),\n escape: sqlEscape,\n quote: sqlQuote,\n json: (value: unknown) => JSON.stringify(value),\n now: isoNow,\n uuid: generateUuid,\n };\n\n return ctx;\n\n}\n\n/**\n * Load all data files in a directory.\n *\n * Scans the directory for supported data file extensions and loads each one.\n * File names are converted to camelCase context keys.\n *\n * @param dir - Directory to scan\n * @returns Object with camelCased keys and loaded data\n */\nasync function loadDataFilesInDir(dir: string): Promise<Record<string, unknown>> {\n\n const data: Record<string, unknown> = {};\n\n const [entries, readErr] = await attempt(() => readdir(dir, { withFileTypes: true }));\n\n if (readErr || !entries) {\n\n observer.emit('error', {\n source: 'template',\n error: readErr ?? new Error('Failed to read directory'),\n context: { dir, operation: 'scan-data-files' },\n });\n\n return data;\n\n }\n\n for (const entry of entries) {\n\n // Skip directories\n if (!entry.isFile()) {\n\n continue;\n\n }\n\n // Skip helper files\n if (entry.name.startsWith(HELPER_FILENAME)) {\n\n continue;\n\n }\n\n // Skip template files\n if (entry.name.endsWith('.tmpl')) {\n\n continue;\n\n }\n\n const ext = path.extname(entry.name).toLowerCase();\n\n // Skip unsupported extensions\n if (!hasLoader(ext)) {\n\n continue;\n\n }\n\n // Skip .sql files in data loading (they're for include())\n if (ext === '.sql') {\n\n continue;\n\n }\n\n const filepath = path.join(dir, entry.name);\n const key = toContextKey(entry.name);\n\n const [loaded, loadErr] = await attempt(() => loadDataFile(filepath));\n\n if (loadErr) {\n\n observer.emit('error', {\n source: 'template',\n error: loadErr,\n context: { filepath, operation: 'load-data-file' },\n });\n continue;\n\n }\n\n data[key] = loaded;\n\n observer.emit('template:load', {\n filepath,\n format: ext,\n });\n\n }\n\n return data;\n\n}\n\n/**\n * Create the include() helper function.\n *\n * The include helper resolves paths relative to the template's directory\n * and cannot escape the project root. If the included file is a template\n * (.sql.tmpl), it will be rendered recursively with the same options.\n *\n * @param templateDir - Template's directory\n * @param projectRoot - Project root (cannot escape)\n * @param options - Render options for nested templates\n * @returns The include helper function\n */\nfunction createIncludeHelper(\n templateDir: string,\n projectRoot: string,\n options: RenderOptions,\n): (includePath: string) => Promise<string> {\n\n return async (includePath: string): Promise<string> => {\n\n // Resolve path relative to template directory\n const resolved = path.resolve(templateDir, includePath);\n\n // Security: ensure we don't escape project root\n if (!resolved.startsWith(projectRoot)) {\n\n throw new Error(`Include path escapes project root: ${includePath}`);\n\n }\n\n // If it's a template, render it recursively\n if (resolved.endsWith('.tmpl')) {\n\n // Dynamic import to avoid circular dependency\n const { processFile } = await import('./engine.js');\n const result = await processFile(resolved, options);\n\n return result.sql;\n\n }\n\n // Load raw file\n const [content, err] = await attempt(() => loadDataFile(resolved));\n\n if (err) {\n\n throw new Error(`Failed to include '${includePath}': ${err.message}`);\n\n }\n\n if (typeof content === 'string') {\n\n return content;\n\n }\n\n // Non-string content (shouldn't happen for .sql files)\n return JSON.stringify(content);\n\n };\n\n}\n","/**\n * Template engine using Eta.\n *\n * Wraps Eta with noorm's custom syntax and integrates with the context builder\n * for auto-loading data files and inherited helpers.\n *\n * @example\n * ```typescript\n * import { processFile, renderTemplate } from './engine'\n *\n * // Process any SQL file (template or raw)\n * const result = await processFile('/path/to/file.sql.tmpl', {\n * config: activeConfig,\n * secrets: { API_KEY: '...' },\n * })\n *\n * // Or render a template string directly\n * const sql = await renderTemplate(\n * '{% for (const r of $.roles) { %}...',\n * context,\n * )\n * ```\n */\nimport { readFile } from 'node:fs/promises';\n\nimport { Eta } from 'eta';\n\nimport { observer } from '../observer.js';\nimport type { TemplateContext, RenderOptions, ProcessResult } from './types.js';\nimport { TEMPLATE_EXTENSION } from './types.js';\nimport { buildContext } from './context.js';\n\n/**\n * Eta instance configured with noorm's custom syntax.\n *\n * Custom delimiters:\n * - `{% %}` for JavaScript code (instead of `<% %>`)\n * - `{%~ %}` for raw output (instead of `<%~ %>`)\n * - `$` as the context variable (instead of `it`)\n */\nconst eta = new Eta({\n // Custom tags for code blocks\n tags: ['{%', '%}'],\n\n // Variable name for context ($ instead of it)\n varName: '$',\n\n // Don't auto-escape (SQL doesn't need HTML escaping)\n autoEscape: false,\n\n // Allow async functions in templates\n useWith: false,\n\n // Don't cache templates (we handle caching at a higher level)\n cache: false,\n});\n\n/**\n * Check if a file path is a template.\n *\n * @param filepath - File path to check\n * @returns True if the file has the .tmpl extension\n */\nexport function isTemplate(filepath: string): boolean {\n\n return filepath.endsWith(TEMPLATE_EXTENSION);\n\n}\n\n/**\n * Render a template string with the given context.\n *\n * @param template - Template string with Eta syntax\n * @param context - The $ context object\n * @returns Rendered SQL string\n *\n * @example\n * ```typescript\n * const sql = await renderTemplate(\n * '{% for (const role of $.roles) { %}INSERT INTO roles (name) VALUES ({%~ $.quote(role) %});\\n{% } %}',\n * { roles: ['admin', 'user'], quote: sqlQuote }\n * )\n * ```\n */\nexport async function renderTemplate(template: string, context: TemplateContext): Promise<string> {\n\n return eta.renderStringAsync(template, context);\n\n}\n\n/**\n * Process a SQL file.\n *\n * If the file is a template (.sql.tmpl), renders it with the context.\n * If it's a raw SQL file (.sql), returns the content as-is.\n *\n * @param filepath - Absolute path to the SQL file\n * @param options - Render options (config, secrets, projectRoot)\n * @returns Process result with SQL content and metadata\n *\n * @example\n * ```typescript\n * const result = await processFile('/project/sql/users/001_create.sql.tmpl', {\n * projectRoot: '/project',\n * config: { name: 'dev', connection: { ... } },\n * secrets: { API_KEY: 'secret123' },\n * })\n *\n * console.log(result.sql) // Rendered SQL\n * console.log(result.isTemplate) // true\n * console.log(result.durationMs) // 12\n * ```\n */\nexport async function processFile(\n filepath: string,\n options: RenderOptions = {},\n): Promise<ProcessResult> {\n\n // Read file content\n const content = await readFile(filepath, 'utf-8');\n\n // If not a template, return raw content\n if (!isTemplate(filepath)) {\n\n return {\n sql: content,\n isTemplate: false,\n };\n\n }\n\n // It's a template - build context and render\n const start = performance.now();\n\n const context = await buildContext(filepath, options);\n const sql = await renderTemplate(content, context);\n\n const durationMs = performance.now() - start;\n\n observer.emit('template:render', {\n filepath,\n durationMs,\n });\n\n return {\n sql,\n isTemplate: true,\n durationMs,\n };\n\n}\n\n/**\n * Process multiple SQL files.\n *\n * @param filepaths - Array of file paths to process\n * @param options - Render options\n * @returns Array of process results\n */\nexport async function processFiles(\n filepaths: string[],\n options: RenderOptions = {},\n): Promise<ProcessResult[]> {\n\n const results: ProcessResult[] = [];\n\n for (const filepath of filepaths) {\n\n const result = await processFile(filepath, options);\n results.push(result);\n\n }\n\n return results;\n\n}\n\n// Export the Eta instance for advanced usage\nexport { eta };\n"]}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { observer } from './chunk-4HW4CS2C.js';
|
|
2
|
+
import { attempt, attemptSync } from '@logosdx/utils';
|
|
3
|
+
import { generateKeyPairSync, randomBytes, createCipheriv, createPrivateKey, createPublicKey, diffieHellman, hkdfSync, createDecipheriv } from 'crypto';
|
|
4
|
+
|
|
5
|
+
// src/core/shared/tables.ts
|
|
6
|
+
var NOORM_TABLES = Object.freeze({
|
|
7
|
+
/** Version tracking table */
|
|
8
|
+
version: "__noorm_version__",
|
|
9
|
+
/** Change/operation tracking table */
|
|
10
|
+
change: "__noorm_change__",
|
|
11
|
+
/** File execution tracking table */
|
|
12
|
+
executions: "__noorm_executions__",
|
|
13
|
+
/** Concurrent operation lock table */
|
|
14
|
+
lock: "__noorm_lock__",
|
|
15
|
+
/** Team member identity table */
|
|
16
|
+
identities: "__noorm_identities__",
|
|
17
|
+
/** Vault secrets table */
|
|
18
|
+
vault: "__noorm_vault__"
|
|
19
|
+
});
|
|
20
|
+
function generateVaultKey() {
|
|
21
|
+
return randomBytes(32);
|
|
22
|
+
}
|
|
23
|
+
function deriveSharedSecret(privateKeyHex, publicKeyHex) {
|
|
24
|
+
const privateKey = createPrivateKey({
|
|
25
|
+
key: Buffer.from(privateKeyHex, "hex"),
|
|
26
|
+
format: "der",
|
|
27
|
+
type: "pkcs8"
|
|
28
|
+
});
|
|
29
|
+
const publicKey = createPublicKey({
|
|
30
|
+
key: Buffer.from(publicKeyHex, "hex"),
|
|
31
|
+
format: "der",
|
|
32
|
+
type: "spki"
|
|
33
|
+
});
|
|
34
|
+
return diffieHellman({
|
|
35
|
+
privateKey,
|
|
36
|
+
publicKey
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function deriveEncryptionKey(sharedSecret, info) {
|
|
40
|
+
return Buffer.from(
|
|
41
|
+
hkdfSync(
|
|
42
|
+
"sha256",
|
|
43
|
+
sharedSecret,
|
|
44
|
+
Buffer.alloc(0),
|
|
45
|
+
info,
|
|
46
|
+
32
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
function encryptVaultKey(vaultKey, recipientPubKey) {
|
|
51
|
+
const { publicKey, privateKey } = generateKeyPairSync("x25519", {
|
|
52
|
+
publicKeyEncoding: { type: "spki", format: "der" },
|
|
53
|
+
privateKeyEncoding: { type: "pkcs8", format: "der" }
|
|
54
|
+
});
|
|
55
|
+
const ephemeralPrivateHex = privateKey.toString("hex");
|
|
56
|
+
const ephemeralPublicHex = publicKey.toString("hex");
|
|
57
|
+
const sharedSecret = deriveSharedSecret(ephemeralPrivateHex, recipientPubKey);
|
|
58
|
+
const encKey = deriveEncryptionKey(sharedSecret, "noorm-vault-key");
|
|
59
|
+
const iv = randomBytes(16);
|
|
60
|
+
const cipher = createCipheriv("aes-256-gcm", encKey, iv);
|
|
61
|
+
const ciphertext = Buffer.concat([cipher.update(vaultKey), cipher.final()]);
|
|
62
|
+
const authTag = cipher.getAuthTag();
|
|
63
|
+
return {
|
|
64
|
+
ephemeralPubKey: ephemeralPublicHex,
|
|
65
|
+
iv: iv.toString("hex"),
|
|
66
|
+
authTag: authTag.toString("hex"),
|
|
67
|
+
ciphertext: ciphertext.toString("hex")
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function decryptVaultKey(encrypted, privateKey) {
|
|
71
|
+
const [result, err] = attemptSync(() => {
|
|
72
|
+
const sharedSecret = deriveSharedSecret(privateKey, encrypted.ephemeralPubKey);
|
|
73
|
+
const decKey = deriveEncryptionKey(sharedSecret, "noorm-vault-key");
|
|
74
|
+
const decipher = createDecipheriv(
|
|
75
|
+
"aes-256-gcm",
|
|
76
|
+
decKey,
|
|
77
|
+
Buffer.from(encrypted.iv, "hex")
|
|
78
|
+
);
|
|
79
|
+
decipher.setAuthTag(Buffer.from(encrypted.authTag, "hex"));
|
|
80
|
+
const vaultKey = Buffer.concat([
|
|
81
|
+
decipher.update(Buffer.from(encrypted.ciphertext, "hex")),
|
|
82
|
+
decipher.final()
|
|
83
|
+
]);
|
|
84
|
+
return vaultKey;
|
|
85
|
+
});
|
|
86
|
+
if (err) return null;
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function encryptSecret(value, vaultKey) {
|
|
90
|
+
const iv = randomBytes(16);
|
|
91
|
+
const cipher = createCipheriv("aes-256-gcm", vaultKey, iv);
|
|
92
|
+
const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
93
|
+
const authTag = cipher.getAuthTag();
|
|
94
|
+
return {
|
|
95
|
+
iv: iv.toString("hex"),
|
|
96
|
+
authTag: authTag.toString("hex"),
|
|
97
|
+
ciphertext: ciphertext.toString("hex")
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function decryptSecret(encrypted, vaultKey) {
|
|
101
|
+
const [result, err] = attemptSync(() => {
|
|
102
|
+
const decipher = createDecipheriv(
|
|
103
|
+
"aes-256-gcm",
|
|
104
|
+
vaultKey,
|
|
105
|
+
Buffer.from(encrypted.iv, "hex")
|
|
106
|
+
);
|
|
107
|
+
decipher.setAuthTag(Buffer.from(encrypted.authTag, "hex"));
|
|
108
|
+
const plaintext = Buffer.concat([
|
|
109
|
+
decipher.update(Buffer.from(encrypted.ciphertext, "hex")),
|
|
110
|
+
decipher.final()
|
|
111
|
+
]);
|
|
112
|
+
return plaintext.toString("utf8");
|
|
113
|
+
});
|
|
114
|
+
if (err) return null;
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/core/vault/storage.ts
|
|
119
|
+
async function initializeVault(db, identityHash, publicKey) {
|
|
120
|
+
const [existing, checkErr] = await attempt(async () => {
|
|
121
|
+
return db.selectFrom(NOORM_TABLES.identities).select("encrypted_vault_key").where("encrypted_vault_key", "is not", null).limit(1).executeTakeFirst();
|
|
122
|
+
});
|
|
123
|
+
if (checkErr) return [null, checkErr];
|
|
124
|
+
if (existing?.encrypted_vault_key) {
|
|
125
|
+
return [null, new Error("Vault already initialized")];
|
|
126
|
+
}
|
|
127
|
+
const vaultKey = generateVaultKey();
|
|
128
|
+
const encrypted = encryptVaultKey(vaultKey, publicKey);
|
|
129
|
+
const encryptedJson = JSON.stringify(encrypted);
|
|
130
|
+
const [, updateErr] = await attempt(async () => {
|
|
131
|
+
return db.updateTable(NOORM_TABLES.identities).set({ encrypted_vault_key: encryptedJson }).where("identity_hash", "=", identityHash).execute();
|
|
132
|
+
});
|
|
133
|
+
if (updateErr) return [null, updateErr];
|
|
134
|
+
observer.emit("vault:initialized", { identityHash });
|
|
135
|
+
return [vaultKey, null];
|
|
136
|
+
}
|
|
137
|
+
async function getVaultKey(db, identityHash, privateKey) {
|
|
138
|
+
const [row, err] = await attempt(async () => {
|
|
139
|
+
return db.selectFrom(NOORM_TABLES.identities).select("encrypted_vault_key").where("identity_hash", "=", identityHash).executeTakeFirst();
|
|
140
|
+
});
|
|
141
|
+
if (err || !row?.encrypted_vault_key) return null;
|
|
142
|
+
const encryptedValue = row.encrypted_vault_key;
|
|
143
|
+
const [parsed, parseErr] = attemptSync(
|
|
144
|
+
() => JSON.parse(encryptedValue)
|
|
145
|
+
);
|
|
146
|
+
if (parseErr) return null;
|
|
147
|
+
return decryptVaultKey(parsed, privateKey);
|
|
148
|
+
}
|
|
149
|
+
async function setVaultSecret(db, vaultKey, secretKey, value, setBy) {
|
|
150
|
+
const encrypted = encryptSecret(value, vaultKey);
|
|
151
|
+
const encryptedJson = JSON.stringify(encrypted);
|
|
152
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
153
|
+
const [existing, checkErr] = await attempt(async () => {
|
|
154
|
+
return db.selectFrom(NOORM_TABLES.vault).select("id").where("secret_key", "=", secretKey).executeTakeFirst();
|
|
155
|
+
});
|
|
156
|
+
if (checkErr) return [void 0, checkErr];
|
|
157
|
+
if (existing) {
|
|
158
|
+
const [, updateErr] = await attempt(async () => {
|
|
159
|
+
return db.updateTable(NOORM_TABLES.vault).set({
|
|
160
|
+
encrypted_value: encryptedJson,
|
|
161
|
+
set_by: setBy,
|
|
162
|
+
updated_at: now
|
|
163
|
+
}).where("secret_key", "=", secretKey).execute();
|
|
164
|
+
});
|
|
165
|
+
if (updateErr) return [void 0, updateErr];
|
|
166
|
+
observer.emit("vault:secret:updated", { key: secretKey, setBy });
|
|
167
|
+
} else {
|
|
168
|
+
const [, insertErr] = await attempt(async () => {
|
|
169
|
+
return db.insertInto(NOORM_TABLES.vault).values({
|
|
170
|
+
secret_key: secretKey,
|
|
171
|
+
encrypted_value: encryptedJson,
|
|
172
|
+
set_by: setBy
|
|
173
|
+
}).execute();
|
|
174
|
+
});
|
|
175
|
+
if (insertErr) return [void 0, insertErr];
|
|
176
|
+
observer.emit("vault:secret:created", { key: secretKey, setBy });
|
|
177
|
+
}
|
|
178
|
+
return [void 0, null];
|
|
179
|
+
}
|
|
180
|
+
async function getVaultSecret(db, vaultKey, secretKey) {
|
|
181
|
+
const [row, err] = await attempt(async () => {
|
|
182
|
+
return db.selectFrom(NOORM_TABLES.vault).select("encrypted_value").where("secret_key", "=", secretKey).executeTakeFirst();
|
|
183
|
+
});
|
|
184
|
+
if (err || !row) return null;
|
|
185
|
+
const [parsed, parseErr] = attemptSync(
|
|
186
|
+
() => JSON.parse(row.encrypted_value)
|
|
187
|
+
);
|
|
188
|
+
if (parseErr) return null;
|
|
189
|
+
return decryptSecret(parsed, vaultKey);
|
|
190
|
+
}
|
|
191
|
+
async function getAllVaultSecrets(db, vaultKey) {
|
|
192
|
+
const [rows, err] = await attempt(async () => {
|
|
193
|
+
return db.selectFrom(NOORM_TABLES.vault).selectAll().execute();
|
|
194
|
+
});
|
|
195
|
+
if (err || !rows) return {};
|
|
196
|
+
const secrets = {};
|
|
197
|
+
for (const row of rows) {
|
|
198
|
+
const [parsed, parseErr] = attemptSync(
|
|
199
|
+
() => JSON.parse(row.encrypted_value)
|
|
200
|
+
);
|
|
201
|
+
if (parseErr) continue;
|
|
202
|
+
const value = decryptSecret(parsed, vaultKey);
|
|
203
|
+
if (!value) continue;
|
|
204
|
+
secrets[row.secret_key] = {
|
|
205
|
+
key: row.secret_key,
|
|
206
|
+
value,
|
|
207
|
+
setBy: row.set_by,
|
|
208
|
+
createdAt: new Date(row.created_at),
|
|
209
|
+
updatedAt: new Date(row.updated_at)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return secrets;
|
|
213
|
+
}
|
|
214
|
+
async function listVaultSecretKeys(db) {
|
|
215
|
+
const [rows, err] = await attempt(async () => {
|
|
216
|
+
return db.selectFrom(NOORM_TABLES.vault).select("secret_key").orderBy("secret_key", "asc").execute();
|
|
217
|
+
});
|
|
218
|
+
if (err || !rows) return [];
|
|
219
|
+
return rows.map((r) => r.secret_key);
|
|
220
|
+
}
|
|
221
|
+
async function deleteVaultSecret(db, secretKey) {
|
|
222
|
+
const [result, err] = await attempt(async () => {
|
|
223
|
+
return db.deleteFrom(NOORM_TABLES.vault).where("secret_key", "=", secretKey).execute();
|
|
224
|
+
});
|
|
225
|
+
if (err || !result) return [false, err ?? new Error("Delete failed")];
|
|
226
|
+
const firstResult = result[0];
|
|
227
|
+
const deleted = !!firstResult && Number(firstResult.numDeletedRows) > 0;
|
|
228
|
+
if (deleted) {
|
|
229
|
+
observer.emit("vault:secret:deleted", { key: secretKey });
|
|
230
|
+
}
|
|
231
|
+
return [deleted, null];
|
|
232
|
+
}
|
|
233
|
+
async function vaultSecretExists(db, secretKey) {
|
|
234
|
+
const [row, err] = await attempt(async () => {
|
|
235
|
+
return db.selectFrom(NOORM_TABLES.vault).select("id").where("secret_key", "=", secretKey).executeTakeFirst();
|
|
236
|
+
});
|
|
237
|
+
if (err) return false;
|
|
238
|
+
return !!row;
|
|
239
|
+
}
|
|
240
|
+
async function getVaultStatus(db, identityHash) {
|
|
241
|
+
const [secretRows] = await attempt(async () => {
|
|
242
|
+
return db.selectFrom(NOORM_TABLES.vault).select(db.fn.count("id").as("count")).executeTakeFirst();
|
|
243
|
+
});
|
|
244
|
+
const secretCount = secretRows ? Number(secretRows.count) : 0;
|
|
245
|
+
const [identityRows] = await attempt(async () => {
|
|
246
|
+
return db.selectFrom(NOORM_TABLES.identities).select([
|
|
247
|
+
"identity_hash",
|
|
248
|
+
"encrypted_vault_key"
|
|
249
|
+
]).execute();
|
|
250
|
+
});
|
|
251
|
+
const identities = identityRows ?? [];
|
|
252
|
+
const usersWithAccess = identities.filter((i) => i.encrypted_vault_key !== null).length;
|
|
253
|
+
const usersWithoutAccess = identities.filter((i) => i.encrypted_vault_key === null).length;
|
|
254
|
+
const isInitialized = usersWithAccess > 0;
|
|
255
|
+
const currentUser = identities.find((i) => i.identity_hash === identityHash);
|
|
256
|
+
const hasAccess = currentUser?.encrypted_vault_key !== null;
|
|
257
|
+
return {
|
|
258
|
+
isInitialized,
|
|
259
|
+
hasAccess,
|
|
260
|
+
secretCount,
|
|
261
|
+
usersWithAccess,
|
|
262
|
+
usersWithoutAccess
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export { NOORM_TABLES, deleteVaultSecret, encryptVaultKey, getAllVaultSecrets, getVaultKey, getVaultSecret, getVaultStatus, initializeVault, listVaultSecretKeys, setVaultSecret, vaultSecretExists };
|
|
267
|
+
//# sourceMappingURL=chunk-ZRZ7AECF.js.map
|
|
268
|
+
//# sourceMappingURL=chunk-ZRZ7AECF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/shared/tables.ts","../../../src/core/vault/key.ts","../../../src/core/vault/storage.ts"],"names":["attemptSync"],"mappings":"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","file":"chunk-ZRZ7AECF.js","sourcesContent":["/**\n * Kysely table types for noorm tracking tables.\n *\n * These types define the shape of the database tables used by noorm\n * to track changes, executions, locks, identities, and versions.\n *\n * For full schema documentation, see plan/datamodel.md\n *\n * WHY: Kysely uses these types to provide type-safe queries.\n * They match the database schema created by migrations.\n */\nimport type { Generated, Insertable, Selectable, Updateable } from 'kysely';\n\n// ─────────────────────────────────────────────────────────────\n// Table Names\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Noorm tracking table names.\n *\n * Use these constants instead of hardcoding table names.\n *\n * @example\n * ```typescript\n * import { NOORM_TABLES } from './shared'\n *\n * await db.selectFrom(NOORM_TABLES.version).selectAll().execute()\n * ```\n */\nexport const NOORM_TABLES = Object.freeze({\n /** Version tracking table */\n version: '__noorm_version__' as const,\n\n /** Change/operation tracking table */\n change: '__noorm_change__' as const,\n\n /** File execution tracking table */\n executions: '__noorm_executions__' as const,\n\n /** Concurrent operation lock table */\n lock: '__noorm_lock__' as const,\n\n /** Team member identity table */\n identities: '__noorm_identities__' as const,\n\n /** Vault secrets table */\n vault: '__noorm_vault__' as const,\n});\n\n/**\n * Type for table names.\n */\nexport type NoormTableName = (typeof NOORM_TABLES)[keyof typeof NOORM_TABLES];\n\n// ─────────────────────────────────────────────────────────────\n// __noorm_version__\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Version tracking table.\n *\n * Tracks noorm CLI version and all internal schema versions for migrations.\n * See: plan/datamodel.md#__noorm_version__\n */\nexport interface NoormVersionTable {\n /** Primary key */\n id: Generated<number>;\n\n /** CLI semver (e.g., \"1.2.3\") */\n cli_version: string;\n\n /** Database tracking tables version */\n noorm_version: number;\n\n /** State file (state.enc) schema version */\n state_version: number;\n\n /** Settings file (settings.yml) schema version */\n settings_version: number;\n\n /** First installation timestamp */\n installed_at: Generated<Date>;\n\n /** Last upgrade timestamp */\n upgraded_at: Generated<Date>;\n}\n\nexport type NoormVersion = Selectable<NoormVersionTable>;\nexport type NewNoormVersion = Insertable<NoormVersionTable>;\nexport type NoormVersionUpdate = Updateable<NoormVersionTable>;\n\n// ─────────────────────────────────────────────────────────────\n// __noorm_change__\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Operation status values.\n *\n * - pending: Operation started but not finished\n * - success: Operation completed successfully\n * - failed: Operation failed with error\n * - reverted: Operation was reverted\n * - stale: Operation's schema objects were torn down (needs re-run)\n */\nexport type OperationStatus = 'pending' | 'success' | 'failed' | 'reverted' | 'stale';\n\n/**\n * Change type values.\n */\nexport type ChangeType = 'build' | 'run' | 'change';\n\n/**\n * Direction values.\n */\nexport type Direction = 'change' | 'revert';\n\n/**\n * Change tracking table.\n *\n * Tracks all operation batches—changes, builds, and ad-hoc runs.\n * See: plan/datamodel.md#__noorm_change__\n */\nexport interface NoormChangeTable {\n /** Primary key */\n id: Generated<number>;\n\n /** Operation identifier */\n name: string;\n\n /** 'build', 'run', or 'change' */\n change_type: ChangeType;\n\n /** 'change' or 'revert' */\n direction: Direction;\n\n /** SHA-256 of sorted file checksums */\n checksum: Generated<string>;\n\n /** When executed */\n executed_at: Generated<Date>;\n\n /** Identity string */\n executed_by: Generated<string>;\n\n /** Which config was used */\n config_name: Generated<string>;\n\n /** noorm version */\n cli_version: Generated<string>;\n\n /** 'pending', 'success', 'failed', 'reverted' */\n status: OperationStatus;\n\n /** Error details (empty = no error) */\n error_message: Generated<string>;\n\n /** Execution time (0 = never ran) */\n duration_ms: Generated<number>;\n}\n\nexport type NoormChange = Selectable<NoormChangeTable>;\nexport type NewNoormChange = Insertable<NoormChangeTable>;\nexport type NoormChangeUpdate = Updateable<NoormChangeTable>;\n\n// ─────────────────────────────────────────────────────────────\n// __noorm_executions__\n// ─────────────────────────────────────────────────────────────\n\n/**\n * File execution status values.\n */\nexport type ExecutionStatus = 'pending' | 'success' | 'failed' | 'skipped';\n\n/**\n * File type values.\n */\nexport type FileType = 'sql' | 'txt';\n\n/**\n * Executions tracking table.\n *\n * Tracks individual file executions within an operation.\n * See: plan/datamodel.md#__noorm_executions__\n */\nexport interface NoormExecutionsTable {\n /** Primary key */\n id: Generated<number>;\n\n /** Parent operation (FK to __noorm_change__) */\n change_id: number;\n\n /** File that was executed */\n filepath: string;\n\n /** 'sql' or 'txt' */\n file_type: FileType;\n\n /** SHA-256 of file contents */\n checksum: Generated<string>;\n\n /** noorm version */\n cli_version: Generated<string>;\n\n /** 'pending', 'success', 'failed', 'skipped' */\n status: ExecutionStatus;\n\n /** Error details (empty = no error) */\n error_message: Generated<string>;\n\n /** 'unchanged', 'already-run', 'change failed' */\n skip_reason: Generated<string>;\n\n /** Execution time (0 = never ran) */\n duration_ms: Generated<number>;\n}\n\nexport type NoormExecution = Selectable<NoormExecutionsTable>;\nexport type NewNoormExecution = Insertable<NoormExecutionsTable>;\nexport type NoormExecutionUpdate = Updateable<NoormExecutionsTable>;\n\n// ─────────────────────────────────────────────────────────────\n// __noorm_lock__\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Lock table.\n *\n * Prevents concurrent operations on the same database.\n * See: plan/datamodel.md#__noorm_lock__\n */\nexport interface NoormLockTable {\n /** Primary key */\n id: Generated<number>;\n\n /** Lock scope (config name) */\n config_name: string;\n\n /** Identity of holder */\n locked_by: string;\n\n /** When acquired */\n locked_at: Generated<Date>;\n\n /** Auto-expiry time */\n expires_at: Date;\n\n /** Lock reason (empty = none) */\n reason: Generated<string>;\n}\n\nexport type NoormLock = Selectable<NoormLockTable>;\nexport type NewNoormLock = Insertable<NoormLockTable>;\nexport type NoormLockUpdate = Updateable<NoormLockTable>;\n\n// ─────────────────────────────────────────────────────────────\n// __noorm_identities__\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Identities table.\n *\n * Stores user identities for team discovery.\n * Auto-populated on first connect when identity is set up.\n * See: plan/datamodel.md#__noorm_identities__\n */\nexport interface NoormIdentitiesTable {\n /** Primary key */\n id: Generated<number>;\n\n /** SHA-256(email + name + machine + os) */\n identity_hash: string;\n\n /** User email */\n email: string;\n\n /** Display name */\n name: string;\n\n /** Machine hostname */\n machine: string;\n\n /** OS platform and version */\n os: string;\n\n /** X25519 public key (hex) */\n public_key: string;\n\n /** First registration */\n registered_at: Generated<Date>;\n\n /** Last activity */\n last_seen_at: Generated<Date>;\n\n /** Vault key encrypted with user's public key (nullable for users without vault access) */\n encrypted_vault_key: string | null;\n}\n\nexport type NoormIdentity = Selectable<NoormIdentitiesTable>;\nexport type NewNoormIdentity = Insertable<NoormIdentitiesTable>;\nexport type NoormIdentityUpdate = Updateable<NoormIdentitiesTable>;\n\n// ─────────────────────────────────────────────────────────────\n// __noorm_vault__\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Vault secrets table.\n *\n * Stores encrypted secrets shared across the team.\n * Secrets are encrypted with a vault key that is distributed\n * to team members via their public keys.\n */\nexport interface NoormVaultTable {\n /** Primary key */\n id: Generated<number>;\n\n /** Secret key name (e.g., 'API_KEY', 'DB_PASSWORD') */\n secret_key: string;\n\n /** AES-256-GCM encrypted value (JSON: {iv, authTag, ciphertext}) */\n encrypted_value: string;\n\n /** Identity who set this secret */\n set_by: string;\n\n /** When created */\n created_at: Generated<Date>;\n\n /** When last updated */\n updated_at: Generated<Date>;\n}\n\nexport type NoormVault = Selectable<NoormVaultTable>;\nexport type NewNoormVault = Insertable<NoormVaultTable>;\nexport type NoormVaultUpdate = Updateable<NoormVaultTable>;\n\n// ─────────────────────────────────────────────────────────────\n// Combined Database Interface\n// ─────────────────────────────────────────────────────────────\n\n/**\n * Combined database interface for all noorm tracking tables.\n *\n * Use this with Kysely<NoormDatabase> for type-safe queries.\n *\n * @example\n * ```typescript\n * import { NoormDatabase, NOORM_TABLES } from './shared'\n *\n * const db = new Kysely<NoormDatabase>({ dialect })\n *\n * const version = await db\n * .selectFrom(NOORM_TABLES.version)\n * .selectAll()\n * .orderBy('id', 'desc')\n * .limit(1)\n * .executeTakeFirst()\n * ```\n */\nexport interface NoormDatabase {\n __noorm_version__: NoormVersionTable;\n __noorm_change__: NoormChangeTable;\n __noorm_executions__: NoormExecutionsTable;\n __noorm_lock__: NoormLockTable;\n __noorm_identities__: NoormIdentitiesTable;\n __noorm_vault__: NoormVaultTable;\n}\n","/**\n * Vault key management.\n *\n * Generates, encrypts, and decrypts the vault key.\n * The vault key is a 256-bit symmetric key used to encrypt vault secrets.\n * Each user's copy is encrypted with their public key.\n */\nimport {\n randomBytes,\n createCipheriv,\n createDecipheriv,\n createPrivateKey,\n createPublicKey,\n diffieHellman,\n generateKeyPairSync,\n hkdfSync,\n} from 'crypto';\n\nimport { attemptSync } from '@logosdx/utils';\n\nimport type { EncryptedVaultKey } from './types.js';\n\n/**\n * Generate a new 256-bit vault key.\n *\n * Called once during vault initialization by the first user.\n *\n * @example\n * ```typescript\n * const vaultKey = generateVaultKey();\n * // vaultKey is 32 bytes of cryptographically secure random data\n * ```\n */\nexport function generateVaultKey(): Buffer {\n\n return randomBytes(32);\n\n}\n\n/**\n * Derive shared secret from private key and public key using X25519 ECDH.\n */\nfunction deriveSharedSecret(privateKeyHex: string, publicKeyHex: string): Buffer {\n\n const privateKey = createPrivateKey({\n key: Buffer.from(privateKeyHex, 'hex'),\n format: 'der',\n type: 'pkcs8',\n });\n\n const publicKey = createPublicKey({\n key: Buffer.from(publicKeyHex, 'hex'),\n format: 'der',\n type: 'spki',\n });\n\n return diffieHellman({\n privateKey,\n publicKey,\n });\n\n}\n\n/**\n * Derive encryption key from shared secret using HKDF.\n */\nfunction deriveEncryptionKey(sharedSecret: Buffer, info: string): Buffer {\n\n return Buffer.from(\n hkdfSync(\n 'sha256',\n sharedSecret,\n Buffer.alloc(0),\n info,\n 32,\n ),\n );\n\n}\n\n/**\n * Encrypt vault key for a recipient using their public key.\n *\n * Uses the ephemeral keypair pattern:\n * 1. Generate ephemeral X25519 keypair\n * 2. ECDH with recipient's public key\n * 3. HKDF to derive encryption key\n * 4. AES-256-GCM encryption\n *\n * @param vaultKey - The 32-byte vault key to encrypt\n * @param recipientPubKey - Recipient's X25519 public key (hex)\n *\n * @example\n * ```typescript\n * const encrypted = encryptVaultKey(vaultKey, userPublicKey);\n * // Store encrypted in user's encrypted_vault_key column\n * ```\n */\nexport function encryptVaultKey(vaultKey: Buffer, recipientPubKey: string): EncryptedVaultKey {\n\n // Generate ephemeral keypair for this encryption\n const { publicKey, privateKey } = generateKeyPairSync('x25519', {\n publicKeyEncoding: { type: 'spki', format: 'der' },\n privateKeyEncoding: { type: 'pkcs8', format: 'der' },\n });\n\n const ephemeralPrivateHex = privateKey.toString('hex');\n const ephemeralPublicHex = publicKey.toString('hex');\n\n // Derive shared secret via ECDH\n const sharedSecret = deriveSharedSecret(ephemeralPrivateHex, recipientPubKey);\n\n // Derive encryption key\n const encKey = deriveEncryptionKey(sharedSecret, 'noorm-vault-key');\n\n // Generate random IV\n const iv = randomBytes(16);\n\n // Encrypt with AES-256-GCM\n const cipher = createCipheriv('aes-256-gcm', encKey, iv);\n const ciphertext = Buffer.concat([cipher.update(vaultKey), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n return {\n ephemeralPubKey: ephemeralPublicHex,\n iv: iv.toString('hex'),\n authTag: authTag.toString('hex'),\n ciphertext: ciphertext.toString('hex'),\n };\n\n}\n\n/**\n * Decrypt vault key using the user's private key.\n *\n * Reverses the ephemeral keypair pattern:\n * 1. ECDH with ephemeral public key and user's private key\n * 2. HKDF to derive decryption key\n * 3. AES-256-GCM decryption\n *\n * @param encrypted - The encrypted vault key payload\n * @param privateKey - User's X25519 private key (hex)\n * @returns The decrypted vault key, or null if decryption fails\n *\n * @example\n * ```typescript\n * const vaultKey = decryptVaultKey(encrypted, userPrivateKey);\n * if (!vaultKey) {\n * console.error('Failed to decrypt vault key');\n * }\n * ```\n */\nexport function decryptVaultKey(\n encrypted: EncryptedVaultKey,\n privateKey: string,\n): Buffer | null {\n\n const [result, err] = attemptSync(() => {\n\n // Derive shared secret from ephemeral pub + user's private\n const sharedSecret = deriveSharedSecret(privateKey, encrypted.ephemeralPubKey);\n\n // Derive decryption key\n const decKey = deriveEncryptionKey(sharedSecret, 'noorm-vault-key');\n\n // Decrypt\n const decipher = createDecipheriv(\n 'aes-256-gcm',\n decKey,\n Buffer.from(encrypted.iv, 'hex'),\n );\n decipher.setAuthTag(Buffer.from(encrypted.authTag, 'hex'));\n\n const vaultKey = Buffer.concat([\n decipher.update(Buffer.from(encrypted.ciphertext, 'hex')),\n decipher.final(),\n ]);\n\n return vaultKey;\n\n });\n\n if (err) return null;\n\n return result;\n\n}\n\n/**\n * Encrypt a secret value with the vault key.\n *\n * Uses AES-256-GCM for authenticated encryption.\n *\n * @param value - The plaintext secret value\n * @param vaultKey - The 32-byte vault key\n *\n * @example\n * ```typescript\n * const encrypted = encryptSecret('my-api-key', vaultKey);\n * // Store JSON.stringify(encrypted) in encrypted_value column\n * ```\n */\nexport function encryptSecret(\n value: string,\n vaultKey: Buffer,\n): { iv: string; authTag: string; ciphertext: string } {\n\n const iv = randomBytes(16);\n const cipher = createCipheriv('aes-256-gcm', vaultKey, iv);\n const ciphertext = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n return {\n iv: iv.toString('hex'),\n authTag: authTag.toString('hex'),\n ciphertext: ciphertext.toString('hex'),\n };\n\n}\n\n/**\n * Decrypt a secret value with the vault key.\n *\n * @param encrypted - The encrypted secret payload\n * @param vaultKey - The 32-byte vault key\n * @returns The decrypted value, or null if decryption fails\n *\n * @example\n * ```typescript\n * const value = decryptSecret(encrypted, vaultKey);\n * if (!value) {\n * console.error('Failed to decrypt secret');\n * }\n * ```\n */\nexport function decryptSecret(\n encrypted: { iv: string; authTag: string; ciphertext: string },\n vaultKey: Buffer,\n): string | null {\n\n const [result, err] = attemptSync(() => {\n\n const decipher = createDecipheriv(\n 'aes-256-gcm',\n vaultKey,\n Buffer.from(encrypted.iv, 'hex'),\n );\n decipher.setAuthTag(Buffer.from(encrypted.authTag, 'hex'));\n\n const plaintext = Buffer.concat([\n decipher.update(Buffer.from(encrypted.ciphertext, 'hex')),\n decipher.final(),\n ]);\n\n return plaintext.toString('utf8');\n\n });\n\n if (err) return null;\n\n return result;\n\n}\n","/**\n * Vault storage operations.\n *\n * Database CRUD operations for the vault table and vault key management.\n * All operations use Kysely for type-safe queries.\n */\nimport type { Kysely } from 'kysely';\n\nimport { attempt, attemptSync } from '@logosdx/utils';\n\nimport type { NoormDatabase } from '../shared/tables.js';\nimport { NOORM_TABLES } from '../shared/tables.js';\nimport { observer } from '../observer.js';\n\nimport type { EncryptedVaultKey, VaultSecret, VaultStatus } from './types.js';\nimport {\n generateVaultKey,\n encryptVaultKey,\n decryptVaultKey,\n encryptSecret,\n decryptSecret,\n} from './key.js';\n\n/**\n * Initialize the vault for a database.\n *\n * Generates a new vault key and stores it encrypted for the current user.\n * Only the first user to initialize gets the vault key.\n *\n * @param db - Kysely database instance\n * @param identityHash - Current user's identity hash\n * @param publicKey - Current user's public key (hex)\n *\n * @example\n * ```typescript\n * const [, err] = await initializeVault(db, identity.identityHash, identity.publicKey);\n * if (err) console.error('Failed to initialize vault');\n * ```\n */\nexport async function initializeVault(\n db: Kysely<NoormDatabase>,\n identityHash: string,\n publicKey: string,\n): Promise<[Buffer | null, Error | null]> {\n\n // Check if vault is already initialized (any user has a vault key)\n const [existing, checkErr] = await attempt(async () => {\n\n return db\n .selectFrom(NOORM_TABLES.identities)\n .select('encrypted_vault_key')\n .where('encrypted_vault_key', 'is not', null)\n .limit(1)\n .executeTakeFirst();\n\n });\n\n if (checkErr) return [null, checkErr];\n\n if (existing?.encrypted_vault_key) {\n\n return [null, new Error('Vault already initialized')];\n\n }\n\n // Generate new vault key\n const vaultKey = generateVaultKey();\n\n // Encrypt for current user\n const encrypted = encryptVaultKey(vaultKey, publicKey);\n const encryptedJson = JSON.stringify(encrypted);\n\n // Update user's identity row\n const [, updateErr] = await attempt(async () => {\n\n return db\n .updateTable(NOORM_TABLES.identities)\n .set({ encrypted_vault_key: encryptedJson })\n .where('identity_hash', '=', identityHash)\n .execute();\n\n });\n\n if (updateErr) return [null, updateErr];\n\n observer.emit('vault:initialized', { identityHash });\n\n return [vaultKey, null];\n\n}\n\n/**\n * Get the vault key for the current user.\n *\n * Fetches and decrypts the vault key from the user's identity row.\n *\n * @param db - Kysely database instance\n * @param identityHash - Current user's identity hash\n * @param privateKey - Current user's private key (hex)\n * @returns The decrypted vault key, or null if not found/accessible\n *\n * @example\n * ```typescript\n * const vaultKey = await getVaultKey(db, identity.identityHash, privateKey);\n * if (!vaultKey) {\n * console.log('No vault access - vault not initialized or not propagated');\n * }\n * ```\n */\nexport async function getVaultKey(\n db: Kysely<NoormDatabase>,\n identityHash: string,\n privateKey: string,\n): Promise<Buffer | null> {\n\n const [row, err] = await attempt(async () => {\n\n return db\n .selectFrom(NOORM_TABLES.identities)\n .select('encrypted_vault_key')\n .where('identity_hash', '=', identityHash)\n .executeTakeFirst();\n\n });\n\n if (err || !row?.encrypted_vault_key) return null;\n\n const encryptedValue = row.encrypted_vault_key;\n\n const [parsed, parseErr] = attemptSync(() =>\n JSON.parse(encryptedValue) as EncryptedVaultKey,\n );\n\n if (parseErr) return null;\n\n return decryptVaultKey(parsed, privateKey);\n\n}\n\n/**\n * Set a vault secret.\n *\n * Encrypts and stores a secret in the vault table.\n * Upserts - creates if new, updates if exists.\n *\n * @param db - Kysely database instance\n * @param vaultKey - The decrypted vault key\n * @param secretKey - The secret's key name\n * @param value - The plaintext secret value\n * @param setBy - Identity string of who set this secret\n *\n * @example\n * ```typescript\n * await setVaultSecret(db, vaultKey, 'API_KEY', 'sk-live-...', 'alice@example.com');\n * ```\n */\nexport async function setVaultSecret(\n db: Kysely<NoormDatabase>,\n vaultKey: Buffer,\n secretKey: string,\n value: string,\n setBy: string,\n): Promise<[void, Error | null]> {\n\n // Encrypt the value\n const encrypted = encryptSecret(value, vaultKey);\n const encryptedJson = JSON.stringify(encrypted);\n const now = new Date().toISOString();\n\n // Check if exists\n const [existing, checkErr] = await attempt(async () => {\n\n return db\n .selectFrom(NOORM_TABLES.vault)\n .select('id')\n .where('secret_key', '=', secretKey)\n .executeTakeFirst();\n\n });\n\n if (checkErr) return [undefined, checkErr];\n\n if (existing) {\n\n // Update existing\n const [, updateErr] = await attempt(async () => {\n\n return db\n .updateTable(NOORM_TABLES.vault)\n .set({\n encrypted_value: encryptedJson,\n set_by: setBy,\n updated_at: now as unknown as Date,\n })\n .where('secret_key', '=', secretKey)\n .execute();\n\n });\n\n if (updateErr) return [undefined, updateErr];\n\n observer.emit('vault:secret:updated', { key: secretKey, setBy });\n\n }\n else {\n\n // Insert new\n const [, insertErr] = await attempt(async () => {\n\n return db\n .insertInto(NOORM_TABLES.vault)\n .values({\n secret_key: secretKey,\n encrypted_value: encryptedJson,\n set_by: setBy,\n })\n .execute();\n\n });\n\n if (insertErr) return [undefined, insertErr];\n\n observer.emit('vault:secret:created', { key: secretKey, setBy });\n\n }\n\n return [undefined, null];\n\n}\n\n/**\n * Get a vault secret by key.\n *\n * @param db - Kysely database instance\n * @param vaultKey - The decrypted vault key\n * @param secretKey - The secret's key name\n * @returns The decrypted secret value, or null if not found\n *\n * @example\n * ```typescript\n * const value = await getVaultSecret(db, vaultKey, 'API_KEY');\n * if (value) {\n * console.log('Secret found:', value);\n * }\n * ```\n */\nexport async function getVaultSecret(\n db: Kysely<NoormDatabase>,\n vaultKey: Buffer,\n secretKey: string,\n): Promise<string | null> {\n\n const [row, err] = await attempt(async () => {\n\n return db\n .selectFrom(NOORM_TABLES.vault)\n .select('encrypted_value')\n .where('secret_key', '=', secretKey)\n .executeTakeFirst();\n\n });\n\n if (err || !row) return null;\n\n const [parsed, parseErr] = attemptSync(() =>\n JSON.parse(row.encrypted_value) as { iv: string; authTag: string; ciphertext: string },\n );\n\n if (parseErr) return null;\n\n return decryptSecret(parsed, vaultKey);\n\n}\n\n/**\n * Get all vault secrets.\n *\n * @param db - Kysely database instance\n * @param vaultKey - The decrypted vault key\n * @returns Map of secret key to VaultSecret\n *\n * @example\n * ```typescript\n * const secrets = await getAllVaultSecrets(db, vaultKey);\n * for (const [key, secret] of Object.entries(secrets)) {\n * console.log(key, secret.value);\n * }\n * ```\n */\nexport async function getAllVaultSecrets(\n db: Kysely<NoormDatabase>,\n vaultKey: Buffer,\n): Promise<Record<string, VaultSecret>> {\n\n const [rows, err] = await attempt(async () => {\n\n return db\n .selectFrom(NOORM_TABLES.vault)\n .selectAll()\n .execute();\n\n });\n\n if (err || !rows) return {};\n\n const secrets: Record<string, VaultSecret> = {};\n\n for (const row of rows) {\n\n const [parsed, parseErr] = attemptSync(() =>\n JSON.parse(row.encrypted_value) as { iv: string; authTag: string; ciphertext: string },\n );\n\n if (parseErr) continue;\n\n const value = decryptSecret(parsed, vaultKey);\n if (!value) continue;\n\n secrets[row.secret_key] = {\n key: row.secret_key,\n value,\n setBy: row.set_by,\n createdAt: new Date(row.created_at),\n updatedAt: new Date(row.updated_at),\n };\n\n }\n\n return secrets;\n\n}\n\n/**\n * List all vault secret keys (without decrypting values).\n *\n * @param db - Kysely database instance\n * @returns Array of secret key names\n *\n * @example\n * ```typescript\n * const keys = await listVaultSecretKeys(db);\n * console.log('Vault contains:', keys);\n * ```\n */\nexport async function listVaultSecretKeys(\n db: Kysely<NoormDatabase>,\n): Promise<string[]> {\n\n const [rows, err] = await attempt(async () => {\n\n return db\n .selectFrom(NOORM_TABLES.vault)\n .select('secret_key')\n .orderBy('secret_key', 'asc')\n .execute();\n\n });\n\n if (err || !rows) return [];\n\n return rows.map((r) => r.secret_key);\n\n}\n\n/**\n * Delete a vault secret.\n *\n * @param db - Kysely database instance\n * @param secretKey - The secret's key name\n *\n * @example\n * ```typescript\n * await deleteVaultSecret(db, 'OLD_API_KEY');\n * ```\n */\nexport async function deleteVaultSecret(\n db: Kysely<NoormDatabase>,\n secretKey: string,\n): Promise<[boolean, Error | null]> {\n\n const [result, err] = await attempt(async () => {\n\n return db\n .deleteFrom(NOORM_TABLES.vault)\n .where('secret_key', '=', secretKey)\n .execute();\n\n });\n\n if (err || !result) return [false, err ?? new Error('Delete failed')];\n\n const firstResult = result[0];\n const deleted = !!firstResult && Number(firstResult.numDeletedRows) > 0;\n\n if (deleted) {\n\n observer.emit('vault:secret:deleted', { key: secretKey });\n\n }\n\n return [deleted, null];\n\n}\n\n/**\n * Check if a vault secret exists.\n *\n * @param db - Kysely database instance\n * @param secretKey - The secret's key name\n * @returns True if the secret exists\n */\nexport async function vaultSecretExists(\n db: Kysely<NoormDatabase>,\n secretKey: string,\n): Promise<boolean> {\n\n const [row, err] = await attempt(async () => {\n\n return db\n .selectFrom(NOORM_TABLES.vault)\n .select('id')\n .where('secret_key', '=', secretKey)\n .executeTakeFirst();\n\n });\n\n if (err) return false;\n\n return !!row;\n\n}\n\n/**\n * Get vault status.\n *\n * @param db - Kysely database instance\n * @param identityHash - Current user's identity hash\n *\n * @example\n * ```typescript\n * const status = await getVaultStatus(db, identity.identityHash);\n * console.log('Vault initialized:', status.isInitialized);\n * console.log('Has access:', status.hasAccess);\n * ```\n */\nexport async function getVaultStatus(\n db: Kysely<NoormDatabase>,\n identityHash: string,\n): Promise<VaultStatus> {\n\n // Count secrets\n const [secretRows] = await attempt(async () => {\n\n return db\n .selectFrom(NOORM_TABLES.vault)\n .select(db.fn.count('id').as('count'))\n .executeTakeFirst();\n\n });\n\n const secretCount = secretRows ? Number(secretRows.count) : 0;\n\n // Count users with/without access\n const [identityRows] = await attempt(async () => {\n\n return db\n .selectFrom(NOORM_TABLES.identities)\n .select([\n 'identity_hash',\n 'encrypted_vault_key',\n ])\n .execute();\n\n });\n\n const identities = identityRows ?? [];\n const usersWithAccess = identities.filter((i) => i.encrypted_vault_key !== null).length;\n const usersWithoutAccess = identities.filter((i) => i.encrypted_vault_key === null).length;\n const isInitialized = usersWithAccess > 0;\n\n // Check if current user has access\n const currentUser = identities.find((i) => i.identity_hash === identityHash);\n const hasAccess = currentUser?.encrypted_vault_key !== null;\n\n return {\n isInitialized,\n hasAccess,\n secretCount,\n usersWithAccess,\n usersWithoutAccess,\n };\n\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"engine-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"engine-5K7FA66A.js"}
|