@camstack/core 0.1.7 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/builtins/local-backup/index.d.mts +42 -0
  2. package/dist/builtins/local-backup/index.d.ts +42 -0
  3. package/dist/builtins/local-backup/index.mjs +2 -10
  4. package/dist/builtins/local-backup/index.mjs.map +1 -1
  5. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.mts +2 -0
  6. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +2 -0
  7. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +2 -5
  8. package/dist/builtins/sqlite-storage/index.d.mts +4 -0
  9. package/dist/builtins/sqlite-storage/index.d.ts +4 -0
  10. package/dist/builtins/sqlite-storage/index.js +95 -197
  11. package/dist/builtins/sqlite-storage/index.js.map +1 -1
  12. package/dist/builtins/sqlite-storage/index.mjs +6 -26
  13. package/dist/builtins/sqlite-storage/index.mjs.map +1 -1
  14. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.mts +2 -0
  15. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +2 -0
  16. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -4
  17. package/dist/builtins/winston-logging/index.d.mts +30 -0
  18. package/dist/builtins/winston-logging/index.d.ts +30 -0
  19. package/dist/builtins/winston-logging/index.mjs +2 -10
  20. package/dist/builtins/winston-logging/index.mjs.map +1 -1
  21. package/dist/chunk-2F3XZYRW.mjs +89 -0
  22. package/dist/chunk-2F3XZYRW.mjs.map +1 -0
  23. package/dist/chunk-LQFPAEQF.mjs +147 -0
  24. package/dist/{chunk-XSLBW5C2.mjs.map → chunk-LQFPAEQF.mjs.map} +1 -1
  25. package/dist/chunk-R3DIIBBX.mjs +532 -0
  26. package/dist/chunk-R3DIIBBX.mjs.map +1 -0
  27. package/dist/chunk-SO4LROOT.mjs +150 -0
  28. package/dist/{chunk-GBWW3JU4.mjs.map → chunk-SO4LROOT.mjs.map} +1 -1
  29. package/dist/chunk-SPA4JBKN.mjs +175 -0
  30. package/dist/{chunk-4YD6WMO6.mjs.map → chunk-SPA4JBKN.mjs.map} +1 -1
  31. package/dist/chunk-YXNXYYHL.mjs +282 -0
  32. package/dist/{chunk-CHFIH4G6.mjs.map → chunk-YXNXYYHL.mjs.map} +1 -1
  33. package/dist/dist-3BY63UQ5.mjs +2151 -0
  34. package/dist/dist-3BY63UQ5.mjs.map +1 -0
  35. package/dist/filesystem-storage.addon-C42r589X.d.mts +57 -0
  36. package/dist/filesystem-storage.addon-C42r589X.d.ts +57 -0
  37. package/dist/index.d.mts +1110 -0
  38. package/dist/index.d.ts +1110 -0
  39. package/dist/index.js +6002 -17264
  40. package/dist/index.js.map +1 -1
  41. package/dist/index.mjs +3408 -16481
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/sql-schema-CKz78rId.d.mts +97 -0
  44. package/dist/sql-schema-CKz78rId.d.ts +97 -0
  45. package/dist/sqlite-settings.addon-DigoKwpZ.d.mts +70 -0
  46. package/dist/sqlite-settings.addon-DigoKwpZ.d.ts +70 -0
  47. package/dist/storage-location-manager-KKDQNAKA.mjs +7 -0
  48. package/package.json +1 -1
  49. package/dist/chunk-4JEXNFZZ.mjs +0 -57
  50. package/dist/chunk-4YD6WMO6.mjs +0 -207
  51. package/dist/chunk-CHFIH4G6.mjs +0 -314
  52. package/dist/chunk-EFQ25JFE.mjs +0 -689
  53. package/dist/chunk-EFQ25JFE.mjs.map +0 -1
  54. package/dist/chunk-GBWW3JU4.mjs +0 -180
  55. package/dist/chunk-XSLBW5C2.mjs +0 -177
  56. package/dist/onnxruntime_binding-6Q6HXASN.node +0 -0
  57. package/dist/onnxruntime_binding-EKZT2NRK.node +0 -0
  58. package/dist/onnxruntime_binding-P6S7V3CI.node +0 -0
  59. package/dist/onnxruntime_binding-PJNNIIUO.node +0 -0
  60. package/dist/onnxruntime_binding-UN6SPTQK.node +0 -0
  61. /package/dist/{chunk-4JEXNFZZ.mjs.map → storage-location-manager-KKDQNAKA.mjs.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/builtins/sqlite-storage/sql-schema.ts","../../../src/builtins/sqlite-storage/index.ts","../../../src/builtins/sqlite-storage/filesystem-storage-provider.ts","../../../src/builtins/sqlite-storage/filesystem-storage.addon.ts","../../../src/builtins/sqlite-storage/sqlite-settings-backend.ts","../../../src/builtins/sqlite-storage/sqlite-settings.addon.ts","../../../src/builtins/sqlite-storage/sqlite-storage.provider.ts","../../../src/builtins/sqlite-storage/sqlite-storage.addon.ts","../../../src/builtins/sqlite-storage/settings-store.ts","../../../src/builtins/sqlite-storage/sql-schema.ts"],"sourcesContent":["/** Core table DDL statements -- executed on first boot */\nexport const CORE_TABLE_DDL: readonly string[] = [\n // Settings tables\n `CREATE TABLE IF NOT EXISTS system_settings (\n key TEXT PRIMARY KEY,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\n )`,\n `CREATE TABLE IF NOT EXISTS addon_settings (\n addon_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (addon_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS provider_settings (\n provider_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (provider_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS device_settings (\n device_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (device_id, key)\n )`,\n\n // Detection events\n `CREATE TABLE IF NOT EXISTS detection_events (\n id TEXT PRIMARY KEY,\n timestamp INTEGER NOT NULL,\n device_id TEXT NOT NULL,\n class_name TEXT NOT NULL,\n score REAL NOT NULL,\n severity TEXT NOT NULL,\n track_id TEXT,\n zones JSON,\n recognition JSON,\n media_files JSON,\n data JSON\n )`,\n `CREATE INDEX IF NOT EXISTS idx_det_device_ts ON detection_events(device_id, timestamp)`,\n `CREATE INDEX IF NOT EXISTS idx_det_class_ts ON detection_events(class_name, timestamp)`,\n\n // Audio levels\n `CREATE TABLE IF NOT EXISTS audio_levels (\n id TEXT PRIMARY KEY,\n timestamp INTEGER NOT NULL,\n device_id TEXT NOT NULL,\n dbfs REAL NOT NULL,\n rms REAL NOT NULL,\n state TEXT NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS idx_audio_device_ts ON audio_levels(device_id, timestamp)`,\n\n // Track trails\n `CREATE TABLE IF NOT EXISTS track_trails (\n track_id TEXT PRIMARY KEY,\n device_id TEXT NOT NULL,\n class_name TEXT NOT NULL,\n first_seen INTEGER NOT NULL,\n last_seen INTEGER NOT NULL,\n positions JSON NOT NULL,\n snapshots JSON,\n total_distance REAL,\n zones_visited JSON\n )`,\n `CREATE INDEX IF NOT EXISTS idx_trails_device_ts ON track_trails(device_id, first_seen)`,\n]\n\n/** Addon table schema declaration */\nexport interface AddonTableSchema {\n readonly name: string\n readonly columns: ReadonlyArray<{\n readonly name: string\n readonly type: 'TEXT' | 'INTEGER' | 'REAL' | 'JSON'\n readonly primaryKey?: boolean\n readonly notNull?: boolean\n }>\n readonly indexes?: ReadonlyArray<{\n readonly name: string\n readonly columns: readonly string[]\n readonly unique?: boolean\n }>\n}\n\n/** Generate CREATE TABLE DDL from addon schema */\nexport function addonTableToDdl(schema: AddonTableSchema): string[] {\n const pks = schema.columns.filter(c => c.primaryKey).map(c => c.name)\n const colDefs = schema.columns.map(c => {\n const parts = [c.name, c.type]\n if (c.notNull) parts.push('NOT NULL')\n return parts.join(' ')\n })\n\n let ddl = `CREATE TABLE IF NOT EXISTS ${schema.name} (\\n ${colDefs.join(',\\n ')}`\n if (pks.length > 0) {\n ddl += `,\\n PRIMARY KEY (${pks.join(', ')})`\n }\n ddl += '\\n)'\n\n const stmts = [ddl]\n for (const idx of schema.indexes ?? []) {\n const unique = idx.unique ? 'UNIQUE ' : ''\n stmts.push(`CREATE ${unique}INDEX IF NOT EXISTS ${idx.name} ON ${schema.name}(${idx.columns.join(', ')})`)\n }\n return stmts\n}\n","// New providers\nexport { FilesystemStorageProvider } from './filesystem-storage-provider'\nexport { FilesystemStorageAddon } from './filesystem-storage.addon'\nexport { SqliteSettingsBackend } from './sqlite-settings-backend'\nexport { SqliteSettingsAddon } from './sqlite-settings.addon'\n\n// Legacy (still used by backend during migration)\nexport { SqliteStorageAddon } from './sqlite-storage.addon'\nexport { SqliteStorageProvider, FileSystemStorage } from './sqlite-storage.provider'\nexport { SettingsStore } from './settings-store'\nexport { CORE_TABLE_DDL, addonTableToDdl } from './sql-schema'\nexport type { AddonTableSchema } from './sql-schema'\n\n// Default export for AddonLoader\nexport { FilesystemStorageAddon as default } from './filesystem-storage.addon'\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type {\n IStorageProvider as INewStorageProvider,\n StorageLocationType,\n} from '@camstack/types'\n\n// Inline constants to avoid runtime dependency on @camstack/types\n// (types is external in tsup bundle, may not be resolvable from data/addons/)\nconst STORAGE_LOCATION_TYPES: readonly StorageLocationType[] = [\n 'recordings-high', 'recordings-low', 'recordings-clips', 'event-images',\n 'models', 'addons-data', 'cache', 'logs',\n]\n\nconst DEFAULT_LOCATION_SUBDIRS: Readonly<Record<StorageLocationType, string>> = {\n 'recordings-high': 'recordings-high',\n 'recordings-low': 'recordings-low',\n 'recordings-clips': 'recordings-clips',\n 'event-images': 'event-images',\n 'models': 'models',\n 'addons-data': 'addons-data',\n 'cache': '/tmp/camstack-cache',\n 'logs': 'logs',\n}\n\n/**\n * Filesystem storage provider — serves all location types from a local directory tree.\n *\n * Default layout:\n * {rootPath}/recordings-high/\n * {rootPath}/recordings-low/\n * {rootPath}/recordings-clips/\n * {rootPath}/event-images/\n * {rootPath}/models/\n * {rootPath}/addons-data/\n * {rootPath}/logs/\n * /tmp/camstack-cache/ (cache is always local)\n *\n * Individual location paths can be overridden.\n */\nexport class FilesystemStorageProvider implements INewStorageProvider {\n readonly id = 'local'\n readonly name = 'Local Filesystem'\n readonly supportedLocations: readonly StorageLocationType[] = [...STORAGE_LOCATION_TYPES]\n\n private readonly rootPath: string\n private readonly locationPaths: Map<StorageLocationType, string>\n\n constructor(rootPath: string, overrides?: Partial<Record<StorageLocationType, string>>) {\n this.rootPath = path.resolve(rootPath)\n this.locationPaths = new Map()\n\n for (const loc of STORAGE_LOCATION_TYPES) {\n const override = overrides?.[loc]\n if (override) {\n this.locationPaths.set(loc, path.resolve(override))\n } else {\n const subdir = DEFAULT_LOCATION_SUBDIRS[loc]\n // Absolute paths (like /tmp/camstack-cache) are used as-is\n this.locationPaths.set(\n loc,\n path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir),\n )\n }\n }\n }\n\n resolve(location: StorageLocationType, relativePath: string): string {\n const base = this.locationPaths.get(location)\n if (!base) throw new Error(`Unknown storage location: ${location}`)\n return path.join(base, relativePath)\n }\n\n async write(location: StorageLocationType, relativePath: string, data: Buffer | NodeJS.ReadableStream): Promise<void> {\n const filePath = this.resolve(location, relativePath)\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true })\n\n if (Buffer.isBuffer(data)) {\n await fs.promises.writeFile(filePath, data)\n } else {\n // Stream\n const writeStream = fs.createWriteStream(filePath)\n await new Promise<void>((resolve, reject) => {\n (data as NodeJS.ReadableStream).pipe(writeStream)\n writeStream.on('finish', resolve)\n writeStream.on('error', reject)\n })\n }\n }\n\n async read(location: StorageLocationType, relativePath: string): Promise<Buffer> {\n return fs.promises.readFile(this.resolve(location, relativePath))\n }\n\n async exists(location: StorageLocationType, relativePath: string): Promise<boolean> {\n try {\n await fs.promises.access(this.resolve(location, relativePath))\n return true\n } catch {\n return false\n }\n }\n\n async list(location: StorageLocationType, prefix?: string): Promise<readonly string[]> {\n const base = this.locationPaths.get(location)\n if (!base) return []\n const dir = prefix ? path.join(base, prefix) : base\n try {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true })\n return entries.map(e => (prefix ? `${prefix}/${e.name}` : e.name))\n } catch {\n return []\n }\n }\n\n async delete(location: StorageLocationType, relativePath: string): Promise<void> {\n const filePath = this.resolve(location, relativePath)\n await fs.promises.rm(filePath, { force: true })\n }\n\n async getAvailableSpace(location: StorageLocationType): Promise<number | null> {\n const base = this.locationPaths.get(location)\n if (!base) return null\n try {\n const stats = await fs.promises.statfs(base)\n return stats.bavail * stats.bsize\n } catch {\n return null\n }\n }\n\n async initialize(): Promise<void> {\n for (const [, dirPath] of this.locationPaths) {\n await fs.promises.mkdir(dirPath, { recursive: true })\n }\n }\n\n async shutdown(): Promise<void> {\n // Nothing to clean up for filesystem\n }\n\n /** Get the resolved path for a location type */\n getLocationPath(location: StorageLocationType): string {\n const p = this.locationPaths.get(location)\n if (!p) throw new Error(`Unknown storage location: ${location}`)\n return p\n }\n\n /** Get the root path */\n getRootPath(): string {\n return this.rootPath\n }\n}\n\nexport default FilesystemStorageProvider\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n} from '@camstack/types'\nimport { FilesystemStorageProvider } from './filesystem-storage-provider'\n\n/**\n * Filesystem Storage addon — provides local disk storage for all location types.\n * Capability: 'storage' (collection)\n */\nexport class FilesystemStorageAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'filesystem-storage',\n name: 'Local Filesystem Storage',\n version: '1.0.0',\n capabilities: [{ name: 'storage', mode: 'collection' }],\n }\n\n private provider: FilesystemStorageProvider | null = null\n private currentConfig = {\n rootPath: 'camstack-data',\n }\n\n async initialize(context: AddonContext): Promise<void> {\n const rootPath = (context.addonConfig.rootPath as string) ?? this.currentConfig.rootPath\n this.currentConfig.rootPath = rootPath\n\n // Read location overrides from config\n const overrides: Partial<Record<string, string>> = {}\n for (const key of Object.keys(context.addonConfig)) {\n if (key.startsWith('override.')) {\n const loc = key.slice('override.'.length)\n overrides[loc] = context.addonConfig[key] as string\n }\n }\n\n this.provider = new FilesystemStorageProvider(rootPath, overrides as any)\n await this.provider.initialize()\n context.logger.info(`Filesystem storage initialized at ${this.provider.getRootPath()}`)\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.shutdown()\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'storage' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getProvider(): FilesystemStorageProvider | null {\n return this.provider\n }\n\n getConfigSchema(): ConfigUISchema {\n return { sections: [] }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentConfig.rootPath = (config.rootPath as string) ?? this.currentConfig.rootPath\n }\n}\n\nexport default FilesystemStorageAddon\n","import Database from 'better-sqlite3'\nimport { randomUUID } from 'node:crypto'\nimport type {\n ISettingsBackend,\n SettingsCollection,\n SettingsRecord,\n QueryFilter,\n} from '@camstack/types'\n\n/**\n * SQLite implementation of ISettingsBackend.\n *\n * One table per collection, auto-created on first access.\n * All data stored as JSON in a TEXT column.\n * WAL mode for concurrent reads.\n */\nexport class SqliteSettingsBackend implements ISettingsBackend {\n private db: Database.Database | null = null\n private readonly ensuredTables = new Set<string>()\n private readonly runtimeDefaults: Record<string, unknown>\n\n constructor(private readonly dbPath: string, runtimeDefaults?: Record<string, unknown>) {\n this.runtimeDefaults = runtimeDefaults ?? {}\n }\n\n async initialize(): Promise<void> {\n // Ensure parent directory exists\n const dir = this.dbPath.substring(0, this.dbPath.lastIndexOf('/'))\n if (dir) {\n const fs = await import('node:fs')\n fs.mkdirSync(dir, { recursive: true })\n }\n this.db = new Database(this.dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('foreign_keys = ON')\n\n // Seed system-settings on first boot\n const isEmpty = await this.isEmpty('system-settings')\n if (isEmpty) {\n await this.seedDefaults()\n }\n }\n\n async shutdown(): Promise<void> {\n this.db?.close()\n this.db = null\n }\n\n // ---------------------------------------------------------------------------\n // ISettingsBackend implementation\n // ---------------------------------------------------------------------------\n\n async get(collection: SettingsCollection, key: string): Promise<unknown> {\n this.ensureTable(collection)\n const row = this.getDb()\n .prepare<[string], { data: string }>(`SELECT data FROM \"${collection}\" WHERE id = ?`)\n .get(key)\n if (!row) return undefined\n return JSON.parse(row.data)\n }\n\n async set(collection: SettingsCollection, key: string, value: unknown): Promise<void> {\n this.ensureTable(collection)\n this.getDb()\n .prepare(`INSERT INTO \"${collection}\" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`)\n .run(key, JSON.stringify(value))\n }\n\n async query(collection: SettingsCollection, filter?: QueryFilter): Promise<readonly SettingsRecord[]> {\n this.ensureTable(collection)\n let sql = `SELECT id, data FROM \"${collection}\"`\n const params: unknown[] = []\n\n const whereClauses: string[] = []\n\n if (filter?.where) {\n for (const [field, value] of Object.entries(filter.where)) {\n if (field === 'id') {\n whereClauses.push('id = ?')\n params.push(value)\n } else {\n whereClauses.push(`json_extract(data, '$.${field}') = ?`)\n params.push(typeof value === 'object' ? JSON.stringify(value) : value)\n }\n }\n }\n\n if (filter?.whereIn) {\n for (const [field, values] of Object.entries(filter.whereIn)) {\n const placeholders = values.map(() => '?').join(', ')\n if (field === 'id') {\n whereClauses.push(`id IN (${placeholders})`)\n } else {\n whereClauses.push(`json_extract(data, '$.${field}') IN (${placeholders})`)\n }\n params.push(...values)\n }\n }\n\n if (filter?.whereBetween) {\n for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {\n if (field === 'id') {\n whereClauses.push('id BETWEEN ? AND ?')\n } else {\n whereClauses.push(`json_extract(data, '$.${field}') BETWEEN ? AND ?`)\n }\n params.push(low, high)\n }\n }\n\n if (whereClauses.length > 0) {\n sql += ` WHERE ${whereClauses.join(' AND ')}`\n }\n\n if (filter?.orderBy) {\n const dir = filter.orderBy.direction === 'desc' ? 'DESC' : 'ASC'\n if (filter.orderBy.field === 'id') {\n sql += ` ORDER BY id ${dir}`\n } else {\n sql += ` ORDER BY json_extract(data, '$.${filter.orderBy.field}') ${dir}`\n }\n }\n\n if (filter?.limit) {\n sql += ` LIMIT ?`\n params.push(filter.limit)\n }\n\n if (filter?.offset) {\n sql += ` OFFSET ?`\n params.push(filter.offset)\n }\n\n const rows = this.getDb()\n .prepare<unknown[], { id: string; data: string }>(sql)\n .all(...params)\n\n return rows.map(row => ({\n id: row.id,\n data: JSON.parse(row.data),\n }))\n }\n\n async insert(collection: SettingsCollection, record: SettingsRecord): Promise<void> {\n this.ensureTable(collection)\n const id = record.id || randomUUID()\n this.getDb()\n .prepare(`INSERT INTO \"${collection}\" (id, data) VALUES (?, ?)`)\n .run(id, JSON.stringify(record.data))\n }\n\n async update(collection: SettingsCollection, id: string, data: Record<string, unknown>): Promise<void> {\n this.ensureTable(collection)\n this.getDb()\n .prepare(`UPDATE \"${collection}\" SET data = ? WHERE id = ?`)\n .run(JSON.stringify(data), id)\n }\n\n async delete(collection: SettingsCollection, key: string): Promise<void> {\n this.ensureTable(collection)\n this.getDb()\n .prepare(`DELETE FROM \"${collection}\" WHERE id = ?`)\n .run(key)\n }\n\n async count(collection: SettingsCollection, filter?: QueryFilter): Promise<number> {\n this.ensureTable(collection)\n if (!filter) {\n const row = this.getDb()\n .prepare<[], { cnt: number }>(`SELECT COUNT(*) AS cnt FROM \"${collection}\"`)\n .get()\n return row?.cnt ?? 0\n }\n const rows = await this.query(collection, { ...filter, limit: undefined, offset: undefined })\n return rows.length\n }\n\n async isEmpty(collection: SettingsCollection): Promise<boolean> {\n this.ensureTable(collection)\n const row = this.getDb()\n .prepare<[], { cnt: number }>(`SELECT COUNT(*) AS cnt FROM \"${collection}\"`)\n .get()\n return (row?.cnt ?? 0) === 0\n }\n\n // ---------------------------------------------------------------------------\n // Legacy SettingsStore compatibility (used by ConfigManager.setSettingsStore)\n // ---------------------------------------------------------------------------\n\n /** Get a system setting by dot-notation key */\n getSystem(key: string): unknown {\n this.ensureTable('system-settings')\n const row = this.getDb()\n .prepare<[string], { data: string }>('SELECT data FROM \"system-settings\" WHERE id = ?')\n .get(key)\n if (!row) return undefined\n return JSON.parse(row.data)\n }\n\n /** Set a system setting */\n setSystem(key: string, value: unknown): void {\n this.ensureTable('system-settings')\n this.getDb()\n .prepare('INSERT INTO \"system-settings\" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data')\n .run(key, JSON.stringify(value))\n }\n\n /** Get all system settings as flat key-value */\n getAllSystem(): Record<string, unknown> {\n this.ensureTable('system-settings')\n const rows = this.getDb()\n .prepare<[], { id: string; data: string }>('SELECT id, data FROM \"system-settings\"')\n .all()\n return Object.fromEntries(rows.map(r => [r.id, JSON.parse(r.data)]))\n }\n\n /** Get all settings for an addon */\n getAllAddon(addonId: string): Record<string, unknown> {\n this.ensureTable('addon-settings')\n const rows = this.getDb()\n .prepare<[string], { id: string; data: string }>('SELECT id, data FROM \"addon-settings\" WHERE json_extract(data, \\'$.addonId\\') = ?')\n .all(addonId)\n if (rows.length === 0) return {}\n // addon-settings stores all keys for an addon as separate rows with id = \"addonId.key\"\n const result: Record<string, unknown> = {}\n for (const row of rows) {\n const parsed = JSON.parse(row.data)\n const key = row.id.startsWith(`${addonId}.`) ? row.id.slice(addonId.length + 1) : row.id\n result[key] = parsed.value ?? parsed\n }\n return result\n }\n\n /** Bulk-set all settings for an addon */\n setAllAddon(addonId: string, config: Record<string, unknown>): void {\n this.ensureTable('addon-settings')\n const db = this.getDb()\n const deleteStmt = db.prepare('DELETE FROM \"addon-settings\" WHERE id LIKE ? || \\'%\\'')\n const insertStmt = db.prepare('INSERT INTO \"addon-settings\" (id, data) VALUES (?, ?)')\n db.transaction(() => {\n deleteStmt.run(`${addonId}.`)\n for (const [key, value] of Object.entries(config)) {\n insertStmt.run(`${addonId}.${key}`, JSON.stringify({ addonId, key, value }))\n }\n })()\n }\n\n /** Get all settings for a provider */\n getAllProvider(providerId: string): Record<string, unknown> {\n return this.getAllScoped('provider-settings', providerId)\n }\n\n /** Set a provider setting */\n setProvider(providerId: string, key: string, value: unknown): void {\n this.setScopedKey('provider-settings', providerId, key, value)\n }\n\n /** Get all settings for a device */\n getAllDevice(deviceId: string): Record<string, unknown> {\n return this.getAllScoped('device-settings', deviceId)\n }\n\n /** Set a device setting */\n setDevice(deviceId: string, key: string, value: unknown): void {\n this.setScopedKey('device-settings', deviceId, key, value)\n }\n\n /** Seed system-settings with runtime defaults (first boot) */\n private async seedDefaults(): Promise<void> {\n this.ensureTable('system-settings')\n const insert = this.getDb().prepare(\n 'INSERT OR IGNORE INTO \"system-settings\" (id, data) VALUES (?, ?)',\n )\n this.getDb().transaction(() => {\n for (const [key, value] of Object.entries(this.runtimeDefaults)) {\n insert.run(key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private getDb(): Database.Database {\n if (!this.db) throw new Error('SqliteSettingsBackend not initialized — call initialize() first')\n return this.db\n }\n\n private ensureTable(collection: string): void {\n if (this.ensuredTables.has(collection)) return\n this.getDb().exec(\n `CREATE TABLE IF NOT EXISTS \"${collection}\" (id TEXT PRIMARY KEY, data TEXT NOT NULL)`,\n )\n this.ensuredTables.add(collection)\n }\n\n private getAllScoped(collection: string, scopeId: string): Record<string, unknown> {\n this.ensureTable(collection)\n const rows = this.getDb()\n .prepare<[string], { id: string; data: string }>(`SELECT id, data FROM \"${collection}\" WHERE id LIKE ? || '.%'`)\n .all(scopeId)\n const result: Record<string, unknown> = {}\n for (const row of rows) {\n const key = row.id.slice(scopeId.length + 1)\n const parsed = JSON.parse(row.data)\n result[key] = parsed.value ?? parsed\n }\n return result\n }\n\n private setScopedKey(collection: string, scopeId: string, key: string, value: unknown): void {\n this.ensureTable(collection)\n this.getDb()\n .prepare(`INSERT INTO \"${collection}\" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`)\n .run(`${scopeId}.${key}`, JSON.stringify({ scopeId, key, value }))\n }\n}\n\nexport default SqliteSettingsBackend\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n} from '@camstack/types'\nimport { SqliteSettingsBackend } from './sqlite-settings-backend'\n\n/**\n * SQLite Settings addon — provides persistent settings storage.\n * Capability: 'settings-store' (singleton)\n *\n * Depends on 'storage' capability for resolving the DB file path.\n */\nexport class SqliteSettingsAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'sqlite-settings',\n name: 'SQLite Settings',\n version: '1.0.0',\n capabilities: [{ name: 'settings-store', mode: 'singleton' }],\n }\n\n private backend: SqliteSettingsBackend | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n // Resolve DB path from storage provider (addons-data location)\n const dbPath = context.storageProvider\n ? context.storageProvider.resolve('addons-data', `${context.id.replace('addon:', '')}/camstack.db`)\n : context.dataDir\n ? `${context.dataDir}/camstack.db`\n : 'camstack-data/addons-data/sqlite-settings/camstack.db'\n\n const runtimeDefaults = (context.addonConfig._runtimeDefaults as Record<string, unknown>) ?? {}\n this.backend = new SqliteSettingsBackend(dbPath, runtimeDefaults)\n await this.backend.initialize()\n context.logger.info(`SQLite settings initialized at ${dbPath}`)\n }\n\n async shutdown(): Promise<void> {\n await this.backend?.shutdown()\n }\n\n getBackend(): SqliteSettingsBackend | null {\n return this.backend\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === ('settings-store' as string) && this.backend) {\n return this.backend as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return { sections: [] }\n }\n\n getConfig(): Record<string, unknown> {\n return {}\n }\n\n async onConfigChange(_config: Record<string, unknown>): Promise<void> {\n // DB path changes require restart\n }\n}\n\nexport default SqliteSettingsAddon\n","import Database from 'better-sqlite3'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { randomUUID } from 'node:crypto'\nimport type {\n IStorageProvider,\n IStorageLocation,\n IStructuredStorage,\n IFileStorage,\n StorageLocationName,\n StorageRecord,\n QueryFilter,\n} from '@camstack/types'\n\ntype LocationType = 'structured' | 'files' | 'both'\n\n/** Which locations need structured (SQL) storage, files storage, or both.\n * All structured locations share a SINGLE SQLite database (camstack.db).\n * File locations use the filesystem at their configured path. */\nconst LOCATION_TYPES: Record<string, LocationType> = {\n // New location names (from StorageLocationManager)\n data: 'structured', // settings, events, trails — SQL only\n media: 'files', // crops, snapshots, thumbnails — files only\n recordings: 'files', // video segments — files only\n models: 'files', // ONNX/TFLite models — files only\n cache: 'files', // temp files — files only\n logs: 'files', // Winston log files — files only\n // Legacy location names (backward compat)\n config: 'structured',\n events: 'structured',\n addon: 'both',\n}\n\nclass SqliteStructuredStorage implements IStructuredStorage {\n private readonly ensuredTables = new Set<string>()\n\n constructor(private readonly db: Database.Database) {}\n\n private ensureTable(collection: string): void {\n if (this.ensuredTables.has(collection)) return\n this.db.exec(\n `CREATE TABLE IF NOT EXISTS \"${collection}\" (id TEXT PRIMARY KEY, data TEXT)`,\n )\n this.ensuredTables.add(collection)\n }\n\n async insert(record: StorageRecord): Promise<StorageRecord> {\n this.ensureTable(record.collection)\n const id = record.id || randomUUID()\n const newRecord: StorageRecord = {\n collection: record.collection,\n id,\n data: record.data,\n }\n this.db\n .prepare(`INSERT INTO \"${record.collection}\" (id, data) VALUES (?, ?)`)\n .run(id, JSON.stringify(newRecord.data))\n return newRecord\n }\n\n async query(collection: string, filter?: QueryFilter): Promise<readonly StorageRecord[]> {\n this.ensureTable(collection)\n const { sql, params } = this.buildSelect(collection, filter)\n const rows = this.db.prepare(sql).all(...params) as Array<{ id: string; data: string }>\n return rows.map((row) => ({\n collection,\n id: row.id,\n data: JSON.parse(row.data) as Record<string, unknown>,\n }))\n }\n\n async update(\n collection: string,\n id: string,\n data: Record<string, unknown>,\n ): Promise<StorageRecord> {\n this.ensureTable(collection)\n this.db\n .prepare(`UPDATE \"${collection}\" SET data = ? WHERE id = ?`)\n .run(JSON.stringify(data), id)\n return { collection, id, data }\n }\n\n async delete(collection: string, id: string): Promise<void> {\n this.ensureTable(collection)\n this.db.prepare(`DELETE FROM \"${collection}\" WHERE id = ?`).run(id)\n }\n\n async count(collection: string, filter?: QueryFilter): Promise<number> {\n this.ensureTable(collection)\n const { sql, params } = this.buildCount(collection, filter)\n const row = this.db.prepare(sql).get(...params) as { cnt: number }\n return row.cnt\n }\n\n private buildWhereClause(filter?: QueryFilter): { clause: string; params: unknown[] } {\n if (!filter) return { clause: '', params: [] }\n\n const conditions: string[] = []\n const params: unknown[] = []\n\n if (filter.where) {\n for (const [field, value] of Object.entries(filter.where)) {\n if (field === 'id') {\n conditions.push('id = ?')\n params.push(value)\n } else {\n conditions.push(`json_extract(data, '$.${field}') = ?`)\n params.push(value)\n }\n }\n }\n\n if (filter.whereIn) {\n for (const [field, values] of Object.entries(filter.whereIn)) {\n const placeholders = values.map(() => '?').join(', ')\n if (field === 'id') {\n conditions.push(`id IN (${placeholders})`)\n } else {\n conditions.push(`json_extract(data, '$.${field}') IN (${placeholders})`)\n }\n params.push(...values)\n }\n }\n\n if (filter.whereBetween) {\n for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {\n if (field === 'id') {\n conditions.push('id BETWEEN ? AND ?')\n } else {\n conditions.push(`json_extract(data, '$.${field}') BETWEEN ? AND ?`)\n }\n params.push(low, high)\n }\n }\n\n const clause = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : ''\n return { clause, params }\n }\n\n private buildSelect(collection: string, filter?: QueryFilter): { sql: string; params: unknown[] } {\n const { clause, params } = this.buildWhereClause(filter)\n let sql = `SELECT id, data FROM \"${collection}\"${clause}`\n\n if (filter?.orderBy) {\n const dir = filter.orderBy.direction === 'desc' ? 'DESC' : 'ASC'\n if (filter.orderBy.field === 'id') {\n sql += ` ORDER BY id ${dir}`\n } else {\n sql += ` ORDER BY json_extract(data, '$.${filter.orderBy.field}') ${dir}`\n }\n }\n\n if (filter?.limit !== undefined) {\n sql += ` LIMIT ?`\n params.push(filter.limit)\n }\n\n if (filter?.offset !== undefined) {\n sql += ` OFFSET ?`\n params.push(filter.offset)\n }\n\n return { sql, params }\n }\n\n private buildCount(collection: string, filter?: QueryFilter): { sql: string; params: unknown[] } {\n const { clause, params } = this.buildWhereClause(filter)\n return { sql: `SELECT COUNT(*) as cnt FROM \"${collection}\"${clause}`, params }\n }\n}\n\nexport class FileSystemStorage implements IFileStorage {\n constructor(private readonly basePath: string) {}\n\n async readFile(filePath: string): Promise<Buffer> {\n const fullPath = path.join(this.basePath, filePath)\n return fs.promises.readFile(fullPath)\n }\n\n async writeFile(filePath: string, data: Buffer): Promise<void> {\n const fullPath = path.join(this.basePath, filePath)\n fs.mkdirSync(path.dirname(fullPath), { recursive: true })\n await fs.promises.writeFile(fullPath, data)\n }\n\n async deleteFile(filePath: string): Promise<void> {\n const fullPath = path.join(this.basePath, filePath)\n await fs.promises.unlink(fullPath)\n }\n\n async listFiles(prefix?: string): Promise<readonly string[]> {\n const searchDir = prefix ? path.join(this.basePath, prefix) : this.basePath\n try {\n const entries = await fs.promises.readdir(searchDir, { recursive: true })\n const files: string[] = []\n for (const entry of entries) {\n const entryStr = String(entry)\n const relative = prefix ? path.join(prefix, entryStr) : entryStr\n const fullPath = path.join(this.basePath, relative)\n try {\n const stat = await fs.promises.stat(fullPath)\n if (stat.isFile()) {\n files.push(relative)\n }\n } catch {\n // Skip entries that can't be stat'd\n }\n }\n return files\n } catch {\n return []\n }\n }\n\n async getFileUrl(_path: string): Promise<string> {\n return null as unknown as string\n }\n\n async exists(filePath: string): Promise<boolean> {\n const fullPath = path.join(this.basePath, filePath)\n try {\n await fs.promises.access(fullPath, fs.constants.F_OK)\n return true\n } catch {\n return false\n }\n }\n}\n\n/** @deprecated Use FilesystemStorageProvider + SqliteSettingsBackend instead */\nexport class SqliteStorageProvider {\n private mainDb: Database.Database | null = null\n private sharedStructured: SqliteStructuredStorage | null = null\n private readonly locations = new Map<StorageLocationName, IStorageLocation>()\n\n async initialize(): Promise<void> {\n // Called by interface contract; actual setup done via configure()\n }\n\n /**\n * Configure all storage locations.\n * ONE single SQLite database (camstack.db) is used for ALL structured storage.\n * File-based locations use the filesystem at their configured path.\n */\n async configure(config: { locations: Record<string, string> }): Promise<void> {\n // Find the 'data' location path for the single DB (fall back to first structured location)\n const dataPath = config.locations['data'] ?? config.locations['config'] ?? Object.values(config.locations)[0]\n if (!dataPath) throw new Error('No data path configured for SQLite storage')\n\n // Create single database\n fs.mkdirSync(dataPath, { recursive: true })\n const dbPath = path.join(dataPath, 'camstack.db')\n this.mainDb = new Database(dbPath)\n this.mainDb.pragma('journal_mode = WAL')\n this.sharedStructured = new SqliteStructuredStorage(this.mainDb)\n\n // Configure each location\n for (const [name, dirPath] of Object.entries(config.locations)) {\n const locationName = name as StorageLocationName\n const locationType = LOCATION_TYPES[name] ?? 'files'\n\n fs.mkdirSync(dirPath, { recursive: true })\n\n const location: IStorageLocation = {}\n\n // All structured storage shares the single DB\n if (locationType === 'structured' || locationType === 'both') {\n location.structured = this.sharedStructured\n }\n\n if (locationType === 'files' || locationType === 'both') {\n location.files = new FileSystemStorage(dirPath)\n }\n\n this.locations.set(locationName, location)\n }\n }\n\n getLocation(name: StorageLocationName): IStorageLocation {\n const location = this.locations.get(name)\n if (!location) {\n throw new Error(`Storage location \"${name}\" not found`)\n }\n return location\n }\n\n async shutdown(): Promise<void> {\n if (this.mainDb) {\n this.mainDb.close()\n this.mainDb = null\n this.sharedStructured = null\n }\n this.locations.clear()\n }\n\n async export(_locationName: StorageLocationName): Promise<Buffer> {\n throw new Error('Export not yet implemented')\n }\n\n async import(_locationName: StorageLocationName, _data: Buffer): Promise<void> {\n throw new Error('Import not yet implemented')\n }\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n} from '@camstack/types'\nimport { SqliteStorageProvider } from './sqlite-storage.provider'\n\nexport class SqliteStorageAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'sqlite-storage',\n name: 'SQLite Storage',\n version: '1.0.0',\n capabilities: ['storage'],\n }\n\n private provider: SqliteStorageProvider | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n const storageConfig = {\n locations: { ...context.locationPaths } as Record<string, string>,\n }\n this.provider = new SqliteStorageProvider()\n await this.provider.configure(storageConfig)\n context.logger.info('SQLite storage initialized')\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.shutdown()\n }\n\n getProvider(): SqliteStorageProvider {\n if (!this.provider) throw new Error('SQLite storage not initialized')\n return this.provider\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'storage' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return {}\n }\n\n async onConfigChange(_config: Record<string, unknown>): Promise<void> {\n // No configurable fields\n }\n}\n","import Database from 'better-sqlite3'\nimport { CORE_TABLE_DDL } from './sql-schema.js'\nimport { RUNTIME_DEFAULTS } from '@camstack/kernel'\n\n/**\n * Thin wrapper over better-sqlite3 that manages the four settings tables:\n * system_settings, addon_settings, provider_settings, device_settings.\n *\n * All values are stored as JSON text and deserialized on read.\n */\nexport class SettingsStore {\n private readonly db: Database.Database\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('foreign_keys = ON')\n this.initTables()\n }\n\n // ---------------------------------------------------------------------------\n // System settings\n // ---------------------------------------------------------------------------\n\n getSystem(key: string): unknown {\n const row = this.db\n .prepare<[string], { value: string }>('SELECT value FROM system_settings WHERE key = ?')\n .get(key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setSystem(key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(key, JSON.stringify(value))\n }\n\n getAllSystem(): Record<string, unknown> {\n const rows = this.db\n .prepare<[], { key: string; value: string }>('SELECT key, value FROM system_settings')\n .all()\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Addon settings\n // ---------------------------------------------------------------------------\n\n getAddon(addonId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM addon_settings WHERE addon_id = ? AND key = ?',\n )\n .get(addonId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setAddon(addonId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(addon_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(addonId, key, JSON.stringify(value))\n }\n\n getAllAddon(addonId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM addon_settings WHERE addon_id = ?',\n )\n .all(addonId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n /** Bulk-replace all keys for an addon (within a transaction). */\n setAllAddon(addonId: string, config: Record<string, unknown>): void {\n const deleteStmt = this.db.prepare('DELETE FROM addon_settings WHERE addon_id = ?')\n const insertStmt = this.db.prepare(\n `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n deleteStmt.run(addonId)\n for (const [key, value] of Object.entries(config)) {\n insertStmt.run(addonId, key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Provider settings\n // ---------------------------------------------------------------------------\n\n getProvider(providerId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM provider_settings WHERE provider_id = ? AND key = ?',\n )\n .get(providerId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setProvider(providerId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO provider_settings (provider_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(provider_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(providerId, key, JSON.stringify(value))\n }\n\n getAllProvider(providerId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM provider_settings WHERE provider_id = ?',\n )\n .all(providerId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Device settings\n // ---------------------------------------------------------------------------\n\n getDevice(deviceId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM device_settings WHERE device_id = ? AND key = ?',\n )\n .get(deviceId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setDevice(deviceId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO device_settings (device_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(device_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(deviceId, key, JSON.stringify(value))\n }\n\n getAllDevice(deviceId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM device_settings WHERE device_id = ?',\n )\n .all(deviceId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /** Close the SQLite connection (call on shutdown). */\n close(): void {\n this.db.close()\n }\n\n /** Check if system_settings is empty (used for first-boot seeding). */\n isSystemSettingsEmpty(): boolean {\n const row = this.db\n .prepare<[], { cnt: number }>('SELECT COUNT(*) AS cnt FROM system_settings')\n .get()\n return (row?.cnt ?? 0) === 0\n }\n\n /** Seed system_settings with RUNTIME_DEFAULTS (only on first boot). */\n seedDefaults(): void {\n const insert = this.db.prepare(\n `INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) {\n insert.run(key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private initTables(): void {\n this.db.transaction(() => {\n for (const stmt of CORE_TABLE_DDL) {\n this.db.prepare(stmt).run()\n }\n })()\n }\n}\n","/** Core table DDL statements -- executed on first boot */\nexport const CORE_TABLE_DDL: readonly string[] = [\n // Settings tables\n `CREATE TABLE IF NOT EXISTS system_settings (\n key TEXT PRIMARY KEY,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\n )`,\n `CREATE TABLE IF NOT EXISTS addon_settings (\n addon_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (addon_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS provider_settings (\n provider_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (provider_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS device_settings (\n device_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (device_id, key)\n )`,\n\n // Detection events\n `CREATE TABLE IF NOT EXISTS detection_events (\n id TEXT PRIMARY KEY,\n timestamp INTEGER NOT NULL,\n device_id TEXT NOT NULL,\n class_name TEXT NOT NULL,\n score REAL NOT NULL,\n severity TEXT NOT NULL,\n track_id TEXT,\n zones JSON,\n recognition JSON,\n media_files JSON,\n data JSON\n )`,\n `CREATE INDEX IF NOT EXISTS idx_det_device_ts ON detection_events(device_id, timestamp)`,\n `CREATE INDEX IF NOT EXISTS idx_det_class_ts ON detection_events(class_name, timestamp)`,\n\n // Audio levels\n `CREATE TABLE IF NOT EXISTS audio_levels (\n id TEXT PRIMARY KEY,\n timestamp INTEGER NOT NULL,\n device_id TEXT NOT NULL,\n dbfs REAL NOT NULL,\n rms REAL NOT NULL,\n state TEXT NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS idx_audio_device_ts ON audio_levels(device_id, timestamp)`,\n\n // Track trails\n `CREATE TABLE IF NOT EXISTS track_trails (\n track_id TEXT PRIMARY KEY,\n device_id TEXT NOT NULL,\n class_name TEXT NOT NULL,\n first_seen INTEGER NOT NULL,\n last_seen INTEGER NOT NULL,\n positions JSON NOT NULL,\n snapshots JSON,\n total_distance REAL,\n zones_visited JSON\n )`,\n `CREATE INDEX IF NOT EXISTS idx_trails_device_ts ON track_trails(device_id, first_seen)`,\n]\n\n/** Addon table schema declaration */\nexport interface AddonTableSchema {\n readonly name: string\n readonly columns: ReadonlyArray<{\n readonly name: string\n readonly type: 'TEXT' | 'INTEGER' | 'REAL' | 'JSON'\n readonly primaryKey?: boolean\n readonly notNull?: boolean\n }>\n readonly indexes?: ReadonlyArray<{\n readonly name: string\n readonly columns: readonly string[]\n readonly unique?: boolean\n }>\n}\n\n/** Generate CREATE TABLE DDL from addon schema */\nexport function addonTableToDdl(schema: AddonTableSchema): string[] {\n const pks = schema.columns.filter(c => c.primaryKey).map(c => c.name)\n const colDefs = schema.columns.map(c => {\n const parts = [c.name, c.type]\n if (c.notNull) parts.push('NOT NULL')\n return parts.join(' ')\n })\n\n let ddl = `CREATE TABLE IF NOT EXISTS ${schema.name} (\\n ${colDefs.join(',\\n ')}`\n if (pks.length > 0) {\n ddl += `,\\n PRIMARY KEY (${pks.join(', ')})`\n }\n ddl += '\\n)'\n\n const stmts = [ddl]\n for (const idx of schema.indexes ?? []) {\n const unique = idx.unique ? 'UNIQUE ' : ''\n stmts.push(`CREATE ${unique}INDEX IF NOT EXISTS ${idx.name} ON ${schema.name}(${idx.columns.join(', ')})`)\n }\n return stmts\n}\n"],"mappings":"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","names":["exports","addonTableToDdl","CORE_TABLE_DDL","resolve","fs","Database","import_better_sqlite3","fs","path","import_node_crypto","Database","import_better_sqlite3","Database","CORE_TABLE_DDL"]}
1
+ {"version":3,"sources":["../../../src/builtins/sqlite-storage/index.ts","../../../src/builtins/sqlite-storage/filesystem-storage-provider.ts","../../../src/builtins/sqlite-storage/filesystem-storage.addon.ts","../../../src/builtins/sqlite-storage/sqlite-settings-backend.ts","../../../src/builtins/sqlite-storage/sqlite-settings.addon.ts","../../../src/builtins/sqlite-storage/sqlite-storage.provider.ts","../../../src/builtins/sqlite-storage/sqlite-storage.addon.ts","../../../src/builtins/sqlite-storage/settings-store.ts","../../../src/builtins/sqlite-storage/sql-schema.ts"],"sourcesContent":["// New providers\nexport { FilesystemStorageProvider } from './filesystem-storage-provider'\nexport { FilesystemStorageAddon } from './filesystem-storage.addon'\nexport { SqliteSettingsBackend } from './sqlite-settings-backend'\nexport { SqliteSettingsAddon } from './sqlite-settings.addon'\n\n// Legacy (still used by backend during migration)\nexport { SqliteStorageAddon } from './sqlite-storage.addon'\nexport { SqliteStorageProvider, FileSystemStorage } from './sqlite-storage.provider'\nexport { SettingsStore } from './settings-store'\nexport { CORE_TABLE_DDL, addonTableToDdl } from './sql-schema'\nexport type { AddonTableSchema } from './sql-schema'\n\n// Default export for AddonLoader\nexport { FilesystemStorageAddon as default } from './filesystem-storage.addon'\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type {\n IStorageProvider as INewStorageProvider,\n StorageLocationType,\n} from '@camstack/types'\n\n// Inline constants to avoid runtime dependency on @camstack/types\n// (types is external in tsup bundle, may not be resolvable from data/addons/)\nconst STORAGE_LOCATION_TYPES: readonly StorageLocationType[] = [\n 'recordings-high', 'recordings-low', 'recordings-clips', 'event-images',\n 'models', 'addons-data', 'cache', 'logs',\n]\n\nconst DEFAULT_LOCATION_SUBDIRS: Readonly<Record<StorageLocationType, string>> = {\n 'recordings-high': 'recordings-high',\n 'recordings-low': 'recordings-low',\n 'recordings-clips': 'recordings-clips',\n 'event-images': 'event-images',\n 'models': 'models',\n 'addons-data': 'addons-data',\n 'cache': '/tmp/camstack-cache',\n 'logs': 'logs',\n}\n\n/**\n * Filesystem storage provider — serves all location types from a local directory tree.\n *\n * Default layout:\n * {rootPath}/recordings-high/\n * {rootPath}/recordings-low/\n * {rootPath}/recordings-clips/\n * {rootPath}/event-images/\n * {rootPath}/models/\n * {rootPath}/addons-data/\n * {rootPath}/logs/\n * /tmp/camstack-cache/ (cache is always local)\n *\n * Individual location paths can be overridden.\n */\nexport class FilesystemStorageProvider implements INewStorageProvider {\n readonly id = 'local'\n readonly name = 'Local Filesystem'\n readonly supportedLocations: readonly StorageLocationType[] = [...STORAGE_LOCATION_TYPES]\n\n private readonly rootPath: string\n private readonly locationPaths: Map<StorageLocationType, string>\n\n constructor(rootPath: string, overrides?: Partial<Record<StorageLocationType, string>>) {\n this.rootPath = path.resolve(rootPath)\n this.locationPaths = new Map()\n\n for (const loc of STORAGE_LOCATION_TYPES) {\n const override = overrides?.[loc]\n if (override) {\n this.locationPaths.set(loc, path.resolve(override))\n } else {\n const subdir = DEFAULT_LOCATION_SUBDIRS[loc]\n // Absolute paths (like /tmp/camstack-cache) are used as-is\n this.locationPaths.set(\n loc,\n path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir),\n )\n }\n }\n }\n\n resolve(location: StorageLocationType, relativePath: string): string {\n const base = this.locationPaths.get(location)\n if (!base) throw new Error(`Unknown storage location: ${location}`)\n return path.join(base, relativePath)\n }\n\n async write(location: StorageLocationType, relativePath: string, data: Buffer | NodeJS.ReadableStream): Promise<void> {\n const filePath = this.resolve(location, relativePath)\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true })\n\n if (Buffer.isBuffer(data)) {\n await fs.promises.writeFile(filePath, data)\n } else {\n // Stream\n const writeStream = fs.createWriteStream(filePath)\n await new Promise<void>((resolve, reject) => {\n (data as NodeJS.ReadableStream).pipe(writeStream)\n writeStream.on('finish', resolve)\n writeStream.on('error', reject)\n })\n }\n }\n\n async read(location: StorageLocationType, relativePath: string): Promise<Buffer> {\n return fs.promises.readFile(this.resolve(location, relativePath))\n }\n\n async exists(location: StorageLocationType, relativePath: string): Promise<boolean> {\n try {\n await fs.promises.access(this.resolve(location, relativePath))\n return true\n } catch {\n return false\n }\n }\n\n async list(location: StorageLocationType, prefix?: string): Promise<readonly string[]> {\n const base = this.locationPaths.get(location)\n if (!base) return []\n const dir = prefix ? path.join(base, prefix) : base\n try {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true })\n return entries.map(e => (prefix ? `${prefix}/${e.name}` : e.name))\n } catch {\n return []\n }\n }\n\n async delete(location: StorageLocationType, relativePath: string): Promise<void> {\n const filePath = this.resolve(location, relativePath)\n await fs.promises.rm(filePath, { force: true })\n }\n\n async getAvailableSpace(location: StorageLocationType): Promise<number | null> {\n const base = this.locationPaths.get(location)\n if (!base) return null\n try {\n const stats = await fs.promises.statfs(base)\n return stats.bavail * stats.bsize\n } catch {\n return null\n }\n }\n\n async initialize(): Promise<void> {\n for (const [, dirPath] of this.locationPaths) {\n await fs.promises.mkdir(dirPath, { recursive: true })\n }\n }\n\n async shutdown(): Promise<void> {\n // Nothing to clean up for filesystem\n }\n\n /** Get the resolved path for a location type */\n getLocationPath(location: StorageLocationType): string {\n const p = this.locationPaths.get(location)\n if (!p) throw new Error(`Unknown storage location: ${location}`)\n return p\n }\n\n /** Get the root path */\n getRootPath(): string {\n return this.rootPath\n }\n}\n\nexport default FilesystemStorageProvider\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n} from '@camstack/types'\nimport { FilesystemStorageProvider } from './filesystem-storage-provider'\n\n/**\n * Filesystem Storage addon — provides local disk storage for all location types.\n * Capability: 'storage' (collection)\n */\nexport class FilesystemStorageAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'filesystem-storage',\n name: 'Local Filesystem Storage',\n version: '1.0.0',\n capabilities: [{ name: 'storage', mode: 'collection' }],\n }\n\n private provider: FilesystemStorageProvider | null = null\n private currentConfig = {\n rootPath: 'camstack-data',\n }\n\n async initialize(context: AddonContext): Promise<void> {\n const rootPath = (context.addonConfig.rootPath as string) ?? this.currentConfig.rootPath\n this.currentConfig.rootPath = rootPath\n\n // Read location overrides from config\n const overrides: Partial<Record<string, string>> = {}\n for (const key of Object.keys(context.addonConfig)) {\n if (key.startsWith('override.')) {\n const loc = key.slice('override.'.length)\n overrides[loc] = context.addonConfig[key] as string\n }\n }\n\n this.provider = new FilesystemStorageProvider(rootPath, overrides as any)\n await this.provider.initialize()\n context.logger.info(`Filesystem storage initialized at ${this.provider.getRootPath()}`)\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.shutdown()\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'storage' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getProvider(): FilesystemStorageProvider | null {\n return this.provider\n }\n\n getConfigSchema(): ConfigUISchema {\n return { sections: [] }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentConfig.rootPath = (config.rootPath as string) ?? this.currentConfig.rootPath\n }\n}\n\nexport default FilesystemStorageAddon\n","import Database from 'better-sqlite3'\nimport { randomUUID } from 'node:crypto'\nimport type {\n ISettingsBackend,\n SettingsCollection,\n SettingsRecord,\n QueryFilter,\n} from '@camstack/types'\n\n/**\n * SQLite implementation of ISettingsBackend.\n *\n * One table per collection, auto-created on first access.\n * All data stored as JSON in a TEXT column.\n * WAL mode for concurrent reads.\n */\nexport class SqliteSettingsBackend implements ISettingsBackend {\n private db: Database.Database | null = null\n private readonly ensuredTables = new Set<string>()\n private readonly runtimeDefaults: Record<string, unknown>\n\n constructor(private readonly dbPath: string, runtimeDefaults?: Record<string, unknown>) {\n this.runtimeDefaults = runtimeDefaults ?? {}\n }\n\n async initialize(): Promise<void> {\n // Ensure parent directory exists\n const dir = this.dbPath.substring(0, this.dbPath.lastIndexOf('/'))\n if (dir) {\n const fs = await import('node:fs')\n fs.mkdirSync(dir, { recursive: true })\n }\n this.db = new Database(this.dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('foreign_keys = ON')\n\n // Seed system-settings on first boot\n const isEmpty = await this.isEmpty('system-settings')\n if (isEmpty) {\n await this.seedDefaults()\n }\n }\n\n async shutdown(): Promise<void> {\n this.db?.close()\n this.db = null\n }\n\n // ---------------------------------------------------------------------------\n // ISettingsBackend implementation\n // ---------------------------------------------------------------------------\n\n async get(collection: SettingsCollection, key: string): Promise<unknown> {\n this.ensureTable(collection)\n const row = this.getDb()\n .prepare<[string], { data: string }>(`SELECT data FROM \"${collection}\" WHERE id = ?`)\n .get(key)\n if (!row) return undefined\n return JSON.parse(row.data)\n }\n\n async set(collection: SettingsCollection, key: string, value: unknown): Promise<void> {\n this.ensureTable(collection)\n this.getDb()\n .prepare(`INSERT INTO \"${collection}\" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`)\n .run(key, JSON.stringify(value))\n }\n\n async query(collection: SettingsCollection, filter?: QueryFilter): Promise<readonly SettingsRecord[]> {\n this.ensureTable(collection)\n let sql = `SELECT id, data FROM \"${collection}\"`\n const params: unknown[] = []\n\n const whereClauses: string[] = []\n\n if (filter?.where) {\n for (const [field, value] of Object.entries(filter.where)) {\n if (field === 'id') {\n whereClauses.push('id = ?')\n params.push(value)\n } else {\n whereClauses.push(`json_extract(data, '$.${field}') = ?`)\n params.push(typeof value === 'object' ? JSON.stringify(value) : value)\n }\n }\n }\n\n if (filter?.whereIn) {\n for (const [field, values] of Object.entries(filter.whereIn)) {\n const placeholders = values.map(() => '?').join(', ')\n if (field === 'id') {\n whereClauses.push(`id IN (${placeholders})`)\n } else {\n whereClauses.push(`json_extract(data, '$.${field}') IN (${placeholders})`)\n }\n params.push(...values)\n }\n }\n\n if (filter?.whereBetween) {\n for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {\n if (field === 'id') {\n whereClauses.push('id BETWEEN ? AND ?')\n } else {\n whereClauses.push(`json_extract(data, '$.${field}') BETWEEN ? AND ?`)\n }\n params.push(low, high)\n }\n }\n\n if (whereClauses.length > 0) {\n sql += ` WHERE ${whereClauses.join(' AND ')}`\n }\n\n if (filter?.orderBy) {\n const dir = filter.orderBy.direction === 'desc' ? 'DESC' : 'ASC'\n if (filter.orderBy.field === 'id') {\n sql += ` ORDER BY id ${dir}`\n } else {\n sql += ` ORDER BY json_extract(data, '$.${filter.orderBy.field}') ${dir}`\n }\n }\n\n if (filter?.limit) {\n sql += ` LIMIT ?`\n params.push(filter.limit)\n }\n\n if (filter?.offset) {\n sql += ` OFFSET ?`\n params.push(filter.offset)\n }\n\n const rows = this.getDb()\n .prepare<unknown[], { id: string; data: string }>(sql)\n .all(...params)\n\n return rows.map(row => ({\n id: row.id,\n data: JSON.parse(row.data),\n }))\n }\n\n async insert(collection: SettingsCollection, record: SettingsRecord): Promise<void> {\n this.ensureTable(collection)\n const id = record.id || randomUUID()\n this.getDb()\n .prepare(`INSERT INTO \"${collection}\" (id, data) VALUES (?, ?)`)\n .run(id, JSON.stringify(record.data))\n }\n\n async update(collection: SettingsCollection, id: string, data: Record<string, unknown>): Promise<void> {\n this.ensureTable(collection)\n this.getDb()\n .prepare(`UPDATE \"${collection}\" SET data = ? WHERE id = ?`)\n .run(JSON.stringify(data), id)\n }\n\n async delete(collection: SettingsCollection, key: string): Promise<void> {\n this.ensureTable(collection)\n this.getDb()\n .prepare(`DELETE FROM \"${collection}\" WHERE id = ?`)\n .run(key)\n }\n\n async count(collection: SettingsCollection, filter?: QueryFilter): Promise<number> {\n this.ensureTable(collection)\n if (!filter) {\n const row = this.getDb()\n .prepare<[], { cnt: number }>(`SELECT COUNT(*) AS cnt FROM \"${collection}\"`)\n .get()\n return row?.cnt ?? 0\n }\n const rows = await this.query(collection, { ...filter, limit: undefined, offset: undefined })\n return rows.length\n }\n\n async isEmpty(collection: SettingsCollection): Promise<boolean> {\n this.ensureTable(collection)\n const row = this.getDb()\n .prepare<[], { cnt: number }>(`SELECT COUNT(*) AS cnt FROM \"${collection}\"`)\n .get()\n return (row?.cnt ?? 0) === 0\n }\n\n // ---------------------------------------------------------------------------\n // Legacy SettingsStore compatibility (used by ConfigManager.setSettingsStore)\n // ---------------------------------------------------------------------------\n\n /** Get a system setting by dot-notation key */\n getSystem(key: string): unknown {\n this.ensureTable('system-settings')\n const row = this.getDb()\n .prepare<[string], { data: string }>('SELECT data FROM \"system-settings\" WHERE id = ?')\n .get(key)\n if (!row) return undefined\n return JSON.parse(row.data)\n }\n\n /** Set a system setting */\n setSystem(key: string, value: unknown): void {\n this.ensureTable('system-settings')\n this.getDb()\n .prepare('INSERT INTO \"system-settings\" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data')\n .run(key, JSON.stringify(value))\n }\n\n /** Get all system settings as flat key-value */\n getAllSystem(): Record<string, unknown> {\n this.ensureTable('system-settings')\n const rows = this.getDb()\n .prepare<[], { id: string; data: string }>('SELECT id, data FROM \"system-settings\"')\n .all()\n return Object.fromEntries(rows.map(r => [r.id, JSON.parse(r.data)]))\n }\n\n /** Get all settings for an addon */\n getAllAddon(addonId: string): Record<string, unknown> {\n this.ensureTable('addon-settings')\n const rows = this.getDb()\n .prepare<[string], { id: string; data: string }>('SELECT id, data FROM \"addon-settings\" WHERE json_extract(data, \\'$.addonId\\') = ?')\n .all(addonId)\n if (rows.length === 0) return {}\n // addon-settings stores all keys for an addon as separate rows with id = \"addonId.key\"\n const result: Record<string, unknown> = {}\n for (const row of rows) {\n const parsed = JSON.parse(row.data)\n const key = row.id.startsWith(`${addonId}.`) ? row.id.slice(addonId.length + 1) : row.id\n result[key] = parsed.value ?? parsed\n }\n return result\n }\n\n /** Bulk-set all settings for an addon */\n setAllAddon(addonId: string, config: Record<string, unknown>): void {\n this.ensureTable('addon-settings')\n const db = this.getDb()\n const deleteStmt = db.prepare('DELETE FROM \"addon-settings\" WHERE id LIKE ? || \\'%\\'')\n const insertStmt = db.prepare('INSERT INTO \"addon-settings\" (id, data) VALUES (?, ?)')\n db.transaction(() => {\n deleteStmt.run(`${addonId}.`)\n for (const [key, value] of Object.entries(config)) {\n insertStmt.run(`${addonId}.${key}`, JSON.stringify({ addonId, key, value }))\n }\n })()\n }\n\n /** Get all settings for a provider */\n getAllProvider(providerId: string): Record<string, unknown> {\n return this.getAllScoped('provider-settings', providerId)\n }\n\n /** Set a provider setting */\n setProvider(providerId: string, key: string, value: unknown): void {\n this.setScopedKey('provider-settings', providerId, key, value)\n }\n\n /** Get all settings for a device */\n getAllDevice(deviceId: string): Record<string, unknown> {\n return this.getAllScoped('device-settings', deviceId)\n }\n\n /** Set a device setting */\n setDevice(deviceId: string, key: string, value: unknown): void {\n this.setScopedKey('device-settings', deviceId, key, value)\n }\n\n /** Seed system-settings with runtime defaults (first boot) */\n private async seedDefaults(): Promise<void> {\n this.ensureTable('system-settings')\n const insert = this.getDb().prepare(\n 'INSERT OR IGNORE INTO \"system-settings\" (id, data) VALUES (?, ?)',\n )\n this.getDb().transaction(() => {\n for (const [key, value] of Object.entries(this.runtimeDefaults)) {\n insert.run(key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private getDb(): Database.Database {\n if (!this.db) throw new Error('SqliteSettingsBackend not initialized — call initialize() first')\n return this.db\n }\n\n private ensureTable(collection: string): void {\n if (this.ensuredTables.has(collection)) return\n this.getDb().exec(\n `CREATE TABLE IF NOT EXISTS \"${collection}\" (id TEXT PRIMARY KEY, data TEXT NOT NULL)`,\n )\n this.ensuredTables.add(collection)\n }\n\n private getAllScoped(collection: string, scopeId: string): Record<string, unknown> {\n this.ensureTable(collection)\n const rows = this.getDb()\n .prepare<[string], { id: string; data: string }>(`SELECT id, data FROM \"${collection}\" WHERE id LIKE ? || '.%'`)\n .all(scopeId)\n const result: Record<string, unknown> = {}\n for (const row of rows) {\n const key = row.id.slice(scopeId.length + 1)\n const parsed = JSON.parse(row.data)\n result[key] = parsed.value ?? parsed\n }\n return result\n }\n\n private setScopedKey(collection: string, scopeId: string, key: string, value: unknown): void {\n this.ensureTable(collection)\n this.getDb()\n .prepare(`INSERT INTO \"${collection}\" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`)\n .run(`${scopeId}.${key}`, JSON.stringify({ scopeId, key, value }))\n }\n}\n\nexport default SqliteSettingsBackend\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n} from '@camstack/types'\nimport { SqliteSettingsBackend } from './sqlite-settings-backend'\n\n/**\n * SQLite Settings addon — provides persistent settings storage.\n * Capability: 'settings-store' (singleton)\n *\n * Depends on 'storage' capability for resolving the DB file path.\n */\nexport class SqliteSettingsAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'sqlite-settings',\n name: 'SQLite Settings',\n version: '1.0.0',\n capabilities: [{ name: 'settings-store', mode: 'singleton' }],\n }\n\n private backend: SqliteSettingsBackend | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n // Resolve DB path from storage provider (addons-data location)\n const dbPath = context.storageProvider\n ? context.storageProvider.resolve('addons-data', `${context.id.replace('addon:', '')}/camstack.db`)\n : context.dataDir\n ? `${context.dataDir}/camstack.db`\n : 'camstack-data/addons-data/sqlite-settings/camstack.db'\n\n const runtimeDefaults = (context.addonConfig._runtimeDefaults as Record<string, unknown>) ?? {}\n this.backend = new SqliteSettingsBackend(dbPath, runtimeDefaults)\n await this.backend.initialize()\n context.logger.info(`SQLite settings initialized at ${dbPath}`)\n }\n\n async shutdown(): Promise<void> {\n await this.backend?.shutdown()\n }\n\n getBackend(): SqliteSettingsBackend | null {\n return this.backend\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === ('settings-store' as string) && this.backend) {\n return this.backend as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return { sections: [] }\n }\n\n getConfig(): Record<string, unknown> {\n return {}\n }\n\n async onConfigChange(_config: Record<string, unknown>): Promise<void> {\n // DB path changes require restart\n }\n}\n\nexport default SqliteSettingsAddon\n","import Database from 'better-sqlite3'\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { randomUUID } from 'node:crypto'\nimport type {\n IStorageProvider,\n IStorageLocation,\n IStructuredStorage,\n IFileStorage,\n StorageLocationName,\n StorageRecord,\n QueryFilter,\n} from '@camstack/types'\n\ntype LocationType = 'structured' | 'files' | 'both'\n\n/** Which locations need structured (SQL) storage, files storage, or both.\n * All structured locations share a SINGLE SQLite database (camstack.db).\n * File locations use the filesystem at their configured path. */\nconst LOCATION_TYPES: Record<string, LocationType> = {\n // New location names (from StorageLocationManager)\n data: 'structured', // settings, events, trails — SQL only\n media: 'files', // crops, snapshots, thumbnails — files only\n recordings: 'files', // video segments — files only\n models: 'files', // ONNX/TFLite models — files only\n cache: 'files', // temp files — files only\n logs: 'files', // Winston log files — files only\n // Legacy location names (backward compat)\n config: 'structured',\n events: 'structured',\n addon: 'both',\n}\n\nclass SqliteStructuredStorage implements IStructuredStorage {\n private readonly ensuredTables = new Set<string>()\n\n constructor(private readonly db: Database.Database) {}\n\n private ensureTable(collection: string): void {\n if (this.ensuredTables.has(collection)) return\n this.db.exec(\n `CREATE TABLE IF NOT EXISTS \"${collection}\" (id TEXT PRIMARY KEY, data TEXT)`,\n )\n this.ensuredTables.add(collection)\n }\n\n async insert(record: StorageRecord): Promise<StorageRecord> {\n this.ensureTable(record.collection)\n const id = record.id || randomUUID()\n const newRecord: StorageRecord = {\n collection: record.collection,\n id,\n data: record.data,\n }\n this.db\n .prepare(`INSERT INTO \"${record.collection}\" (id, data) VALUES (?, ?)`)\n .run(id, JSON.stringify(newRecord.data))\n return newRecord\n }\n\n async query(collection: string, filter?: QueryFilter): Promise<readonly StorageRecord[]> {\n this.ensureTable(collection)\n const { sql, params } = this.buildSelect(collection, filter)\n const rows = this.db.prepare(sql).all(...params) as Array<{ id: string; data: string }>\n return rows.map((row) => ({\n collection,\n id: row.id,\n data: JSON.parse(row.data) as Record<string, unknown>,\n }))\n }\n\n async update(\n collection: string,\n id: string,\n data: Record<string, unknown>,\n ): Promise<StorageRecord> {\n this.ensureTable(collection)\n this.db\n .prepare(`UPDATE \"${collection}\" SET data = ? WHERE id = ?`)\n .run(JSON.stringify(data), id)\n return { collection, id, data }\n }\n\n async delete(collection: string, id: string): Promise<void> {\n this.ensureTable(collection)\n this.db.prepare(`DELETE FROM \"${collection}\" WHERE id = ?`).run(id)\n }\n\n async count(collection: string, filter?: QueryFilter): Promise<number> {\n this.ensureTable(collection)\n const { sql, params } = this.buildCount(collection, filter)\n const row = this.db.prepare(sql).get(...params) as { cnt: number }\n return row.cnt\n }\n\n private buildWhereClause(filter?: QueryFilter): { clause: string; params: unknown[] } {\n if (!filter) return { clause: '', params: [] }\n\n const conditions: string[] = []\n const params: unknown[] = []\n\n if (filter.where) {\n for (const [field, value] of Object.entries(filter.where)) {\n if (field === 'id') {\n conditions.push('id = ?')\n params.push(value)\n } else {\n conditions.push(`json_extract(data, '$.${field}') = ?`)\n params.push(value)\n }\n }\n }\n\n if (filter.whereIn) {\n for (const [field, values] of Object.entries(filter.whereIn)) {\n const placeholders = values.map(() => '?').join(', ')\n if (field === 'id') {\n conditions.push(`id IN (${placeholders})`)\n } else {\n conditions.push(`json_extract(data, '$.${field}') IN (${placeholders})`)\n }\n params.push(...values)\n }\n }\n\n if (filter.whereBetween) {\n for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {\n if (field === 'id') {\n conditions.push('id BETWEEN ? AND ?')\n } else {\n conditions.push(`json_extract(data, '$.${field}') BETWEEN ? AND ?`)\n }\n params.push(low, high)\n }\n }\n\n const clause = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : ''\n return { clause, params }\n }\n\n private buildSelect(collection: string, filter?: QueryFilter): { sql: string; params: unknown[] } {\n const { clause, params } = this.buildWhereClause(filter)\n let sql = `SELECT id, data FROM \"${collection}\"${clause}`\n\n if (filter?.orderBy) {\n const dir = filter.orderBy.direction === 'desc' ? 'DESC' : 'ASC'\n if (filter.orderBy.field === 'id') {\n sql += ` ORDER BY id ${dir}`\n } else {\n sql += ` ORDER BY json_extract(data, '$.${filter.orderBy.field}') ${dir}`\n }\n }\n\n if (filter?.limit !== undefined) {\n sql += ` LIMIT ?`\n params.push(filter.limit)\n }\n\n if (filter?.offset !== undefined) {\n sql += ` OFFSET ?`\n params.push(filter.offset)\n }\n\n return { sql, params }\n }\n\n private buildCount(collection: string, filter?: QueryFilter): { sql: string; params: unknown[] } {\n const { clause, params } = this.buildWhereClause(filter)\n return { sql: `SELECT COUNT(*) as cnt FROM \"${collection}\"${clause}`, params }\n }\n}\n\nexport class FileSystemStorage implements IFileStorage {\n constructor(private readonly basePath: string) {}\n\n async readFile(filePath: string): Promise<Buffer> {\n const fullPath = path.join(this.basePath, filePath)\n return fs.promises.readFile(fullPath)\n }\n\n async writeFile(filePath: string, data: Buffer): Promise<void> {\n const fullPath = path.join(this.basePath, filePath)\n fs.mkdirSync(path.dirname(fullPath), { recursive: true })\n await fs.promises.writeFile(fullPath, data)\n }\n\n async deleteFile(filePath: string): Promise<void> {\n const fullPath = path.join(this.basePath, filePath)\n await fs.promises.unlink(fullPath)\n }\n\n async listFiles(prefix?: string): Promise<readonly string[]> {\n const searchDir = prefix ? path.join(this.basePath, prefix) : this.basePath\n try {\n const entries = await fs.promises.readdir(searchDir, { recursive: true })\n const files: string[] = []\n for (const entry of entries) {\n const entryStr = String(entry)\n const relative = prefix ? path.join(prefix, entryStr) : entryStr\n const fullPath = path.join(this.basePath, relative)\n try {\n const stat = await fs.promises.stat(fullPath)\n if (stat.isFile()) {\n files.push(relative)\n }\n } catch {\n // Skip entries that can't be stat'd\n }\n }\n return files\n } catch {\n return []\n }\n }\n\n async getFileUrl(_path: string): Promise<string> {\n return null as unknown as string\n }\n\n async exists(filePath: string): Promise<boolean> {\n const fullPath = path.join(this.basePath, filePath)\n try {\n await fs.promises.access(fullPath, fs.constants.F_OK)\n return true\n } catch {\n return false\n }\n }\n}\n\n/** @deprecated Use FilesystemStorageProvider + SqliteSettingsBackend instead */\nexport class SqliteStorageProvider {\n private mainDb: Database.Database | null = null\n private sharedStructured: SqliteStructuredStorage | null = null\n private readonly locations = new Map<StorageLocationName, IStorageLocation>()\n\n async initialize(): Promise<void> {\n // Called by interface contract; actual setup done via configure()\n }\n\n /**\n * Configure all storage locations.\n * ONE single SQLite database (camstack.db) is used for ALL structured storage.\n * File-based locations use the filesystem at their configured path.\n */\n async configure(config: { locations: Record<string, string> }): Promise<void> {\n // Find the 'data' location path for the single DB (fall back to first structured location)\n const dataPath = config.locations['data'] ?? config.locations['config'] ?? Object.values(config.locations)[0]\n if (!dataPath) throw new Error('No data path configured for SQLite storage')\n\n // Create single database\n fs.mkdirSync(dataPath, { recursive: true })\n const dbPath = path.join(dataPath, 'camstack.db')\n this.mainDb = new Database(dbPath)\n this.mainDb.pragma('journal_mode = WAL')\n this.sharedStructured = new SqliteStructuredStorage(this.mainDb)\n\n // Configure each location\n for (const [name, dirPath] of Object.entries(config.locations)) {\n const locationName = name as StorageLocationName\n const locationType = LOCATION_TYPES[name] ?? 'files'\n\n fs.mkdirSync(dirPath, { recursive: true })\n\n const location: IStorageLocation = {}\n\n // All structured storage shares the single DB\n if (locationType === 'structured' || locationType === 'both') {\n location.structured = this.sharedStructured\n }\n\n if (locationType === 'files' || locationType === 'both') {\n location.files = new FileSystemStorage(dirPath)\n }\n\n this.locations.set(locationName, location)\n }\n }\n\n getLocation(name: StorageLocationName): IStorageLocation {\n const location = this.locations.get(name)\n if (!location) {\n throw new Error(`Storage location \"${name}\" not found`)\n }\n return location\n }\n\n async shutdown(): Promise<void> {\n if (this.mainDb) {\n this.mainDb.close()\n this.mainDb = null\n this.sharedStructured = null\n }\n this.locations.clear()\n }\n\n async export(_locationName: StorageLocationName): Promise<Buffer> {\n throw new Error('Export not yet implemented')\n }\n\n async import(_locationName: StorageLocationName, _data: Buffer): Promise<void> {\n throw new Error('Import not yet implemented')\n }\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n} from '@camstack/types'\nimport { SqliteStorageProvider } from './sqlite-storage.provider'\n\nexport class SqliteStorageAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'sqlite-storage',\n name: 'SQLite Storage',\n version: '1.0.0',\n capabilities: ['storage'],\n }\n\n private provider: SqliteStorageProvider | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n const storageConfig = {\n locations: { ...context.locationPaths } as Record<string, string>,\n }\n this.provider = new SqliteStorageProvider()\n await this.provider.configure(storageConfig)\n context.logger.info('SQLite storage initialized')\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.shutdown()\n }\n\n getProvider(): SqliteStorageProvider {\n if (!this.provider) throw new Error('SQLite storage not initialized')\n return this.provider\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'storage' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return {}\n }\n\n async onConfigChange(_config: Record<string, unknown>): Promise<void> {\n // No configurable fields\n }\n}\n","import Database from 'better-sqlite3'\nimport { CORE_TABLE_DDL } from './sql-schema.js'\nimport { RUNTIME_DEFAULTS } from '@camstack/kernel'\n\n/**\n * Thin wrapper over better-sqlite3 that manages the four settings tables:\n * system_settings, addon_settings, provider_settings, device_settings.\n *\n * All values are stored as JSON text and deserialized on read.\n */\nexport class SettingsStore {\n private readonly db: Database.Database\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('foreign_keys = ON')\n this.initTables()\n }\n\n // ---------------------------------------------------------------------------\n // System settings\n // ---------------------------------------------------------------------------\n\n getSystem(key: string): unknown {\n const row = this.db\n .prepare<[string], { value: string }>('SELECT value FROM system_settings WHERE key = ?')\n .get(key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setSystem(key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(key, JSON.stringify(value))\n }\n\n getAllSystem(): Record<string, unknown> {\n const rows = this.db\n .prepare<[], { key: string; value: string }>('SELECT key, value FROM system_settings')\n .all()\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Addon settings\n // ---------------------------------------------------------------------------\n\n getAddon(addonId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM addon_settings WHERE addon_id = ? AND key = ?',\n )\n .get(addonId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setAddon(addonId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(addon_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(addonId, key, JSON.stringify(value))\n }\n\n getAllAddon(addonId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM addon_settings WHERE addon_id = ?',\n )\n .all(addonId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n /** Bulk-replace all keys for an addon (within a transaction). */\n setAllAddon(addonId: string, config: Record<string, unknown>): void {\n const deleteStmt = this.db.prepare('DELETE FROM addon_settings WHERE addon_id = ?')\n const insertStmt = this.db.prepare(\n `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n deleteStmt.run(addonId)\n for (const [key, value] of Object.entries(config)) {\n insertStmt.run(addonId, key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Provider settings\n // ---------------------------------------------------------------------------\n\n getProvider(providerId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM provider_settings WHERE provider_id = ? AND key = ?',\n )\n .get(providerId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setProvider(providerId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO provider_settings (provider_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(provider_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(providerId, key, JSON.stringify(value))\n }\n\n getAllProvider(providerId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM provider_settings WHERE provider_id = ?',\n )\n .all(providerId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Device settings\n // ---------------------------------------------------------------------------\n\n getDevice(deviceId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM device_settings WHERE device_id = ? AND key = ?',\n )\n .get(deviceId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setDevice(deviceId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO device_settings (device_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(device_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(deviceId, key, JSON.stringify(value))\n }\n\n getAllDevice(deviceId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM device_settings WHERE device_id = ?',\n )\n .all(deviceId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /** Close the SQLite connection (call on shutdown). */\n close(): void {\n this.db.close()\n }\n\n /** Check if system_settings is empty (used for first-boot seeding). */\n isSystemSettingsEmpty(): boolean {\n const row = this.db\n .prepare<[], { cnt: number }>('SELECT COUNT(*) AS cnt FROM system_settings')\n .get()\n return (row?.cnt ?? 0) === 0\n }\n\n /** Seed system_settings with RUNTIME_DEFAULTS (only on first boot). */\n seedDefaults(): void {\n const insert = this.db.prepare(\n `INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) {\n insert.run(key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private initTables(): void {\n this.db.transaction(() => {\n for (const stmt of CORE_TABLE_DDL) {\n this.db.prepare(stmt).run()\n }\n })()\n }\n}\n","/** Core table DDL statements -- executed on first boot */\nexport const CORE_TABLE_DDL: readonly string[] = [\n // Settings tables\n `CREATE TABLE IF NOT EXISTS system_settings (\n key TEXT PRIMARY KEY,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\n )`,\n `CREATE TABLE IF NOT EXISTS addon_settings (\n addon_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (addon_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS provider_settings (\n provider_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (provider_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS device_settings (\n device_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (device_id, key)\n )`,\n\n // Detection events\n `CREATE TABLE IF NOT EXISTS detection_events (\n id TEXT PRIMARY KEY,\n timestamp INTEGER NOT NULL,\n device_id TEXT NOT NULL,\n class_name TEXT NOT NULL,\n score REAL NOT NULL,\n severity TEXT NOT NULL,\n track_id TEXT,\n zones JSON,\n recognition JSON,\n media_files JSON,\n data JSON\n )`,\n `CREATE INDEX IF NOT EXISTS idx_det_device_ts ON detection_events(device_id, timestamp)`,\n `CREATE INDEX IF NOT EXISTS idx_det_class_ts ON detection_events(class_name, timestamp)`,\n\n // Audio levels\n `CREATE TABLE IF NOT EXISTS audio_levels (\n id TEXT PRIMARY KEY,\n timestamp INTEGER NOT NULL,\n device_id TEXT NOT NULL,\n dbfs REAL NOT NULL,\n rms REAL NOT NULL,\n state TEXT NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS idx_audio_device_ts ON audio_levels(device_id, timestamp)`,\n\n // Track trails\n `CREATE TABLE IF NOT EXISTS track_trails (\n track_id TEXT PRIMARY KEY,\n device_id TEXT NOT NULL,\n class_name TEXT NOT NULL,\n first_seen INTEGER NOT NULL,\n last_seen INTEGER NOT NULL,\n positions JSON NOT NULL,\n snapshots JSON,\n total_distance REAL,\n zones_visited JSON\n )`,\n `CREATE INDEX IF NOT EXISTS idx_trails_device_ts ON track_trails(device_id, first_seen)`,\n]\n\n/** Addon table schema declaration */\nexport interface AddonTableSchema {\n readonly name: string\n readonly columns: ReadonlyArray<{\n readonly name: string\n readonly type: 'TEXT' | 'INTEGER' | 'REAL' | 'JSON'\n readonly primaryKey?: boolean\n readonly notNull?: boolean\n }>\n readonly indexes?: ReadonlyArray<{\n readonly name: string\n readonly columns: readonly string[]\n readonly unique?: boolean\n }>\n}\n\n/** Generate CREATE TABLE DDL from addon schema */\nexport function addonTableToDdl(schema: AddonTableSchema): string[] {\n const pks = schema.columns.filter(c => c.primaryKey).map(c => c.name)\n const colDefs = schema.columns.map(c => {\n const parts = [c.name, c.type]\n if (c.notNull) parts.push('NOT NULL')\n return parts.join(' ')\n })\n\n let ddl = `CREATE TABLE IF NOT EXISTS ${schema.name} (\\n ${colDefs.join(',\\n ')}`\n if (pks.length > 0) {\n ddl += `,\\n PRIMARY KEY (${pks.join(', ')})`\n }\n ddl += '\\n)'\n\n const stmts = [ddl]\n for (const idx of schema.indexes ?? []) {\n const unique = idx.unique ? 'UNIQUE ' : ''\n stmts.push(`CREATE ${unique}INDEX IF NOT EXISTS ${idx.name} ON ${schema.name}(${idx.columns.join(', ')})`)\n }\n return stmts\n}\n"],"mappings":"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","names":["resolve","fs","Database","import_better_sqlite3","fs","path","import_node_crypto","Database","import_better_sqlite3","Database"]}
@@ -4,36 +4,16 @@ import {
4
4
  SettingsStore,
5
5
  SqliteStorageAddon,
6
6
  SqliteStorageProvider,
7
- addonTableToDdl,
8
- init_settings_store,
9
- init_sql_schema,
10
- init_sqlite_storage_addon,
11
- init_sqlite_storage_provider
12
- } from "../../chunk-EFQ25JFE.mjs";
7
+ addonTableToDdl
8
+ } from "../../chunk-R3DIIBBX.mjs";
13
9
  import {
14
10
  FilesystemStorageAddon,
15
- FilesystemStorageProvider,
16
- init_filesystem_storage_addon,
17
- init_filesystem_storage_provider
18
- } from "../../chunk-4YD6WMO6.mjs";
11
+ FilesystemStorageProvider
12
+ } from "../../chunk-SPA4JBKN.mjs";
19
13
  import {
20
14
  SqliteSettingsAddon,
21
- SqliteSettingsBackend,
22
- init_sqlite_settings_addon,
23
- init_sqlite_settings_backend
24
- } from "../../chunk-CHFIH4G6.mjs";
25
- import "../../chunk-4JEXNFZZ.mjs";
26
-
27
- // src/builtins/sqlite-storage/index.ts
28
- init_filesystem_storage_provider();
29
- init_filesystem_storage_addon();
30
- init_sqlite_settings_backend();
31
- init_sqlite_settings_addon();
32
- init_sqlite_storage_addon();
33
- init_sqlite_storage_provider();
34
- init_settings_store();
35
- init_sql_schema();
36
- init_filesystem_storage_addon();
15
+ SqliteSettingsBackend
16
+ } from "../../chunk-YXNXYYHL.mjs";
37
17
  export {
38
18
  CORE_TABLE_DDL,
39
19
  FileSystemStorage,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/builtins/sqlite-storage/index.ts"],"sourcesContent":["// New providers\nexport { FilesystemStorageProvider } from './filesystem-storage-provider'\nexport { FilesystemStorageAddon } from './filesystem-storage.addon'\nexport { SqliteSettingsBackend } from './sqlite-settings-backend'\nexport { SqliteSettingsAddon } from './sqlite-settings.addon'\n\n// Legacy (still used by backend during migration)\nexport { SqliteStorageAddon } from './sqlite-storage.addon'\nexport { SqliteStorageProvider, FileSystemStorage } from './sqlite-storage.provider'\nexport { SettingsStore } from './settings-store'\nexport { CORE_TABLE_DDL, addonTableToDdl } from './sql-schema'\nexport type { AddonTableSchema } from './sql-schema'\n\n// Default export for AddonLoader\nexport { FilesystemStorageAddon as default } from './filesystem-storage.addon'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AAIA;","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,2 @@
1
+ import '@camstack/types';
2
+ export { S as SqliteSettingsAddon, S as default } from '../../sqlite-settings.addon-DigoKwpZ.mjs';
@@ -0,0 +1,2 @@
1
+ import '@camstack/types';
2
+ export { S as SqliteSettingsAddon, S as default } from '../../sqlite-settings.addon-DigoKwpZ.js';
@@ -1,10 +1,7 @@
1
1
  import {
2
2
  SqliteSettingsAddon,
3
- init_sqlite_settings_addon,
4
3
  sqlite_settings_addon_default
5
- } from "../../chunk-CHFIH4G6.mjs";
6
- import "../../chunk-4JEXNFZZ.mjs";
7
- init_sqlite_settings_addon();
4
+ } from "../../chunk-YXNXYYHL.mjs";
8
5
  export {
9
6
  SqliteSettingsAddon,
10
7
  sqlite_settings_addon_default as default
@@ -0,0 +1,30 @@
1
+ import { ILogDestination, LogEntry, LogFilter, ICamstackAddon, IConfigurable, AddonManifest, AddonContext, CapabilityProviderMap, ConfigUISchema } from '@camstack/types';
2
+
3
+ interface WinstonConfig {
4
+ readonly level: string;
5
+ readonly retentionDays: number;
6
+ /** Resolved absolute path to the logs directory (replaces the old dataPath field). */
7
+ readonly logsDir: string;
8
+ }
9
+ declare class WinstonDestination implements ILogDestination {
10
+ private logger;
11
+ initialize(config?: WinstonConfig): Promise<void>;
12
+ write(entry: LogEntry): void;
13
+ query(_filter: LogFilter): Promise<readonly LogEntry[]>;
14
+ shutdown(): Promise<void>;
15
+ }
16
+
17
+ declare class WinstonLoggingAddon implements ICamstackAddon, IConfigurable {
18
+ readonly manifest: AddonManifest;
19
+ private destination;
20
+ private currentConfig;
21
+ initialize(context: AddonContext): Promise<void>;
22
+ shutdown(): Promise<void>;
23
+ getDestination(): WinstonDestination;
24
+ getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
25
+ getConfigSchema(): ConfigUISchema;
26
+ getConfig(): Record<string, unknown>;
27
+ onConfigChange(config: Record<string, unknown>): Promise<void>;
28
+ }
29
+
30
+ export { WinstonDestination, WinstonLoggingAddon, WinstonLoggingAddon as default };
@@ -0,0 +1,30 @@
1
+ import { ILogDestination, LogEntry, LogFilter, ICamstackAddon, IConfigurable, AddonManifest, AddonContext, CapabilityProviderMap, ConfigUISchema } from '@camstack/types';
2
+
3
+ interface WinstonConfig {
4
+ readonly level: string;
5
+ readonly retentionDays: number;
6
+ /** Resolved absolute path to the logs directory (replaces the old dataPath field). */
7
+ readonly logsDir: string;
8
+ }
9
+ declare class WinstonDestination implements ILogDestination {
10
+ private logger;
11
+ initialize(config?: WinstonConfig): Promise<void>;
12
+ write(entry: LogEntry): void;
13
+ query(_filter: LogFilter): Promise<readonly LogEntry[]>;
14
+ shutdown(): Promise<void>;
15
+ }
16
+
17
+ declare class WinstonLoggingAddon implements ICamstackAddon, IConfigurable {
18
+ readonly manifest: AddonManifest;
19
+ private destination;
20
+ private currentConfig;
21
+ initialize(context: AddonContext): Promise<void>;
22
+ shutdown(): Promise<void>;
23
+ getDestination(): WinstonDestination;
24
+ getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
25
+ getConfigSchema(): ConfigUISchema;
26
+ getConfig(): Record<string, unknown>;
27
+ onConfigChange(config: Record<string, unknown>): Promise<void>;
28
+ }
29
+
30
+ export { WinstonDestination, WinstonLoggingAddon, WinstonLoggingAddon as default };
@@ -1,15 +1,7 @@
1
1
  import {
2
2
  WinstonDestination,
3
- WinstonLoggingAddon,
4
- init_winston_destination,
5
- init_winston_logging_addon
6
- } from "../../chunk-XSLBW5C2.mjs";
7
- import "../../chunk-4JEXNFZZ.mjs";
8
-
9
- // src/builtins/winston-logging/index.ts
10
- init_winston_logging_addon();
11
- init_winston_destination();
12
- init_winston_logging_addon();
3
+ WinstonLoggingAddon
4
+ } from "../../chunk-LQFPAEQF.mjs";
13
5
  export {
14
6
  WinstonDestination,
15
7
  WinstonLoggingAddon,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/builtins/winston-logging/index.ts"],"sourcesContent":["export { WinstonLoggingAddon } from './winston-logging.addon'\nexport { WinstonDestination } from './winston-destination'\nexport { WinstonLoggingAddon as default } from './winston-logging.addon'\n"],"mappings":";;;;;;;;;AAAA;AACA;AACA;","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,89 @@
1
+ // src/storage/storage-location-manager.ts
2
+ import { join as join2, isAbsolute as isAbsolute2 } from "path";
3
+
4
+ // src/storage/fs-storage-backend.ts
5
+ import { existsSync, mkdirSync, accessSync, constants } from "fs";
6
+ import { join, resolve, isAbsolute } from "path";
7
+ var FsStorageBackend = class {
8
+ type = "local";
9
+ basePath;
10
+ constructor(basePath) {
11
+ this.basePath = resolve(basePath);
12
+ }
13
+ resolve(subpath) {
14
+ if (isAbsolute(subpath)) return subpath;
15
+ return join(this.basePath, subpath);
16
+ }
17
+ isAvailable() {
18
+ try {
19
+ if (!existsSync(this.basePath)) return false;
20
+ accessSync(this.basePath, constants.W_OK);
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+ async initialize() {
27
+ mkdirSync(this.basePath, { recursive: true });
28
+ }
29
+ };
30
+
31
+ // src/storage/storage-location-manager.ts
32
+ var StorageLocationManager = class {
33
+ backends = /* @__PURE__ */ new Map();
34
+ dataPath;
35
+ constructor(dataPath) {
36
+ this.dataPath = dataPath;
37
+ }
38
+ /** Initialize all locations with default paths */
39
+ async initializeDefaults() {
40
+ const defaults = {
41
+ data: join2(this.dataPath, "db"),
42
+ media: join2(this.dataPath, "media"),
43
+ recordings: join2(this.dataPath, "recordings"),
44
+ models: join2(this.dataPath, "models"),
45
+ cache: "/tmp/camstack-cache",
46
+ logs: join2(this.dataPath, "logs")
47
+ };
48
+ for (const [name, path] of Object.entries(defaults)) {
49
+ const backend = new FsStorageBackend(path);
50
+ await backend.initialize();
51
+ this.backends.set(name, backend);
52
+ }
53
+ }
54
+ /** Override a specific location's backend path */
55
+ async setLocationPath(name, basePath) {
56
+ const resolved = isAbsolute2(basePath) ? basePath : join2(this.dataPath, basePath);
57
+ const backend = new FsStorageBackend(resolved);
58
+ await backend.initialize();
59
+ this.backends.set(name, backend);
60
+ }
61
+ /** Get the backend for a location */
62
+ getBackend(name) {
63
+ const backend = this.backends.get(name);
64
+ if (!backend) throw new Error(`Storage location "${name}" not initialized`);
65
+ return backend;
66
+ }
67
+ /** Resolve a path within a location */
68
+ resolve(location, subpath) {
69
+ return this.getBackend(location).resolve(subpath);
70
+ }
71
+ /** Check if all locations are available */
72
+ getStatus() {
73
+ return Array.from(this.backends.entries()).map(([name, backend]) => ({
74
+ name,
75
+ available: backend.isAvailable(),
76
+ path: backend.basePath
77
+ }));
78
+ }
79
+ /** All location names */
80
+ getLocationNames() {
81
+ return Array.from(this.backends.keys());
82
+ }
83
+ };
84
+
85
+ export {
86
+ FsStorageBackend,
87
+ StorageLocationManager
88
+ };
89
+ //# sourceMappingURL=chunk-2F3XZYRW.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage/storage-location-manager.ts","../src/storage/fs-storage-backend.ts"],"sourcesContent":["import { join, isAbsolute } from 'node:path'\nimport { FsStorageBackend, type IStorageBackend } from './fs-storage-backend.js'\n\nexport type StorageLocationName = 'data' | 'media' | 'recordings' | 'models' | 'cache' | 'logs'\n\nexport class StorageLocationManager {\n private readonly backends: Map<StorageLocationName, IStorageBackend> = new Map()\n private readonly dataPath: string\n\n constructor(dataPath: string) {\n this.dataPath = dataPath\n }\n\n /** Initialize all locations with default paths */\n async initializeDefaults(): Promise<void> {\n const defaults: Record<StorageLocationName, string> = {\n data: join(this.dataPath, 'db'),\n media: join(this.dataPath, 'media'),\n recordings: join(this.dataPath, 'recordings'),\n models: join(this.dataPath, 'models'),\n cache: '/tmp/camstack-cache',\n logs: join(this.dataPath, 'logs'),\n }\n\n for (const [name, path] of Object.entries(defaults)) {\n const backend = new FsStorageBackend(path)\n await backend.initialize()\n this.backends.set(name as StorageLocationName, backend)\n }\n }\n\n /** Override a specific location's backend path */\n async setLocationPath(name: StorageLocationName, basePath: string): Promise<void> {\n const resolved = isAbsolute(basePath) ? basePath : join(this.dataPath, basePath)\n const backend = new FsStorageBackend(resolved)\n await backend.initialize()\n this.backends.set(name, backend)\n }\n\n /** Get the backend for a location */\n getBackend(name: StorageLocationName): IStorageBackend {\n const backend = this.backends.get(name)\n if (!backend) throw new Error(`Storage location \"${name}\" not initialized`)\n return backend\n }\n\n /** Resolve a path within a location */\n resolve(location: StorageLocationName, subpath: string): string {\n return this.getBackend(location).resolve(subpath)\n }\n\n /** Check if all locations are available */\n getStatus(): Array<{ name: StorageLocationName; available: boolean; path: string }> {\n return Array.from(this.backends.entries()).map(([name, backend]) => ({\n name,\n available: backend.isAvailable(),\n path: backend.basePath,\n }))\n }\n\n /** All location names */\n getLocationNames(): StorageLocationName[] {\n return Array.from(this.backends.keys())\n }\n}\n","import { existsSync, mkdirSync, accessSync, constants } from 'node:fs'\nimport { join, resolve, isAbsolute } from 'node:path'\n\nexport interface IStorageBackend {\n /** Backend type identifier */\n readonly type: string\n /** Base path of this backend */\n readonly basePath: string\n /** Resolve a subpath to an absolute path */\n resolve(subpath: string): string\n /** Check if the backend path exists and is writable */\n isAvailable(): boolean\n /** Ensure base directory exists (mkdir -p equivalent) */\n initialize(): Promise<void>\n}\n\nexport class FsStorageBackend implements IStorageBackend {\n readonly type = 'local'\n readonly basePath: string\n\n constructor(basePath: string) {\n this.basePath = resolve(basePath)\n }\n\n resolve(subpath: string): string {\n if (isAbsolute(subpath)) return subpath\n return join(this.basePath, subpath)\n }\n\n isAvailable(): boolean {\n try {\n if (!existsSync(this.basePath)) return false\n accessSync(this.basePath, constants.W_OK)\n return true\n } catch {\n return false\n }\n }\n\n async initialize(): Promise<void> {\n mkdirSync(this.basePath, { recursive: true })\n }\n}\n"],"mappings":";AAAA,SAAS,QAAAA,OAAM,cAAAC,mBAAkB;;;ACAjC,SAAS,YAAY,WAAW,YAAY,iBAAiB;AAC7D,SAAS,MAAM,SAAS,kBAAkB;AAenC,IAAM,mBAAN,MAAkD;AAAA,EAC9C,OAAO;AAAA,EACP;AAAA,EAET,YAAY,UAAkB;AAC5B,SAAK,WAAW,QAAQ,QAAQ;AAAA,EAClC;AAAA,EAEA,QAAQ,SAAyB;AAC/B,QAAI,WAAW,OAAO,EAAG,QAAO;AAChC,WAAO,KAAK,KAAK,UAAU,OAAO;AAAA,EACpC;AAAA,EAEA,cAAuB;AACrB,QAAI;AACF,UAAI,CAAC,WAAW,KAAK,QAAQ,EAAG,QAAO;AACvC,iBAAW,KAAK,UAAU,UAAU,IAAI;AACxC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,aAA4B;AAChC,cAAU,KAAK,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,EAC9C;AACF;;;ADrCO,IAAM,yBAAN,MAA6B;AAAA,EACjB,WAAsD,oBAAI,IAAI;AAAA,EAC9D;AAAA,EAEjB,YAAY,UAAkB;AAC5B,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,MAAM,qBAAoC;AACxC,UAAM,WAAgD;AAAA,MACpD,MAAMC,MAAK,KAAK,UAAU,IAAI;AAAA,MAC9B,OAAOA,MAAK,KAAK,UAAU,OAAO;AAAA,MAClC,YAAYA,MAAK,KAAK,UAAU,YAAY;AAAA,MAC5C,QAAQA,MAAK,KAAK,UAAU,QAAQ;AAAA,MACpC,OAAO;AAAA,MACP,MAAMA,MAAK,KAAK,UAAU,MAAM;AAAA,IAClC;AAEA,eAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACnD,YAAM,UAAU,IAAI,iBAAiB,IAAI;AACzC,YAAM,QAAQ,WAAW;AACzB,WAAK,SAAS,IAAI,MAA6B,OAAO;AAAA,IACxD;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,gBAAgB,MAA2B,UAAiC;AAChF,UAAM,WAAWC,YAAW,QAAQ,IAAI,WAAWD,MAAK,KAAK,UAAU,QAAQ;AAC/E,UAAM,UAAU,IAAI,iBAAiB,QAAQ;AAC7C,UAAM,QAAQ,WAAW;AACzB,SAAK,SAAS,IAAI,MAAM,OAAO;AAAA,EACjC;AAAA;AAAA,EAGA,WAAW,MAA4C;AACrD,UAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,qBAAqB,IAAI,mBAAmB;AAC1E,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,QAAQ,UAA+B,SAAyB;AAC9D,WAAO,KAAK,WAAW,QAAQ,EAAE,QAAQ,OAAO;AAAA,EAClD;AAAA;AAAA,EAGA,YAAoF;AAClF,WAAO,MAAM,KAAK,KAAK,SAAS,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,OAAO,OAAO;AAAA,MACnE;AAAA,MACA,WAAW,QAAQ,YAAY;AAAA,MAC/B,MAAM,QAAQ;AAAA,IAChB,EAAE;AAAA,EACJ;AAAA;AAAA,EAGA,mBAA0C;AACxC,WAAO,MAAM,KAAK,KAAK,SAAS,KAAK,CAAC;AAAA,EACxC;AACF;","names":["join","isAbsolute","join","isAbsolute"]}