@noormdev/sdk 1.0.0-alpha.7 → 1.0.0-alpha.9

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.
@@ -4,9 +4,14 @@ import { ObserverEngine } from '@logosdx/observer';
4
4
  import path from 'path';
5
5
  import { attempt } from '@logosdx/utils';
6
6
  import { pathToFileURL } from 'url';
7
- import JSON5 from 'json5';
7
+ import JSON52 from 'json5';
8
8
  import { parse as parse$1 } from 'yaml';
9
9
  import { parse } from 'csv-parse/sync';
10
+ import { readFileSync, createReadStream } from 'fs';
11
+ import { gunzipSync, createGunzip } from 'zlib';
12
+ import { createInterface } from 'readline';
13
+ import { PassThrough } from 'stream';
14
+ import { randomBytes, pbkdf2Sync, createCipheriv, createDecipheriv } from 'crypto';
10
15
  import v from 'voca';
11
16
 
12
17
  // src/core/template/engine.ts
@@ -81,7 +86,7 @@ async function loadHelpers(fromDir, projectRoot) {
81
86
  }
82
87
  async function loadJson5(filepath) {
83
88
  const content = await readFile(filepath, "utf-8");
84
- return JSON5.parse(content);
89
+ return JSON52.parse(content);
85
90
  }
86
91
  async function loadYaml(filepath) {
87
92
  const content = await readFile(filepath, "utf-8");
@@ -99,6 +104,188 @@ async function loadSql(filepath) {
99
104
  return readFile(filepath, "utf-8");
100
105
  }
101
106
 
107
+ // src/core/dt/constants.ts
108
+ var FORMAT_VERSION = 1;
109
+ var GZIP_THRESHOLD = 128;
110
+ var GZIP_RATIO_THRESHOLD = 0.85;
111
+ var ENCODED_TYPES = {
112
+ json: true,
113
+ binary: true,
114
+ vector: true,
115
+ array: true,
116
+ custom: true
117
+ };
118
+ var DT_EXTENSIONS = {
119
+ /** Raw .dt file (uncompressed JSON5 lines). */
120
+ RAW: ".dt",
121
+ /** Gzip-compressed .dt file. */
122
+ COMPRESSED: ".dtz",
123
+ /** Gzip-compressed and AES-256-GCM encrypted .dt file. */
124
+ ENCRYPTED: ".dtzx"
125
+ };
126
+ var ALGORITHM = "aes-256-gcm";
127
+ var IV_LENGTH = 16;
128
+ var SALT_LENGTH = 32;
129
+ var AUTH_TAG_LENGTH = 16;
130
+ var KEY_LENGTH = 32;
131
+ var PBKDF2_ITERATIONS = 1e5;
132
+ var PBKDF2_DIGEST = "sha256";
133
+ function encryptWithPassphrase(data, passphrase) {
134
+ const salt = randomBytes(SALT_LENGTH);
135
+ const iv = randomBytes(IV_LENGTH);
136
+ const key = pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, PBKDF2_DIGEST);
137
+ const cipher = createCipheriv(ALGORITHM, key, iv, {
138
+ authTagLength: AUTH_TAG_LENGTH
139
+ });
140
+ const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]);
141
+ const authTag = cipher.getAuthTag();
142
+ return {
143
+ salt: salt.toString("base64"),
144
+ iv: iv.toString("base64"),
145
+ authTag: authTag.toString("base64"),
146
+ ciphertext: ciphertext.toString("base64")
147
+ };
148
+ }
149
+ function decryptWithPassphrase(payload, passphrase) {
150
+ const salt = Buffer.from(payload.salt, "base64");
151
+ const iv = Buffer.from(payload.iv, "base64");
152
+ const authTag = Buffer.from(payload.authTag, "base64");
153
+ const ciphertext = Buffer.from(payload.ciphertext, "base64");
154
+ const key = pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, PBKDF2_DIGEST);
155
+ const decipher = createDecipheriv(ALGORITHM, key, iv, {
156
+ authTagLength: AUTH_TAG_LENGTH
157
+ });
158
+ decipher.setAuthTag(authTag);
159
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
160
+ return decrypted;
161
+ }
162
+
163
+ // src/core/dt/reader.ts
164
+ var DtReader = class {
165
+ #filepath;
166
+ #passphrase;
167
+ #extension;
168
+ #schema = null;
169
+ #lineReader = null;
170
+ #inputStream = null;
171
+ /**
172
+ * Create a new DtReader.
173
+ *
174
+ * @param options - File path and optional passphrase for .dtzx
175
+ */
176
+ constructor(options) {
177
+ this.#filepath = options.filepath;
178
+ this.#passphrase = options.passphrase;
179
+ this.#extension = path.extname(options.filepath).toLowerCase();
180
+ }
181
+ /** The parsed schema from line 1. Available after open(). */
182
+ get schema() {
183
+ return this.#schema;
184
+ }
185
+ /**
186
+ * Open the file and parse the schema header.
187
+ *
188
+ * Must be called before iterating rows.
189
+ */
190
+ async open() {
191
+ const readable = this.#createReadableStream();
192
+ this.#inputStream = readable;
193
+ const rl = createInterface({ input: readable, crlfDelay: Infinity });
194
+ const lines = rl[Symbol.asyncIterator]();
195
+ const firstLine = await lines.next();
196
+ if (firstLine.done || !firstLine.value) {
197
+ throw new Error("Empty .dt file \u2014 no schema line");
198
+ }
199
+ this.#schema = JSON52.parse(firstLine.value);
200
+ if (this.#schema.v !== 1) {
201
+ throw new Error(`Unsupported .dt format version: ${this.#schema.v}`);
202
+ }
203
+ this.#lineReader = this.#createRowIterator(lines);
204
+ }
205
+ /**
206
+ * Async generator yielding parsed row value arrays.
207
+ *
208
+ * Each yield is a `DtValue[]` in column order matching the schema.
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * for await (const values of reader.rows()) {
213
+ * console.log(values); // [1, "alice@example.com", true, "2024-01-15T10:30:00Z"]
214
+ * }
215
+ * ```
216
+ */
217
+ async *rows() {
218
+ if (!this.#lineReader) {
219
+ throw new Error("Reader not opened \u2014 call open() first");
220
+ }
221
+ yield* this.#lineReader;
222
+ }
223
+ /**
224
+ * Close the reader and release resources.
225
+ */
226
+ close() {
227
+ if (this.#inputStream) {
228
+ this.#inputStream.destroy();
229
+ this.#inputStream = null;
230
+ }
231
+ }
232
+ /**
233
+ * Create the readable stream based on file extension.
234
+ */
235
+ #createReadableStream() {
236
+ if (this.#extension === DT_EXTENSIONS.ENCRYPTED) {
237
+ if (!this.#passphrase) {
238
+ throw new Error("Passphrase required for .dtzx files");
239
+ }
240
+ const encrypted = readFileSync(this.#filepath, "utf8");
241
+ const payload = JSON.parse(encrypted);
242
+ const compressed = decryptWithPassphrase(payload, this.#passphrase);
243
+ const raw = gunzipSync(compressed);
244
+ const stream = new PassThrough();
245
+ stream.end(raw);
246
+ return stream;
247
+ }
248
+ if (this.#extension === DT_EXTENSIONS.COMPRESSED) {
249
+ const fileStream = createReadStream(this.#filepath);
250
+ const gunzip = createGunzip();
251
+ fileStream.pipe(gunzip);
252
+ return gunzip;
253
+ }
254
+ return createReadStream(this.#filepath, { encoding: "utf8" });
255
+ }
256
+ /**
257
+ * Create async generator from remaining readline lines.
258
+ */
259
+ async *#createRowIterator(lines) {
260
+ for await (const line of { [Symbol.asyncIterator]: () => lines }) {
261
+ const trimmed = line.trim();
262
+ if (trimmed.length === 0) continue;
263
+ yield JSON52.parse(trimmed);
264
+ }
265
+ }
266
+ };
267
+
268
+ // src/core/template/loaders/dt.ts
269
+ async function loadDt(filepath) {
270
+ const reader = new DtReader({ filepath });
271
+ await reader.open();
272
+ const schema = reader.schema;
273
+ if (!schema) {
274
+ throw new Error(`Failed to read .dt schema from: ${filepath}`);
275
+ }
276
+ const columnNames = schema.columns.map((c) => c.name);
277
+ const rows = [];
278
+ for await (const values of reader.rows()) {
279
+ const row = {};
280
+ for (let i = 0; i < columnNames.length; i++) {
281
+ row[columnNames[i]] = values[i] ?? null;
282
+ }
283
+ rows.push(row);
284
+ }
285
+ reader.close();
286
+ return rows;
287
+ }
288
+
102
289
  // src/core/template/loaders/index.ts
103
290
  var loaders = {
104
291
  ".json": loadJson5,
@@ -109,7 +296,10 @@ var loaders = {
109
296
  ".js": loadJs,
110
297
  ".mjs": loadJs,
111
298
  ".ts": loadJs,
112
- ".sql": loadSql
299
+ ".sql": loadSql,
300
+ ".dt": loadDt,
301
+ ".dtz": loadDt
302
+ // NO .dtzx — can't provide passphrase in template context
113
303
  };
114
304
  function hasLoader(ext) {
115
305
  return ext in loaders;
@@ -229,7 +419,7 @@ function createIncludeHelper(templateDir, projectRoot, options) {
229
419
  throw new Error(`Include path escapes project root: ${includePath}`);
230
420
  }
231
421
  if (resolved.endsWith(".tmpl")) {
232
- const { processFile: processFile2 } = await import('./engine-L5OIWEOI.js');
422
+ const { processFile: processFile2 } = await import('./engine-HESCBITZ.js');
233
423
  const result = await processFile2(resolved, options);
234
424
  return result.sql;
235
425
  }
@@ -294,6 +484,6 @@ async function processFiles(filepaths, options = {}) {
294
484
  return results;
295
485
  }
296
486
 
297
- export { eta, isTemplate, observer, processFile, processFiles, renderTemplate };
298
- //# sourceMappingURL=chunk-AT6SZ6UD.js.map
299
- //# sourceMappingURL=chunk-AT6SZ6UD.js.map
487
+ export { DT_EXTENSIONS, DtReader, ENCODED_TYPES, FORMAT_VERSION, GZIP_RATIO_THRESHOLD, GZIP_THRESHOLD, encryptWithPassphrase, eta, isTemplate, observer, processFile, processFiles, renderTemplate };
488
+ //# sourceMappingURL=chunk-BMB5MF2T.js.map
489
+ //# sourceMappingURL=chunk-BMB5MF2T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/observer.ts","../../../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":"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","file":"chunk-BMB5MF2T.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","/**\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 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,3 @@
1
+ export { eta, isTemplate, processFile, processFiles, renderTemplate } from './chunk-BMB5MF2T.js';
2
+ //# sourceMappingURL=engine-HESCBITZ.js.map
3
+ //# sourceMappingURL=engine-HESCBITZ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"engine-L5OIWEOI.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"engine-HESCBITZ.js"}