@camstack/core 0.1.14 → 0.1.15

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 (161) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +220 -0
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js.map +1 -0
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +9 -0
  4. package/dist/builtins/addon-pages-aggregator/index.js +222 -0
  5. package/dist/builtins/addon-pages-aggregator/index.js.map +1 -0
  6. package/dist/builtins/addon-pages-aggregator/index.mjs +9 -0
  7. package/dist/builtins/addon-pages-aggregator/index.mjs.map +1 -0
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +200 -0
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js.map +1 -0
  10. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +9 -0
  11. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs.map +1 -0
  12. package/dist/builtins/addon-widgets-aggregator/index.js +202 -0
  13. package/dist/builtins/addon-widgets-aggregator/index.js.map +1 -0
  14. package/dist/builtins/addon-widgets-aggregator/index.mjs +9 -0
  15. package/dist/builtins/addon-widgets-aggregator/index.mjs.map +1 -0
  16. package/dist/builtins/alerts/alerts.addon.js +443 -0
  17. package/dist/builtins/alerts/alerts.addon.js.map +1 -0
  18. package/dist/builtins/alerts/alerts.addon.mjs +9 -0
  19. package/dist/builtins/alerts/alerts.addon.mjs.map +1 -0
  20. package/dist/builtins/alerts/index.js +443 -0
  21. package/dist/builtins/alerts/index.js.map +1 -0
  22. package/dist/builtins/alerts/index.mjs +8 -0
  23. package/dist/builtins/alerts/index.mjs.map +1 -0
  24. package/dist/builtins/console-logging/index.js +242 -0
  25. package/dist/builtins/console-logging/index.js.map +1 -0
  26. package/dist/builtins/console-logging/index.mjs +11 -0
  27. package/dist/builtins/console-logging/index.mjs.map +1 -0
  28. package/dist/builtins/device-manager/device-manager.addon.js +2155 -0
  29. package/dist/builtins/device-manager/device-manager.addon.js.map +1 -0
  30. package/dist/builtins/device-manager/device-manager.addon.mjs +9 -0
  31. package/dist/builtins/device-manager/device-manager.addon.mjs.map +1 -0
  32. package/dist/builtins/device-manager/index.js +2157 -0
  33. package/dist/builtins/device-manager/index.js.map +1 -0
  34. package/dist/builtins/device-manager/index.mjs +10 -0
  35. package/dist/builtins/device-manager/index.mjs.map +1 -0
  36. package/dist/builtins/hub-forwarder/index.js +297 -0
  37. package/dist/builtins/hub-forwarder/index.js.map +1 -0
  38. package/dist/builtins/hub-forwarder/index.mjs +11 -0
  39. package/dist/builtins/hub-forwarder/index.mjs.map +1 -0
  40. package/dist/builtins/local-auth/index.js +623 -0
  41. package/dist/builtins/local-auth/index.js.map +1 -0
  42. package/dist/builtins/local-auth/index.mjs +8 -0
  43. package/dist/builtins/local-auth/index.mjs.map +1 -0
  44. package/dist/builtins/local-auth/local-auth.addon.js +623 -0
  45. package/dist/builtins/local-auth/local-auth.addon.js.map +1 -0
  46. package/dist/builtins/local-auth/local-auth.addon.mjs +9 -0
  47. package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -0
  48. package/dist/builtins/local-backup/index.js +53 -68
  49. package/dist/builtins/local-backup/index.js.map +1 -1
  50. package/dist/builtins/local-backup/index.mjs +1 -1
  51. package/dist/builtins/native-metrics/native-metrics.addon.js +898 -0
  52. package/dist/builtins/native-metrics/native-metrics.addon.js.map +1 -0
  53. package/dist/builtins/native-metrics/native-metrics.addon.mjs +7 -0
  54. package/dist/builtins/native-metrics/native-metrics.addon.mjs.map +1 -0
  55. package/dist/builtins/snapshot/index.js +504 -0
  56. package/dist/builtins/snapshot/index.js.map +1 -0
  57. package/dist/builtins/snapshot/index.mjs +477 -0
  58. package/dist/builtins/snapshot/index.mjs.map +1 -0
  59. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +16 -166
  60. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js.map +1 -1
  61. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  62. package/dist/builtins/sqlite-storage/index.js +554 -621
  63. package/dist/builtins/sqlite-storage/index.js.map +1 -1
  64. package/dist/builtins/sqlite-storage/index.mjs +9 -11
  65. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +368 -130
  66. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js.map +1 -1
  67. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  68. package/dist/builtins/system-config/index.js +189 -0
  69. package/dist/builtins/system-config/index.js.map +1 -0
  70. package/dist/builtins/system-config/index.mjs +10 -0
  71. package/dist/builtins/system-config/index.mjs.map +1 -0
  72. package/dist/builtins/system-config/system-config.addon.js +187 -0
  73. package/dist/builtins/system-config/system-config.addon.js.map +1 -0
  74. package/dist/builtins/system-config/system-config.addon.mjs +9 -0
  75. package/dist/builtins/system-config/system-config.addon.mjs.map +1 -0
  76. package/dist/builtins/winston-logging/index.js +185 -65
  77. package/dist/builtins/winston-logging/index.js.map +1 -1
  78. package/dist/builtins/winston-logging/index.mjs +2 -1
  79. package/dist/chunk-2CIYKDRN.mjs +1 -0
  80. package/dist/chunk-2CIYKDRN.mjs.map +1 -0
  81. package/dist/chunk-2F76X6NL.mjs +136 -0
  82. package/dist/chunk-2F76X6NL.mjs.map +1 -0
  83. package/dist/chunk-2QUFBZ7M.mjs +1 -0
  84. package/dist/chunk-2QUFBZ7M.mjs.map +1 -0
  85. package/dist/chunk-3BK2Y7GY.mjs +593 -0
  86. package/dist/chunk-3BK2Y7GY.mjs.map +1 -0
  87. package/dist/chunk-4OOHFJHT.mjs +421 -0
  88. package/dist/chunk-4OOHFJHT.mjs.map +1 -0
  89. package/dist/chunk-4XHB7IHT.mjs +809 -0
  90. package/dist/chunk-4XHB7IHT.mjs.map +1 -0
  91. package/dist/{chunk-2F3XZYRW.mjs → chunk-6M2HSSTQ.mjs} +16 -7
  92. package/dist/chunk-6M2HSSTQ.mjs.map +1 -0
  93. package/dist/{chunk-SO4LROOT.mjs → chunk-7FI7SQS7.mjs} +54 -69
  94. package/dist/chunk-7FI7SQS7.mjs.map +1 -0
  95. package/dist/chunk-ED57RCQE.mjs +171 -0
  96. package/dist/chunk-ED57RCQE.mjs.map +1 -0
  97. package/dist/chunk-FZN56HGQ.mjs +626 -0
  98. package/dist/chunk-FZN56HGQ.mjs.map +1 -0
  99. package/dist/chunk-GL4OOB25.mjs +51 -0
  100. package/dist/chunk-GL4OOB25.mjs.map +1 -0
  101. package/dist/chunk-KDG2NTDB.mjs +137 -0
  102. package/dist/chunk-KDG2NTDB.mjs.map +1 -0
  103. package/dist/chunk-NRBQWBDM.mjs +191 -0
  104. package/dist/chunk-NRBQWBDM.mjs.map +1 -0
  105. package/dist/chunk-O4V246GG.mjs +2137 -0
  106. package/dist/chunk-O4V246GG.mjs.map +1 -0
  107. package/dist/chunk-QT57H266.mjs +163 -0
  108. package/dist/chunk-QT57H266.mjs.map +1 -0
  109. package/dist/chunk-QX4RH25I.mjs +141 -0
  110. package/dist/chunk-QX4RH25I.mjs.map +1 -0
  111. package/dist/chunk-TB562PZX.mjs +86 -0
  112. package/dist/chunk-TB562PZX.mjs.map +1 -0
  113. package/dist/chunk-TDYPZXK5.mjs +1 -0
  114. package/dist/chunk-TDYPZXK5.mjs.map +1 -0
  115. package/dist/chunk-UJI4LN5P.mjs +36 -0
  116. package/dist/chunk-UJI4LN5P.mjs.map +1 -0
  117. package/dist/chunk-W6RTHQGP.mjs +1 -0
  118. package/dist/chunk-W6RTHQGP.mjs.map +1 -0
  119. package/dist/chunk-ZELBCPDC.mjs +369 -0
  120. package/dist/chunk-ZELBCPDC.mjs.map +1 -0
  121. package/dist/index.d.mts +1103 -544
  122. package/dist/index.d.ts +1103 -544
  123. package/dist/index.js +7032 -6033
  124. package/dist/index.js.map +1 -1
  125. package/dist/index.mjs +568 -2226
  126. package/dist/index.mjs.map +1 -1
  127. package/dist/resource-monitor-UZUGPIAU.mjs +9 -0
  128. package/dist/resource-monitor-UZUGPIAU.mjs.map +1 -0
  129. package/dist/storage-location-manager-HFNB3PCS.mjs +7 -0
  130. package/dist/storage-location-manager-HFNB3PCS.mjs.map +1 -0
  131. package/package.json +123 -2
  132. package/dist/builtins/local-backup/index.d.mts +0 -42
  133. package/dist/builtins/local-backup/index.d.ts +0 -42
  134. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.mts +0 -2
  135. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +0 -2
  136. package/dist/builtins/sqlite-storage/index.d.mts +0 -4
  137. package/dist/builtins/sqlite-storage/index.d.ts +0 -4
  138. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.mts +0 -2
  139. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +0 -2
  140. package/dist/builtins/winston-logging/index.d.mts +0 -30
  141. package/dist/builtins/winston-logging/index.d.ts +0 -30
  142. package/dist/chunk-2F3XZYRW.mjs.map +0 -1
  143. package/dist/chunk-LQFPAEQF.mjs +0 -147
  144. package/dist/chunk-LQFPAEQF.mjs.map +0 -1
  145. package/dist/chunk-R3DIIBBX.mjs +0 -532
  146. package/dist/chunk-R3DIIBBX.mjs.map +0 -1
  147. package/dist/chunk-SMNR44VG.mjs +0 -386
  148. package/dist/chunk-SMNR44VG.mjs.map +0 -1
  149. package/dist/chunk-SO4LROOT.mjs.map +0 -1
  150. package/dist/chunk-SPA4JBKN.mjs +0 -175
  151. package/dist/chunk-SPA4JBKN.mjs.map +0 -1
  152. package/dist/dist-3BY63UQ5.mjs +0 -2151
  153. package/dist/dist-3BY63UQ5.mjs.map +0 -1
  154. package/dist/filesystem-storage.addon-C42r589X.d.mts +0 -57
  155. package/dist/filesystem-storage.addon-C42r589X.d.ts +0 -57
  156. package/dist/sql-schema-CKz78rId.d.mts +0 -97
  157. package/dist/sql-schema-CKz78rId.d.ts +0 -97
  158. package/dist/sqlite-settings.addon-KwG-uKMP.d.mts +0 -79
  159. package/dist/sqlite-settings.addon-KwG-uKMP.d.ts +0 -79
  160. package/dist/storage-location-manager-KKDQNAKA.mjs +0 -7
  161. /package/dist/{storage-location-manager-KKDQNAKA.mjs.map → builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/builtins/sqlite-storage/sqlite-settings.addon.ts","../../../src/builtins/sqlite-storage/sqlite-settings-backend.ts"],"sourcesContent":["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 { randomUUID } from 'node:crypto'\nimport type {\n ISettingsBackend,\n SettingsCollection,\n SettingsRecord,\n QueryFilter,\n TableSchema,\n TableQueryOptions,\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 structuredTables = 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.ensureCollectionTable(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.ensureCollectionTable(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.ensureCollectionTable(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.ensureCollectionTable(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.ensureCollectionTable(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.ensureCollectionTable(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.ensureCollectionTable(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.ensureCollectionTable(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.ensureCollectionTable('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.ensureCollectionTable('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.ensureCollectionTable('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.ensureCollectionTable('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.ensureCollectionTable('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.ensureCollectionTable('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 ensureCollectionTable(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.ensureCollectionTable(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.ensureCollectionTable(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 // ── Structured table operations ────────────────────────────────────\n\n async ensureTable(table: string, schema?: TableSchema): Promise<void> {\n if (!schema) {\n // Legacy: ensure JSON-blob collection table\n if (!this.ensuredTables.has(table)) {\n this.getDb().exec(`CREATE TABLE IF NOT EXISTS \"${table}\" (id TEXT PRIMARY KEY, data TEXT NOT NULL)`)\n this.ensuredTables.add(table)\n }\n return\n }\n if (this.structuredTables.has(table)) return\n\n const colDefs = schema.columns.map(col => {\n const parts = [`\"${col.name}\" ${col.type}`]\n if (col.primaryKey) parts.push('PRIMARY KEY')\n if (col.notNull) parts.push('NOT NULL')\n if (col.unique) parts.push('UNIQUE')\n if (col.defaultValue !== undefined) {\n parts.push(`DEFAULT ${typeof col.defaultValue === 'string' ? `'${col.defaultValue}'` : col.defaultValue === null ? 'NULL' : String(col.defaultValue)}`)\n }\n return parts.join(' ')\n })\n\n this.getDb().exec(`CREATE TABLE IF NOT EXISTS \"${table}\" (${colDefs.join(', ')})`)\n\n if (schema.indexes) {\n for (const idx of schema.indexes) {\n const unique = idx.unique ? 'UNIQUE ' : ''\n const cols = idx.columns.map(c => `\"${c}\"`).join(', ')\n this.getDb().exec(`CREATE ${unique}INDEX IF NOT EXISTS \"${idx.name}\" ON \"${table}\" (${cols})`)\n }\n }\n\n this.structuredTables.add(table)\n }\n\n async tableInsert(table: string, row: Record<string, unknown>): Promise<void> {\n const keys = Object.keys(row)\n const cols = keys.map(k => `\"${k}\"`).join(', ')\n const placeholders = keys.map(() => '?').join(', ')\n const values = keys.map(k => {\n const v = row[k]\n return typeof v === 'object' && v !== null ? JSON.stringify(v) : v\n })\n this.getDb().prepare(`INSERT INTO \"${table}\" (${cols}) VALUES (${placeholders})`).run(...values)\n }\n\n async tableUpdate(table: string, filter: Record<string, unknown>, updates: Record<string, unknown>): Promise<number> {\n const setClauses: string[] = []\n const setValues: unknown[] = []\n for (const [k, v] of Object.entries(updates)) {\n setClauses.push(`\"${k}\" = ?`)\n setValues.push(typeof v === 'object' && v !== null ? JSON.stringify(v) : v)\n }\n\n const { whereSql, whereValues } = this.buildWhere(filter)\n const result = this.getDb()\n .prepare(`UPDATE \"${table}\" SET ${setClauses.join(', ')}${whereSql}`)\n .run(...setValues, ...whereValues)\n return result.changes\n }\n\n async tableDelete(table: string, filter: Record<string, unknown>): Promise<number> {\n const { whereSql, whereValues } = this.buildWhere(filter)\n const result = this.getDb().prepare(`DELETE FROM \"${table}\"${whereSql}`).run(...whereValues)\n return result.changes\n }\n\n async tableQuery(table: string, options?: TableQueryOptions): Promise<readonly Record<string, unknown>[]> {\n let sql = `SELECT * FROM \"${table}\"`\n const values: unknown[] = []\n\n if (options?.where) {\n const { whereSql, whereValues } = this.buildWhere(options.where)\n sql += whereSql\n values.push(...whereValues)\n }\n\n if (options?.orderBy) {\n sql += ` ORDER BY \"${options.orderBy.field}\" ${options.orderBy.direction === 'desc' ? 'DESC' : 'ASC'}`\n }\n\n if (options?.limit !== undefined) {\n sql += ` LIMIT ?`\n values.push(options.limit)\n }\n\n if (options?.offset !== undefined) {\n sql += ` OFFSET ?`\n values.push(options.offset)\n }\n\n return this.getDb().prepare(sql).all(...values) as Record<string, unknown>[]\n }\n\n async tableGet(table: string, filter: Record<string, unknown>): Promise<Record<string, unknown> | null> {\n const { whereSql, whereValues } = this.buildWhere(filter)\n const row = this.getDb().prepare(`SELECT * FROM \"${table}\"${whereSql} LIMIT 1`).get(...whereValues)\n return (row as Record<string, unknown>) ?? null\n }\n\n async tableCount(table: string, filter?: Record<string, unknown>): Promise<number> {\n let sql = `SELECT COUNT(*) as count FROM \"${table}\"`\n const values: unknown[] = []\n\n if (filter) {\n const { whereSql, whereValues } = this.buildWhere(filter)\n sql += whereSql\n values.push(...whereValues)\n }\n\n const row = this.getDb().prepare(sql).get(...values) as { count: number }\n return row.count\n }\n\n private buildWhere(filter: Record<string, unknown>): { whereSql: string; whereValues: unknown[] } {\n const conditions: string[] = []\n const values: unknown[] = []\n for (const [k, v] of Object.entries(filter)) {\n conditions.push(`\"${k}\" = ?`)\n values.push(typeof v === 'object' && v !== null ? JSON.stringify(v) : v)\n }\n const whereSql = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : ''\n return { whereSql, whereValues: values }\n }\n}\n\nexport default SqliteSettingsBackend\n"],"mappings":"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","names":["Database"]}
1
+ {"version":3,"sources":["../../../src/builtins/sqlite-storage/sqlite-settings.addon.ts","../../../src/builtins/sqlite-storage/sqlite-settings-backend.ts"],"sourcesContent":["import type { ProviderRegistration } from '@camstack/types'\nimport { BaseAddon, RUNTIME_DEFAULTS, errMsg, settingsStoreCapability } from '@camstack/types'\nimport { SqliteSettingsBackend } from './sqlite-settings-backend.js'\n\n/**\n * SQLite Settings addon — provides persistent settings storage.\n * Capability: 'settings-store' (singleton)\n */\nexport class SqliteSettingsAddon extends BaseAddon {\n private backend: SqliteSettingsBackend | null = null\n\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const addonId = this.ctx.id.replace('addon:', '')\n let pathSource = 'fallback (hardcoded)'\n const dbPath = await this.ctx.api.storage.resolve.query({ location: 'addons-data', relativePath: `${addonId}/camstack.db` })\n .then((p: string) => { pathSource = 'storage capability (addons-data)'; return p })\n .catch(() => {\n if (this.ctx.dataDir) { pathSource = 'dataDir'; return `${this.ctx.dataDir}/camstack.db` }\n return 'camstack-data/addons-data/sqlite-settings/camstack.db'\n })\n\n let dbExists = false\n try {\n const fs = await import('node:fs')\n dbExists = fs.existsSync(dbPath)\n } catch (err) { this.ctx.logger.warn('Failed to check DB file existence', { meta: { error: errMsg(err) } }) }\n\n this.ctx.logger.info('DB path resolved', { meta: { pathSource, dbPath } })\n this.ctx.logger.info('DB file status', { meta: { dbExists } })\n\n this.backend = new SqliteSettingsBackend(dbPath, { ...RUNTIME_DEFAULTS })\n await this.backend.initialize()\n this.ctx.logger.info('Initialized successfully')\n return [{ capability: settingsStoreCapability, provider: this.backend }]\n }\n\n protected async onShutdown(): Promise<void> {\n await this.backend?.shutdown()\n }\n\n getBackend(): SqliteSettingsBackend | null {\n return this.backend\n }\n}\n\nexport default SqliteSettingsAddon\n","import Database from 'better-sqlite3'\nimport { randomUUID } from 'node:crypto'\nimport type {\n ISettingsBackend,\n SettingsRecord,\n TableSchema,\n TableQueryOptions,\n SettingsGetInput,\n SettingsSetInput,\n SettingsQueryInput,\n SettingsInsertInput,\n SettingsUpdateInput,\n SettingsDeleteInput,\n SettingsCountInput,\n SettingsIsEmptyInput,\n CollectionColumn,\n CollectionIndex,\n} from '@camstack/types'\nimport { asJsonObject, parseJsonUnknown } from '@camstack/types'\n\nfunction parseRowData(raw: string): Record<string, unknown> {\n return asJsonObject(parseJsonUnknown(raw)) ?? {}\n}\n\n/**\n * SQLite implementation of ISettingsBackend.\n *\n * Every collection is structured: declared at boot (canonical KV\n * collections) or via `declareCollection` (typed schemas like\n * `pipeline-analytics:tracks`). The legacy auto-create-on-first-access\n * path was removed — see commit history for the migration. Operations\n * on undeclared collections throw at runtime so callers fail fast.\n *\n * WAL mode for concurrent reads.\n */\nexport class SqliteSettingsBackend implements ISettingsBackend {\n private db: Database.Database | null = null\n private readonly structuredTables = new Set<string>()\n /** Map from scoped collection name → set of column names (non-id) that\n * the structured schema owns. Routes set/get/insert/update/query to\n * typed columns. Every collection MUST be declared here before use. */\n private readonly declaredCollections = new Map<string, { readonly primaryKey: string; readonly columns: ReadonlySet<string> }>()\n private readonly runtimeDefaults: Record<string, unknown>\n\n /**\n * Canonical key/value collections — declared with a `(id TEXT PK,\n * data TEXT NOT NULL)` schema at boot so existing JSON-blob rows\n * keep working through the structured path. Generates SQL identical\n * to the previous legacy path; only the routing is unified.\n */\n private static readonly CANONICAL_KV_COLLECTIONS: readonly string[] = [\n 'system-settings',\n 'addon-settings',\n 'addon-device-settings',\n 'addon-devices',\n 'device-runtime-state',\n 'sections',\n 'provider-settings',\n 'device-settings',\n 'alerts',\n ]\n\n // Domain schemas (auth, analytics events, …) live in the addons that\n // own them — same pattern as `pipeline-analytics` (`event-store.ts`,\n // `track-store.ts`, `media-store.ts`) and `local-auth` (`auth-schema.ts`).\n // The backend exposes `declareCollection` and stays out of domain\n // knowledge.\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 // Declare canonical KV collections so set/get/insert/update/query\n // reach the structured path uniformly. Schema is the same shape\n // legacy created on demand — `(id TEXT PK, data TEXT NOT NULL)` —\n // so existing rows continue to work with no migration.\n for (const collection of SqliteSettingsBackend.CANONICAL_KV_COLLECTIONS) {\n await this.ensureTable(collection, {\n columns: [\n { name: 'id', type: 'TEXT', primaryKey: true, notNull: true },\n { name: 'data', type: 'TEXT', notNull: true },\n ],\n })\n this.declaredCollections.set(collection, {\n primaryKey: 'id',\n columns: new Set(['data']),\n })\n }\n\n // Seed system-settings on first boot\n const isEmpty = await this.isEmpty({ collection: 'system-settings' })\n if (isEmpty) {\n await this.seedDefaults()\n }\n }\n\n private requireDeclared(scoped: string): { readonly primaryKey: string; readonly columns: ReadonlySet<string> } {\n const decl = this.declaredCollections.get(scoped)\n if (!decl) {\n throw new Error(`SqliteSettingsBackend: collection \"${scoped}\" is not declared. Call declareCollection() first or add it to CANONICAL_KV_COLLECTIONS.`)\n }\n return decl\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({ namespace, collection, key }: SettingsGetInput): Promise<unknown> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n // Canonical KV collections store the value as a single `data`\n // JSON-blob column; their declared columns are exactly `['data']`\n // so we read it back, parse, and return. Structured collections\n // (multi-column) return the row map untouched.\n const cols = [`\"${decl.primaryKey}\"`, ...[...decl.columns].map(c => `\"${c}\"`)].join(', ')\n const row = this.getDb()\n .prepare<[string], Record<string, unknown>>(`SELECT ${cols} FROM \"${scoped}\" WHERE \"${decl.primaryKey}\" = ?`)\n .get(key)\n if (!row) return undefined\n if (decl.columns.size === 1 && decl.columns.has('data')) {\n const raw = row['data']\n return typeof raw === 'string' ? JSON.parse(raw) : raw\n }\n const data: Record<string, unknown> = {}\n for (const c of decl.columns) {\n const v = row[c]\n if (typeof v === 'string' && (v.startsWith('{') || v.startsWith('['))) {\n try { data[c] = JSON.parse(v) } catch { data[c] = v }\n } else {\n data[c] = v ?? null\n }\n }\n return data\n }\n\n async set({ namespace, collection, key, value }: SettingsSetInput): Promise<void> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n // Build the column row. KV-shape collections (`['data']`) store\n // `value` as a JSON blob in `data`; structured collections\n // destructure `value` into its declared columns.\n const row: Record<string, unknown> = { [decl.primaryKey]: key }\n if (decl.columns.size === 1 && decl.columns.has('data')) {\n row['data'] = JSON.stringify(value)\n } else {\n const valueObj = (value !== null && typeof value === 'object' ? value : {}) as Record<string, unknown>\n for (const [k, v] of Object.entries(valueObj)) {\n if (decl.columns.has(k)) row[k] = this.serializeColumnValue(v)\n }\n }\n const keys = Object.keys(row)\n const cols = keys.map(k => `\"${k}\"`).join(', ')\n const placeholders = keys.map(() => '?').join(', ')\n const updates = keys\n .filter(k => k !== decl.primaryKey)\n .map(k => `\"${k}\" = excluded.\"${k}\"`)\n .join(', ')\n const values = keys.map(k => row[k])\n const sql = updates.length > 0\n ? `INSERT INTO \"${scoped}\" (${cols}) VALUES (${placeholders}) ON CONFLICT(\"${decl.primaryKey}\") DO UPDATE SET ${updates}`\n : `INSERT INTO \"${scoped}\" (${cols}) VALUES (${placeholders}) ON CONFLICT(\"${decl.primaryKey}\") DO NOTHING`\n this.getDb().prepare(sql).run(...values)\n }\n\n async query<T extends object = Record<string, unknown>>(\n { namespace, collection, filter }: SettingsQueryInput,\n ): Promise<readonly SettingsRecord<T>[]> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n return this.queryDeclared<T>(scoped, decl, filter)\n }\n\n async insert<T extends object = Record<string, unknown>>({ namespace, collection, record }: SettingsInsertInput<T>): Promise<void> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n const id = record.id || randomUUID()\n const row: Record<string, unknown> = { [decl.primaryKey]: id }\n if (decl.columns.size === 1 && decl.columns.has('data')) {\n row['data'] = JSON.stringify(record.data)\n } else {\n for (const [k, v] of Object.entries(record.data)) {\n if (decl.columns.has(k)) row[k] = this.serializeColumnValue(v)\n }\n }\n await this.tableInsert(scoped, row)\n }\n\n async update({ namespace, collection, id, data }: SettingsUpdateInput): Promise<void> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n const updates: Record<string, unknown> = {}\n if (decl.columns.size === 1 && decl.columns.has('data')) {\n updates['data'] = JSON.stringify(data)\n } else {\n for (const [k, v] of Object.entries(data)) {\n if (decl.columns.has(k)) updates[k] = this.serializeColumnValue(v)\n }\n }\n if (Object.keys(updates).length > 0) {\n await this.tableUpdate(scoped, { [decl.primaryKey]: id }, updates)\n }\n }\n\n async delete({ namespace, collection, key }: SettingsDeleteInput): Promise<void> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n await this.tableDelete(scoped, { [decl.primaryKey]: key })\n }\n\n async count({ namespace, collection, filter }: SettingsCountInput): Promise<number> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n const isKvShape = decl.columns.size === 1 && decl.columns.has('data')\n const isColumn = (f: string): boolean => f === decl.primaryKey || decl.columns.has(f)\n const fieldExpr = (f: string): string => {\n if (isColumn(f)) return `\"${f}\"`\n if (isKvShape) return `json_extract(\"data\", '$.${f}')`\n return ''\n }\n let sql = `SELECT COUNT(*) AS cnt FROM \"${scoped}\"`\n const params: unknown[] = []\n if (filter?.where) {\n const clauses: string[] = []\n for (const [field, value] of Object.entries(filter.where)) {\n const expr = fieldExpr(field)\n if (!expr) continue\n clauses.push(`${expr} = ?`)\n params.push(this.serializeColumnValue(value))\n }\n if (clauses.length > 0) sql += ` WHERE ${clauses.join(' AND ')}`\n }\n const row = this.getDb()\n .prepare<unknown[], { cnt: number }>(sql)\n .get(...params)\n return row?.cnt ?? 0\n }\n\n async isEmpty({ namespace, collection }: SettingsIsEmptyInput): Promise<boolean> {\n const scoped = this.scopedName(namespace, collection)\n this.requireDeclared(scoped)\n return (await this.tableCount(scoped)) === 0\n }\n\n private async queryDeclared<T extends object = Record<string, unknown>>(\n table: string,\n decl: { readonly primaryKey: string; readonly columns: ReadonlySet<string> },\n filter: import('@camstack/types').QueryFilter | undefined,\n ): Promise<readonly SettingsRecord<T>[]> {\n // KV-shape detection: a single `data` JSON-blob column. Canonical\n // collections (users, alerts, api-keys, …) and the catalogue of\n // \"legacy\" collections fall in this branch — the structured per-\n // column path is reserved for explicitly declared schemas.\n const isKvShape = decl.columns.size === 1 && decl.columns.has('data')\n const cols = [`\"${decl.primaryKey}\"`, ...[...decl.columns].map(c => `\"${c}\"`)].join(', ')\n let sql = `SELECT ${cols} FROM \"${table}\"`\n const params: unknown[] = []\n const whereClauses: string[] = []\n\n const isColumn = (f: string): boolean => f === decl.primaryKey || decl.columns.has(f)\n /**\n * SQL expression for `field` on this table.\n * - real column → `\"field\"`\n * - KV blob field (e.g. `username` inside `data`) → `json_extract`\n * - structured table + non-column → empty string (filter dropped,\n * matches legacy structured-table behaviour)\n */\n const fieldExpr = (f: string): string => {\n if (isColumn(f)) return `\"${f}\"`\n if (isKvShape) return `json_extract(\"data\", '$.${f}')`\n return ''\n }\n\n if (filter?.where) {\n for (const [field, value] of Object.entries(filter.where)) {\n const expr = fieldExpr(field)\n if (!expr) continue\n whereClauses.push(`${expr} = ?`)\n params.push(this.serializeColumnValue(value))\n }\n }\n if (filter?.whereIn) {\n for (const [field, values] of Object.entries(filter.whereIn)) {\n const expr = fieldExpr(field)\n if (!expr) continue\n const placeholders = values.map(() => '?').join(', ')\n whereClauses.push(`${expr} IN (${placeholders})`)\n for (const v of values) params.push(this.serializeColumnValue(v))\n }\n }\n if (filter?.whereBetween) {\n for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {\n const expr = fieldExpr(field)\n if (!expr) continue\n whereClauses.push(`${expr} BETWEEN ? AND ?`)\n params.push(this.serializeColumnValue(low), this.serializeColumnValue(high))\n }\n }\n if (whereClauses.length > 0) sql += ` WHERE ${whereClauses.join(' AND ')}`\n\n if (filter?.orderBy) {\n const expr = fieldExpr(filter.orderBy.field)\n if (expr) {\n const dir = filter.orderBy.direction === 'desc' ? 'DESC' : 'ASC'\n sql += ` ORDER BY ${expr} ${dir}`\n }\n }\n if (filter?.limit !== undefined) { sql += ` LIMIT ?`; params.push(filter.limit) }\n if (filter?.offset !== undefined) { sql += ` OFFSET ?`; params.push(filter.offset) }\n\n const rows = this.getDb()\n .prepare<unknown[], Record<string, unknown>>(sql)\n .all(...params)\n return rows.map(r => {\n const id = String(r[decl.primaryKey] ?? '')\n // KV-shape: unwrap the JSON blob so consumers receive\n // `{ id, data: <parsedJSON> }` directly. Without this every caller\n // in the auth chain (UserManager, ScopedTokenManager, …) gets a\n // double-wrapped record (`{ data: { data: <real> } }`) and the\n // schema parse throws on undefined fields.\n if (isKvShape) {\n const v = r['data']\n if (typeof v === 'string' && (v.startsWith('{') || v.startsWith('['))) {\n try {\n return { id, data: JSON.parse(v) as T }\n } catch {\n return { id, data: { value: v } as unknown as T }\n }\n }\n return { id, data: (v == null ? {} : { value: v }) as T }\n }\n // Structured tables: reassemble the record from typed columns and\n // include the primary key field so callers (e.g. `UserRecordSchema.parse`)\n // see the same shape they originally inserted.\n const data: Record<string, unknown> = { [decl.primaryKey]: r[decl.primaryKey] ?? null }\n for (const c of decl.columns) {\n // Auto-deserialize JSON strings back into objects. Heuristic:\n // values starting with `{` or `[` are attempted as JSON.parse,\n // fallback to the raw string on parse failure. This preserves\n // primitive column types (TEXT/INTEGER/REAL) as-is.\n const v = r[c]\n if (typeof v === 'string' && (v.startsWith('{') || v.startsWith('['))) {\n try { data[c] = JSON.parse(v) } catch { data[c] = v }\n } else {\n data[c] = v ?? null\n }\n }\n return { id, data: data as T }\n })\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.requireDeclared('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.requireDeclared('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.requireDeclared('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.requireDeclared('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\n // = \"addonId.key\" and data = `{ addonId, key, value }`. Detect the\n // wrapper shape explicitly: a corrupted legacy row that lacks the\n // `value` field would otherwise be returned as the wrapper itself\n // (the previous `parsed.value ?? parsed` fallback masked the bug as a\n // valid value, breaking downstream type-narrowed reads like\n // `pipelineDefaults`). Skip those rows entirely so the caller sees\n // them as missing.\n const result: Record<string, unknown> = {}\n for (const row of rows) {\n const parsed = parseRowData(row.data)\n const key = row.id.startsWith(`${addonId}.`) ? row.id.slice(addonId.length + 1) : row.id\n const isWrapper = parsed !== null\n && typeof parsed === 'object'\n && 'addonId' in parsed\n && 'key' in (parsed as Record<string, unknown>)\n if (isWrapper) {\n const wrapper = parsed as { readonly addonId: string; readonly key: string; readonly value?: unknown }\n if ('value' in wrapper) {\n result[key] = wrapper.value\n }\n // else: corrupted wrapper-without-value — skip\n } else {\n result[key] = parsed\n }\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.requireDeclared('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 // Skip undefined values: the JSON wrapper `{addonId, key, value}`\n // serializes as `{addonId, key}` when value is undefined, which\n // round-trips as a corrupted \"wrapper without value\" row.\n if (value === undefined) continue\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 // ── Addon-device settings (per-device overrides of an addon's config) ──\n //\n // Storage key format: \"<addonId>:<deviceId>.<field>\" inside the\n // \"addon-device-settings\" collection. Uses the same JSON-blob layout\n // as the other scoped collections so no schema change is required.\n\n getAddonDevice(addonId: string, deviceId: string): Record<string, unknown> {\n return this.getAllScoped('addon-device-settings', `${addonId}:${deviceId}`)\n }\n\n setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void {\n this.requireDeclared('addon-device-settings')\n const db = this.getDb()\n const prefix = `${addonId}:${deviceId}.`\n const deleteStmt = db.prepare(`DELETE FROM \"addon-device-settings\" WHERE id LIKE ? || '%'`)\n const insertStmt = db.prepare(\n `INSERT INTO \"addon-device-settings\" (id, data) VALUES (?, ?)\n ON CONFLICT(id) DO UPDATE SET data = excluded.data`,\n )\n db.transaction(() => {\n deleteStmt.run(prefix)\n for (const [key, value] of Object.entries(values)) {\n insertStmt.run(\n `${prefix}${key}`,\n JSON.stringify({ addonId, deviceId, key, value }),\n )\n }\n })()\n }\n\n clearAddonDevice(addonId: string, deviceId: string): void {\n this.requireDeclared('addon-device-settings')\n const prefix = `${addonId}:${deviceId}.`\n this.getDb()\n .prepare(`DELETE FROM \"addon-device-settings\" WHERE id LIKE ? || '%'`)\n .run(prefix)\n }\n\n /** Seed system-settings with runtime defaults (first boot) */\n private async seedDefaults(): Promise<void> {\n this.requireDeclared('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 /**\n * Expose the raw better-sqlite3 Database instance for components that\n * need direct SQL access (e.g. DeviceStore, ConfigStore).\n * Returns null if the backend has not been initialized yet.\n */\n getDatabase(): Database.Database | null {\n return this.db\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 getAllScoped(collection: string, scopeId: string): Record<string, unknown> {\n this.requireDeclared(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 = parseRowData(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.requireDeclared(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 // ── Declared collections (typed SQL tables behind the generic cap) ──\n\n private scopedName(namespace: string | undefined, collection: string): string {\n return namespace ? `${namespace}:${collection}` : collection\n }\n\n async declareCollection(input: {\n namespace?: string\n collection: string\n columns: readonly CollectionColumn[]\n indexes?: readonly CollectionIndex[]\n }): Promise<void> {\n const table = this.scopedName(input.namespace, input.collection)\n if (this.declaredCollections.has(table)) return\n\n // Ensure id column is explicit — every declared collection needs a\n // primary key that maps to the cap's record.id.\n const hasId = input.columns.some(c => c.primaryKey === true)\n const columns = hasId\n ? input.columns\n : [{ name: 'id', type: 'TEXT' as const, primaryKey: true, notNull: true }, ...input.columns]\n\n // Translate to the TableSchema shape ensureTable expects.\n const schema: TableSchema = {\n columns: columns.map(c => ({\n name: c.name,\n // SQLite treats JSON as TEXT; we serialize object values on write\n // and parse on read.\n type: c.type === 'JSON' ? 'TEXT' : c.type,\n ...(c.primaryKey !== undefined ? { primaryKey: c.primaryKey } : {}),\n ...(c.notNull !== undefined ? { notNull: c.notNull } : {}),\n ...(c.unique !== undefined ? { unique: c.unique } : {}),\n })),\n ...(input.indexes ? { indexes: input.indexes.map(i => ({\n name: i.name,\n columns: i.columns,\n ...(i.unique !== undefined ? { unique: i.unique } : {}),\n })) } : {}),\n }\n await this.ensureTable(table, schema)\n\n const pkCol = columns.find(c => c.primaryKey === true)\n const primaryKey = pkCol ? pkCol.name : 'id'\n const columnNames = new Set(columns.filter(c => c.name !== primaryKey).map(c => c.name))\n this.declaredCollections.set(table, { primaryKey, columns: columnNames })\n }\n\n /** Serialise per-column values for SQL binding: objects → JSON, booleans → 0/1. */\n private serializeColumnValue(v: unknown): unknown {\n if (v === null || v === undefined) return v ?? null\n if (typeof v === 'boolean') return v ? 1 : 0\n if (typeof v === 'object') return JSON.stringify(v)\n return v\n }\n\n // ── Structured table operations ────────────────────────────────────\n\n async ensureTable(table: string, schema: TableSchema): Promise<void> {\n if (this.structuredTables.has(table)) return\n\n const colDefs = schema.columns.map(col => {\n const parts = [`\"${col.name}\" ${col.type}`]\n if (col.primaryKey) parts.push('PRIMARY KEY')\n if (col.notNull) parts.push('NOT NULL')\n if (col.unique) parts.push('UNIQUE')\n if (col.defaultValue !== undefined) {\n parts.push(`DEFAULT ${typeof col.defaultValue === 'string' ? `'${col.defaultValue}'` : col.defaultValue === null ? 'NULL' : String(col.defaultValue)}`)\n }\n return parts.join(' ')\n })\n\n // Drop a pre-existing legacy `(id, data)` shape when the caller\n // is now declaring a structured schema. Without this, an earlier\n // `ensureTable(table)` call (no schema → legacy KV table) wins\n // because `CREATE TABLE IF NOT EXISTS` is a no-op when the table\n // is already there with a different shape, and every subsequent\n // typed query throws \"no such column: deviceId\" / \"...id\".\n // Destructive — the caller (analytics tracks/events/media) is the\n // only consumer of the table, so the operator only sees a one-time\n // wipe of yet-unmigrated observability rings.\n type ColInfoRow = { name: string }\n const existingCols = this.getDb()\n .prepare(`PRAGMA table_info(\"${table}\")`)\n .all() as ColInfoRow[]\n if (existingCols.length > 0) {\n const existingNames = new Set(existingCols.map(c => c.name))\n const declaredNames = new Set(schema.columns.map(c => c.name))\n const missingDeclared = schema.columns.some(c => !existingNames.has(c.name))\n const isLegacyKv =\n existingCols.length <= 2 &&\n existingNames.has('data') &&\n !declaredNames.has('data')\n if (isLegacyKv || missingDeclared) {\n try { this.getDb().exec(`DROP TABLE \"${table}\"`) } catch { /* best-effort */ }\n }\n }\n\n this.getDb().exec(`CREATE TABLE IF NOT EXISTS \"${table}\" (${colDefs.join(', ')})`)\n\n if (schema.indexes) {\n for (const idx of schema.indexes) {\n const unique = idx.unique ? 'UNIQUE ' : ''\n const cols = idx.columns.map(c => `\"${c}\"`).join(', ')\n this.getDb().exec(`CREATE ${unique}INDEX IF NOT EXISTS \"${idx.name}\" ON \"${table}\" (${cols})`)\n }\n }\n\n this.structuredTables.add(table)\n }\n\n async tableInsert(table: string, row: Record<string, unknown>): Promise<void> {\n const keys = Object.keys(row)\n const cols = keys.map(k => `\"${k}\"`).join(', ')\n const placeholders = keys.map(() => '?').join(', ')\n const values = keys.map(k => {\n const v = row[k]\n return typeof v === 'object' && v !== null ? JSON.stringify(v) : v\n })\n this.getDb().prepare(`INSERT INTO \"${table}\" (${cols}) VALUES (${placeholders})`).run(...values)\n }\n\n async tableUpdate(table: string, filter: Record<string, unknown>, updates: Record<string, unknown>): Promise<number> {\n const setClauses: string[] = []\n const setValues: unknown[] = []\n for (const [k, v] of Object.entries(updates)) {\n setClauses.push(`\"${k}\" = ?`)\n setValues.push(typeof v === 'object' && v !== null ? JSON.stringify(v) : v)\n }\n\n const { whereSql, whereValues } = this.buildWhere(filter)\n const result = this.getDb()\n .prepare(`UPDATE \"${table}\" SET ${setClauses.join(', ')}${whereSql}`)\n .run(...setValues, ...whereValues)\n return result.changes\n }\n\n async tableDelete(table: string, filter: Record<string, unknown>): Promise<number> {\n const { whereSql, whereValues } = this.buildWhere(filter)\n const result = this.getDb().prepare(`DELETE FROM \"${table}\"${whereSql}`).run(...whereValues)\n return result.changes\n }\n\n async tableQuery(table: string, options?: TableQueryOptions): Promise<readonly Record<string, unknown>[]> {\n let sql = `SELECT * FROM \"${table}\"`\n const values: unknown[] = []\n\n if (options?.where) {\n const { whereSql, whereValues } = this.buildWhere(options.where)\n sql += whereSql\n values.push(...whereValues)\n }\n\n if (options?.orderBy) {\n sql += ` ORDER BY \"${options.orderBy.field}\" ${options.orderBy.direction === 'desc' ? 'DESC' : 'ASC'}`\n }\n\n if (options?.limit !== undefined) {\n sql += ` LIMIT ?`\n values.push(options.limit)\n }\n\n if (options?.offset !== undefined) {\n sql += ` OFFSET ?`\n values.push(options.offset)\n }\n\n const rows = this.getDb().prepare(sql).all(...values)\n return rows.flatMap((r) => asJsonObject(r) ? [asJsonObject(r)!] : [])\n }\n\n async tableGet(table: string, filter: Record<string, unknown>): Promise<Record<string, unknown> | null> {\n const { whereSql, whereValues } = this.buildWhere(filter)\n const row = this.getDb().prepare(`SELECT * FROM \"${table}\"${whereSql} LIMIT 1`).get(...whereValues)\n return asJsonObject(row)\n }\n\n async tableCount(table: string, filter?: Record<string, unknown>): Promise<number> {\n let sql = `SELECT COUNT(*) as count FROM \"${table}\"`\n const values: unknown[] = []\n\n if (filter) {\n const { whereSql, whereValues } = this.buildWhere(filter)\n sql += whereSql\n values.push(...whereValues)\n }\n\n const row = asJsonObject(this.getDb().prepare(sql).get(...values))\n return typeof row?.count === 'number' ? row.count : 0\n }\n\n private buildWhere(filter: Record<string, unknown>): { whereSql: string; whereValues: unknown[] } {\n const conditions: string[] = []\n const values: unknown[] = []\n for (const [k, v] of Object.entries(filter)) {\n conditions.push(`\"${k}\" = ?`)\n values.push(typeof v === 'object' && v !== null ? JSON.stringify(v) : v)\n }\n const whereSql = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : ''\n return { whereSql, whereValues: values }\n }\n}\n\nexport default SqliteSettingsBackend\n"],"mappings":"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","names":["import_types","Database"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SqliteSettingsAddon,
3
3
  sqlite_settings_addon_default
4
- } from "../../chunk-SMNR44VG.mjs";
4
+ } from "../../chunk-FZN56HGQ.mjs";
5
5
  export {
6
6
  SqliteSettingsAddon,
7
7
  sqlite_settings_addon_default as default
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/builtins/system-config/index.ts
21
+ var system_config_exports = {};
22
+ __export(system_config_exports, {
23
+ SystemConfigAddon: () => SystemConfigAddon,
24
+ default: () => system_config_addon_default
25
+ });
26
+ module.exports = __toCommonJS(system_config_exports);
27
+
28
+ // src/builtins/system-config/system-config.addon.ts
29
+ var import_types = require("@camstack/types");
30
+ var SECTION_TITLES = {
31
+ server: "Server",
32
+ auth: "Authentication",
33
+ ffmpeg: "FFmpeg"
34
+ };
35
+ var KEY_TO_SECTION = {
36
+ // server (read-only)
37
+ port: "server",
38
+ host: "server",
39
+ dataPath: "server",
40
+ // auth
41
+ tokenExpiry: "auth",
42
+ // ffmpeg
43
+ binaryPath: "ffmpeg",
44
+ hwAccel: "ffmpeg",
45
+ threadCount: "ffmpeg"
46
+ };
47
+ var SystemConfigAddon = class extends import_types.BaseAddon {
48
+ id = "system-config";
49
+ constructor() {
50
+ super({});
51
+ }
52
+ async onInitialize() {
53
+ this.ctx.logger.info("Initialized \u2014 exposes yml-backed sections via getGlobalSettings");
54
+ }
55
+ // ── Three-level settings API (only `getGlobalSettings` is implemented) ──
56
+ buildGlobalSchema() {
57
+ return {
58
+ sections: [
59
+ {
60
+ id: "system-config-server",
61
+ title: SECTION_TITLES["server"],
62
+ description: "Core server connection settings \u2014 read-only, loaded from config.yaml at bootstrap.",
63
+ columns: 2,
64
+ fields: [
65
+ {
66
+ type: "info",
67
+ key: "server-restart-note",
68
+ label: "Restart required",
69
+ content: "Server settings are read-only. Change them in config.yaml and restart.",
70
+ variant: "warning"
71
+ },
72
+ { type: "text", key: "port", label: "Port", description: "Listening port", disabled: true },
73
+ { type: "text", key: "host", label: "Host", description: "Bind address", disabled: true },
74
+ { type: "text", key: "dataPath", label: "Data Path", description: "Root data directory", disabled: true, span: 2 }
75
+ ]
76
+ },
77
+ {
78
+ id: "system-config-auth",
79
+ title: SECTION_TITLES["auth"],
80
+ description: "Token and session settings.",
81
+ columns: 1,
82
+ fields: [
83
+ {
84
+ type: "text",
85
+ key: "tokenExpiry",
86
+ label: "Token Expiry",
87
+ description: "JWT token lifetime (e.g. 24h, 7d, 1h)",
88
+ placeholder: "24h",
89
+ default: "24h"
90
+ }
91
+ ]
92
+ },
93
+ {
94
+ id: "system-config-ffmpeg",
95
+ title: SECTION_TITLES["ffmpeg"],
96
+ description: "FFmpeg binary and hardware acceleration settings.",
97
+ columns: 2,
98
+ fields: [
99
+ {
100
+ type: "text",
101
+ key: "binaryPath",
102
+ label: "Binary Path",
103
+ description: "Path to ffmpeg executable",
104
+ placeholder: "ffmpeg",
105
+ default: "ffmpeg",
106
+ span: 2
107
+ },
108
+ {
109
+ type: "select",
110
+ key: "hwAccel",
111
+ label: "Hardware Acceleration",
112
+ description: "GPU decoding/encoding backend",
113
+ default: "auto",
114
+ options: [
115
+ { value: "auto", label: "Auto-detect" },
116
+ { value: "none", label: "None (CPU only)" },
117
+ { value: "videotoolbox", label: "VideoToolbox (macOS)" },
118
+ { value: "vaapi", label: "VA-API (Linux Intel/AMD)" },
119
+ { value: "qsv", label: "QSV (Intel Quick Sync)" },
120
+ { value: "cuda", label: "CUDA (NVIDIA)" }
121
+ ]
122
+ },
123
+ {
124
+ type: "number",
125
+ key: "threadCount",
126
+ label: "Thread Count",
127
+ description: "0 = auto (let FFmpeg decide)",
128
+ min: 0,
129
+ max: 16,
130
+ step: 1,
131
+ default: 0
132
+ }
133
+ ]
134
+ }
135
+ ]
136
+ };
137
+ }
138
+ async getGlobalSettings() {
139
+ const schema = this.buildGlobalSchema();
140
+ if (!this.ctx.settings) {
141
+ return (0, import_types.hydrateSchema)(schema, {});
142
+ }
143
+ const sectionNames = Object.keys(SECTION_TITLES);
144
+ const merged = {};
145
+ for (const section of sectionNames) {
146
+ try {
147
+ const values = await this.ctx.settings.getSection(section);
148
+ Object.assign(merged, values);
149
+ } catch (err) {
150
+ const msg = (0, import_types.errMsg)(err);
151
+ this.ctx.logger.debug("Failed to read section", { meta: { section, error: msg } });
152
+ }
153
+ }
154
+ return (0, import_types.hydrateSchema)(schema, merged);
155
+ }
156
+ async updateGlobalSettings(patch) {
157
+ if (!this.ctx.settings) {
158
+ throw new Error("system-config: ctx.settings not available \u2014 cannot write sections");
159
+ }
160
+ const perSection = /* @__PURE__ */ new Map();
161
+ for (const [key, value] of Object.entries(patch)) {
162
+ const section = KEY_TO_SECTION[key];
163
+ if (section === void 0) {
164
+ this.ctx.logger.warn("update ignored unknown key (not mapped to any section)", { meta: { key } });
165
+ continue;
166
+ }
167
+ if (section === "server") {
168
+ this.ctx.logger.warn("update ignored read-only server-section key", { meta: { key } });
169
+ continue;
170
+ }
171
+ let bucket = perSection.get(section);
172
+ if (!bucket) {
173
+ bucket = {};
174
+ perSection.set(section, bucket);
175
+ }
176
+ bucket[key] = value;
177
+ }
178
+ for (const [section, sectionPatch] of perSection) {
179
+ await this.ctx.settings.setSection(section, sectionPatch);
180
+ this.ctx.logger.info("Wrote keys to section", { meta: { section, keyCount: Object.keys(sectionPatch).length } });
181
+ }
182
+ }
183
+ };
184
+ var system_config_addon_default = SystemConfigAddon;
185
+ // Annotate the CommonJS export names for ESM import in node:
186
+ 0 && (module.exports = {
187
+ SystemConfigAddon
188
+ });
189
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/builtins/system-config/index.ts","../../../src/builtins/system-config/system-config.addon.ts"],"sourcesContent":["export { SystemConfigAddon, default } from './system-config.addon.js'\n","import type {\n ConfigUISchema,\n ConfigUISchemaWithValues,\n} from '@camstack/types'\nimport { BaseAddon, hydrateSchema , errMsg } from '@camstack/types'\n\n/**\n * Built-in `system-config` addon — Phase 4 of the settings redesign.\n *\n * Exposes the cluster-wide yml-backed sections that the bootstrap\n * loader reads before any addon is instantiated — things that cannot\n * live inside an addon because changing them requires a server\n * restart (port, host, dataPath) or because they own no runtime\n * state of their own (auth token expiry, global ffmpeg binary path).\n *\n * Sections exposed:\n * - server (port, host, dataPath — READ-ONLY, loaded from config.yaml at bootstrap)\n * - auth (tokenExpiry)\n * - ffmpeg (binaryPath, hwAccel, threadCount)\n *\n * Sections NOT exposed (each owned by the addon that implements them):\n * - logging → `winston-logging` addon (level + retention)\n * - recording → `recording` addon (segment duration, retention)\n * - retention → analytics / detection addons (per-table retention)\n *\n * Implementation notes:\n * - Runs only on the hub process (yml sections are hub-side state).\n * On agent nodes this addon would still instantiate, but every\n * `ctx.settings.getSection(section)` call would hit an empty\n * ConfigManager section so the UI would show the schema defaults.\n * In practice the built-in loader can skip it on agents; for now we\n * just let it run everywhere because it's harmless.\n * - Reads via `ctx.settings.getSection(section)` and writes via\n * `ctx.settings.setSection(section, patch)` — the explicit\n * yml-section pair on `AddonSettingsApi`.\n * - Does NOT register as a capability provider. It's a pure \"settings\n * surface\" addon — its sole purpose is to expose the yml sections\n * through the new three-level settings API.\n *\n * This addon is declared in `@camstack/core`'s package.json `camstack.addons[]`\n * and loaded automatically via AddonLoader like any other core built-in.\n */\n\n/** Section key → user-facing section title. */\nconst SECTION_TITLES: Readonly<Record<string, string>> = {\n server: 'Server',\n auth: 'Authentication',\n ffmpeg: 'FFmpeg',\n}\n\n/** Keys that map 1:1 to a given yml section — used by `updateGlobalSettings`\n * to figure out which section owns each key in the incoming patch. */\nconst KEY_TO_SECTION: Readonly<Record<string, string>> = {\n // server (read-only)\n port: 'server',\n host: 'server',\n dataPath: 'server',\n // auth\n tokenExpiry: 'auth',\n // ffmpeg\n binaryPath: 'ffmpeg',\n hwAccel: 'ffmpeg',\n threadCount: 'ffmpeg',\n}\n\nexport class SystemConfigAddon extends BaseAddon {\n readonly id = 'system-config'\n\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<void> {\n this.ctx.logger.info('Initialized — exposes yml-backed sections via getGlobalSettings')\n }\n\n // ── Three-level settings API (only `getGlobalSettings` is implemented) ──\n\n private buildGlobalSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'system-config-server',\n title: SECTION_TITLES['server']!,\n description: 'Core server connection settings — read-only, loaded from config.yaml at bootstrap.',\n columns: 2,\n fields: [\n {\n type: 'info',\n key: 'server-restart-note',\n label: 'Restart required',\n content: 'Server settings are read-only. Change them in config.yaml and restart.',\n variant: 'warning',\n },\n { type: 'text', key: 'port', label: 'Port', description: 'Listening port', disabled: true },\n { type: 'text', key: 'host', label: 'Host', description: 'Bind address', disabled: true },\n { type: 'text', key: 'dataPath', label: 'Data Path', description: 'Root data directory', disabled: true, span: 2 },\n ],\n },\n {\n id: 'system-config-auth',\n title: SECTION_TITLES['auth']!,\n description: 'Token and session settings.',\n columns: 1,\n fields: [\n {\n type: 'text',\n key: 'tokenExpiry',\n label: 'Token Expiry',\n description: 'JWT token lifetime (e.g. 24h, 7d, 1h)',\n placeholder: '24h',\n default: '24h',\n },\n ],\n },\n {\n id: 'system-config-ffmpeg',\n title: SECTION_TITLES['ffmpeg']!,\n description: 'FFmpeg binary and hardware acceleration settings.',\n columns: 2,\n fields: [\n {\n type: 'text',\n key: 'binaryPath',\n label: 'Binary Path',\n description: 'Path to ffmpeg executable',\n placeholder: 'ffmpeg',\n default: 'ffmpeg',\n span: 2,\n },\n {\n type: 'select',\n key: 'hwAccel',\n label: 'Hardware Acceleration',\n description: 'GPU decoding/encoding backend',\n default: 'auto',\n options: [\n { value: 'auto', label: 'Auto-detect' },\n { value: 'none', label: 'None (CPU only)' },\n { value: 'videotoolbox', label: 'VideoToolbox (macOS)' },\n { value: 'vaapi', label: 'VA-API (Linux Intel/AMD)' },\n { value: 'qsv', label: 'QSV (Intel Quick Sync)' },\n { value: 'cuda', label: 'CUDA (NVIDIA)' },\n ],\n },\n {\n type: 'number',\n key: 'threadCount',\n label: 'Thread Count',\n description: '0 = auto (let FFmpeg decide)',\n min: 0,\n max: 16,\n step: 1,\n default: 0,\n },\n ],\n },\n ],\n }\n }\n\n async getGlobalSettings(): Promise<ConfigUISchemaWithValues> {\n const schema = this.buildGlobalSchema()\n if (!this.ctx.settings) {\n return hydrateSchema(schema, {})\n }\n\n // Read all yml sections in parallel and merge their values into a\n // single flat record keyed by field name. The `KEY_TO_SECTION` map\n // guarantees uniqueness (each key belongs to exactly one section).\n const sectionNames = Object.keys(SECTION_TITLES)\n const merged: Record<string, unknown> = {}\n for (const section of sectionNames) {\n try {\n const values = await this.ctx.settings!.getSection(section)\n Object.assign(merged, values)\n } catch (err: unknown) {\n const msg = errMsg(err)\n this.ctx.logger.debug('Failed to read section', { meta: { section, error: msg } })\n }\n }\n\n return hydrateSchema(schema, merged)\n }\n\n async updateGlobalSettings(patch: Record<string, unknown>): Promise<void> {\n if (!this.ctx.settings) {\n throw new Error('system-config: ctx.settings not available — cannot write sections')\n }\n\n // Group the patch keys by the section they belong to, then write\n // each section in one shot via ctx.settings.setSection(). The\n // server section is read-only, so any keys targeting it are skipped\n // with a warning.\n const perSection = new Map<string, Record<string, unknown>>()\n for (const [key, value] of Object.entries(patch)) {\n const section = KEY_TO_SECTION[key]\n if (section === undefined) {\n this.ctx.logger.warn('update ignored unknown key (not mapped to any section)', { meta: { key } })\n continue\n }\n if (section === 'server') {\n this.ctx.logger.warn('update ignored read-only server-section key', { meta: { key } })\n continue\n }\n let bucket = perSection.get(section)\n if (!bucket) {\n bucket = {}\n perSection.set(section, bucket)\n }\n bucket[key] = value\n }\n\n for (const [section, sectionPatch] of perSection) {\n await this.ctx.settings!.setSection(section, sectionPatch)\n this.ctx.logger.info('Wrote keys to section', { meta: { section, keyCount: Object.keys(sectionPatch).length } })\n }\n }\n}\n\nexport default SystemConfigAddon\n"],"mappings":"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","names":[]}
@@ -0,0 +1,10 @@
1
+ import "../../chunk-W6RTHQGP.mjs";
2
+ import {
3
+ SystemConfigAddon,
4
+ system_config_addon_default
5
+ } from "../../chunk-QT57H266.mjs";
6
+ export {
7
+ SystemConfigAddon,
8
+ system_config_addon_default as default
9
+ };
10
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/builtins/system-config/system-config.addon.ts
21
+ var system_config_addon_exports = {};
22
+ __export(system_config_addon_exports, {
23
+ SystemConfigAddon: () => SystemConfigAddon,
24
+ default: () => system_config_addon_default
25
+ });
26
+ module.exports = __toCommonJS(system_config_addon_exports);
27
+ var import_types = require("@camstack/types");
28
+ var SECTION_TITLES = {
29
+ server: "Server",
30
+ auth: "Authentication",
31
+ ffmpeg: "FFmpeg"
32
+ };
33
+ var KEY_TO_SECTION = {
34
+ // server (read-only)
35
+ port: "server",
36
+ host: "server",
37
+ dataPath: "server",
38
+ // auth
39
+ tokenExpiry: "auth",
40
+ // ffmpeg
41
+ binaryPath: "ffmpeg",
42
+ hwAccel: "ffmpeg",
43
+ threadCount: "ffmpeg"
44
+ };
45
+ var SystemConfigAddon = class extends import_types.BaseAddon {
46
+ id = "system-config";
47
+ constructor() {
48
+ super({});
49
+ }
50
+ async onInitialize() {
51
+ this.ctx.logger.info("Initialized \u2014 exposes yml-backed sections via getGlobalSettings");
52
+ }
53
+ // ── Three-level settings API (only `getGlobalSettings` is implemented) ──
54
+ buildGlobalSchema() {
55
+ return {
56
+ sections: [
57
+ {
58
+ id: "system-config-server",
59
+ title: SECTION_TITLES["server"],
60
+ description: "Core server connection settings \u2014 read-only, loaded from config.yaml at bootstrap.",
61
+ columns: 2,
62
+ fields: [
63
+ {
64
+ type: "info",
65
+ key: "server-restart-note",
66
+ label: "Restart required",
67
+ content: "Server settings are read-only. Change them in config.yaml and restart.",
68
+ variant: "warning"
69
+ },
70
+ { type: "text", key: "port", label: "Port", description: "Listening port", disabled: true },
71
+ { type: "text", key: "host", label: "Host", description: "Bind address", disabled: true },
72
+ { type: "text", key: "dataPath", label: "Data Path", description: "Root data directory", disabled: true, span: 2 }
73
+ ]
74
+ },
75
+ {
76
+ id: "system-config-auth",
77
+ title: SECTION_TITLES["auth"],
78
+ description: "Token and session settings.",
79
+ columns: 1,
80
+ fields: [
81
+ {
82
+ type: "text",
83
+ key: "tokenExpiry",
84
+ label: "Token Expiry",
85
+ description: "JWT token lifetime (e.g. 24h, 7d, 1h)",
86
+ placeholder: "24h",
87
+ default: "24h"
88
+ }
89
+ ]
90
+ },
91
+ {
92
+ id: "system-config-ffmpeg",
93
+ title: SECTION_TITLES["ffmpeg"],
94
+ description: "FFmpeg binary and hardware acceleration settings.",
95
+ columns: 2,
96
+ fields: [
97
+ {
98
+ type: "text",
99
+ key: "binaryPath",
100
+ label: "Binary Path",
101
+ description: "Path to ffmpeg executable",
102
+ placeholder: "ffmpeg",
103
+ default: "ffmpeg",
104
+ span: 2
105
+ },
106
+ {
107
+ type: "select",
108
+ key: "hwAccel",
109
+ label: "Hardware Acceleration",
110
+ description: "GPU decoding/encoding backend",
111
+ default: "auto",
112
+ options: [
113
+ { value: "auto", label: "Auto-detect" },
114
+ { value: "none", label: "None (CPU only)" },
115
+ { value: "videotoolbox", label: "VideoToolbox (macOS)" },
116
+ { value: "vaapi", label: "VA-API (Linux Intel/AMD)" },
117
+ { value: "qsv", label: "QSV (Intel Quick Sync)" },
118
+ { value: "cuda", label: "CUDA (NVIDIA)" }
119
+ ]
120
+ },
121
+ {
122
+ type: "number",
123
+ key: "threadCount",
124
+ label: "Thread Count",
125
+ description: "0 = auto (let FFmpeg decide)",
126
+ min: 0,
127
+ max: 16,
128
+ step: 1,
129
+ default: 0
130
+ }
131
+ ]
132
+ }
133
+ ]
134
+ };
135
+ }
136
+ async getGlobalSettings() {
137
+ const schema = this.buildGlobalSchema();
138
+ if (!this.ctx.settings) {
139
+ return (0, import_types.hydrateSchema)(schema, {});
140
+ }
141
+ const sectionNames = Object.keys(SECTION_TITLES);
142
+ const merged = {};
143
+ for (const section of sectionNames) {
144
+ try {
145
+ const values = await this.ctx.settings.getSection(section);
146
+ Object.assign(merged, values);
147
+ } catch (err) {
148
+ const msg = (0, import_types.errMsg)(err);
149
+ this.ctx.logger.debug("Failed to read section", { meta: { section, error: msg } });
150
+ }
151
+ }
152
+ return (0, import_types.hydrateSchema)(schema, merged);
153
+ }
154
+ async updateGlobalSettings(patch) {
155
+ if (!this.ctx.settings) {
156
+ throw new Error("system-config: ctx.settings not available \u2014 cannot write sections");
157
+ }
158
+ const perSection = /* @__PURE__ */ new Map();
159
+ for (const [key, value] of Object.entries(patch)) {
160
+ const section = KEY_TO_SECTION[key];
161
+ if (section === void 0) {
162
+ this.ctx.logger.warn("update ignored unknown key (not mapped to any section)", { meta: { key } });
163
+ continue;
164
+ }
165
+ if (section === "server") {
166
+ this.ctx.logger.warn("update ignored read-only server-section key", { meta: { key } });
167
+ continue;
168
+ }
169
+ let bucket = perSection.get(section);
170
+ if (!bucket) {
171
+ bucket = {};
172
+ perSection.set(section, bucket);
173
+ }
174
+ bucket[key] = value;
175
+ }
176
+ for (const [section, sectionPatch] of perSection) {
177
+ await this.ctx.settings.setSection(section, sectionPatch);
178
+ this.ctx.logger.info("Wrote keys to section", { meta: { section, keyCount: Object.keys(sectionPatch).length } });
179
+ }
180
+ }
181
+ };
182
+ var system_config_addon_default = SystemConfigAddon;
183
+ // Annotate the CommonJS export names for ESM import in node:
184
+ 0 && (module.exports = {
185
+ SystemConfigAddon
186
+ });
187
+ //# sourceMappingURL=system-config.addon.js.map