@wgtechlabs/config-engine 0.1.0-dev.d0d9e7f
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/LICENSE +21 -0
- package/README.md +306 -0
- package/dist/cache.d.ts +36 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/chunk-9bm5582z.js +35 -0
- package/dist/chunk-9bm5582z.js.map +10 -0
- package/dist/dot-prop.d.ts +35 -0
- package/dist/dot-prop.d.ts.map +1 -0
- package/dist/encryption.d.ts +31 -0
- package/dist/encryption.d.ts.map +1 -0
- package/dist/index.d.ts +124 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +967 -0
- package/dist/index.js.map +17 -0
- package/dist/migrations.d.ts +22 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/platform.d.ts +23 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/runtime.d.ts +15 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/store.d.ts +48 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/validation.d.ts +28 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +10 -0
- package/dist/validation.js.map +9 -0
- package/package.json +82 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/cache.ts", "../src/dot-prop.ts", "../src/encryption.ts", "../src/migrations.ts", "../src/platform.ts", "../src/runtime.ts", "../src/store.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @module config-engine\n * Fast, Bun-first configuration engine with SQLite-backed storage.\n *\n * @example\n * ```ts\n * import { ConfigEngine } from \"@wgtechlabs/config-engine\";\n * import { z } from \"zod\";\n *\n * const config = await ConfigEngine.open({\n * projectName: \"my-app\",\n * defaults: { theme: \"dark\", fontSize: 14 },\n * schema: z.object({\n * theme: z.enum([\"light\", \"dark\"]),\n * fontSize: z.number().min(8).max(72),\n * }),\n * });\n *\n * config.get(\"theme\"); // \"dark\"\n * config.set(\"fontSize\", 16);\n * config.set({ theme: \"light\", fontSize: 12 });\n *\n * config.close();\n * ```\n */\n\nimport { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { watch, type FSWatcher } from \"node:fs\";\n\nimport { ConfigCache } from \"./cache.js\";\nimport { deleteByPath, getByPath, hasByPath, setByPath } from \"./dot-prop.js\";\nimport { resolveEncryptor } from \"./encryption.js\";\nimport { runMigrations } from \"./migrations.js\";\nimport { resolveConfigPath } from \"./platform.js\";\nimport { openDatabase } from \"./runtime.js\";\nimport { ConfigStore } from \"./store.js\";\nimport type {\n\tAnyChangeCallback,\n\tChangeCallback,\n\tConfigEngineOptions,\n\tEncryptor,\n\tFlushStrategy,\n\tUnsubscribe,\n\tValidator,\n} from \"./types.js\";\nimport { resolveValidator } from \"./validation.js\";\n\n// Re-export public types\nexport type {\n\tConfigEngineOptions,\n\tValidator,\n\tValidationResult,\n\tValidationSuccess,\n\tValidationFailure,\n\tEncryptor,\n\tMigrationDefinition,\n\tMigrationContext,\n\tMigrationHookContext,\n\tFlushStrategy as FlushStrategyType,\n\tChangeCallback,\n\tAnyChangeCallback,\n\tUnsubscribe,\n} from \"./types.js\";\n\nexport { createZodValidator, resolveValidator } from \"./validation.js\";\nexport { resolveConfigDir, resolveConfigPath } from \"./platform.js\";\nexport type { FlushStrategy } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Errors\n// ---------------------------------------------------------------------------\n\nexport class ConfigEngineError extends Error {\n\toverride name = \"ConfigEngineError\";\n}\n\nexport class ValidationError extends ConfigEngineError {\n\toverride name = \"ValidationError\";\n\terrors: string[];\n\n\tconstructor(errors: string[]) {\n\t\tsuper(`Config validation failed:\\n - ${errors.join(\"\\n - \")}`);\n\t\tthis.errors = errors;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// ConfigEngine\n// ---------------------------------------------------------------------------\n\nexport class ConfigEngine<T extends Record<string, unknown>> {\n\treadonly #store: ConfigStore;\n\treadonly #cache: ConfigCache<T>;\n\treadonly #validator: Validator<T> | undefined;\n\treadonly #encryptor: Encryptor | undefined;\n\treadonly #defaults: Partial<T>;\n\treadonly #dotNotation: boolean;\n\treadonly #filePath: string;\n\treadonly #listeners = new Map<string, Set<ChangeCallback<unknown>>>();\n\treadonly #anyListeners = new Set<AnyChangeCallback<T>>();\n\t#watcher: FSWatcher | null = null;\n\t#lastKnownWrite = 0;\n\t#closed = false;\n\n\t/**\n\t * Private constructor — use `ConfigEngine.open()` instead.\n\t */\n\tprivate constructor(\n\t\tstore: ConfigStore,\n\t\tcache: ConfigCache<T>,\n\t\toptions: {\n\t\t\tvalidator?: Validator<T>;\n\t\t\tencryptor?: Encryptor;\n\t\t\tdefaults: Partial<T>;\n\t\t\tdotNotation: boolean;\n\n\t\t\tfilePath: string;\n\t\t\twatch: boolean;\n\t\t},\n\t) {\n\t\tthis.#store = store;\n\t\tthis.#cache = cache;\n\t\tthis.#validator = options.validator;\n\t\tthis.#encryptor = options.encryptor;\n\t\tthis.#defaults = options.defaults;\n\t\tthis.#dotNotation = options.dotNotation;\n\t\tthis.#filePath = options.filePath;\n\t\tthis.#lastKnownWrite = store.getLastWrite();\n\n\t\tif (options.watch) {\n\t\t\tthis.#startWatching();\n\t\t}\n\t}\n\n\t/**\n\t * Async factory — the only way to create a `ConfigEngine` instance.\n\t * Handles encryption init, migration, and initial validation.\n\t */\n\tstatic async open<T extends Record<string, unknown>>(\n\t\toptions: ConfigEngineOptions<T>,\n\t): Promise<ConfigEngine<T>> {\n\t\tconst {\n\t\t\tprojectName,\n\t\t\tprojectVersion,\n\t\t\tcwd,\n\t\t\tconfigName,\n\t\t\tdefaults = {} as Partial<T>,\n\t\t\tencryptionKey,\n\t\t\tmigrations,\n\t\t\tbeforeEachMigration,\n\t\t\tafterEachMigration,\n\t\t\tflushStrategy = \"batched\",\n\t\t\taccessPropertiesByDotNotation = true,\n\t\t\twatch: enableWatch = false,\n\t\t\tclearInvalidConfig = false,\n\t\t} = options;\n\n\t\t// Resolve file path and ensure directory exists\n\t\tconst filePath = resolveConfigPath({ projectName, cwd, configName });\n\t\tmkdirSync(dirname(filePath), { recursive: true });\n\n\t\t// Open SQLite database\n\t\tconst db = openDatabase(filePath);\n\t\tconst store = new ConfigStore(db);\n\n\t\t// Resolve encryption\n\t\tconst encryptor = await resolveEncryptor(encryptionKey);\n\n\t\t// Resolve validator\n\t\tconst validator = resolveValidator<T>(options);\n\n\t\t// Load existing data (with optional decryption)\n\t\tlet storedData: Record<string, unknown>;\n\t\ttry {\n\t\t\tstoredData = store.getAll();\n\t\t\tif (encryptor) {\n\t\t\t\tstoredData = await decryptStore(storedData, encryptor);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (clearInvalidConfig) {\n\t\t\t\tstore.deleteAll();\n\t\t\t\tstoredData = {};\n\t\t\t} else {\n\t\t\t\tstore.close();\n\t\t\t\tthrow new ConfigEngineError(\n\t\t\t\t\t`Failed to load config: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Merge defaults (defaults fill in missing keys, don't overwrite)\n\t\tconst merged = { ...defaults, ...storedData } as T;\n\n\t\t// Validate initial store\n\t\tif (validator) {\n\t\t\tconst result = validator.validate(merged);\n\t\t\tif (!result.success) {\n\t\t\t\tif (clearInvalidConfig) {\n\t\t\t\t\tstore.deleteAll();\n\t\t\t\t\t// Re-merge with just defaults\n\t\t\t\t\tconst freshData = { ...defaults } as T;\n\t\t\t\t\tconst freshResult = validator.validate(freshData);\n\t\t\t\t\tif (!freshResult.success) {\n\t\t\t\t\t\tstore.close();\n\t\t\t\t\t\tthrow new ValidationError(freshResult.errors);\n\t\t\t\t\t}\n\t\t\t\t\t// Write defaults\n\t\t\t\t\tconst entries = Object.entries(freshData);\n\t\t\t\t\tif (encryptor) {\n\t\t\t\t\t\tconst encrypted = await encryptStore(freshData, encryptor);\n\t\t\t\t\t\tstore.setMany(Object.entries(encrypted));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstore.setMany(entries);\n\t\t\t\t\t}\n\t\t\t\t\tstore.touchLastWrite();\n\n\t\t\t\t\tconst cache = new ConfigCache<T>(store, flushStrategy);\n\t\t\t\t\tcache.load(freshData);\n\n\t\t\t\t\treturn new ConfigEngine<T>(store, cache, {\n\t\t\t\t\t\tvalidator,\n\t\t\t\t\t\tencryptor,\n\t\t\t\t\t\tdefaults,\n\t\t\t\t\t\tdotNotation: accessPropertiesByDotNotation,\n\t\t\t\t\t\tfilePath,\n\t\t\t\t\t\twatch: enableWatch,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tstore.close();\n\t\t\t\tthrow new ValidationError(result.errors);\n\t\t\t}\n\t\t}\n\n\t\t// Persist merged data if defaults added new keys\n\t\tconst hasNewDefaults = Object.keys(defaults).some(\n\t\t\t(key) => !(key in storedData),\n\t\t);\n\t\tif (hasNewDefaults) {\n\t\t\tif (encryptor) {\n\t\t\t\tconst encrypted = await encryptStore(merged, encryptor);\n\t\t\t\tstore.setMany(Object.entries(encrypted));\n\t\t\t} else {\n\t\t\t\tstore.setMany(Object.entries(merged));\n\t\t\t}\n\t\t\tstore.touchLastWrite();\n\t\t}\n\n\t\t// Run migrations if configured\n\t\tif (migrations && migrations.length > 0) {\n\t\t\tif (!projectVersion) {\n\t\t\t\tstore.close();\n\t\t\t\tthrow new ConfigEngineError(\n\t\t\t\t\t'\"projectVersion\" is required when \"migrations\" is provided.',\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait runMigrations({\n\t\t\t\tstore,\n\t\t\t\tmigrations,\n\t\t\t\tprojectVersion,\n\t\t\t\tbeforeEachMigration,\n\t\t\t\tafterEachMigration,\n\t\t\t});\n\t\t}\n\n\t\t// Initialize cache\n\t\tconst cache = new ConfigCache<T>(store, flushStrategy);\n\t\tlet finalData: Record<string, unknown>;\n\t\tif (encryptor) {\n\t\t\tfinalData = await decryptStore(store.getAll(), encryptor);\n\t\t} else {\n\t\t\tfinalData = store.getAll();\n\t\t}\n\t\t// Re-merge defaults in case migrations changed data\n\t\tconst cacheData = { ...defaults, ...finalData } as T;\n\t\tcache.load(cacheData);\n\n\t\treturn new ConfigEngine<T>(store, cache, {\n\t\t\tvalidator,\n\t\t\tencryptor,\n\t\t\tdefaults,\n\t\t\tdotNotation: accessPropertiesByDotNotation,\n\t\t\tfilePath,\n\t\t\twatch: enableWatch,\n\t\t});\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Read operations\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Get a config value by key. Supports dot-notation by default.\n\t * @param key - The configuration key.\n\t * @param defaultValue - Fallback value if the key doesn't exist.\n\t */\n\tget<K extends string & keyof T>(key: K): T[K] | undefined;\n\tget<K extends string & keyof T>(key: K, defaultValue: T[K]): T[K];\n\tget<V = unknown>(key: string, defaultValue?: V): V | undefined;\n\tget(key: string, defaultValue?: unknown): unknown {\n\t\tthis.#ensureOpen();\n\n\t\tif (this.#dotNotation && key.includes(\".\")) {\n\t\t\tconst full = this.#cache.getAll();\n\t\t\tconst value = getByPath(full, key);\n\t\t\treturn value !== undefined ? value : defaultValue;\n\t\t}\n\n\t\tconst value = this.#cache.get(key);\n\t\treturn value !== undefined ? value : defaultValue;\n\t}\n\n\t/**\n\t * Check whether a key exists in the config.\n\t */\n\thas(key: string): boolean {\n\t\tthis.#ensureOpen();\n\n\t\tif (this.#dotNotation && key.includes(\".\")) {\n\t\t\treturn hasByPath(this.#cache.getAll(), key);\n\t\t}\n\t\treturn this.#cache.has(key);\n\t}\n\n\t/**\n\t * Get the entire config store as a typed object (shallow copy).\n\t */\n\tget store(): T {\n\t\tthis.#ensureOpen();\n\t\treturn this.#cache.getAll();\n\t}\n\n\t/**\n\t * Replace the entire config store.\n\t */\n\tset store(value: T) {\n\t\tthis.#ensureOpen();\n\t\tthis.#validateFull(value);\n\n\t\tconst oldStore = this.#cache.getAll();\n\t\tthis.#cache.replaceAll(value);\n\t\tthis.#notifyAnyChange(value, oldStore);\n\t}\n\n\t/**\n\t * Number of top-level config entries.\n\t */\n\tget size(): number {\n\t\tthis.#ensureOpen();\n\t\treturn this.#cache.size;\n\t}\n\n\t/**\n\t * Absolute path to the SQLite database file.\n\t */\n\tget path(): string {\n\t\treturn this.#filePath;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Write operations\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Set a config value. Supports two signatures:\n\t * - `set(key, value)` — set a single key\n\t * - `set(object)` — set multiple keys at once\n\t */\n\tset<K extends string & keyof T>(key: K, value: T[K]): void;\n\tset(key: string, value: unknown): void;\n\tset(object: Partial<T>): void;\n\tset(keyOrObject: string | Partial<T>, value?: unknown): void {\n\t\tthis.#ensureOpen();\n\n\t\tif (typeof keyOrObject === \"string\") {\n\t\t\tthis.#setKey(keyOrObject, value);\n\t\t} else {\n\t\t\tconst entries = Object.entries(keyOrObject);\n\t\t\tconst oldStore = this.#cache.getAll();\n\n\t\t\tfor (const [key, val] of entries) {\n\t\t\t\tif (this.#dotNotation && key.includes(\".\")) {\n\t\t\t\t\tconst full = this.#cache.getAll();\n\t\t\t\t\tconst updated = setByPath(full, key, val);\n\t\t\t\t\tthis.#validateFull(updated as T);\n\t\t\t\t\tthis.#cache.replaceAll(updated as T);\n\t\t\t\t} else {\n\t\t\t\t\tthis.#cache.set(key, val);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Validate the full store after batch update\n\t\t\tthis.#validateFull(this.#cache.getAll());\n\t\t\tthis.#notifyAnyChange(this.#cache.getAll(), oldStore);\n\t\t}\n\t}\n\n\t#setKey(key: string, value: unknown): void {\n\t\tconst oldStore = this.#cache.getAll();\n\t\tconst oldValue = this.get(key);\n\n\t\tif (this.#dotNotation && key.includes(\".\")) {\n\t\t\tconst full = this.#cache.getAll();\n\t\t\tconst updated = setByPath(full, key, value);\n\t\t\tthis.#validateFull(updated as T);\n\t\t\tthis.#cache.replaceAll(updated as T);\n\t\t} else {\n\t\t\t// Validate with this change applied\n\t\t\tconst testStore = { ...this.#cache.getAll(), [key]: value } as T;\n\t\t\tthis.#validateFull(testStore);\n\t\t\tthis.#cache.set(key, value);\n\t\t}\n\n\t\tthis.#notifyKeyChange(key, value, oldValue);\n\t\tthis.#notifyAnyChange(this.#cache.getAll(), oldStore);\n\t}\n\n\t/**\n\t * Delete a config key.\n\t */\n\tdelete(key: string): void {\n\t\tthis.#ensureOpen();\n\n\t\tconst oldStore = this.#cache.getAll();\n\t\tconst oldValue = this.get(key);\n\n\t\tif (this.#dotNotation && key.includes(\".\")) {\n\t\t\tconst full = this.#cache.getAll();\n\t\t\tconst updated = deleteByPath(full, key);\n\t\t\tthis.#cache.replaceAll(updated as T);\n\t\t} else {\n\t\t\tthis.#cache.delete(key);\n\t\t}\n\n\t\tthis.#notifyKeyChange(key, undefined, oldValue);\n\t\tthis.#notifyAnyChange(this.#cache.getAll(), oldStore);\n\t}\n\n\t/**\n\t * Delete all config entries and restore defaults.\n\t */\n\tclear(): void {\n\t\tthis.#ensureOpen();\n\n\t\tconst oldStore = this.#cache.getAll();\n\t\tthis.#cache.clear();\n\n\t\t// Restore defaults\n\t\tif (this.#defaults && Object.keys(this.#defaults).length > 0) {\n\t\t\tthis.#cache.setMany(Object.entries(this.#defaults));\n\t\t}\n\n\t\tthis.#notifyAnyChange(this.#cache.getAll(), oldStore);\n\t}\n\n\t/**\n\t * Reset specific keys to their default values.\n\t */\n\treset(...keys: (string & keyof T)[]): void {\n\t\tthis.#ensureOpen();\n\n\t\tconst oldStore = this.#cache.getAll();\n\n\t\tfor (const key of keys) {\n\t\t\tif (key in this.#defaults) {\n\t\t\t\tthis.#cache.set(key, this.#defaults[key]);\n\t\t\t} else {\n\t\t\t\tthis.#cache.delete(key);\n\t\t\t}\n\t\t}\n\n\t\tthis.#notifyAnyChange(this.#cache.getAll(), oldStore);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Change events\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Watch a specific key for changes.\n\t * @returns An unsubscribe function.\n\t */\n\tonDidChange<K extends string & keyof T>(\n\t\tkey: K,\n\t\tcallback: ChangeCallback<T[K]>,\n\t): Unsubscribe {\n\t\tif (!this.#listeners.has(key)) {\n\t\t\tthis.#listeners.set(key, new Set());\n\t\t}\n\t\tconst set = this.#listeners.get(key)!;\n\t\tset.add(callback as ChangeCallback<unknown>);\n\n\t\treturn () => {\n\t\t\tset.delete(callback as ChangeCallback<unknown>);\n\t\t\tif (set.size === 0) this.#listeners.delete(key);\n\t\t};\n\t}\n\n\t/**\n\t * Watch the entire store for any change.\n\t * @returns An unsubscribe function.\n\t */\n\tonDidAnyChange(callback: AnyChangeCallback<T>): Unsubscribe {\n\t\tthis.#anyListeners.add(callback);\n\t\treturn () => {\n\t\t\tthis.#anyListeners.delete(callback);\n\t\t};\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Flush & lifecycle\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Flush any pending writes to disk.\n\t * Required when using `flushStrategy: \"manual\"`.\n\t */\n\tasync flush(): Promise<void> {\n\t\tthis.#ensureOpen();\n\t\tawait this.#cache.flush();\n\t}\n\n\t/**\n\t * Synchronously flush any pending writes to disk.\n\t */\n\tflushSync(): void {\n\t\tthis.#ensureOpen();\n\t\tthis.#cache.flushSync();\n\t}\n\n\t/**\n\t * Close the config engine. Flushes pending writes and releases\n\t * the database connection. The instance cannot be used after closing.\n\t */\n\tclose(): void {\n\t\tif (this.#closed) return;\n\n\t\tthis.#stopWatching();\n\t\tthis.#cache.flushSync();\n\t\tthis.#store.close();\n\t\tthis.#closed = true;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Iterable\n\t// -----------------------------------------------------------------------\n\n\t*[Symbol.iterator](): IterableIterator<[string, unknown]> {\n\t\tthis.#ensureOpen();\n\t\tyield* this.#cache.entries();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Private helpers\n\t// -----------------------------------------------------------------------\n\n\t#ensureOpen(): void {\n\t\tif (this.#closed) {\n\t\t\tthrow new ConfigEngineError(\n\t\t\t\t\"This ConfigEngine instance has been closed. Create a new one with ConfigEngine.open().\",\n\t\t\t);\n\t\t}\n\t}\n\n\t#validateFull(data: T): void {\n\t\tif (!this.#validator) return;\n\n\t\tconst result = this.#validator.validate(data);\n\t\tif (!result.success) {\n\t\t\tthrow new ValidationError(result.errors);\n\t\t}\n\t}\n\n\t#notifyKeyChange(key: string, newValue: unknown, oldValue: unknown): void {\n\t\tif (newValue === oldValue) return;\n\t\tif (\n\t\t\ttypeof newValue === \"object\" &&\n\t\t\ttypeof oldValue === \"object\" &&\n\t\t\tJSON.stringify(newValue) === JSON.stringify(oldValue)\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst listeners = this.#listeners.get(key);\n\t\tif (listeners) {\n\t\t\tfor (const cb of listeners) {\n\t\t\t\tcb(newValue, oldValue);\n\t\t\t}\n\t\t}\n\t}\n\n\t#notifyAnyChange(newStore: T, oldStore: T): void {\n\t\tif (JSON.stringify(newStore) === JSON.stringify(oldStore)) return;\n\n\t\tfor (const cb of this.#anyListeners) {\n\t\t\tcb(newStore, oldStore);\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// File watching\n\t// -----------------------------------------------------------------------\n\n\t#startWatching(): void {\n\t\ttry {\n\t\t\tthis.#watcher = watch(dirname(this.#filePath), (_event, filename) => {\n\t\t\t\tif (!filename) return;\n\t\t\t\tconst expectedName = this.#filePath.split(/[\\\\/]/).pop();\n\t\t\t\tif (filename !== expectedName) return;\n\n\t\t\t\t// Check if an external process updated the DB\n\t\t\t\tconst currentWrite = this.#store.getLastWrite();\n\t\t\t\tif (currentWrite > this.#lastKnownWrite) {\n\t\t\t\t\tthis.#lastKnownWrite = currentWrite;\n\t\t\t\t\tconst oldStore = this.#cache.getAll();\n\t\t\t\t\tthis.#cache.reload();\n\t\t\t\t\tconst newStore = this.#cache.getAll();\n\t\t\t\t\tthis.#notifyAnyChange(newStore, oldStore);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Don't keep the process alive just for file watching\n\t\t\tif (this.#watcher && \"unref\" in this.#watcher) {\n\t\t\t\tthis.#watcher.unref();\n\t\t\t}\n\t\t} catch {\n\t\t\t// If watching fails (e.g. some CI environments), silently ignore\n\t\t}\n\t}\n\n\t#stopWatching(): void {\n\t\tif (this.#watcher) {\n\t\t\tthis.#watcher.close();\n\t\t\tthis.#watcher = null;\n\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Encryption helpers\n// ---------------------------------------------------------------------------\n\nasync function encryptStore(\n\tdata: Record<string, unknown>,\n\tencryptor: Encryptor,\n): Promise<Record<string, string>> {\n\tconst result: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tresult[key] = await encryptor.encrypt(JSON.stringify(value));\n\t}\n\treturn result;\n}\n\nasync function decryptStore(\n\tdata: Record<string, unknown>,\n\tencryptor: Encryptor,\n): Promise<Record<string, unknown>> {\n\tconst result: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (typeof value === \"string\") {\n\t\t\ttry {\n\t\t\t\tconst decrypted = await encryptor.decrypt(value);\n\t\t\t\tresult[key] = JSON.parse(decrypted);\n\t\t\t} catch {\n\t\t\t\t// Value might not be encrypted (e.g. first load with encryption enabled)\n\t\t\t\tresult[key] = value;\n\t\t\t}\n\t\t} else {\n\t\t\tresult[key] = value;\n\t\t}\n\t}\n\treturn result;\n}\n",
|
|
6
|
+
"/**\n * @module cache\n * In-memory cache with configurable write-behind strategy.\n * All `.get()` calls hit the Map (zero disk I/O). Writes are\n * flushed to the SQLite store based on the configured strategy.\n */\n\nimport type { ConfigStore } from \"./store.js\";\nimport type { FlushStrategy } from \"./types.js\";\n\nexport class ConfigCache<T extends Record<string, unknown>> {\n\treadonly #store: ConfigStore;\n\treadonly #strategy: FlushStrategy;\n\treadonly #data: Map<string, unknown> = new Map();\n\treadonly #dirty: Set<string> = new Set();\n\treadonly #deleted: Set<string> = new Set();\n\t#flushScheduled = false;\n\t#flushPromise: Promise<void> | null = null;\n\n\tconstructor(store: ConfigStore, strategy: FlushStrategy = \"batched\") {\n\t\tthis.#store = store;\n\t\tthis.#strategy = strategy;\n\t}\n\n\t/** Load all data from the store into memory. */\n\tload(initial?: Record<string, unknown>): void {\n\t\tthis.#data.clear();\n\t\tthis.#dirty.clear();\n\t\tthis.#deleted.clear();\n\n\t\tconst stored = initial ?? this.#store.getAll();\n\t\tfor (const [key, value] of Object.entries(stored)) {\n\t\t\tthis.#data.set(key, value);\n\t\t}\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Read operations (zero I/O)\n\t// -----------------------------------------------------------------------\n\n\tget(key: string): unknown {\n\t\treturn this.#data.get(key);\n\t}\n\n\thas(key: string): boolean {\n\t\treturn this.#data.has(key);\n\t}\n\n\tget size(): number {\n\t\treturn this.#data.size;\n\t}\n\n\t/** Return a shallow copy of the entire store as a plain object. */\n\tgetAll(): T {\n\t\tconst obj = Object.create(null) as Record<string, unknown>;\n\t\tfor (const [key, value] of this.#data) {\n\t\t\tobj[key] = value;\n\t\t}\n\t\treturn obj as T;\n\t}\n\n\tentries(): IterableIterator<[string, unknown]> {\n\t\treturn this.#data.entries();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Write operations\n\t// -----------------------------------------------------------------------\n\n\tset(key: string, value: unknown): void {\n\t\tthis.#data.set(key, value);\n\t\tthis.#dirty.add(key);\n\t\tthis.#deleted.delete(key);\n\t\tthis.#scheduleFlush();\n\t}\n\n\tsetMany(entries: Array<[string, unknown]>): void {\n\t\tfor (const [key, value] of entries) {\n\t\t\tthis.#data.set(key, value);\n\t\t\tthis.#dirty.add(key);\n\t\t\tthis.#deleted.delete(key);\n\t\t}\n\t\tthis.#scheduleFlush();\n\t}\n\n\tdelete(key: string): boolean {\n\t\tconst existed = this.#data.delete(key);\n\t\tif (existed) {\n\t\t\tthis.#dirty.delete(key);\n\t\t\tthis.#deleted.add(key);\n\t\t\tthis.#scheduleFlush();\n\t\t}\n\t\treturn existed;\n\t}\n\n\tclear(): void {\n\t\t// Mark all current keys for deletion\n\t\tfor (const key of this.#data.keys()) {\n\t\t\tthis.#deleted.add(key);\n\t\t}\n\t\tthis.#data.clear();\n\t\tthis.#dirty.clear();\n\t\tthis.#scheduleFlush();\n\t}\n\n\t/** Replace the entire in-memory store and schedule a full flush. */\n\treplaceAll(data: T): void {\n\t\t// Mark old keys for deletion\n\t\tfor (const key of this.#data.keys()) {\n\t\t\tif (!(key in data)) {\n\t\t\t\tthis.#deleted.add(key);\n\t\t\t}\n\t\t}\n\t\tthis.#data.clear();\n\t\tthis.#dirty.clear();\n\n\t\tfor (const [key, value] of Object.entries(data)) {\n\t\t\tthis.#data.set(key, value);\n\t\t\tthis.#dirty.add(key);\n\t\t\tthis.#deleted.delete(key);\n\t\t}\n\t\tthis.#scheduleFlush();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Flush\n\t// -----------------------------------------------------------------------\n\n\t#scheduleFlush(): void {\n\t\tif (this.#strategy === \"manual\") return;\n\n\t\tif (this.#strategy === \"immediate\") {\n\t\t\tthis.flushSync();\n\t\t\treturn;\n\t\t}\n\n\t\t// \"batched\" — schedule a microtask flush\n\t\tif (!this.#flushScheduled) {\n\t\t\tthis.#flushScheduled = true;\n\t\t\tthis.#flushPromise = Promise.resolve().then(() => {\n\t\t\t\tthis.flushSync();\n\t\t\t\tthis.#flushScheduled = false;\n\t\t\t\tthis.#flushPromise = null;\n\t\t\t});\n\t\t}\n\t}\n\n\t/** Synchronously flush all pending changes to SQLite. */\n\tflushSync(): void {\n\t\tif (this.#dirty.size === 0 && this.#deleted.size === 0) return;\n\n\t\tthis.#store.transaction(() => {\n\t\t\t// Persist dirty entries\n\t\t\tif (this.#dirty.size > 0) {\n\t\t\t\tconst entries: Array<[string, unknown]> = [];\n\t\t\t\tfor (const key of this.#dirty) {\n\t\t\t\t\tentries.push([key, this.#data.get(key)]);\n\t\t\t\t}\n\t\t\t\tthis.#store.setMany(entries);\n\t\t\t}\n\n\t\t\t// Delete removed entries\n\t\t\tfor (const key of this.#deleted) {\n\t\t\t\tthis.#store.deleteOne(key);\n\t\t\t}\n\n\t\t\tthis.#store.touchLastWrite();\n\t\t});\n\n\t\tthis.#dirty.clear();\n\t\tthis.#deleted.clear();\n\t}\n\n\t/**\n\t * Wait for any pending batched flush to complete.\n\t * Returns immediately if no flush is pending.\n\t */\n\tasync flush(): Promise<void> {\n\t\tif (this.#flushPromise) {\n\t\t\tawait this.#flushPromise;\n\t\t} else if (this.#dirty.size > 0 || this.#deleted.size > 0) {\n\t\t\tthis.flushSync();\n\t\t}\n\t}\n\n\t/** Reload from disk, discarding uncommitted changes. */\n\treload(): void {\n\t\tthis.#dirty.clear();\n\t\tthis.#deleted.clear();\n\t\tthis.load();\n\t}\n}\n",
|
|
7
|
+
"/**\n * @module dot-prop\n * Lightweight dot-notation property access utilities.\n * Zero dependencies — replaces the `dot-prop` npm package.\n */\n\n/**\n * Get a nested value using dot-notation path.\n *\n * ```ts\n * getByPath({ a: { b: { c: 42 } } }, \"a.b.c\") // 42\n * getByPath({ a: { b: 1 } }, \"a.x\") // undefined\n * ```\n */\nexport function getByPath(obj: unknown, path: string): unknown {\n\tif (typeof obj !== \"object\" || obj === null) return undefined;\n\n\tconst keys = path.split(\".\");\n\tlet current: unknown = obj;\n\n\tfor (const key of keys) {\n\t\tif (typeof current !== \"object\" || current === null) return undefined;\n\t\tcurrent = (current as Record<string, unknown>)[key];\n\t}\n\n\treturn current;\n}\n\n/**\n * Set a nested value using dot-notation path. Creates intermediate\n * objects as needed. Returns a new object (shallow copies along the path).\n *\n * ```ts\n * setByPath({}, \"a.b.c\", 42) // { a: { b: { c: 42 } } }\n * setByPath({ a: { x: 1 } }, \"a.b\", 2) // { a: { x: 1, b: 2 } }\n * ```\n */\nexport function setByPath<T extends Record<string, unknown>>(\n\tobj: T,\n\tpath: string,\n\tvalue: unknown,\n): T {\n\tconst keys = path.split(\".\");\n\tif (keys.length === 0) return obj;\n\n\t// biome-ignore lint/suspicious/noExplicitAny: deep clone helper\n\tconst result = { ...obj } as any;\n\tlet current = result;\n\n\tfor (let i = 0; i < keys.length - 1; i++) {\n\t\tconst key = keys[i]!;\n\t\tif (typeof current[key] !== \"object\" || current[key] === null) {\n\t\t\tcurrent[key] = {};\n\t\t} else {\n\t\t\tcurrent[key] = { ...current[key] };\n\t\t}\n\t\tcurrent = current[key];\n\t}\n\n\tconst lastKey = keys[keys.length - 1]!;\n\tcurrent[lastKey] = value;\n\n\treturn result as T;\n}\n\n/**\n * Check if a nested path exists.\n */\nexport function hasByPath(obj: unknown, path: string): boolean {\n\tif (typeof obj !== \"object\" || obj === null) return false;\n\n\tconst keys = path.split(\".\");\n\tlet current: unknown = obj;\n\n\tfor (let i = 0; i < keys.length; i++) {\n\t\tif (typeof current !== \"object\" || current === null) return false;\n\t\tconst key = keys[i]!;\n\t\tif (!Object.prototype.hasOwnProperty.call(current, key)) return false;\n\t\tcurrent = (current as Record<string, unknown>)[key];\n\t}\n\n\treturn true;\n}\n\n/**\n * Delete a nested property using dot-notation path.\n * Returns a new object (shallow copies along the path).\n * Returns the original object if the path doesn't exist.\n */\nexport function deleteByPath<T extends Record<string, unknown>>(\n\tobj: T,\n\tpath: string,\n): T {\n\tconst keys = path.split(\".\");\n\tif (keys.length === 0) return obj;\n\n\t// biome-ignore lint/suspicious/noExplicitAny: deep clone helper\n\tconst result = { ...obj } as any;\n\tlet current = result;\n\n\tfor (let i = 0; i < keys.length - 1; i++) {\n\t\tconst key = keys[i]!;\n\t\tif (typeof current[key] !== \"object\" || current[key] === null) {\n\t\t\treturn obj; // Path doesn't exist — return unchanged\n\t\t}\n\t\tcurrent[key] = { ...current[key] };\n\t\tcurrent = current[key];\n\t}\n\n\tconst lastKey = keys[keys.length - 1]!;\n\tdelete current[lastKey];\n\n\treturn result as T;\n}\n",
|
|
8
|
+
"/**\n * @module encryption\n * Optional encryption integration via `@wgtechlabs/secrets-engine`.\n * Also supports custom `Encryptor` implementations.\n */\n\nimport type { Encryptor } from \"./types.js\";\n\n/**\n * Encryption adapter that uses `@wgtechlabs/secrets-engine`.\n *\n * The encryption key is stored securely in secrets-engine (machine-bound,\n * AES-256-GCM). This adapter retrieves the key on init and uses it to\n * encrypt/decrypt config values via Node's `crypto` module.\n */\nexport class SecretsEngineEncryptor implements Encryptor {\n\t#keyName: string;\n\t#derivedKey: CryptoKey | null = null;\n\t// biome-ignore lint/suspicious/noExplicitAny: secrets-engine instance\n\t#secrets: any = null;\n\n\tconstructor(keyName: string) {\n\t\tthis.#keyName = keyName;\n\t}\n\n\t/** Initialize the encryptor — must be called before encrypt/decrypt. */\n\tasync init(): Promise<void> {\n\t\tlet SecretsEngine: { open: () => Promise<unknown> };\n\t\ttry {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: dynamic import\n\t\t\tconst mod = (await import(\"@wgtechlabs/secrets-engine\")) as any;\n\t\t\tSecretsEngine = mod.SecretsEngine ?? mod.default?.SecretsEngine ?? mod;\n\t\t} catch {\n\t\t\tthrow new Error(\n\t\t\t\t'Encryption requires \"@wgtechlabs/secrets-engine\" as a peer dependency. ' +\n\t\t\t\t\t\"Install it with: bun add @wgtechlabs/secrets-engine\",\n\t\t\t);\n\t\t}\n\n\t\tthis.#secrets = await SecretsEngine.open();\n\n\t\t// Ensure the encryption key exists in secrets-engine\n\t\tconst hasKey = await this.#secrets.has(this.#keyName);\n\t\tif (!hasKey) {\n\t\t\t// Generate a random 256-bit key and store it\n\t\t\tconst keyBytes = crypto.getRandomValues(new Uint8Array(32));\n\t\t\tconst keyHex = Array.from(keyBytes)\n\t\t\t\t.map((b: number) => b.toString(16).padStart(2, \"0\"))\n\t\t\t\t.join(\"\");\n\t\t\tawait this.#secrets.set(this.#keyName, keyHex);\n\t\t}\n\n\t\t// Import the key for Web Crypto API usage\n\t\tconst keyHex = await this.#secrets.get(this.#keyName);\n\t\tconst keyBuffer = hexToUint8Array(keyHex as string);\n\t\tthis.#derivedKey = await crypto.subtle.importKey(\n\t\t\t\"raw\",\n\t\t\tkeyBuffer.buffer as ArrayBuffer,\n\t\t\t{ name: \"AES-GCM\" },\n\t\t\tfalse,\n\t\t\t[\"encrypt\", \"decrypt\"],\n\t\t);\n\t}\n\n\tasync encrypt(plaintext: string): Promise<string> {\n\t\tif (!this.#derivedKey) throw new Error(\"Encryptor not initialized. Call init() first.\");\n\n\t\tconst iv = crypto.getRandomValues(new Uint8Array(12));\n\t\tconst encoded = new TextEncoder().encode(plaintext);\n\t\tconst cipherBuffer = await crypto.subtle.encrypt(\n\t\t\t{ name: \"AES-GCM\", iv: iv.buffer as ArrayBuffer },\n\t\t\tthis.#derivedKey,\n\t\t\tencoded,\n\t\t);\n\n\t\t// Format: base64(iv):base64(ciphertext)\n\t\tconst ivB64 = uint8ArrayToBase64(iv);\n\t\tconst cipherB64 = uint8ArrayToBase64(new Uint8Array(cipherBuffer));\n\t\treturn `${ivB64}:${cipherB64}`;\n\t}\n\n\tasync decrypt(ciphertext: string): Promise<string> {\n\t\tif (!this.#derivedKey) throw new Error(\"Encryptor not initialized. Call init() first.\");\n\n\t\tconst [ivB64, cipherB64] = ciphertext.split(\":\");\n\t\tif (!ivB64 || !cipherB64) throw new Error(\"Invalid encrypted data format.\");\n\n\t\tconst iv = base64ToUint8Array(ivB64);\n\t\tconst cipherBuffer = base64ToUint8Array(cipherB64);\n\n\t\tconst plainBuffer = await crypto.subtle.decrypt(\n\t\t\t{ name: \"AES-GCM\", iv: iv.buffer as ArrayBuffer },\n\t\t\tthis.#derivedKey,\n\t\t\tcipherBuffer.buffer as ArrayBuffer,\n\t\t);\n\n\t\treturn new TextDecoder().decode(plainBuffer);\n\t}\n\n\t/** Close the underlying secrets-engine instance. */\n\tasync close(): Promise<void> {\n\t\tif (this.#secrets) {\n\t\t\tthis.#secrets.close();\n\t\t\tthis.#secrets = null;\n\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction hexToUint8Array(hex: string): Uint8Array {\n\tconst bytes = new Uint8Array(hex.length / 2);\n\tfor (let i = 0; i < hex.length; i += 2) {\n\t\tbytes[i / 2] = Number.parseInt(hex.substring(i, i + 2), 16);\n\t}\n\treturn bytes;\n}\n\nfunction uint8ArrayToBase64(bytes: Uint8Array): string {\n\tif (typeof Buffer !== \"undefined\") {\n\t\treturn Buffer.from(bytes).toString(\"base64\");\n\t}\n\t// Fallback for non-Node environments\n\tlet binary = \"\";\n\tfor (const byte of bytes) {\n\t\tbinary += String.fromCharCode(byte);\n\t}\n\treturn btoa(binary);\n}\n\nfunction base64ToUint8Array(b64: string): Uint8Array {\n\tif (typeof Buffer !== \"undefined\") {\n\t\treturn new Uint8Array(Buffer.from(b64, \"base64\"));\n\t}\n\tconst binary = atob(b64);\n\tconst bytes = new Uint8Array(binary.length);\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes;\n}\n\n/**\n * Resolve the encryptor from options.\n * - If a string key name is provided, create a `SecretsEngineEncryptor`.\n * - If an `Encryptor` object is provided, use it directly.\n * - If undefined, return undefined (no encryption).\n */\nexport async function resolveEncryptor(\n\tencryptionKey?: string | Encryptor,\n): Promise<Encryptor | undefined> {\n\tif (!encryptionKey) return undefined;\n\n\tif (typeof encryptionKey === \"string\") {\n\t\tconst enc = new SecretsEngineEncryptor(encryptionKey);\n\t\tawait enc.init();\n\t\treturn enc;\n\t}\n\n\t// Custom Encryptor instance\n\treturn encryptionKey;\n}\n",
|
|
9
|
+
"/**\n * @module migrations\n * Version-based migration system with SQLite transaction rollback.\n */\n\nimport type { ConfigStore } from \"./store.js\";\nimport type { MigrationContext, MigrationDefinition, MigrationHookContext } from \"./types.js\";\n\nconst META_KEY = \"schema_version\";\nconst INITIAL_VERSION = \"0.0.0\";\n\n/**\n * Run pending migrations against the store. Executes in a SQLite\n * transaction — if any migration throws, ALL changes are rolled back.\n */\nexport async function runMigrations(options: {\n\tstore: ConfigStore;\n\tmigrations: MigrationDefinition[];\n\tprojectVersion: string;\n\tbeforeEachMigration?: (ctx: MigrationHookContext) => void | Promise<void>;\n\tafterEachMigration?: (ctx: MigrationHookContext) => void | Promise<void>;\n}): Promise<void> {\n\tconst { store, migrations, projectVersion, beforeEachMigration, afterEachMigration } = options;\n\n\tif (migrations.length === 0) return;\n\n\tconst currentVersion = store.getMeta(META_KEY) ?? INITIAL_VERSION;\n\tconst versions = migrations.map((m) => m.version);\n\tconst finalVersion = projectVersion;\n\n\t// Filter migrations that need to run (versions > currentVersion)\n\tconst pending = migrations.filter((m) => compareVersions(m.version, currentVersion) > 0);\n\n\tif (pending.length === 0) {\n\t\t// Ensure version is up to date even if no migrations ran\n\t\tstore.setMeta(META_KEY, projectVersion);\n\t\treturn;\n\t}\n\n\t// Sort by version ascending\n\tpending.sort((a, b) => compareVersions(a.version, b.version));\n\n\t// Create a migration context that operates directly on the store\n\tconst ctx = createMigrationContext(store);\n\n\t// Execute migrations — some may be async, so we can't use\n\t// a SQLite transaction wrapper for the whole batch. Instead,\n\t// we snapshot before and restore on failure.\n\tconst snapshot = store.getAll();\n\tconst snapshotVersion = currentVersion;\n\n\ttry {\n\t\tfor (const migration of pending) {\n\t\t\tconst hookCtx: MigrationHookContext = {\n\t\t\t\tfromVersion: currentVersion,\n\t\t\t\ttoVersion: migration.version,\n\t\t\t\tfinalVersion,\n\t\t\t\tversions,\n\t\t\t};\n\n\t\t\tif (beforeEachMigration) {\n\t\t\t\tawait beforeEachMigration(hookCtx);\n\t\t\t}\n\n\t\t\tawait migration.up(ctx);\n\n\t\t\tif (afterEachMigration) {\n\t\t\t\tawait afterEachMigration(hookCtx);\n\t\t\t}\n\t\t}\n\n\t\t// All migrations succeeded — update schema version\n\t\tstore.setMeta(META_KEY, projectVersion);\n\t\tstore.touchLastWrite();\n\t} catch (error) {\n\t\t// Rollback: restore the snapshot\n\t\tstore.transaction(() => {\n\t\t\tstore.deleteAll();\n\t\t\tconst entries = Object.entries(snapshot);\n\t\t\tif (entries.length > 0) {\n\t\t\t\tstore.setMany(entries);\n\t\t\t}\n\t\t\tstore.setMeta(META_KEY, snapshotVersion);\n\t\t});\n\n\t\tthrow new Error(\n\t\t\t`Migration to version \"${pending.find(() => true)?.version}\" failed. ` +\n\t\t\t\t`All changes have been rolled back. Original error: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n}\n\n/**\n * Create a MigrationContext that reads/writes directly to the store,\n * bypassing validation and caching.\n */\nfunction createMigrationContext(store: ConfigStore): MigrationContext {\n\treturn {\n\t\tget<V = unknown>(key: string): V | undefined {\n\t\t\treturn store.getOne(key) as V | undefined;\n\t\t},\n\t\tset(key: string, value: unknown): void {\n\t\t\tstore.setOne(key, value);\n\t\t},\n\t\thas(key: string): boolean {\n\t\t\treturn store.has(key);\n\t\t},\n\t\tdelete(key: string): boolean {\n\t\t\treturn store.deleteOne(key);\n\t\t},\n\t};\n}\n\n/**\n * Simple semver comparison. Returns:\n * - negative if a < b\n * - 0 if a === b\n * - positive if a > b\n */\nfunction compareVersions(a: string, b: string): number {\n\tconst partsA = a.split(\".\").map(Number);\n\tconst partsB = b.split(\".\").map(Number);\n\n\tfor (let i = 0; i < 3; i++) {\n\t\tconst va = partsA[i] ?? 0;\n\t\tconst vb = partsB[i] ?? 0;\n\t\tif (va !== vb) return va - vb;\n\t}\n\treturn 0;\n}\n\n/**\n * Get the current schema version from the store.\n */\nexport function getSchemaVersion(store: ConfigStore): string {\n\treturn store.getMeta(META_KEY) ?? INITIAL_VERSION;\n}\n",
|
|
10
|
+
"/**\n * @module platform\n * OS-specific config directory resolution.\n * Replaces the `env-paths` package with a zero-dep implementation.\n */\n\nimport { homedir, platform } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * Resolve the default config directory for a given project name,\n * following platform conventions:\n *\n * - **macOS**: `~/Library/Preferences/<projectName>/`\n * - **Windows**: `%APPDATA%/<projectName>/`\n * - **Linux**: `$XDG_CONFIG_HOME/<projectName>/` (defaults to `~/.config`)\n */\nexport function resolveConfigDir(projectName: string): string {\n\tconst home = homedir();\n\tconst os = platform();\n\n\tswitch (os) {\n\t\tcase \"darwin\":\n\t\t\treturn join(home, \"Library\", \"Preferences\", projectName);\n\n\t\tcase \"win32\":\n\t\t\treturn join(process.env.APPDATA ?? join(home, \"AppData\", \"Roaming\"), projectName);\n\n\t\tdefault:\n\t\t\t// Linux / FreeBSD / other Unix\n\t\t\treturn join(process.env.XDG_CONFIG_HOME ?? join(home, \".config\"), projectName);\n\t}\n}\n\n/**\n * Build the full database file path from options.\n */\nexport function resolveConfigPath(options: {\n\tprojectName: string;\n\tcwd?: string;\n\tconfigName?: string;\n}): string {\n\tconst dir = options.cwd ?? resolveConfigDir(options.projectName);\n\tconst name = options.configName ?? \"config\";\n\treturn join(dir, `${name}.db`);\n}\n",
|
|
11
|
+
"/**\n * @module runtime\n * Runtime detection and SQLite adapter layer.\n * Uses `bun:sqlite` on Bun, `better-sqlite3` on Node.js.\n */\n\nimport type { DatabaseAdapter, StatementAdapter } from \"./types.js\";\n\n/** Returns `true` when running under Bun. */\nexport function isBun(): boolean {\n\treturn typeof globalThis.Bun !== \"undefined\";\n}\n\n/**\n * Open a SQLite database. Automatically selects the driver:\n * - Bun → `bun:sqlite` (built-in, zero deps)\n * - Node → `better-sqlite3` (peer dependency)\n */\nexport function openDatabase(filepath: string): DatabaseAdapter {\n\tif (isBun()) {\n\t\treturn openBunDatabase(filepath);\n\t}\n\treturn openNodeDatabase(filepath);\n}\n\n// ---------------------------------------------------------------------------\n// Bun adapter\n// ---------------------------------------------------------------------------\n\nfunction openBunDatabase(filepath: string): DatabaseAdapter {\n\t// Using dynamic import syntax to avoid static analysis issues on Node\n\t// biome-ignore lint/suspicious/noExplicitAny: bun:sqlite types vary\n\tconst { Database } = require(\"bun:sqlite\") as any;\n\tconst db = new Database(filepath);\n\n\t// Enable WAL for concurrent read performance\n\tdb.exec(\"PRAGMA journal_mode = WAL;\");\n\tdb.exec(\"PRAGMA busy_timeout = 5000;\");\n\n\treturn {\n\t\tprepare(sql: string): StatementAdapter {\n\t\t\tconst stmt = db.prepare(sql);\n\t\t\treturn {\n\t\t\t\trun(...params: unknown[]) {\n\t\t\t\t\tstmt.run(...params);\n\t\t\t\t},\n\t\t\t\tget(...params: unknown[]): unknown {\n\t\t\t\t\treturn stmt.get(...params);\n\t\t\t\t},\n\t\t\t\tall(...params: unknown[]): unknown[] {\n\t\t\t\t\treturn stmt.all(...params);\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\texec(sql: string) {\n\t\t\tdb.exec(sql);\n\t\t},\n\t\tclose() {\n\t\t\tdb.close();\n\t\t},\n\t\ttransaction<T>(fn: () => T): () => T {\n\t\t\treturn db.transaction(fn);\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Node adapter (better-sqlite3)\n// ---------------------------------------------------------------------------\n\nfunction openNodeDatabase(filepath: string): DatabaseAdapter {\n\t// better-sqlite3 is a peer dep — error if missing\n\tlet BetterSqlite3: typeof import(\"better-sqlite3\");\n\ttry {\n\t\t// biome-ignore lint/suspicious/noExplicitAny: dynamic require\n\t\tBetterSqlite3 = require(\"better-sqlite3\") as any;\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'config-engine requires \"better-sqlite3\" as a peer dependency when running on Node.js. ' +\n\t\t\t\t\"Install it with: npm install better-sqlite3\",\n\t\t);\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: better-sqlite3 constructor\n\tconst db = new (BetterSqlite3 as any)(filepath);\n\n\tdb.pragma(\"journal_mode = WAL\");\n\tdb.pragma(\"busy_timeout = 5000\");\n\n\treturn {\n\t\tprepare(sql: string): StatementAdapter {\n\t\t\tconst stmt = db.prepare(sql);\n\t\t\treturn {\n\t\t\t\trun(...params: unknown[]) {\n\t\t\t\t\tstmt.run(...params);\n\t\t\t\t},\n\t\t\t\tget(...params: unknown[]): unknown {\n\t\t\t\t\treturn stmt.get(...params);\n\t\t\t\t},\n\t\t\t\tall(...params: unknown[]): unknown[] {\n\t\t\t\t\treturn stmt.all(...params);\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t\texec(sql: string) {\n\t\t\tdb.exec(sql);\n\t\t},\n\t\tclose() {\n\t\t\tdb.close();\n\t\t},\n\t\ttransaction<T>(fn: () => T): () => T {\n\t\t\treturn db.transaction(fn) as () => T;\n\t\t},\n\t};\n}\n",
|
|
12
|
+
"/**\n * @module store\n * Low-level SQLite abstraction for config persistence.\n * All values are stored as JSON-stringified text.\n */\n\nimport type { DatabaseAdapter } from \"./types.js\";\n\n/** Row shape for the `config` table. */\ninterface ConfigRow {\n\tkey: string;\n\tvalue: string;\n}\n\n/**\n * Direct SQLite store — thin wrapper around the database adapter.\n * This layer does NOT cache or validate; that's handled by higher layers.\n */\nexport class ConfigStore {\n\treadonly #db: DatabaseAdapter;\n\n\tconstructor(db: DatabaseAdapter) {\n\t\tthis.#db = db;\n\t\tthis.#initialize();\n\t}\n\n\t/** Create tables if they don't exist. */\n\t#initialize(): void {\n\t\tthis.#db.exec(`\n\t\t\tCREATE TABLE IF NOT EXISTS config (\n\t\t\t\tkey TEXT PRIMARY KEY,\n\t\t\t\tvalue TEXT NOT NULL\n\t\t\t);\n\t\t`);\n\t\tthis.#db.exec(`\n\t\t\tCREATE TABLE IF NOT EXISTS _meta (\n\t\t\t\tkey TEXT PRIMARY KEY,\n\t\t\t\tvalue TEXT NOT NULL\n\t\t\t);\n\t\t`);\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Config CRUD\n\t// -----------------------------------------------------------------------\n\n\t/** Get all config entries as a flat `Record<string, unknown>`. */\n\tgetAll(): Record<string, unknown> {\n\t\tconst rows = this.#db.prepare(\"SELECT key, value FROM config\").all() as ConfigRow[];\n\t\tconst result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n\t\tfor (const row of rows) {\n\t\t\tresult[row.key] = JSON.parse(row.value);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/** Get a single value by key. Returns `undefined` if not found. */\n\tgetOne(key: string): unknown {\n\t\tconst row = this.#db.prepare(\"SELECT value FROM config WHERE key = ?\").get(key) as\n\t\t\t| ConfigRow\n\t\t\t| undefined;\n\t\treturn row ? JSON.parse(row.value) : undefined;\n\t}\n\n\t/** Insert or replace a single key/value. */\n\tsetOne(key: string, value: unknown): void {\n\t\tthis.#db\n\t\t\t.prepare(\"INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)\")\n\t\t\t.run(key, JSON.stringify(value));\n\t}\n\n\t/**\n\t * Set multiple entries atomically within a transaction.\n\t * Significantly faster than individual `setOne` calls.\n\t */\n\tsetMany(entries: Array<[string, unknown]>): void {\n\t\tconst stmt = this.#db.prepare(\n\t\t\t\"INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)\",\n\t\t);\n\t\tconst runTransaction = this.#db.transaction(() => {\n\t\t\tfor (const [key, value] of entries) {\n\t\t\t\tstmt.run(key, JSON.stringify(value));\n\t\t\t}\n\t\t});\n\t\trunTransaction();\n\t}\n\n\t/** Delete a single key. Returns `true` if the key existed. */\n\tdeleteOne(key: string): boolean {\n\t\tconst before = this.count();\n\t\tthis.#db.prepare(\"DELETE FROM config WHERE key = ?\").run(key);\n\t\treturn this.count() < before;\n\t}\n\n\t/** Delete all config entries. */\n\tdeleteAll(): void {\n\t\tthis.#db.exec(\"DELETE FROM config;\");\n\t}\n\n\t/** Check if a key exists. */\n\thas(key: string): boolean {\n\t\tconst row = this.#db\n\t\t\t.prepare(\"SELECT 1 FROM config WHERE key = ? LIMIT 1\")\n\t\t\t.get(key);\n\t\treturn row !== undefined && row !== null;\n\t}\n\n\t/** Number of config entries. */\n\tcount(): number {\n\t\tconst row = this.#db.prepare(\"SELECT COUNT(*) as cnt FROM config\").get() as {\n\t\t\tcnt: number;\n\t\t};\n\t\treturn row.cnt;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Meta CRUD (internal state: migration version, timestamps, etc.)\n\t// -----------------------------------------------------------------------\n\n\tgetMeta(key: string): string | undefined {\n\t\tconst row = this.#db\n\t\t\t.prepare(\"SELECT value FROM _meta WHERE key = ?\")\n\t\t\t.get(key) as ConfigRow | undefined;\n\t\treturn row?.value;\n\t}\n\n\tsetMeta(key: string, value: string): void {\n\t\tthis.#db\n\t\t\t.prepare(\"INSERT OR REPLACE INTO _meta (key, value) VALUES (?, ?)\")\n\t\t\t.run(key, value);\n\t}\n\n\thasMeta(key: string): boolean {\n\t\tconst row = this.#db\n\t\t\t.prepare(\"SELECT 1 FROM _meta WHERE key = ? LIMIT 1\")\n\t\t\t.get(key);\n\t\treturn row !== undefined && row !== null;\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Transaction helper\n\t// -----------------------------------------------------------------------\n\n\t/**\n\t * Execute `fn` inside a SQLite transaction.\n\t * If `fn` throws, the transaction is rolled back automatically.\n\t */\n\ttransaction<T>(fn: () => T): T {\n\t\tconst wrapped = this.#db.transaction(fn);\n\t\treturn wrapped();\n\t}\n\n\t// -----------------------------------------------------------------------\n\t// Lifecycle\n\t// -----------------------------------------------------------------------\n\n\t/** Update the `last_write` meta timestamp. Called after any mutation. */\n\ttouchLastWrite(): void {\n\t\tthis.setMeta(\"last_write\", Date.now().toString());\n\t}\n\n\t/** Get the last write timestamp (ms since epoch), or 0 if never written. */\n\tgetLastWrite(): number {\n\t\tconst val = this.getMeta(\"last_write\");\n\t\treturn val ? Number(val) : 0;\n\t}\n\n\t/** Close the underlying database connection. */\n\tclose(): void {\n\t\tthis.#db.close();\n\t}\n}\n"
|
|
13
|
+
],
|
|
14
|
+
"mappings": ";;;;;;;AA0BA;AACA;AACA;;;AClBO,MAAM,YAA+C;AAAA,EAClD;AAAA,EACA;AAAA,EACA,QAA8B,IAAI;AAAA,EAClC,SAAsB,IAAI;AAAA,EAC1B,WAAwB,IAAI;AAAA,EACrC,kBAAkB;AAAA,EAClB,gBAAsC;AAAA,EAEtC,WAAW,CAAC,OAAoB,WAA0B,WAAW;AAAA,IACpE,KAAK,SAAS;AAAA,IACd,KAAK,YAAY;AAAA;AAAA,EAIlB,IAAI,CAAC,SAAyC;AAAA,IAC7C,KAAK,MAAM,MAAM;AAAA,IACjB,KAAK,OAAO,MAAM;AAAA,IAClB,KAAK,SAAS,MAAM;AAAA,IAEpB,MAAM,SAAS,WAAW,KAAK,OAAO,OAAO;AAAA,IAC7C,YAAY,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;AAAA,MAClD,KAAK,MAAM,IAAI,KAAK,KAAK;AAAA,IAC1B;AAAA;AAAA,EAOD,GAAG,CAAC,KAAsB;AAAA,IACzB,OAAO,KAAK,MAAM,IAAI,GAAG;AAAA;AAAA,EAG1B,GAAG,CAAC,KAAsB;AAAA,IACzB,OAAO,KAAK,MAAM,IAAI,GAAG;AAAA;AAAA,MAGtB,IAAI,GAAW;AAAA,IAClB,OAAO,KAAK,MAAM;AAAA;AAAA,EAInB,MAAM,GAAM;AAAA,IACX,MAAM,MAAM,OAAO,OAAO,IAAI;AAAA,IAC9B,YAAY,KAAK,UAAU,KAAK,OAAO;AAAA,MACtC,IAAI,OAAO;AAAA,IACZ;AAAA,IACA,OAAO;AAAA;AAAA,EAGR,OAAO,GAAwC;AAAA,IAC9C,OAAO,KAAK,MAAM,QAAQ;AAAA;AAAA,EAO3B,GAAG,CAAC,KAAa,OAAsB;AAAA,IACtC,KAAK,MAAM,IAAI,KAAK,KAAK;AAAA,IACzB,KAAK,OAAO,IAAI,GAAG;AAAA,IACnB,KAAK,SAAS,OAAO,GAAG;AAAA,IACxB,KAAK,eAAe;AAAA;AAAA,EAGrB,OAAO,CAAC,SAAyC;AAAA,IAChD,YAAY,KAAK,UAAU,SAAS;AAAA,MACnC,KAAK,MAAM,IAAI,KAAK,KAAK;AAAA,MACzB,KAAK,OAAO,IAAI,GAAG;AAAA,MACnB,KAAK,SAAS,OAAO,GAAG;AAAA,IACzB;AAAA,IACA,KAAK,eAAe;AAAA;AAAA,EAGrB,MAAM,CAAC,KAAsB;AAAA,IAC5B,MAAM,UAAU,KAAK,MAAM,OAAO,GAAG;AAAA,IACrC,IAAI,SAAS;AAAA,MACZ,KAAK,OAAO,OAAO,GAAG;AAAA,MACtB,KAAK,SAAS,IAAI,GAAG;AAAA,MACrB,KAAK,eAAe;AAAA,IACrB;AAAA,IACA,OAAO;AAAA;AAAA,EAGR,KAAK,GAAS;AAAA,IAEb,WAAW,OAAO,KAAK,MAAM,KAAK,GAAG;AAAA,MACpC,KAAK,SAAS,IAAI,GAAG;AAAA,IACtB;AAAA,IACA,KAAK,MAAM,MAAM;AAAA,IACjB,KAAK,OAAO,MAAM;AAAA,IAClB,KAAK,eAAe;AAAA;AAAA,EAIrB,UAAU,CAAC,MAAe;AAAA,IAEzB,WAAW,OAAO,KAAK,MAAM,KAAK,GAAG;AAAA,MACpC,IAAI,EAAE,OAAO,OAAO;AAAA,QACnB,KAAK,SAAS,IAAI,GAAG;AAAA,MACtB;AAAA,IACD;AAAA,IACA,KAAK,MAAM,MAAM;AAAA,IACjB,KAAK,OAAO,MAAM;AAAA,IAElB,YAAY,KAAK,UAAU,OAAO,QAAQ,IAAI,GAAG;AAAA,MAChD,KAAK,MAAM,IAAI,KAAK,KAAK;AAAA,MACzB,KAAK,OAAO,IAAI,GAAG;AAAA,MACnB,KAAK,SAAS,OAAO,GAAG;AAAA,IACzB;AAAA,IACA,KAAK,eAAe;AAAA;AAAA,EAOrB,cAAc,GAAS;AAAA,IACtB,IAAI,KAAK,cAAc;AAAA,MAAU;AAAA,IAEjC,IAAI,KAAK,cAAc,aAAa;AAAA,MACnC,KAAK,UAAU;AAAA,MACf;AAAA,IACD;AAAA,IAGA,IAAI,CAAC,KAAK,iBAAiB;AAAA,MAC1B,KAAK,kBAAkB;AAAA,MACvB,KAAK,gBAAgB,QAAQ,QAAQ,EAAE,KAAK,MAAM;AAAA,QACjD,KAAK,UAAU;AAAA,QACf,KAAK,kBAAkB;AAAA,QACvB,KAAK,gBAAgB;AAAA,OACrB;AAAA,IACF;AAAA;AAAA,EAID,SAAS,GAAS;AAAA,IACjB,IAAI,KAAK,OAAO,SAAS,KAAK,KAAK,SAAS,SAAS;AAAA,MAAG;AAAA,IAExD,KAAK,OAAO,YAAY,MAAM;AAAA,MAE7B,IAAI,KAAK,OAAO,OAAO,GAAG;AAAA,QACzB,MAAM,UAAoC,CAAC;AAAA,QAC3C,WAAW,OAAO,KAAK,QAAQ;AAAA,UAC9B,QAAQ,KAAK,CAAC,KAAK,KAAK,MAAM,IAAI,GAAG,CAAC,CAAC;AAAA,QACxC;AAAA,QACA,KAAK,OAAO,QAAQ,OAAO;AAAA,MAC5B;AAAA,MAGA,WAAW,OAAO,KAAK,UAAU;AAAA,QAChC,KAAK,OAAO,UAAU,GAAG;AAAA,MAC1B;AAAA,MAEA,KAAK,OAAO,eAAe;AAAA,KAC3B;AAAA,IAED,KAAK,OAAO,MAAM;AAAA,IAClB,KAAK,SAAS,MAAM;AAAA;AAAA,OAOf,MAAK,GAAkB;AAAA,IAC5B,IAAI,KAAK,eAAe;AAAA,MACvB,MAAM,KAAK;AAAA,IACZ,EAAO,SAAI,KAAK,OAAO,OAAO,KAAK,KAAK,SAAS,OAAO,GAAG;AAAA,MAC1D,KAAK,UAAU;AAAA,IAChB;AAAA;AAAA,EAID,MAAM,GAAS;AAAA,IACd,KAAK,OAAO,MAAM;AAAA,IAClB,KAAK,SAAS,MAAM;AAAA,IACpB,KAAK,KAAK;AAAA;AAEZ;;;ACjLO,SAAS,SAAS,CAAC,KAAc,MAAuB;AAAA,EAC9D,IAAI,OAAO,QAAQ,YAAY,QAAQ;AAAA,IAAM;AAAA,EAE7C,MAAM,OAAO,KAAK,MAAM,GAAG;AAAA,EAC3B,IAAI,UAAmB;AAAA,EAEvB,WAAW,OAAO,MAAM;AAAA,IACvB,IAAI,OAAO,YAAY,YAAY,YAAY;AAAA,MAAM;AAAA,IACrD,UAAW,QAAoC;AAAA,EAChD;AAAA,EAEA,OAAO;AAAA;AAYD,SAAS,SAA4C,CAC3D,KACA,MACA,OACI;AAAA,EACJ,MAAM,OAAO,KAAK,MAAM,GAAG;AAAA,EAC3B,IAAI,KAAK,WAAW;AAAA,IAAG,OAAO;AAAA,EAG9B,MAAM,SAAS,KAAK,IAAI;AAAA,EACxB,IAAI,UAAU;AAAA,EAEd,SAAS,IAAI,EAAG,IAAI,KAAK,SAAS,GAAG,KAAK;AAAA,IACzC,MAAM,MAAM,KAAK;AAAA,IACjB,IAAI,OAAO,QAAQ,SAAS,YAAY,QAAQ,SAAS,MAAM;AAAA,MAC9D,QAAQ,OAAO,CAAC;AAAA,IACjB,EAAO;AAAA,MACN,QAAQ,OAAO,KAAK,QAAQ,KAAK;AAAA;AAAA,IAElC,UAAU,QAAQ;AAAA,EACnB;AAAA,EAEA,MAAM,UAAU,KAAK,KAAK,SAAS;AAAA,EACnC,QAAQ,WAAW;AAAA,EAEnB,OAAO;AAAA;AAMD,SAAS,SAAS,CAAC,KAAc,MAAuB;AAAA,EAC9D,IAAI,OAAO,QAAQ,YAAY,QAAQ;AAAA,IAAM,OAAO;AAAA,EAEpD,MAAM,OAAO,KAAK,MAAM,GAAG;AAAA,EAC3B,IAAI,UAAmB;AAAA,EAEvB,SAAS,IAAI,EAAG,IAAI,KAAK,QAAQ,KAAK;AAAA,IACrC,IAAI,OAAO,YAAY,YAAY,YAAY;AAAA,MAAM,OAAO;AAAA,IAC5D,MAAM,MAAM,KAAK;AAAA,IACjB,IAAI,CAAC,OAAO,UAAU,eAAe,KAAK,SAAS,GAAG;AAAA,MAAG,OAAO;AAAA,IAChE,UAAW,QAAoC;AAAA,EAChD;AAAA,EAEA,OAAO;AAAA;AAQD,SAAS,YAA+C,CAC9D,KACA,MACI;AAAA,EACJ,MAAM,OAAO,KAAK,MAAM,GAAG;AAAA,EAC3B,IAAI,KAAK,WAAW;AAAA,IAAG,OAAO;AAAA,EAG9B,MAAM,SAAS,KAAK,IAAI;AAAA,EACxB,IAAI,UAAU;AAAA,EAEd,SAAS,IAAI,EAAG,IAAI,KAAK,SAAS,GAAG,KAAK;AAAA,IACzC,MAAM,MAAM,KAAK;AAAA,IACjB,IAAI,OAAO,QAAQ,SAAS,YAAY,QAAQ,SAAS,MAAM;AAAA,MAC9D,OAAO;AAAA,IACR;AAAA,IACA,QAAQ,OAAO,KAAK,QAAQ,KAAK;AAAA,IACjC,UAAU,QAAQ;AAAA,EACnB;AAAA,EAEA,MAAM,UAAU,KAAK,KAAK,SAAS;AAAA,EACnC,OAAO,QAAQ;AAAA,EAEf,OAAO;AAAA;;;ACjGD,MAAM,uBAA4C;AAAA,EACxD;AAAA,EACA,cAAgC;AAAA,EAEhC,WAAgB;AAAA,EAEhB,WAAW,CAAC,SAAiB;AAAA,IAC5B,KAAK,WAAW;AAAA;AAAA,OAIX,KAAI,GAAkB;AAAA,IAC3B,IAAI;AAAA,IACJ,IAAI;AAAA,MAEH,MAAM,MAAO,MAAa;AAAA,MAC1B,gBAAgB,IAAI,iBAAiB,IAAI,SAAS,iBAAiB;AAAA,MAClE,MAAM;AAAA,MACP,MAAM,IAAI,MACT,4HAED;AAAA;AAAA,IAGD,KAAK,WAAW,MAAM,cAAc,KAAK;AAAA,IAGzC,MAAM,SAAS,MAAM,KAAK,SAAS,IAAI,KAAK,QAAQ;AAAA,IACpD,IAAI,CAAC,QAAQ;AAAA,MAEZ,MAAM,WAAW,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC;AAAA,MAC1D,MAAM,UAAS,MAAM,KAAK,QAAQ,EAChC,IAAI,CAAC,MAAc,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAClD,KAAK,EAAE;AAAA,MACT,MAAM,KAAK,SAAS,IAAI,KAAK,UAAU,OAAM;AAAA,IAC9C;AAAA,IAGA,MAAM,SAAS,MAAM,KAAK,SAAS,IAAI,KAAK,QAAQ;AAAA,IACpD,MAAM,YAAY,gBAAgB,MAAgB;AAAA,IAClD,KAAK,cAAc,MAAM,OAAO,OAAO,UACtC,OACA,UAAU,QACV,EAAE,MAAM,UAAU,GAClB,OACA,CAAC,WAAW,SAAS,CACtB;AAAA;AAAA,OAGK,QAAO,CAAC,WAAoC;AAAA,IACjD,IAAI,CAAC,KAAK;AAAA,MAAa,MAAM,IAAI,MAAM,+CAA+C;AAAA,IAEtF,MAAM,KAAK,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC;AAAA,IACpD,MAAM,UAAU,IAAI,YAAY,EAAE,OAAO,SAAS;AAAA,IAClD,MAAM,eAAe,MAAM,OAAO,OAAO,QACxC,EAAE,MAAM,WAAW,IAAI,GAAG,OAAsB,GAChD,KAAK,aACL,OACD;AAAA,IAGA,MAAM,QAAQ,mBAAmB,EAAE;AAAA,IACnC,MAAM,YAAY,mBAAmB,IAAI,WAAW,YAAY,CAAC;AAAA,IACjE,OAAO,GAAG,SAAS;AAAA;AAAA,OAGd,QAAO,CAAC,YAAqC;AAAA,IAClD,IAAI,CAAC,KAAK;AAAA,MAAa,MAAM,IAAI,MAAM,+CAA+C;AAAA,IAEtF,OAAO,OAAO,aAAa,WAAW,MAAM,GAAG;AAAA,IAC/C,IAAI,CAAC,SAAS,CAAC;AAAA,MAAW,MAAM,IAAI,MAAM,gCAAgC;AAAA,IAE1E,MAAM,KAAK,mBAAmB,KAAK;AAAA,IACnC,MAAM,eAAe,mBAAmB,SAAS;AAAA,IAEjD,MAAM,cAAc,MAAM,OAAO,OAAO,QACvC,EAAE,MAAM,WAAW,IAAI,GAAG,OAAsB,GAChD,KAAK,aACL,aAAa,MACd;AAAA,IAEA,OAAO,IAAI,YAAY,EAAE,OAAO,WAAW;AAAA;AAAA,OAItC,MAAK,GAAkB;AAAA,IAC5B,IAAI,KAAK,UAAU;AAAA,MAClB,KAAK,SAAS,MAAM;AAAA,MACpB,KAAK,WAAW;AAAA,IACjB;AAAA;AAEF;AAMA,SAAS,eAAe,CAAC,KAAyB;AAAA,EACjD,MAAM,QAAQ,IAAI,WAAW,IAAI,SAAS,CAAC;AAAA,EAC3C,SAAS,IAAI,EAAG,IAAI,IAAI,QAAQ,KAAK,GAAG;AAAA,IACvC,MAAM,IAAI,KAAK,OAAO,SAAS,IAAI,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE;AAAA,EAC3D;AAAA,EACA,OAAO;AAAA;AAGR,SAAS,kBAAkB,CAAC,OAA2B;AAAA,EACtD,IAAI,OAAO,WAAW,aAAa;AAAA,IAClC,OAAO,OAAO,KAAK,KAAK,EAAE,SAAS,QAAQ;AAAA,EAC5C;AAAA,EAEA,IAAI,SAAS;AAAA,EACb,WAAW,QAAQ,OAAO;AAAA,IACzB,UAAU,OAAO,aAAa,IAAI;AAAA,EACnC;AAAA,EACA,OAAO,KAAK,MAAM;AAAA;AAGnB,SAAS,kBAAkB,CAAC,KAAyB;AAAA,EACpD,IAAI,OAAO,WAAW,aAAa;AAAA,IAClC,OAAO,IAAI,WAAW,OAAO,KAAK,KAAK,QAAQ,CAAC;AAAA,EACjD;AAAA,EACA,MAAM,SAAS,KAAK,GAAG;AAAA,EACvB,MAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;AAAA,EAC1C,SAAS,IAAI,EAAG,IAAI,OAAO,QAAQ,KAAK;AAAA,IACvC,MAAM,KAAK,OAAO,WAAW,CAAC;AAAA,EAC/B;AAAA,EACA,OAAO;AAAA;AASR,eAAsB,gBAAgB,CACrC,eACiC;AAAA,EACjC,IAAI,CAAC;AAAA,IAAe;AAAA,EAEpB,IAAI,OAAO,kBAAkB,UAAU;AAAA,IACtC,MAAM,MAAM,IAAI,uBAAuB,aAAa;AAAA,IACpD,MAAM,IAAI,KAAK;AAAA,IACf,OAAO;AAAA,EACR;AAAA,EAGA,OAAO;AAAA;;;AC1JR,IAAM,WAAW;AACjB,IAAM,kBAAkB;AAMxB,eAAsB,aAAa,CAAC,SAMlB;AAAA,EACjB,QAAQ,OAAO,YAAY,gBAAgB,qBAAqB,uBAAuB;AAAA,EAEvF,IAAI,WAAW,WAAW;AAAA,IAAG;AAAA,EAE7B,MAAM,iBAAiB,MAAM,QAAQ,QAAQ,KAAK;AAAA,EAClD,MAAM,WAAW,WAAW,IAAI,CAAC,MAAM,EAAE,OAAO;AAAA,EAChD,MAAM,eAAe;AAAA,EAGrB,MAAM,UAAU,WAAW,OAAO,CAAC,MAAM,gBAAgB,EAAE,SAAS,cAAc,IAAI,CAAC;AAAA,EAEvF,IAAI,QAAQ,WAAW,GAAG;AAAA,IAEzB,MAAM,QAAQ,UAAU,cAAc;AAAA,IACtC;AAAA,EACD;AAAA,EAGA,QAAQ,KAAK,CAAC,GAAG,MAAM,gBAAgB,EAAE,SAAS,EAAE,OAAO,CAAC;AAAA,EAG5D,MAAM,MAAM,uBAAuB,KAAK;AAAA,EAKxC,MAAM,WAAW,MAAM,OAAO;AAAA,EAC9B,MAAM,kBAAkB;AAAA,EAExB,IAAI;AAAA,IACH,WAAW,aAAa,SAAS;AAAA,MAChC,MAAM,UAAgC;AAAA,QACrC,aAAa;AAAA,QACb,WAAW,UAAU;AAAA,QACrB;AAAA,QACA;AAAA,MACD;AAAA,MAEA,IAAI,qBAAqB;AAAA,QACxB,MAAM,oBAAoB,OAAO;AAAA,MAClC;AAAA,MAEA,MAAM,UAAU,GAAG,GAAG;AAAA,MAEtB,IAAI,oBAAoB;AAAA,QACvB,MAAM,mBAAmB,OAAO;AAAA,MACjC;AAAA,IACD;AAAA,IAGA,MAAM,QAAQ,UAAU,cAAc;AAAA,IACtC,MAAM,eAAe;AAAA,IACpB,OAAO,OAAO;AAAA,IAEf,MAAM,YAAY,MAAM;AAAA,MACvB,MAAM,UAAU;AAAA,MAChB,MAAM,UAAU,OAAO,QAAQ,QAAQ;AAAA,MACvC,IAAI,QAAQ,SAAS,GAAG;AAAA,QACvB,MAAM,QAAQ,OAAO;AAAA,MACtB;AAAA,MACA,MAAM,QAAQ,UAAU,eAAe;AAAA,KACvC;AAAA,IAED,MAAM,IAAI,MACT,yBAAyB,QAAQ,KAAK,MAAM,IAAI,GAAG,sBAClD,sDAAsD,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAC7G;AAAA;AAAA;AAQF,SAAS,sBAAsB,CAAC,OAAsC;AAAA,EACrE,OAAO;AAAA,IACN,GAAgB,CAAC,KAA4B;AAAA,MAC5C,OAAO,MAAM,OAAO,GAAG;AAAA;AAAA,IAExB,GAAG,CAAC,KAAa,OAAsB;AAAA,MACtC,MAAM,OAAO,KAAK,KAAK;AAAA;AAAA,IAExB,GAAG,CAAC,KAAsB;AAAA,MACzB,OAAO,MAAM,IAAI,GAAG;AAAA;AAAA,IAErB,MAAM,CAAC,KAAsB;AAAA,MAC5B,OAAO,MAAM,UAAU,GAAG;AAAA;AAAA,EAE5B;AAAA;AASD,SAAS,eAAe,CAAC,GAAW,GAAmB;AAAA,EACtD,MAAM,SAAS,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AAAA,EACtC,MAAM,SAAS,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AAAA,EAEtC,SAAS,IAAI,EAAG,IAAI,GAAG,KAAK;AAAA,IAC3B,MAAM,KAAK,OAAO,MAAM;AAAA,IACxB,MAAM,KAAK,OAAO,MAAM;AAAA,IACxB,IAAI,OAAO;AAAA,MAAI,OAAO,KAAK;AAAA,EAC5B;AAAA,EACA,OAAO;AAAA;;;AC1HR;AACA;AAUO,SAAS,gBAAgB,CAAC,aAA6B;AAAA,EAC7D,MAAM,OAAO,QAAQ;AAAA,EACrB,MAAM,KAAK,SAAS;AAAA,EAEpB,QAAQ;AAAA,SACF;AAAA,MACJ,OAAO,KAAK,MAAM,WAAW,eAAe,WAAW;AAAA,SAEnD;AAAA,MACJ,OAAO,KAAK,QAAQ,IAAI,WAAW,KAAK,MAAM,WAAW,SAAS,GAAG,WAAW;AAAA;AAAA,MAIhF,OAAO,KAAK,QAAQ,IAAI,mBAAmB,KAAK,MAAM,SAAS,GAAG,WAAW;AAAA;AAAA;AAOzE,SAAS,iBAAiB,CAAC,SAIvB;AAAA,EACV,MAAM,MAAM,QAAQ,OAAO,iBAAiB,QAAQ,WAAW;AAAA,EAC/D,MAAM,OAAO,QAAQ,cAAc;AAAA,EACnC,OAAO,KAAK,KAAK,GAAG,SAAS;AAAA;;;ACnCvB,SAAS,KAAK,GAAY;AAAA,EAChC,OAAO,OAAO,WAAW,QAAQ;AAAA;AAQ3B,SAAS,YAAY,CAAC,UAAmC;AAAA,EAC/D,IAAI,MAAM,GAAG;AAAA,IACZ,OAAO,gBAAgB,QAAQ;AAAA,EAChC;AAAA,EACA,OAAO,iBAAiB,QAAQ;AAAA;AAOjC,SAAS,eAAe,CAAC,UAAmC;AAAA,EAG3D,QAAQ;AAAA,EACR,MAAM,KAAK,IAAI,SAAS,QAAQ;AAAA,EAGhC,GAAG,KAAK,4BAA4B;AAAA,EACpC,GAAG,KAAK,6BAA6B;AAAA,EAErC,OAAO;AAAA,IACN,OAAO,CAAC,KAA+B;AAAA,MACtC,MAAM,OAAO,GAAG,QAAQ,GAAG;AAAA,MAC3B,OAAO;AAAA,QACN,GAAG,IAAI,QAAmB;AAAA,UACzB,KAAK,IAAI,GAAG,MAAM;AAAA;AAAA,QAEnB,GAAG,IAAI,QAA4B;AAAA,UAClC,OAAO,KAAK,IAAI,GAAG,MAAM;AAAA;AAAA,QAE1B,GAAG,IAAI,QAA8B;AAAA,UACpC,OAAO,KAAK,IAAI,GAAG,MAAM;AAAA;AAAA,MAE3B;AAAA;AAAA,IAED,IAAI,CAAC,KAAa;AAAA,MACjB,GAAG,KAAK,GAAG;AAAA;AAAA,IAEZ,KAAK,GAAG;AAAA,MACP,GAAG,MAAM;AAAA;AAAA,IAEV,WAAc,CAAC,IAAsB;AAAA,MACpC,OAAO,GAAG,YAAY,EAAE;AAAA;AAAA,EAE1B;AAAA;AAOD,SAAS,gBAAgB,CAAC,UAAmC;AAAA,EAE5D,IAAI;AAAA,EACJ,IAAI;AAAA,IAEH;AAAA,IACC,MAAM;AAAA,IACP,MAAM,IAAI,MACT,2FACC,6CACF;AAAA;AAAA,EAID,MAAM,KAAK,IAAK,cAAsB,QAAQ;AAAA,EAE9C,GAAG,OAAO,oBAAoB;AAAA,EAC9B,GAAG,OAAO,qBAAqB;AAAA,EAE/B,OAAO;AAAA,IACN,OAAO,CAAC,KAA+B;AAAA,MACtC,MAAM,OAAO,GAAG,QAAQ,GAAG;AAAA,MAC3B,OAAO;AAAA,QACN,GAAG,IAAI,QAAmB;AAAA,UACzB,KAAK,IAAI,GAAG,MAAM;AAAA;AAAA,QAEnB,GAAG,IAAI,QAA4B;AAAA,UAClC,OAAO,KAAK,IAAI,GAAG,MAAM;AAAA;AAAA,QAE1B,GAAG,IAAI,QAA8B;AAAA,UACpC,OAAO,KAAK,IAAI,GAAG,MAAM;AAAA;AAAA,MAE3B;AAAA;AAAA,IAED,IAAI,CAAC,KAAa;AAAA,MACjB,GAAG,KAAK,GAAG;AAAA;AAAA,IAEZ,KAAK,GAAG;AAAA,MACP,GAAG,MAAM;AAAA;AAAA,IAEV,WAAc,CAAC,IAAsB;AAAA,MACpC,OAAO,GAAG,YAAY,EAAE;AAAA;AAAA,EAE1B;AAAA;;;AC/FM,MAAM,YAAY;AAAA,EACf;AAAA,EAET,WAAW,CAAC,IAAqB;AAAA,IAChC,KAAK,MAAM;AAAA,IACX,KAAK,YAAY;AAAA;AAAA,EAIlB,WAAW,GAAS;AAAA,IACnB,KAAK,IAAI,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,GAKb;AAAA,IACD,KAAK,IAAI,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,GAKb;AAAA;AAAA,EAQF,MAAM,GAA4B;AAAA,IACjC,MAAM,OAAO,KAAK,IAAI,QAAQ,+BAA+B,EAAE,IAAI;AAAA,IACnE,MAAM,SAAkC,OAAO,OAAO,IAAI;AAAA,IAC1D,WAAW,OAAO,MAAM;AAAA,MACvB,OAAO,IAAI,OAAO,KAAK,MAAM,IAAI,KAAK;AAAA,IACvC;AAAA,IACA,OAAO;AAAA;AAAA,EAIR,MAAM,CAAC,KAAsB;AAAA,IAC5B,MAAM,MAAM,KAAK,IAAI,QAAQ,wCAAwC,EAAE,IAAI,GAAG;AAAA,IAG9E,OAAO,MAAM,KAAK,MAAM,IAAI,KAAK,IAAI;AAAA;AAAA,EAItC,MAAM,CAAC,KAAa,OAAsB;AAAA,IACzC,KAAK,IACH,QAAQ,0DAA0D,EAClE,IAAI,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA;AAAA,EAOjC,OAAO,CAAC,SAAyC;AAAA,IAChD,MAAM,OAAO,KAAK,IAAI,QACrB,0DACD;AAAA,IACA,MAAM,iBAAiB,KAAK,IAAI,YAAY,MAAM;AAAA,MACjD,YAAY,KAAK,UAAU,SAAS;AAAA,QACnC,KAAK,IAAI,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,MACpC;AAAA,KACA;AAAA,IACD,eAAe;AAAA;AAAA,EAIhB,SAAS,CAAC,KAAsB;AAAA,IAC/B,MAAM,SAAS,KAAK,MAAM;AAAA,IAC1B,KAAK,IAAI,QAAQ,kCAAkC,EAAE,IAAI,GAAG;AAAA,IAC5D,OAAO,KAAK,MAAM,IAAI;AAAA;AAAA,EAIvB,SAAS,GAAS;AAAA,IACjB,KAAK,IAAI,KAAK,qBAAqB;AAAA;AAAA,EAIpC,GAAG,CAAC,KAAsB;AAAA,IACzB,MAAM,MAAM,KAAK,IACf,QAAQ,4CAA4C,EACpD,IAAI,GAAG;AAAA,IACT,OAAO,QAAQ,aAAa,QAAQ;AAAA;AAAA,EAIrC,KAAK,GAAW;AAAA,IACf,MAAM,MAAM,KAAK,IAAI,QAAQ,oCAAoC,EAAE,IAAI;AAAA,IAGvE,OAAO,IAAI;AAAA;AAAA,EAOZ,OAAO,CAAC,KAAiC;AAAA,IACxC,MAAM,MAAM,KAAK,IACf,QAAQ,uCAAuC,EAC/C,IAAI,GAAG;AAAA,IACT,OAAO,KAAK;AAAA;AAAA,EAGb,OAAO,CAAC,KAAa,OAAqB;AAAA,IACzC,KAAK,IACH,QAAQ,yDAAyD,EACjE,IAAI,KAAK,KAAK;AAAA;AAAA,EAGjB,OAAO,CAAC,KAAsB;AAAA,IAC7B,MAAM,MAAM,KAAK,IACf,QAAQ,2CAA2C,EACnD,IAAI,GAAG;AAAA,IACT,OAAO,QAAQ,aAAa,QAAQ;AAAA;AAAA,EAWrC,WAAc,CAAC,IAAgB;AAAA,IAC9B,MAAM,UAAU,KAAK,IAAI,YAAY,EAAE;AAAA,IACvC,OAAO,QAAQ;AAAA;AAAA,EAQhB,cAAc,GAAS;AAAA,IACtB,KAAK,QAAQ,cAAc,KAAK,IAAI,EAAE,SAAS,CAAC;AAAA;AAAA,EAIjD,YAAY,GAAW;AAAA,IACtB,MAAM,MAAM,KAAK,QAAQ,YAAY;AAAA,IACrC,OAAO,MAAM,OAAO,GAAG,IAAI;AAAA;AAAA,EAI5B,KAAK,GAAS;AAAA,IACb,KAAK,IAAI,MAAM;AAAA;AAEjB;;;APlGO,MAAM,0BAA0B,MAAM;AAAA,EACnC,OAAO;AACjB;AAAA;AAEO,MAAM,wBAAwB,kBAAkB;AAAA,EAC7C,OAAO;AAAA,EAChB;AAAA,EAEA,WAAW,CAAC,QAAkB;AAAA,IAC7B,MAAM;AAAA,MAAkC,OAAO,KAAK;AAAA,KAAQ,GAAG;AAAA,IAC/D,KAAK,SAAS;AAAA;AAEhB;AAAA;AAMO,MAAM,aAAgD;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa,IAAI;AAAA,EACjB,gBAAgB,IAAI;AAAA,EAC7B,WAA6B;AAAA,EAC7B,kBAAkB;AAAA,EAClB,UAAU;AAAA,EAKF,WAAW,CAClB,OACA,OACA,SASC;AAAA,IACD,KAAK,SAAS;AAAA,IACd,KAAK,SAAS;AAAA,IACd,KAAK,aAAa,QAAQ;AAAA,IAC1B,KAAK,aAAa,QAAQ;AAAA,IAC1B,KAAK,YAAY,QAAQ;AAAA,IACzB,KAAK,eAAe,QAAQ;AAAA,IAC5B,KAAK,YAAY,QAAQ;AAAA,IACzB,KAAK,kBAAkB,MAAM,aAAa;AAAA,IAE1C,IAAI,QAAQ,OAAO;AAAA,MAClB,KAAK,eAAe;AAAA,IACrB;AAAA;AAAA,cAOY,KAAuC,CACnD,SAC2B;AAAA,IAC3B;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,CAAC;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,gCAAgC;AAAA,MAChC,OAAO,cAAc;AAAA,MACrB,qBAAqB;AAAA,QAClB;AAAA,IAGJ,MAAM,WAAW,kBAAkB,EAAE,aAAa,KAAK,WAAW,CAAC;AAAA,IACnE,UAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAAA,IAGhD,MAAM,KAAK,aAAa,QAAQ;AAAA,IAChC,MAAM,QAAQ,IAAI,YAAY,EAAE;AAAA,IAGhC,MAAM,YAAY,MAAM,iBAAiB,aAAa;AAAA,IAGtD,MAAM,YAAY,iBAAoB,OAAO;AAAA,IAG7C,IAAI;AAAA,IACJ,IAAI;AAAA,MACH,aAAa,MAAM,OAAO;AAAA,MAC1B,IAAI,WAAW;AAAA,QACd,aAAa,MAAM,aAAa,YAAY,SAAS;AAAA,MACtD;AAAA,MACC,OAAO,OAAO;AAAA,MACf,IAAI,oBAAoB;AAAA,QACvB,MAAM,UAAU;AAAA,QAChB,aAAa,CAAC;AAAA,MACf,EAAO;AAAA,QACN,MAAM,MAAM;AAAA,QACZ,MAAM,IAAI,kBACT,0BAA0B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAChF;AAAA;AAAA;AAAA,IAKF,MAAM,SAAS,KAAK,aAAa,WAAW;AAAA,IAG5C,IAAI,WAAW;AAAA,MACd,MAAM,SAAS,UAAU,SAAS,MAAM;AAAA,MACxC,IAAI,CAAC,OAAO,SAAS;AAAA,QACpB,IAAI,oBAAoB;AAAA,UACvB,MAAM,UAAU;AAAA,UAEhB,MAAM,YAAY,KAAK,SAAS;AAAA,UAChC,MAAM,cAAc,UAAU,SAAS,SAAS;AAAA,UAChD,IAAI,CAAC,YAAY,SAAS;AAAA,YACzB,MAAM,MAAM;AAAA,YACZ,MAAM,IAAI,gBAAgB,YAAY,MAAM;AAAA,UAC7C;AAAA,UAEA,MAAM,UAAU,OAAO,QAAQ,SAAS;AAAA,UACxC,IAAI,WAAW;AAAA,YACd,MAAM,YAAY,MAAM,aAAa,WAAW,SAAS;AAAA,YACzD,MAAM,QAAQ,OAAO,QAAQ,SAAS,CAAC;AAAA,UACxC,EAAO;AAAA,YACN,MAAM,QAAQ,OAAO;AAAA;AAAA,UAEtB,MAAM,eAAe;AAAA,UAErB,MAAM,SAAQ,IAAI,YAAe,OAAO,aAAa;AAAA,UACrD,OAAM,KAAK,SAAS;AAAA,UAEpB,OAAO,IAAI,aAAgB,OAAO,QAAO;AAAA,YACxC;AAAA,YACA;AAAA,YACA;AAAA,YACA,aAAa;AAAA,YACb;AAAA,YACA,OAAO;AAAA,UACR,CAAC;AAAA,QACF;AAAA,QACA,MAAM,MAAM;AAAA,QACZ,MAAM,IAAI,gBAAgB,OAAO,MAAM;AAAA,MACxC;AAAA,IACD;AAAA,IAGA,MAAM,iBAAiB,OAAO,KAAK,QAAQ,EAAE,KAC5C,CAAC,QAAQ,EAAE,OAAO,WACnB;AAAA,IACA,IAAI,gBAAgB;AAAA,MACnB,IAAI,WAAW;AAAA,QACd,MAAM,YAAY,MAAM,aAAa,QAAQ,SAAS;AAAA,QACtD,MAAM,QAAQ,OAAO,QAAQ,SAAS,CAAC;AAAA,MACxC,EAAO;AAAA,QACN,MAAM,QAAQ,OAAO,QAAQ,MAAM,CAAC;AAAA;AAAA,MAErC,MAAM,eAAe;AAAA,IACtB;AAAA,IAGA,IAAI,cAAc,WAAW,SAAS,GAAG;AAAA,MACxC,IAAI,CAAC,gBAAgB;AAAA,QACpB,MAAM,MAAM;AAAA,QACZ,MAAM,IAAI,kBACT,6DACD;AAAA,MACD;AAAA,MACA,MAAM,cAAc;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACD,CAAC;AAAA,IACF;AAAA,IAGA,MAAM,QAAQ,IAAI,YAAe,OAAO,aAAa;AAAA,IACrD,IAAI;AAAA,IACJ,IAAI,WAAW;AAAA,MACd,YAAY,MAAM,aAAa,MAAM,OAAO,GAAG,SAAS;AAAA,IACzD,EAAO;AAAA,MACN,YAAY,MAAM,OAAO;AAAA;AAAA,IAG1B,MAAM,YAAY,KAAK,aAAa,UAAU;AAAA,IAC9C,MAAM,KAAK,SAAS;AAAA,IAEpB,OAAO,IAAI,aAAgB,OAAO,OAAO;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb;AAAA,MACA,OAAO;AAAA,IACR,CAAC;AAAA;AAAA,EAeF,GAAG,CAAC,KAAa,cAAiC;AAAA,IACjD,KAAK,YAAY;AAAA,IAEjB,IAAI,KAAK,gBAAgB,IAAI,SAAS,GAAG,GAAG;AAAA,MAC3C,MAAM,OAAO,KAAK,OAAO,OAAO;AAAA,MAChC,MAAM,SAAQ,UAAU,MAAM,GAAG;AAAA,MACjC,OAAO,WAAU,YAAY,SAAQ;AAAA,IACtC;AAAA,IAEA,MAAM,QAAQ,KAAK,OAAO,IAAI,GAAG;AAAA,IACjC,OAAO,UAAU,YAAY,QAAQ;AAAA;AAAA,EAMtC,GAAG,CAAC,KAAsB;AAAA,IACzB,KAAK,YAAY;AAAA,IAEjB,IAAI,KAAK,gBAAgB,IAAI,SAAS,GAAG,GAAG;AAAA,MAC3C,OAAO,UAAU,KAAK,OAAO,OAAO,GAAG,GAAG;AAAA,IAC3C;AAAA,IACA,OAAO,KAAK,OAAO,IAAI,GAAG;AAAA;AAAA,MAMvB,KAAK,GAAM;AAAA,IACd,KAAK,YAAY;AAAA,IACjB,OAAO,KAAK,OAAO,OAAO;AAAA;AAAA,MAMvB,KAAK,CAAC,OAAU;AAAA,IACnB,KAAK,YAAY;AAAA,IACjB,KAAK,cAAc,KAAK;AAAA,IAExB,MAAM,WAAW,KAAK,OAAO,OAAO;AAAA,IACpC,KAAK,OAAO,WAAW,KAAK;AAAA,IAC5B,KAAK,iBAAiB,OAAO,QAAQ;AAAA;AAAA,MAMlC,IAAI,GAAW;AAAA,IAClB,KAAK,YAAY;AAAA,IACjB,OAAO,KAAK,OAAO;AAAA;AAAA,MAMhB,IAAI,GAAW;AAAA,IAClB,OAAO,KAAK;AAAA;AAAA,EAeb,GAAG,CAAC,aAAkC,OAAuB;AAAA,IAC5D,KAAK,YAAY;AAAA,IAEjB,IAAI,OAAO,gBAAgB,UAAU;AAAA,MACpC,KAAK,QAAQ,aAAa,KAAK;AAAA,IAChC,EAAO;AAAA,MACN,MAAM,UAAU,OAAO,QAAQ,WAAW;AAAA,MAC1C,MAAM,WAAW,KAAK,OAAO,OAAO;AAAA,MAEpC,YAAY,KAAK,QAAQ,SAAS;AAAA,QACjC,IAAI,KAAK,gBAAgB,IAAI,SAAS,GAAG,GAAG;AAAA,UAC3C,MAAM,OAAO,KAAK,OAAO,OAAO;AAAA,UAChC,MAAM,UAAU,UAAU,MAAM,KAAK,GAAG;AAAA,UACxC,KAAK,cAAc,OAAY;AAAA,UAC/B,KAAK,OAAO,WAAW,OAAY;AAAA,QACpC,EAAO;AAAA,UACN,KAAK,OAAO,IAAI,KAAK,GAAG;AAAA;AAAA,MAE1B;AAAA,MAGA,KAAK,cAAc,KAAK,OAAO,OAAO,CAAC;AAAA,MACvC,KAAK,iBAAiB,KAAK,OAAO,OAAO,GAAG,QAAQ;AAAA;AAAA;AAAA,EAItD,OAAO,CAAC,KAAa,OAAsB;AAAA,IAC1C,MAAM,WAAW,KAAK,OAAO,OAAO;AAAA,IACpC,MAAM,WAAW,KAAK,IAAI,GAAG;AAAA,IAE7B,IAAI,KAAK,gBAAgB,IAAI,SAAS,GAAG,GAAG;AAAA,MAC3C,MAAM,OAAO,KAAK,OAAO,OAAO;AAAA,MAChC,MAAM,UAAU,UAAU,MAAM,KAAK,KAAK;AAAA,MAC1C,KAAK,cAAc,OAAY;AAAA,MAC/B,KAAK,OAAO,WAAW,OAAY;AAAA,IACpC,EAAO;AAAA,MAEN,MAAM,YAAY,KAAK,KAAK,OAAO,OAAO,IAAI,MAAM,MAAM;AAAA,MAC1D,KAAK,cAAc,SAAS;AAAA,MAC5B,KAAK,OAAO,IAAI,KAAK,KAAK;AAAA;AAAA,IAG3B,KAAK,iBAAiB,KAAK,OAAO,QAAQ;AAAA,IAC1C,KAAK,iBAAiB,KAAK,OAAO,OAAO,GAAG,QAAQ;AAAA;AAAA,EAMrD,MAAM,CAAC,KAAmB;AAAA,IACzB,KAAK,YAAY;AAAA,IAEjB,MAAM,WAAW,KAAK,OAAO,OAAO;AAAA,IACpC,MAAM,WAAW,KAAK,IAAI,GAAG;AAAA,IAE7B,IAAI,KAAK,gBAAgB,IAAI,SAAS,GAAG,GAAG;AAAA,MAC3C,MAAM,OAAO,KAAK,OAAO,OAAO;AAAA,MAChC,MAAM,UAAU,aAAa,MAAM,GAAG;AAAA,MACtC,KAAK,OAAO,WAAW,OAAY;AAAA,IACpC,EAAO;AAAA,MACN,KAAK,OAAO,OAAO,GAAG;AAAA;AAAA,IAGvB,KAAK,iBAAiB,KAAK,WAAW,QAAQ;AAAA,IAC9C,KAAK,iBAAiB,KAAK,OAAO,OAAO,GAAG,QAAQ;AAAA;AAAA,EAMrD,KAAK,GAAS;AAAA,IACb,KAAK,YAAY;AAAA,IAEjB,MAAM,WAAW,KAAK,OAAO,OAAO;AAAA,IACpC,KAAK,OAAO,MAAM;AAAA,IAGlB,IAAI,KAAK,aAAa,OAAO,KAAK,KAAK,SAAS,EAAE,SAAS,GAAG;AAAA,MAC7D,KAAK,OAAO,QAAQ,OAAO,QAAQ,KAAK,SAAS,CAAC;AAAA,IACnD;AAAA,IAEA,KAAK,iBAAiB,KAAK,OAAO,OAAO,GAAG,QAAQ;AAAA;AAAA,EAMrD,KAAK,IAAI,MAAkC;AAAA,IAC1C,KAAK,YAAY;AAAA,IAEjB,MAAM,WAAW,KAAK,OAAO,OAAO;AAAA,IAEpC,WAAW,OAAO,MAAM;AAAA,MACvB,IAAI,OAAO,KAAK,WAAW;AAAA,QAC1B,KAAK,OAAO,IAAI,KAAK,KAAK,UAAU,IAAI;AAAA,MACzC,EAAO;AAAA,QACN,KAAK,OAAO,OAAO,GAAG;AAAA;AAAA,IAExB;AAAA,IAEA,KAAK,iBAAiB,KAAK,OAAO,OAAO,GAAG,QAAQ;AAAA;AAAA,EAWrD,WAAuC,CACtC,KACA,UACc;AAAA,IACd,IAAI,CAAC,KAAK,WAAW,IAAI,GAAG,GAAG;AAAA,MAC9B,KAAK,WAAW,IAAI,KAAK,IAAI,GAAK;AAAA,IACnC;AAAA,IACA,MAAM,MAAM,KAAK,WAAW,IAAI,GAAG;AAAA,IACnC,IAAI,IAAI,QAAmC;AAAA,IAE3C,OAAO,MAAM;AAAA,MACZ,IAAI,OAAO,QAAmC;AAAA,MAC9C,IAAI,IAAI,SAAS;AAAA,QAAG,KAAK,WAAW,OAAO,GAAG;AAAA;AAAA;AAAA,EAQhD,cAAc,CAAC,UAA6C;AAAA,IAC3D,KAAK,cAAc,IAAI,QAAQ;AAAA,IAC/B,OAAO,MAAM;AAAA,MACZ,KAAK,cAAc,OAAO,QAAQ;AAAA;AAAA;AAAA,OAY9B,MAAK,GAAkB;AAAA,IAC5B,KAAK,YAAY;AAAA,IACjB,MAAM,KAAK,OAAO,MAAM;AAAA;AAAA,EAMzB,SAAS,GAAS;AAAA,IACjB,KAAK,YAAY;AAAA,IACjB,KAAK,OAAO,UAAU;AAAA;AAAA,EAOvB,KAAK,GAAS;AAAA,IACb,IAAI,KAAK;AAAA,MAAS;AAAA,IAElB,KAAK,cAAc;AAAA,IACnB,KAAK,OAAO,UAAU;AAAA,IACtB,KAAK,OAAO,MAAM;AAAA,IAClB,KAAK,UAAU;AAAA;AAAA,IAOd,OAAO,SAAS,GAAwC;AAAA,IACzD,KAAK,YAAY;AAAA,IACjB,OAAO,KAAK,OAAO,QAAQ;AAAA;AAAA,EAO5B,WAAW,GAAS;AAAA,IACnB,IAAI,KAAK,SAAS;AAAA,MACjB,MAAM,IAAI,kBACT,wFACD;AAAA,IACD;AAAA;AAAA,EAGD,aAAa,CAAC,MAAe;AAAA,IAC5B,IAAI,CAAC,KAAK;AAAA,MAAY;AAAA,IAEtB,MAAM,SAAS,KAAK,WAAW,SAAS,IAAI;AAAA,IAC5C,IAAI,CAAC,OAAO,SAAS;AAAA,MACpB,MAAM,IAAI,gBAAgB,OAAO,MAAM;AAAA,IACxC;AAAA;AAAA,EAGD,gBAAgB,CAAC,KAAa,UAAmB,UAAyB;AAAA,IACzE,IAAI,aAAa;AAAA,MAAU;AAAA,IAC3B,IACC,OAAO,aAAa,YACpB,OAAO,aAAa,YACpB,KAAK,UAAU,QAAQ,MAAM,KAAK,UAAU,QAAQ,GACnD;AAAA,MACD;AAAA,IACD;AAAA,IAEA,MAAM,YAAY,KAAK,WAAW,IAAI,GAAG;AAAA,IACzC,IAAI,WAAW;AAAA,MACd,WAAW,MAAM,WAAW;AAAA,QAC3B,GAAG,UAAU,QAAQ;AAAA,MACtB;AAAA,IACD;AAAA;AAAA,EAGD,gBAAgB,CAAC,UAAa,UAAmB;AAAA,IAChD,IAAI,KAAK,UAAU,QAAQ,MAAM,KAAK,UAAU,QAAQ;AAAA,MAAG;AAAA,IAE3D,WAAW,MAAM,KAAK,eAAe;AAAA,MACpC,GAAG,UAAU,QAAQ;AAAA,IACtB;AAAA;AAAA,EAOD,cAAc,GAAS;AAAA,IACtB,IAAI;AAAA,MACH,KAAK,WAAW,MAAM,QAAQ,KAAK,SAAS,GAAG,CAAC,QAAQ,aAAa;AAAA,QACpE,IAAI,CAAC;AAAA,UAAU;AAAA,QACf,MAAM,eAAe,KAAK,UAAU,MAAM,OAAO,EAAE,IAAI;AAAA,QACvD,IAAI,aAAa;AAAA,UAAc;AAAA,QAG/B,MAAM,eAAe,KAAK,OAAO,aAAa;AAAA,QAC9C,IAAI,eAAe,KAAK,iBAAiB;AAAA,UACxC,KAAK,kBAAkB;AAAA,UACvB,MAAM,WAAW,KAAK,OAAO,OAAO;AAAA,UACpC,KAAK,OAAO,OAAO;AAAA,UACnB,MAAM,WAAW,KAAK,OAAO,OAAO;AAAA,UACpC,KAAK,iBAAiB,UAAU,QAAQ;AAAA,QACzC;AAAA,OACA;AAAA,MAGD,IAAI,KAAK,YAAY,WAAW,KAAK,UAAU;AAAA,QAC9C,KAAK,SAAS,MAAM;AAAA,MACrB;AAAA,MACC,MAAM;AAAA;AAAA,EAKT,aAAa,GAAS;AAAA,IACrB,IAAI,KAAK,UAAU;AAAA,MAClB,KAAK,SAAS,MAAM;AAAA,MACpB,KAAK,WAAW;AAAA,IACjB;AAAA;AAEF;AAMA,eAAe,YAAY,CAC1B,MACA,WACkC;AAAA,EAClC,MAAM,SAAiC,CAAC;AAAA,EACxC,YAAY,KAAK,UAAU,OAAO,QAAQ,IAAI,GAAG;AAAA,IAChD,OAAO,OAAO,MAAM,UAAU,QAAQ,KAAK,UAAU,KAAK,CAAC;AAAA,EAC5D;AAAA,EACA,OAAO;AAAA;AAGR,eAAe,YAAY,CAC1B,MACA,WACmC;AAAA,EACnC,MAAM,SAAkC,CAAC;AAAA,EACzC,YAAY,KAAK,UAAU,OAAO,QAAQ,IAAI,GAAG;AAAA,IAChD,IAAI,OAAO,UAAU,UAAU;AAAA,MAC9B,IAAI;AAAA,QACH,MAAM,YAAY,MAAM,UAAU,QAAQ,KAAK;AAAA,QAC/C,OAAO,OAAO,KAAK,MAAM,SAAS;AAAA,QACjC,MAAM;AAAA,QAEP,OAAO,OAAO;AAAA;AAAA,IAEhB,EAAO;AAAA,MACN,OAAO,OAAO;AAAA;AAAA,EAEhB;AAAA,EACA,OAAO;AAAA;",
|
|
15
|
+
"debugId": "61711D0C75DC415164756E2164756E21",
|
|
16
|
+
"names": []
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module migrations
|
|
3
|
+
* Version-based migration system with SQLite transaction rollback.
|
|
4
|
+
*/
|
|
5
|
+
import type { ConfigStore } from "./store.js";
|
|
6
|
+
import type { MigrationDefinition, MigrationHookContext } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Run pending migrations against the store. Executes in a SQLite
|
|
9
|
+
* transaction — if any migration throws, ALL changes are rolled back.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runMigrations(options: {
|
|
12
|
+
store: ConfigStore;
|
|
13
|
+
migrations: MigrationDefinition[];
|
|
14
|
+
projectVersion: string;
|
|
15
|
+
beforeEachMigration?: (ctx: MigrationHookContext) => void | Promise<void>;
|
|
16
|
+
afterEachMigration?: (ctx: MigrationHookContext) => void | Promise<void>;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Get the current schema version from the store.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getSchemaVersion(store: ConfigStore): string;
|
|
22
|
+
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAoB,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAK9F;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAC5C,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqEhB;AAyCD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAE3D"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module platform
|
|
3
|
+
* OS-specific config directory resolution.
|
|
4
|
+
* Replaces the `env-paths` package with a zero-dep implementation.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the default config directory for a given project name,
|
|
8
|
+
* following platform conventions:
|
|
9
|
+
*
|
|
10
|
+
* - **macOS**: `~/Library/Preferences/<projectName>/`
|
|
11
|
+
* - **Windows**: `%APPDATA%/<projectName>/`
|
|
12
|
+
* - **Linux**: `$XDG_CONFIG_HOME/<projectName>/` (defaults to `~/.config`)
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveConfigDir(projectName: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Build the full database file path from options.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveConfigPath(options: {
|
|
19
|
+
projectName: string;
|
|
20
|
+
cwd?: string;
|
|
21
|
+
configName?: string;
|
|
22
|
+
}): string;
|
|
23
|
+
//# sourceMappingURL=platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAe5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,MAAM,CAIT"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module runtime
|
|
3
|
+
* Runtime detection and SQLite adapter layer.
|
|
4
|
+
* Uses `bun:sqlite` on Bun, `better-sqlite3` on Node.js.
|
|
5
|
+
*/
|
|
6
|
+
import type { DatabaseAdapter } from "./types.js";
|
|
7
|
+
/** Returns `true` when running under Bun. */
|
|
8
|
+
export declare function isBun(): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Open a SQLite database. Automatically selects the driver:
|
|
11
|
+
* - Bun → `bun:sqlite` (built-in, zero deps)
|
|
12
|
+
* - Node → `better-sqlite3` (peer dependency)
|
|
13
|
+
*/
|
|
14
|
+
export declare function openDatabase(filepath: string): DatabaseAdapter;
|
|
15
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAoB,MAAM,YAAY,CAAC;AAEpE,6CAA6C;AAC7C,wBAAgB,KAAK,IAAI,OAAO,CAE/B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAK9D"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module store
|
|
3
|
+
* Low-level SQLite abstraction for config persistence.
|
|
4
|
+
* All values are stored as JSON-stringified text.
|
|
5
|
+
*/
|
|
6
|
+
import type { DatabaseAdapter } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Direct SQLite store — thin wrapper around the database adapter.
|
|
9
|
+
* This layer does NOT cache or validate; that's handled by higher layers.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ConfigStore {
|
|
12
|
+
#private;
|
|
13
|
+
constructor(db: DatabaseAdapter);
|
|
14
|
+
/** Get all config entries as a flat `Record<string, unknown>`. */
|
|
15
|
+
getAll(): Record<string, unknown>;
|
|
16
|
+
/** Get a single value by key. Returns `undefined` if not found. */
|
|
17
|
+
getOne(key: string): unknown;
|
|
18
|
+
/** Insert or replace a single key/value. */
|
|
19
|
+
setOne(key: string, value: unknown): void;
|
|
20
|
+
/**
|
|
21
|
+
* Set multiple entries atomically within a transaction.
|
|
22
|
+
* Significantly faster than individual `setOne` calls.
|
|
23
|
+
*/
|
|
24
|
+
setMany(entries: Array<[string, unknown]>): void;
|
|
25
|
+
/** Delete a single key. Returns `true` if the key existed. */
|
|
26
|
+
deleteOne(key: string): boolean;
|
|
27
|
+
/** Delete all config entries. */
|
|
28
|
+
deleteAll(): void;
|
|
29
|
+
/** Check if a key exists. */
|
|
30
|
+
has(key: string): boolean;
|
|
31
|
+
/** Number of config entries. */
|
|
32
|
+
count(): number;
|
|
33
|
+
getMeta(key: string): string | undefined;
|
|
34
|
+
setMeta(key: string, value: string): void;
|
|
35
|
+
hasMeta(key: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Execute `fn` inside a SQLite transaction.
|
|
38
|
+
* If `fn` throws, the transaction is rolled back automatically.
|
|
39
|
+
*/
|
|
40
|
+
transaction<T>(fn: () => T): T;
|
|
41
|
+
/** Update the `last_write` meta timestamp. Called after any mutation. */
|
|
42
|
+
touchLastWrite(): void;
|
|
43
|
+
/** Get the last write timestamp (ms since epoch), or 0 if never written. */
|
|
44
|
+
getLastWrite(): number;
|
|
45
|
+
/** Close the underlying database connection. */
|
|
46
|
+
close(): void;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAQlD;;;GAGG;AACH,qBAAa,WAAW;;gBAGX,EAAE,EAAE,eAAe;IAyB/B,kEAAkE;IAClE,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IASjC,mEAAmE;IACnE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAO5B,4CAA4C;IAC5C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAMzC;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI;IAYhD,8DAA8D;IAC9D,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAM/B,iCAAiC;IACjC,SAAS,IAAI,IAAI;IAIjB,6BAA6B;IAC7B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAOzB,gCAAgC;IAChC,KAAK,IAAI,MAAM;IAWf,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAOxC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAMzC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAW7B;;;OAGG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;IAS9B,yEAAyE;IACzE,cAAc,IAAI,IAAI;IAItB,4EAA4E;IAC5E,YAAY,IAAI,MAAM;IAKtB,gDAAgD;IAChD,KAAK,IAAI,IAAI;CAGb"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module types
|
|
3
|
+
* Core type definitions for config-engine.
|
|
4
|
+
*/
|
|
5
|
+
import type { ZodSchema } from "zod";
|
|
6
|
+
/**
|
|
7
|
+
* Minimal interface that both `bun:sqlite` and `better-sqlite3` satisfy.
|
|
8
|
+
* We program against this so the rest of the codebase is runtime-agnostic.
|
|
9
|
+
*/
|
|
10
|
+
export interface DatabaseAdapter {
|
|
11
|
+
prepare(sql: string): StatementAdapter;
|
|
12
|
+
exec(sql: string): void;
|
|
13
|
+
close(): void;
|
|
14
|
+
transaction<T>(fn: () => T): () => T;
|
|
15
|
+
}
|
|
16
|
+
export interface StatementAdapter {
|
|
17
|
+
run(...params: unknown[]): void;
|
|
18
|
+
get(...params: unknown[]): unknown;
|
|
19
|
+
all(...params: unknown[]): unknown[];
|
|
20
|
+
}
|
|
21
|
+
export interface ValidationSuccess<T> {
|
|
22
|
+
success: true;
|
|
23
|
+
data: T;
|
|
24
|
+
}
|
|
25
|
+
export interface ValidationFailure {
|
|
26
|
+
success: false;
|
|
27
|
+
errors: string[];
|
|
28
|
+
}
|
|
29
|
+
export type ValidationResult<T> = ValidationSuccess<T> | ValidationFailure;
|
|
30
|
+
/**
|
|
31
|
+
* Pluggable validator interface. Implement this to bring your own
|
|
32
|
+
* validation library (Valibot, AJV, custom, etc.).
|
|
33
|
+
*/
|
|
34
|
+
export interface Validator<T> {
|
|
35
|
+
validate(data: unknown): ValidationResult<T>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Pluggable encryption interface. The built-in adapter uses
|
|
39
|
+
* `@wgtechlabs/secrets-engine` but you can provide your own.
|
|
40
|
+
*/
|
|
41
|
+
export interface Encryptor {
|
|
42
|
+
encrypt(plaintext: string): Promise<string>;
|
|
43
|
+
decrypt(ciphertext: string): Promise<string>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Context exposed to migration functions. Operates directly on the
|
|
47
|
+
* SQLite store, bypassing validation so migrations can fix invalid data.
|
|
48
|
+
*/
|
|
49
|
+
export interface MigrationContext {
|
|
50
|
+
get<V = unknown>(key: string): V | undefined;
|
|
51
|
+
set(key: string, value: unknown): void;
|
|
52
|
+
has(key: string): boolean;
|
|
53
|
+
delete(key: string): boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface MigrationDefinition {
|
|
56
|
+
/** Semver version this migration targets. */
|
|
57
|
+
version: string;
|
|
58
|
+
/** Migration function. May be async. */
|
|
59
|
+
up(ctx: MigrationContext): void | Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
export interface MigrationHookContext {
|
|
62
|
+
fromVersion: string;
|
|
63
|
+
toVersion: string;
|
|
64
|
+
finalVersion: string;
|
|
65
|
+
versions: string[];
|
|
66
|
+
}
|
|
67
|
+
export type FlushStrategy = "immediate" | "batched" | "manual";
|
|
68
|
+
export type ChangeCallback<V> = (newValue: V | undefined, oldValue: V | undefined) => void;
|
|
69
|
+
export type AnyChangeCallback<T> = (newValue: T, oldValue: T) => void;
|
|
70
|
+
export type Unsubscribe = () => void;
|
|
71
|
+
export interface ConfigEngineOptions<T extends Record<string, unknown>> {
|
|
72
|
+
/** Application / project name — used to resolve the config directory. */
|
|
73
|
+
projectName: string;
|
|
74
|
+
/**
|
|
75
|
+
* Current project version (semver). Required when `migrations` is set.
|
|
76
|
+
* Used to determine which migrations to run.
|
|
77
|
+
*/
|
|
78
|
+
projectVersion?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Override the default config directory. When set, `projectName` is still
|
|
81
|
+
* required but only used for identification, not path resolution.
|
|
82
|
+
*/
|
|
83
|
+
cwd?: string;
|
|
84
|
+
/** Config file name without extension. @default "config" */
|
|
85
|
+
configName?: string;
|
|
86
|
+
/** Default config values. Deep-merged into the store on first open. */
|
|
87
|
+
defaults?: Partial<T>;
|
|
88
|
+
/**
|
|
89
|
+
* Zod schema for built-in validation. If provided, all `.set()` calls
|
|
90
|
+
* are validated against this schema.
|
|
91
|
+
*/
|
|
92
|
+
schema?: ZodSchema<T>;
|
|
93
|
+
/**
|
|
94
|
+
* Custom validator (pluggable). Takes precedence over `schema` if both
|
|
95
|
+
* are provided. Use this for Valibot, AJV, or any custom validator.
|
|
96
|
+
*/
|
|
97
|
+
validator?: Validator<T>;
|
|
98
|
+
/**
|
|
99
|
+
* Encryption key name (to be resolved via `@wgtechlabs/secrets-engine`),
|
|
100
|
+
* OR a custom `Encryptor` instance for full control.
|
|
101
|
+
*/
|
|
102
|
+
encryptionKey?: string | Encryptor;
|
|
103
|
+
/**
|
|
104
|
+
* Ordered list of migrations. Executed sequentially in a SQLite
|
|
105
|
+
* transaction with full rollback on failure.
|
|
106
|
+
*/
|
|
107
|
+
migrations?: MigrationDefinition[];
|
|
108
|
+
/** Hook called before each migration runs. */
|
|
109
|
+
beforeEachMigration?: (ctx: MigrationHookContext) => void | Promise<void>;
|
|
110
|
+
/** Hook called after each migration runs. */
|
|
111
|
+
afterEachMigration?: (ctx: MigrationHookContext) => void | Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Write-behind strategy.
|
|
114
|
+
* - `"immediate"` — sync write on every `.set()`
|
|
115
|
+
* - `"batched"` — debounced microtask flush (default)
|
|
116
|
+
* - `"manual"` — user calls `.flush()` explicitly
|
|
117
|
+
* @default "batched"
|
|
118
|
+
*/
|
|
119
|
+
flushStrategy?: FlushStrategy;
|
|
120
|
+
/**
|
|
121
|
+
* Enable dot-notation access for nested keys.
|
|
122
|
+
* e.g. `config.get("a.b.c")` traverses into `{ a: { b: { c: ... } } }`.
|
|
123
|
+
* @default true
|
|
124
|
+
*/
|
|
125
|
+
accessPropertiesByDotNotation?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Watch the SQLite database file for external changes and
|
|
128
|
+
* refresh the in-memory cache automatically.
|
|
129
|
+
* @default false
|
|
130
|
+
*/
|
|
131
|
+
watch?: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Clear the store if the existing data fails validation or
|
|
134
|
+
* decryption, rather than throwing an error.
|
|
135
|
+
* @default false
|
|
136
|
+
*/
|
|
137
|
+
clearInvalidConfig?: boolean;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module validation
|
|
3
|
+
* Pluggable validation system with built-in Zod adapter.
|
|
4
|
+
*/
|
|
5
|
+
import { type ZodSchema } from "zod";
|
|
6
|
+
import type { Validator } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Create a `Validator<T>` backed by a Zod schema.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { z } from "zod";
|
|
12
|
+
* import { createZodValidator } from "@wgtechlabs/config-engine/validators/zod";
|
|
13
|
+
*
|
|
14
|
+
* const validator = createZodValidator(z.object({
|
|
15
|
+
* theme: z.enum(["light", "dark"]),
|
|
16
|
+
* fontSize: z.number().min(8).max(72),
|
|
17
|
+
* }));
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function createZodValidator<T>(schema: ZodSchema<T>): Validator<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Build a validator from the options — resolves `schema` vs `validator`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveValidator<T>(options: {
|
|
25
|
+
schema?: ZodSchema<T>;
|
|
26
|
+
validator?: Validator<T>;
|
|
27
|
+
}): Validator<T> | undefined;
|
|
28
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAiB,MAAM,KAAK,CAAC;AACpD,OAAO,KAAK,EAAoB,SAAS,EAAE,MAAM,YAAY,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAaxE;AAYD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE;IAC5C,MAAM,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IACtB,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;CACzB,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAM3B"}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wgtechlabs/config-engine",
|
|
3
|
+
"version": "0.1.0-dev.d0d9e7f",
|
|
4
|
+
"description": "Configs should be easy. Fast, Bun-first configuration SDK for AI agent frameworks, CLI apps, and applications.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./validators/zod": {
|
|
14
|
+
"types": "./dist/validation.d.ts",
|
|
15
|
+
"import": "./dist/validation.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "bun run scripts/build.ts",
|
|
25
|
+
"test": "bun test",
|
|
26
|
+
"lint": "biome check .",
|
|
27
|
+
"lint:fix": "biome check --write .",
|
|
28
|
+
"format": "biome format --write .",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"prepublishOnly": "bun run build"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"config",
|
|
34
|
+
"configuration",
|
|
35
|
+
"store",
|
|
36
|
+
"storage",
|
|
37
|
+
"settings",
|
|
38
|
+
"preferences",
|
|
39
|
+
"sqlite",
|
|
40
|
+
"bun",
|
|
41
|
+
"fast",
|
|
42
|
+
"typed",
|
|
43
|
+
"zod",
|
|
44
|
+
"ai",
|
|
45
|
+
"agent",
|
|
46
|
+
"cli",
|
|
47
|
+
"sdk"
|
|
48
|
+
],
|
|
49
|
+
"author": "Waren Gonzaga <sirgonzaga@wgtechlabs.com>",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/wgtechlabs/config-engine.git"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/wgtechlabs/config-engine/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/wgtechlabs/config-engine#readme",
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=20"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"zod": "^3.24.2"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@biomejs/biome": "^1.9.4",
|
|
67
|
+
"@types/bun": "^1.2.4",
|
|
68
|
+
"typescript": "^5.7.3"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"better-sqlite3": ">=11.0.0",
|
|
72
|
+
"@wgtechlabs/secrets-engine": ">=1.0.0"
|
|
73
|
+
},
|
|
74
|
+
"peerDependenciesMeta": {
|
|
75
|
+
"better-sqlite3": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"@wgtechlabs/secrets-engine": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|