@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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/builtins/local-auth/local-auth.addon.ts","../../../src/auth/auth-manager.ts","../../../src/auth/user-manager.ts","../../../src/auth/parse-record.ts","../../../src/auth/api-key-manager.ts","../../../src/auth/scoped-token-manager.ts","../../../src/builtins/local-auth/auth-schema.ts"],"sourcesContent":["/**\n * Local Auth Addon — owns user accounts, API keys, scoped tokens,\n * and local password authentication.\n *\n * Capabilities registered:\n * - `auth-provider` (collection) — local password authentication\n * - `user-management` (singleton) — user CRUD, API keys, scoped tokens\n *\n * Extension: an OIDC addon can register another `auth-provider` entry.\n * The server's login flow iterates the `auth-provider` collection.\n */\nimport type { IUserManagementProvider, ProviderRegistration } from '@camstack/types'\nimport { BaseAddon, authProviderCapability, userManagementCapability } from '@camstack/types'\nimport { AuthManager } from '../../auth/auth-manager.js'\nimport { UserManager, type UserRecord, type UserStorageAccess } from '../../auth/user-manager.js'\nimport { ApiKeyManager, type ApiKeyStorageAccess } from '../../auth/api-key-manager.js'\nimport { ScopedTokenManager } from '../../auth/scoped-token-manager.js'\nimport { declareAuthSchema } from './auth-schema.js'\n\n// ── Auth result (mirrors auth-provider cap) ─────────────────────────\n\ninterface AuthResult {\n readonly userId: string\n readonly username: string\n readonly email?: string\n readonly displayName?: string\n readonly roles?: readonly string[]\n}\n\nfunction toAuthResult(user: UserRecord): AuthResult {\n return {\n userId: user.id,\n username: user.username,\n displayName: user.username,\n roles: [user.role],\n }\n}\n\n// ── Config reader shim ──────────────────────────────────────────────\n\ninterface LocalAuthConfig {\n jwtSecret?: string\n adminUsername?: string\n adminPassword?: string\n}\n\nexport class LocalAuthAddon extends BaseAddon<LocalAuthConfig> {\n private authManager: AuthManager | null = null\n private userManager: UserManager | null = null\n private apiKeyManager: ApiKeyManager | null = null\n private scopedTokenManager: ScopedTokenManager | null = null\n\n constructor() { super({ jwtSecret: '', adminUsername: '', adminPassword: '' }) }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n // Read auth section from the global settings store (config.yaml + env\n // overrides + SQL). Addon-level config (`this.config`) is kept as a\n // fallback for isolated/test environments where ctx.settings is absent.\n const authSection = (await this.ctx.settings?.getSection('auth')) ?? {}\n const resolvedJwtSecret = typeof authSection['jwtSecret'] === 'string'\n ? authSection['jwtSecret']\n : (this.config.jwtSecret ?? '')\n const resolvedAdminUser = typeof authSection['adminUsername'] === 'string' && authSection['adminUsername']\n ? authSection['adminUsername']\n : (this.config.adminUsername ?? '')\n const resolvedAdminPass = typeof authSection['adminPassword'] === 'string' && authSection['adminPassword']\n ? authSection['adminPassword']\n : (this.config.adminPassword ?? '')\n\n const reader = {\n get<T>(path: string): T {\n if (path === 'auth.jwtSecret') return resolvedJwtSecret as unknown as T\n if (path === 'auth.adminUsername') return resolvedAdminUser as unknown as T\n if (path === 'auth.adminPassword') return resolvedAdminPass as unknown as T\n return undefined as unknown as T\n },\n update: (_section: string, data: Record<string, unknown>): void => {\n if (typeof data['jwtSecret'] === 'string') {\n void this.ctx.settings?.setSection('auth', { jwtSecret: data['jwtSecret'] })\n }\n },\n }\n\n this.authManager = new AuthManager(reader, this.ctx.logger)\n\n const store = this.ctx.api?.settingsStore\n if (store) {\n // Own the schema. Same pattern as `pipeline-analytics` — each\n // addon declares its typed collections in `onInitialize`. The\n // backend just exposes the `declareCollection` primitive and\n // doesn't carry domain knowledge about auth tables.\n await declareAuthSchema(store)\n\n const storageAccess: UserStorageAccess & ApiKeyStorageAccess = {\n getStore() { return store },\n }\n this.userManager = new UserManager(storageAccess, this.authManager, reader)\n this.apiKeyManager = new ApiKeyManager(storageAccess, this.authManager)\n this.scopedTokenManager = new ScopedTokenManager(store)\n try {\n await this.userManager.ensureAdminExists()\n } catch (err: unknown) {\n // Surface the underlying cause loudly: AddonRegistry catches\n // and skips on throw, leaving auth.login to return the opaque\n // \"User management not available\" message at runtime. The\n // wrap below makes the actual fault (settings-store query\n // shape regression, sqlite migration glitch, …) visible in\n // both the addon error log and the user-facing error chain.\n const detail = err instanceof Error ? err.message : String(err)\n throw new Error(\n 'local-auth bootstrap failed: ensureAdminExists threw before '\n + '`user-management` could be registered. Most likely a '\n + '`users` collection schema mismatch in the settings-store. '\n + `Underlying: ${detail}`,\n { cause: err },\n )\n }\n } else {\n // Hard fail rather than register a no-op cap that throws\n // \"User management not available\" on every call later — the\n // operator sees the boot-time error instead of a confused login\n // panel. Local-auth without a settings store cannot persist\n // anything, so registering the cap would be a lie.\n throw new Error(\n 'local-auth: settings-store API not available — refusing to '\n + 'register `user-management` cap. Check that `sqlite-storage` '\n + 'addon initialized before `local-auth`.',\n )\n }\n\n // ── auth-provider capability (collection) ──────────────────────\n\n const authProvider = {\n validateCredentials: async (input: { username: string; password: string }): Promise<AuthResult | null> => {\n if (!this.userManager) return null\n const user = await this.userManager.validateCredentials(input.username, input.password)\n return user ? toAuthResult(user) : null\n },\n getLoginUrl: async (): Promise<string> => {\n throw new Error('local-auth: URL-based login not supported')\n },\n handleCallback: async (): Promise<AuthResult> => {\n throw new Error('local-auth: URL-based login not supported')\n },\n validateToken: async (input: { token: string }): Promise<AuthResult | null> => {\n if (!this.authManager || !this.userManager) return null\n try {\n const payload = this.authManager.verifyToken(input.token)\n const userId = payload.userId\n if (!userId) return null\n const user = await this.userManager.findById(userId)\n return user ? toAuthResult(user) : null\n } catch {\n return null\n }\n },\n }\n\n // ── user-management capability (singleton) ─────────────────────\n\n const userMgmt: IUserManagementProvider = {\n listUsers: async () => {\n if (!this.userManager) return []\n return this.userManager.listAll()\n },\n createUser: async (input) => {\n if (!this.userManager) throw new Error('User management not available')\n const record = await this.userManager.create(input)\n const { passwordHash: _, ...summary } = record\n return summary\n },\n updateUser: async (input) => {\n if (!this.userManager) throw new Error('User management not available')\n const { id, ...data } = input\n await this.userManager.update(id, data)\n return { success: true as const }\n },\n deleteUser: async (input) => {\n if (!this.userManager) throw new Error('User management not available')\n await this.userManager.delete(input.id)\n return { success: true as const }\n },\n resetPassword: async (input) => {\n if (!this.userManager) throw new Error('User management not available')\n await this.userManager.resetPassword(input.id, input.newPassword)\n return { success: true as const }\n },\n validateCredentials: async (input) => {\n if (!this.userManager) return null\n const user = await this.userManager.validateCredentials(input.username, input.password)\n return user ?? null\n },\n listApiKeys: async () => {\n if (!this.apiKeyManager) return []\n return this.apiKeyManager.listAll()\n },\n createApiKey: async (input) => {\n if (!this.apiKeyManager) throw new Error('API key management not available')\n const { record, token } = await this.apiKeyManager.create(input)\n const { tokenHash: _, ...summary } = record\n return { token, record: summary }\n },\n revokeApiKey: async (input) => {\n if (!this.apiKeyManager) throw new Error('API key management not available')\n await this.apiKeyManager.revoke(input.id)\n return { success: true as const }\n },\n validateApiKey: async (input) => {\n if (!this.apiKeyManager) return null\n const record = await this.apiKeyManager.validateToken(input.token)\n if (!record) return null\n const { tokenHash: _, ...summary } = record\n return summary\n },\n createScopedToken: async (input) => {\n if (!this.scopedTokenManager) throw new Error('Scoped token management not available')\n // userId comes from the tRPC context in production — for now\n // we use a placeholder. The server's login flow sets the real userId.\n const { token, record } = await this.scopedTokenManager.create('system', input.name, input.scopes, input.expiresAt)\n return { token, record }\n },\n revokeScopedToken: async (input) => {\n if (!this.scopedTokenManager) throw new Error('Scoped token management not available')\n await this.scopedTokenManager.revoke(input.id)\n return { success: true as const }\n },\n validateScopedToken: async (input) => {\n if (!this.scopedTokenManager) return null\n return this.scopedTokenManager.validate(input.token)\n },\n listScopedTokens: async (input) => {\n if (!this.scopedTokenManager) return []\n return this.scopedTokenManager.listForUser(input.userId)\n },\n }\n\n this.ctx.logger.info('registered auth-provider + user-management capabilities')\n return [\n { capability: authProviderCapability, provider: authProvider },\n { capability: userManagementCapability, provider: userMgmt },\n ]\n }\n\n protected async onShutdown(): Promise<void> {\n this.authManager = null\n this.userManager = null\n this.apiKeyManager = null\n this.scopedTokenManager = null\n }\n}\n\nexport default LocalAuthAddon\n","import * as jwt from 'jsonwebtoken'\nimport * as bcrypt from 'bcryptjs'\nimport * as crypto from 'node:crypto'\nimport type { ScopedToken, UserRole, TokenPayload, IScopedLogger } from '@camstack/types'\nimport type { ScopedTokenManager } from './scoped-token-manager.js'\n\n/** Minimal no-op logger for default parameter */\nconst noopLogger: IScopedLogger = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n child() { return noopLogger },\n withTags() { return noopLogger },\n}\n\nexport type { UserRole, TokenPayload }\n\nexport type AuthConfigReader = {\n get<T>(path: string): T\n update(section: string, data: Record<string, unknown>): void\n}\n\nexport class AuthManager {\n private readonly jwtSecret: string\n private scopedTokenManager: ScopedTokenManager | null = null\n private readonly logger: IScopedLogger\n\n constructor(private readonly config: AuthConfigReader, logger: IScopedLogger = noopLogger) {\n this.logger = logger\n const configured = this.config.get<string>('auth.jwtSecret')\n if (configured) {\n this.jwtSecret = configured\n } else {\n const secret = crypto.randomBytes(32).toString('hex')\n // Persist directly into config.yaml so it survives restarts\n this.config.update('auth', { jwtSecret: secret })\n this.logger.info('Generated JWT secret and saved to config.yaml (auth.jwtSecret)')\n this.jwtSecret = secret\n }\n }\n\n signToken(payload: Omit<TokenPayload, 'iat' | 'exp'>): string {\n return jwt.sign({ ...payload }, this.jwtSecret, { expiresIn: '24h' })\n }\n\n verifyToken(token: string): TokenPayload {\n return jwt.verify(token, this.jwtSecret) as TokenPayload\n }\n\n async hashPassword(password: string): Promise<string> {\n return bcrypt.hash(password, 10)\n }\n\n async comparePassword(password: string, hash: string): Promise<boolean> {\n return bcrypt.compare(password, hash)\n }\n\n generateApiKey(): { token: string; hash: string; prefix: string } {\n const token = crypto.randomBytes(32).toString('hex')\n const hash = crypto.createHash('sha256').update(token).digest('hex')\n const prefix = token.slice(0, 8)\n return { token, hash, prefix }\n }\n\n validateApiKey(token: string, storedHash: string): boolean {\n const hash = crypto.createHash('sha256').update(token).digest('hex')\n return hash === storedHash\n }\n\n /**\n * Create a service token for agent/worker authentication.\n * Used when forking workers or when agents register.\n */\n createServiceToken(opts: {\n readonly agentId: string\n readonly role?: string\n readonly expiresIn?: string\n }): string {\n const payload: Record<string, unknown> = {\n userId: opts.agentId,\n username: opts.agentId,\n role: opts.role ?? 'agent',\n type: 'service',\n agentId: opts.agentId,\n allowedProviders: '*',\n allowedDevices: {},\n }\n const expiresIn = (opts.expiresIn ?? '24h') as jwt.SignOptions['expiresIn']\n return jwt.sign(payload as object, this.jwtSecret, { expiresIn })\n }\n\n /**\n * Set the scoped token manager for the auth chain.\n */\n setScopedTokenManager(manager: ScopedTokenManager): void {\n this.scopedTokenManager = manager\n }\n\n /**\n * Validate a scoped token string.\n * Returns the token record if valid, null otherwise.\n */\n async validateScopedToken(rawToken: string): Promise<ScopedToken | null> {\n if (!this.scopedTokenManager) {\n return null\n }\n return this.scopedTokenManager.validate(rawToken)\n }\n\n /**\n * Check whether a scoped token grants access to a given addon/route/capability.\n */\n matchesScopedTokenScope(\n token: ScopedToken,\n addonId?: string,\n routePath?: string,\n capability?: string,\n ): boolean {\n if (!this.scopedTokenManager) {\n return false\n }\n return this.scopedTokenManager.matchesScope(token, addonId, routePath, capability)\n }\n}\n","import * as crypto from 'node:crypto'\nimport type { AuthManager } from './auth-manager.js'\nimport { UserRecordSchema } from '@camstack/types'\nimport type { UserRecord, UserRole, SettingsStoreClient } from '@camstack/types'\nimport { parseRecord } from './parse-record.js'\n\nexport type { UserRecord }\n\ninterface CreateUserInput {\n username: string\n password: string\n role: UserRole\n allowedProviders?: string[] | '*'\n allowedDevices?: Record<string, string[] | '*'>\n}\n\ntype UpdatableUserFields = Partial<Pick<UserRecord, 'role' | 'allowedProviders' | 'allowedDevices'>>\n\nconst USERS_COLLECTION = 'users'\n\nexport interface UserStorageAccess {\n getStore(): SettingsStoreClient\n}\n\nexport interface UserConfigReader {\n get<T>(path: string): T\n}\n\nfunction parseUser(data: Record<string, unknown>): UserRecord {\n return parseRecord('user', UserRecordSchema, data)\n}\n\nexport class UserManager {\n constructor(\n private readonly storageAccess: UserStorageAccess,\n private readonly auth: AuthManager,\n private readonly config: UserConfigReader,\n ) {}\n\n private get store(): SettingsStoreClient {\n return this.storageAccess.getStore()\n }\n\n async create(input: CreateUserInput): Promise<UserRecord> {\n const existing = await this.findByUsername(input.username)\n if (existing) throw new Error(`User with username \"${input.username}\" already exists`)\n\n const passwordHash = await this.auth.hashPassword(input.password)\n const now = Date.now()\n const record: UserRecord = {\n id: crypto.randomUUID(),\n username: input.username,\n passwordHash,\n role: input.role,\n allowedProviders: input.allowedProviders ?? '*',\n allowedDevices: input.allowedDevices ?? {},\n createdAt: now,\n updatedAt: now,\n }\n\n await this.store.insert.mutate({ collection: USERS_COLLECTION, record: { id: record.id, data: { ...record } } })\n return record\n }\n\n async findByUsername(username: string): Promise<UserRecord | null> {\n const results = await this.store.query.query({ collection: USERS_COLLECTION, filter: { where: { username } } })\n if (results.length === 0) return null\n return parseUser(results[0]!.data)\n }\n\n async findById(id: string): Promise<UserRecord | null> {\n const results = await this.store.query.query({ collection: USERS_COLLECTION, filter: { where: { id } } })\n if (results.length === 0) return null\n return parseUser(results[0]!.data)\n }\n\n async validateCredentials(username: string, password: string): Promise<UserRecord | null> {\n const user = await this.findByUsername(username)\n if (!user) return null\n const valid = await this.auth.comparePassword(password, user.passwordHash)\n return valid ? user : null\n }\n\n async listAll(): Promise<Omit<UserRecord, 'passwordHash'>[]> {\n const results = await this.store.query.query({ collection: USERS_COLLECTION })\n return results.map((r) => {\n const parsed = parseUser(r.data)\n const { passwordHash: _ph, ...rest } = parsed\n return rest\n })\n }\n\n async update(id: string, data: UpdatableUserFields): Promise<void> {\n const existing = await this.findById(id)\n if (!existing) throw new Error(`User with id \"${id}\" not found`)\n await this.store.update.mutate({ collection: USERS_COLLECTION, id, data: { ...existing, ...data, updatedAt: Date.now() } })\n }\n\n async delete(id: string): Promise<void> {\n await this.store.delete.mutate({ collection: USERS_COLLECTION, key: id })\n }\n\n async resetPassword(id: string, newPassword: string): Promise<void> {\n const existing = await this.findById(id)\n if (!existing) throw new Error(`User with id \"${id}\" not found`)\n const passwordHash = await this.auth.hashPassword(newPassword)\n await this.store.update.mutate({ collection: USERS_COLLECTION, id, data: { ...existing, passwordHash, updatedAt: Date.now() } })\n }\n\n async ensureAdminExists(): Promise<void> {\n const adminUsername = this.config.get<string>('auth.adminUsername')\n const adminPassword = this.config.get<string>('auth.adminPassword')\n if (!adminUsername || !adminPassword) return\n const existing = await this.findByUsername(adminUsername)\n if (existing) {\n // Admin already exists — sync the password if the configured one has\n // changed. Comparison uses bcrypt.compare against the stored hash\n // (bcrypt includes the per-record salt). When they diverge, re-hash\n // and persist so the config.yaml / env-var is the source of truth.\n const matches = await this.auth.comparePassword(adminPassword, existing.passwordHash)\n if (!matches) {\n await this.resetPassword(existing.id, adminPassword)\n }\n return\n }\n await this.create({ username: adminUsername, password: adminPassword, role: 'super_admin', allowedProviders: '*', allowedDevices: {} })\n }\n}\n","/**\n * Wrap a Zod schema parse with an actionable error message. The\n * settings-store regression that broke local-auth in May 2026 surfaced\n * as an opaque Zod error listing every required field as `undefined`\n * — confusing because the data IS in the DB, just exposed through the\n * wrong shape by `queryDeclared`. This helper makes the failure mode\n * obvious: it explicitly mentions the kind, the offending value's top-\n * level keys, and a hint about the most common cause.\n *\n * `schema` is typed loosely (any with a `parse` method) to avoid the\n * generic ZodTypeAny variance error across Zod major versions when this\n * helper is reused by callers that import schemas from `@camstack/types`.\n */\ninterface ZodLikeSchema<T> {\n parse(data: unknown): T\n}\n\nexport function parseRecord<T>(\n kind: string,\n schema: ZodLikeSchema<T>,\n data: unknown,\n): T {\n try {\n return schema.parse(data)\n } catch (err: unknown) {\n const topKeys = data && typeof data === 'object' && !Array.isArray(data)\n ? Object.keys(data as Record<string, unknown>).join(', ') || '(empty)'\n : `(non-object: ${typeof data})`\n const detail = err instanceof Error ? err.message : String(err)\n const looksDoubleWrapped =\n data\n && typeof data === 'object'\n && !Array.isArray(data)\n && Object.keys(data as Record<string, unknown>).length === 1\n && 'data' in (data as Record<string, unknown>)\n const hint = looksDoubleWrapped\n ? ' This shape is the legacy double-wrap from a stale settings-store query path. Restart the server with the latest @camstack/core build.'\n : ''\n throw new Error(\n `Failed to parse ${kind} from settings store. Top-level keys=[${topKeys}].${hint} Underlying: ${detail}`,\n { cause: err },\n )\n }\n}\n","import * as crypto from 'node:crypto'\nimport type { AuthManager } from './auth-manager.js'\nimport { ApiKeyRecordSchema } from '@camstack/types'\nimport type { ApiKeyRecord, UserRole, SettingsStoreClient } from '@camstack/types'\nimport { parseRecord } from './parse-record.js'\n\nexport type { ApiKeyRecord }\n\ninterface CreateApiKeyInput {\n label: string\n role: UserRole\n allowedProviders?: string[] | '*'\n allowedDevices?: Record<string, string[] | '*'>\n}\n\nconst API_KEYS_COLLECTION = 'api_keys'\n\nexport interface ApiKeyStorageAccess {\n getStore(): SettingsStoreClient\n}\n\nfunction parseApiKey(data: Record<string, unknown>): ApiKeyRecord {\n return parseRecord('api-key', ApiKeyRecordSchema, data)\n}\n\nexport class ApiKeyManager {\n constructor(\n private readonly storageAccess: ApiKeyStorageAccess,\n private readonly auth: AuthManager,\n ) {}\n\n private get store(): SettingsStoreClient {\n return this.storageAccess.getStore()\n }\n\n async create(input: CreateApiKeyInput): Promise<{ record: ApiKeyRecord; token: string }> {\n const { token: rawToken, hash, prefix } = this.auth.generateApiKey()\n const now = Date.now()\n const record: ApiKeyRecord = {\n id: crypto.randomUUID(),\n label: input.label,\n role: input.role,\n allowedProviders: input.allowedProviders ?? '*',\n allowedDevices: input.allowedDevices ?? {},\n tokenHash: hash,\n tokenPrefix: prefix,\n createdAt: now,\n }\n\n await this.store.insert.mutate({ collection: API_KEYS_COLLECTION, record: { id: record.id, data: { ...record } } })\n return { record, token: rawToken }\n }\n\n async validateToken(token: string): Promise<ApiKeyRecord | null> {\n const allKeys = await this.store.query.query({ collection: API_KEYS_COLLECTION })\n for (const entry of allKeys) {\n const record = parseApiKey(entry.data)\n if (this.auth.validateApiKey(token, record.tokenHash)) {\n const updatedData: ApiKeyRecord = { ...record, lastUsedAt: Date.now() }\n await this.store.update.mutate({ collection: API_KEYS_COLLECTION, id: record.id, data: { ...updatedData } })\n return updatedData\n }\n }\n return null\n }\n\n async listAll(): Promise<Omit<ApiKeyRecord, 'tokenHash'>[]> {\n const results = await this.store.query.query({ collection: API_KEYS_COLLECTION })\n return results.map((r) => {\n const parsed = parseApiKey(r.data)\n const { tokenHash: _th, ...rest } = parsed\n return rest\n })\n }\n\n async revoke(id: string): Promise<void> {\n await this.store.delete.mutate({ collection: API_KEYS_COLLECTION, key: id })\n }\n\n async findById(id: string): Promise<ApiKeyRecord | null> {\n const results = await this.store.query.query({ collection: API_KEYS_COLLECTION, filter: { where: { id } } })\n if (results.length === 0) return null\n return parseApiKey(results[0]!.data)\n }\n}\n","import * as crypto from 'node:crypto'\nimport { ScopedTokenSchema } from '@camstack/types'\nimport type { ScopedToken, TokenScope, SettingsStoreClient } from '@camstack/types'\nimport { parseRecord } from './parse-record.js'\n\nconst TOKENS_COLLECTION = 'scoped_tokens'\nconst TOKEN_PREFIX = 'cst_'\n\nfunction parseToken(data: Record<string, unknown>): ScopedToken {\n return parseRecord('scoped-token', ScopedTokenSchema, data)\n}\n\n/**\n * Manages scoped API tokens with restricted addon/route/capability access.\n */\nexport class ScopedTokenManager {\n constructor(private readonly store: SettingsStoreClient) {}\n\n async create(\n userId: string,\n name: string,\n scopes: TokenScope[],\n expiresAt?: number,\n ): Promise<{ token: string; record: ScopedToken }> {\n const rawHex = crypto.randomBytes(32).toString('hex')\n const rawToken = `${TOKEN_PREFIX}${rawHex}`\n const tokenHash = crypto.createHash('sha256').update(rawToken).digest('hex')\n const tokenPrefix = rawToken.slice(0, 12)\n\n const record: ScopedToken = {\n id: crypto.randomUUID(),\n userId,\n name,\n tokenHash,\n tokenPrefix,\n scopes: scopes.map((s) => ({ ...s })),\n expiresAt,\n lastUsedAt: undefined,\n createdAt: Date.now(),\n }\n\n await this.store.insert.mutate({ collection: TOKENS_COLLECTION, record: { id: record.id, data: { ...record } } })\n return { token: rawToken, record }\n }\n\n async validate(rawToken: string): Promise<ScopedToken | null> {\n if (!rawToken.startsWith(TOKEN_PREFIX)) return null\n const tokenHash = crypto.createHash('sha256').update(rawToken).digest('hex')\n const results = await this.store.query.query({ collection: TOKENS_COLLECTION, filter: { where: { tokenHash } } })\n if (results.length === 0) return null\n\n const record = parseToken(results[0]!.data)\n if (record.expiresAt !== undefined && record.expiresAt !== null && Date.now() > record.expiresAt) return null\n this.updateLastUsed(record.id).catch(() => {})\n return record\n }\n\n matchesScope(token: ScopedToken, addonId?: string, routePath?: string, capability?: string): boolean {\n for (const scope of token.scopes) {\n switch (scope.type) {\n case 'addon': if (addonId && scope.target === addonId) return true; break\n case 'route-prefix': if (routePath && routePath.startsWith(scope.target)) return true; break\n case 'capability': if (capability && scope.target === capability) return true; break\n }\n }\n return false\n }\n\n async revoke(tokenId: string): Promise<void> {\n await this.store.delete.mutate({ collection: TOKENS_COLLECTION, key: tokenId })\n }\n\n async listForUser(userId: string): Promise<ScopedToken[]> {\n const results = await this.store.query.query({ collection: TOKENS_COLLECTION, filter: { where: { userId } } })\n return results.map((r) => parseToken(r.data))\n }\n\n async updateLastUsed(tokenId: string): Promise<void> {\n const results = await this.store.query.query({ collection: TOKENS_COLLECTION, filter: { where: { id: tokenId } } })\n if (results.length === 0) return\n const existing = parseToken(results[0]!.data)\n await this.store.update.mutate({ collection: TOKENS_COLLECTION, id: tokenId, data: { ...existing, lastUsedAt: Date.now() } })\n }\n}\n","/**\n * Auth-schema — typed SQL schemas for local-auth's three collections.\n *\n * Mirrors the pattern used by pipeline-analytics (`event-store.ts`,\n * `track-store.ts`): each addon owns its own schema, declared once\n * in `onInitialize` via `store.declareCollection.mutate(...)`.\n *\n * Collection names (no namespace — these are global, single-tenant\n * tables backing the auth layer):\n * - users\n * - api_keys\n * - scoped_tokens\n *\n * Schema migration: when the backend receives `declareCollection` for\n * a name that already exists with a legacy `(id, data)` KV shape, the\n * `ensureTable` helper drops the old table and recreates it. Existing\n * data is wiped — `ensureAdminExists` re-seeds the admin from\n * CAMSTACK_ADMIN_USER / CAMSTACK_ADMIN_PASS on the next boot.\n */\nimport type { SettingsStoreClient } from '@camstack/types'\n\nexport const USERS_COLLECTION = 'users'\nexport const API_KEYS_COLLECTION = 'api_keys'\nexport const SCOPED_TOKENS_COLLECTION = 'scoped_tokens'\n\nconst USERS_COLUMNS = [\n { name: 'id', type: 'TEXT' as const, primaryKey: true, notNull: true },\n { name: 'username', type: 'TEXT' as const, notNull: true, unique: true },\n { name: 'passwordHash', type: 'TEXT' as const, notNull: true },\n { name: 'role', type: 'TEXT' as const, notNull: true },\n { name: 'allowedProviders', type: 'JSON' as const },\n { name: 'allowedDevices', type: 'JSON' as const },\n { name: 'createdAt', type: 'INTEGER' as const, notNull: true },\n { name: 'updatedAt', type: 'INTEGER' as const, notNull: true },\n]\n\nconst API_KEYS_COLUMNS = [\n { name: 'id', type: 'TEXT' as const, primaryKey: true, notNull: true },\n { name: 'label', type: 'TEXT' as const, notNull: true },\n { name: 'role', type: 'TEXT' as const, notNull: true },\n { name: 'tokenHash', type: 'TEXT' as const, notNull: true, unique: true },\n { name: 'tokenPrefix', type: 'TEXT' as const, notNull: true },\n { name: 'allowedProviders', type: 'JSON' as const },\n { name: 'allowedDevices', type: 'JSON' as const },\n { name: 'createdAt', type: 'INTEGER' as const, notNull: true },\n { name: 'lastUsedAt', type: 'INTEGER' as const },\n]\n\nconst SCOPED_TOKENS_COLUMNS = [\n { name: 'id', type: 'TEXT' as const, primaryKey: true, notNull: true },\n { name: 'userId', type: 'TEXT' as const, notNull: true },\n { name: 'name', type: 'TEXT' as const, notNull: true },\n { name: 'tokenHash', type: 'TEXT' as const, notNull: true, unique: true },\n { name: 'tokenPrefix', type: 'TEXT' as const, notNull: true },\n { name: 'scopes', type: 'JSON' as const },\n { name: 'expiresAt', type: 'INTEGER' as const },\n { name: 'lastUsedAt', type: 'INTEGER' as const },\n { name: 'createdAt', type: 'INTEGER' as const, notNull: true },\n]\n\n/**\n * Declare every auth collection with the typed schema. Called by\n * `LocalAuthAddon.onInitialize` before constructing the per-collection\n * managers. Idempotent — `declareCollection` is a no-op when the\n * collection has already been declared (same shape).\n */\nexport async function declareAuthSchema(store: SettingsStoreClient): Promise<void> {\n await store.declareCollection.mutate({\n collection: USERS_COLLECTION,\n columns: USERS_COLUMNS,\n })\n await store.declareCollection.mutate({\n collection: API_KEYS_COLLECTION,\n columns: API_KEYS_COLUMNS,\n indexes: [\n { name: 'idx_api_keys_token_prefix', columns: ['tokenPrefix'] },\n ],\n })\n await store.declareCollection.mutate({\n collection: SCOPED_TOKENS_COLLECTION,\n columns: SCOPED_TOKENS_COLUMNS,\n indexes: [\n { name: 'idx_scoped_tokens_user_id', columns: ['userId'] },\n ],\n })\n}\n"],"mappings":"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","names":["import_types","hash","crypto","crypto","import_types","hash","crypto","import_types","USERS_COLLECTION","API_KEYS_COLLECTION"]}
@@ -0,0 +1,9 @@
1
+ import {
2
+ LocalAuthAddon,
3
+ local_auth_addon_default
4
+ } from "../../chunk-3BK2Y7GY.mjs";
5
+ export {
6
+ LocalAuthAddon,
7
+ local_auth_addon_default as default
8
+ };
9
+ //# sourceMappingURL=local-auth.addon.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -38,20 +38,23 @@ module.exports = __toCommonJS(local_backup_exports);
38
38
 
39
39
  // src/builtins/local-backup/local-backup.ts
40
40
  var import_node_crypto = require("crypto");
41
+ var import_types = require("@camstack/types");
41
42
  var LocalBackupService = class {
42
- constructor(config, logger, eventBus, storage) {
43
+ constructor(config, logger, eventBus) {
43
44
  this.config = config;
44
45
  this.logger = logger;
45
46
  this.eventBus = eventBus;
46
- this.storage = storage;
47
47
  }
48
+ config;
49
+ logger;
50
+ eventBus;
48
51
  manifests = [];
49
52
  /** Create a backup of specified locations */
50
53
  async backup(options) {
51
54
  const id = (0, import_node_crypto.randomUUID)();
52
55
  const timestamp = Date.now();
53
56
  const locations = options?.locations ?? ["config", "events", "logs"];
54
- this.logger.info(`Starting backup ${id} (${locations.join(", ")})`);
57
+ this.logger.info("Starting backup", { meta: { backupId: id, locations } });
55
58
  const manifest = {
56
59
  id,
57
60
  timestamp,
@@ -67,22 +70,22 @@ var LocalBackupService = class {
67
70
  id: (0, import_node_crypto.randomUUID)(),
68
71
  timestamp: /* @__PURE__ */ new Date(),
69
72
  source: { type: "addon", id: "local-backup" },
70
- category: "backup.completed",
73
+ category: import_types.EventCategory.BackupCompleted,
71
74
  data: { backupId: id, locations: [...locations], sizeMB: manifest.sizeMB }
72
75
  });
73
- this.logger.info(`Backup ${id} completed`);
76
+ this.logger.info("Backup completed", { meta: { backupId: id } });
74
77
  return manifest;
75
78
  }
76
79
  /** Restore from a backup */
77
80
  async restore(backupId) {
78
81
  const manifest = this.manifests.find((m) => m.id === backupId);
79
82
  if (!manifest) throw new Error(`Backup ${backupId} not found`);
80
- this.logger.info(`Restoring from backup ${backupId}`);
83
+ this.logger.info("Restoring from backup", { meta: { backupId } });
81
84
  this.eventBus.emit({
82
85
  id: (0, import_node_crypto.randomUUID)(),
83
86
  timestamp: /* @__PURE__ */ new Date(),
84
87
  source: { type: "addon", id: "local-backup" },
85
- category: "backup.restored",
88
+ category: import_types.EventCategory.BackupRestored,
86
89
  data: { backupId }
87
90
  });
88
91
  }
@@ -99,7 +102,7 @@ var LocalBackupService = class {
99
102
  while (sorted.length > this.config.retentionCount) {
100
103
  const oldest = sorted.shift();
101
104
  if (oldest) {
102
- this.logger.info(`Pruning old backup ${oldest.id}`);
105
+ this.logger.info("Pruning old backup", { meta: { backupId: oldest.id } });
103
106
  this.manifests = this.manifests.filter((m) => m.id !== oldest.id);
104
107
  }
105
108
  }
@@ -108,76 +111,58 @@ var LocalBackupService = class {
108
111
 
109
112
  // src/builtins/local-backup/local-backup.addon.ts
110
113
  var path = __toESM(require("path"));
111
- var LocalBackupAddon = class {
112
- manifest = {
113
- id: "local-backup",
114
- name: "Local Backup",
115
- version: "1.0.0",
116
- capabilities: ["backup"]
117
- };
114
+ var import_types2 = require("@camstack/types");
115
+ var LocalBackupAddon = class extends import_types2.BaseAddon {
118
116
  service = null;
119
- currentConfig = {
120
- retentionCount: 7
121
- };
122
- async initialize(context) {
123
- this.currentConfig = {
124
- retentionCount: context.addonConfig.retentionCount ?? this.currentConfig.retentionCount
125
- };
117
+ constructor() {
118
+ super({ retentionCount: 7 });
119
+ }
120
+ async onInitialize() {
126
121
  const backupConfig = {
127
- backupDir: path.join(context.locationPaths.data, "backups"),
128
- retentionCount: this.currentConfig.retentionCount
122
+ backupDir: path.join(
123
+ await this.ctx.api.storage.resolve.query({ location: "data", relativePath: "" }).catch(() => "camstack-data"),
124
+ "backups"
125
+ ),
126
+ retentionCount: this.config.retentionCount
127
+ };
128
+ this.service = new LocalBackupService(backupConfig, this.ctx.logger, this.ctx.eventBus);
129
+ const backupProvider = {
130
+ trigger: (input) => this.service.backup(input),
131
+ list: () => this.service.list(),
132
+ restore: (input) => this.service.restore(input.backupId),
133
+ delete: (input) => this.service.delete(input.backupId)
129
134
  };
130
- this.service = new LocalBackupService(
131
- backupConfig,
132
- context.logger,
133
- context.eventBus,
134
- context.storage
135
- );
136
- context.logger.info("Local Backup initialized");
135
+ this.ctx.logger.info("Local Backup initialized");
136
+ return [{ capability: import_types2.backupCapability, provider: backupProvider }];
137
137
  }
138
- async shutdown() {
138
+ async onShutdown() {
139
139
  this.service = null;
140
140
  }
141
141
  getService() {
142
142
  if (!this.service) throw new Error("Local Backup not initialized");
143
143
  return this.service;
144
144
  }
145
- getCapabilityProvider(name) {
146
- if (name === "backup" && this.service) {
147
- return this.service;
148
- }
149
- return null;
150
- }
151
- getConfigSchema() {
152
- return {
153
- sections: [
154
- {
155
- id: "backup-retention",
156
- title: "Backup Retention",
157
- description: "How many local backup snapshots to keep on disk.",
158
- columns: 1,
159
- fields: [
160
- {
161
- type: "number",
162
- key: "retentionCount",
163
- label: "Retention Count",
164
- description: "Number of backup snapshots to keep before deleting the oldest",
165
- min: 1,
166
- max: 100,
167
- step: 1
168
- }
169
- ]
170
- }
171
- ]
172
- };
173
- }
174
- getConfig() {
175
- return { ...this.currentConfig };
176
- }
177
- async onConfigChange(config) {
178
- this.currentConfig = {
179
- retentionCount: config.retentionCount ?? this.currentConfig.retentionCount
180
- };
145
+ globalSettingsSchema() {
146
+ return this.schema({
147
+ sections: [{
148
+ id: "backup-retention",
149
+ title: "Backup Retention",
150
+ description: "How many local backup snapshots to keep on disk.",
151
+ columns: 1,
152
+ fields: [
153
+ this.field({
154
+ type: "number",
155
+ key: "retentionCount",
156
+ label: "Retention Count",
157
+ description: "Number of backup snapshots to keep before deleting the oldest",
158
+ min: 1,
159
+ max: 100,
160
+ step: 1,
161
+ default: 7
162
+ })
163
+ ]
164
+ }]
165
+ });
181
166
  }
182
167
  };
183
168
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/builtins/local-backup/index.ts","../../../src/builtins/local-backup/local-backup.ts","../../../src/builtins/local-backup/local-backup.addon.ts"],"sourcesContent":["export { LocalBackupService } from './local-backup'\nexport type { BackupManifest, BackupConfig } from './local-backup'\nexport { LocalBackupAddon } from './local-backup.addon'\nexport { LocalBackupAddon as default } from './local-backup.addon'\n","import { randomUUID } from 'node:crypto'\nimport type { IScopedLogger, IEventBus, IStorageLocation, BackupManifest } from '@camstack/types'\n\nexport type { BackupManifest }\n\nexport interface BackupConfig {\n readonly backupDir: string\n readonly retentionCount: number\n}\n\nexport class LocalBackupService {\n private manifests: BackupManifest[] = []\n\n constructor(\n private readonly config: BackupConfig,\n private readonly logger: IScopedLogger,\n private readonly eventBus: IEventBus,\n private readonly storage: IStorageLocation,\n ) {}\n\n /** Create a backup of specified locations */\n async backup(options?: {\n locations?: string[]\n label?: string\n }): Promise<BackupManifest> {\n const id = randomUUID()\n const timestamp = Date.now()\n const locations = options?.locations ?? ['config', 'events', 'logs']\n\n this.logger.info(`Starting backup ${id} (${locations.join(', ')})`)\n\n const manifest: BackupManifest = {\n id,\n timestamp,\n label: options?.label,\n locations,\n sizeMB: 0,\n path: `${this.config.backupDir}/${id}`,\n }\n\n const updated = [...this.manifests, manifest]\n this.manifests = updated\n\n await this.pruneOldBackups()\n\n this.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'addon', id: 'local-backup' },\n category: 'backup.completed',\n data: { backupId: id, locations: [...locations], sizeMB: manifest.sizeMB },\n })\n\n this.logger.info(`Backup ${id} completed`)\n return manifest\n }\n\n /** Restore from a backup */\n async restore(backupId: string): Promise<void> {\n const manifest = this.manifests.find((m) => m.id === backupId)\n if (!manifest) throw new Error(`Backup ${backupId} not found`)\n\n this.logger.info(`Restoring from backup ${backupId}`)\n\n this.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'addon', id: 'local-backup' },\n category: 'backup.restored',\n data: { backupId },\n })\n }\n\n /** List all backups sorted by timestamp descending */\n list(): readonly BackupManifest[] {\n return [...this.manifests].sort((a, b) => b.timestamp - a.timestamp)\n }\n\n /** Delete a specific backup */\n async delete(backupId: string): Promise<void> {\n this.manifests = this.manifests.filter((m) => m.id !== backupId)\n }\n\n private async pruneOldBackups(): Promise<void> {\n const sorted = [...this.manifests].sort((a, b) => a.timestamp - b.timestamp)\n\n while (sorted.length > this.config.retentionCount) {\n const oldest = sorted.shift()\n if (oldest) {\n this.logger.info(`Pruning old backup ${oldest.id}`)\n this.manifests = this.manifests.filter((m) => m.id !== oldest.id)\n }\n }\n }\n}\n","import * as path from 'node:path'\nimport type {\n ICamstackAddon, AddonManifest, AddonContext,\n IConfigurable, ConfigUISchema, CapabilityProviderMap,\n} from '@camstack/types'\nimport { LocalBackupService } from './local-backup'\nimport type { BackupConfig } from './local-backup'\n\nexport class LocalBackupAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'local-backup',\n name: 'Local Backup',\n version: '1.0.0',\n capabilities: ['backup'],\n }\n\n private service: LocalBackupService | null = null\n private currentConfig = {\n retentionCount: 7,\n }\n\n async initialize(context: AddonContext): Promise<void> {\n this.currentConfig = {\n retentionCount: (context.addonConfig.retentionCount as number) ?? this.currentConfig.retentionCount,\n }\n const backupConfig: BackupConfig = {\n backupDir: path.join(context.locationPaths.data, 'backups'),\n retentionCount: this.currentConfig.retentionCount,\n }\n this.service = new LocalBackupService(\n backupConfig,\n context.logger,\n context.eventBus,\n context.storage,\n )\n context.logger.info('Local Backup initialized')\n }\n\n async shutdown(): Promise<void> {\n this.service = null\n }\n\n getService(): LocalBackupService {\n if (!this.service) throw new Error('Local Backup not initialized')\n return this.service\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'backup' as string && this.service) {\n return this.service as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'backup-retention',\n title: 'Backup Retention',\n description: 'How many local backup snapshots to keep on disk.',\n columns: 1,\n fields: [\n {\n type: 'number',\n key: 'retentionCount',\n label: 'Retention Count',\n description: 'Number of backup snapshots to keep before deleting the oldest',\n min: 1,\n max: 100,\n step: 1,\n },\n ],\n },\n ],\n }\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 = {\n retentionCount: (config.retentionCount as number) ?? this.currentConfig.retentionCount,\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/builtins/local-backup/index.ts","../../../src/builtins/local-backup/local-backup.ts","../../../src/builtins/local-backup/local-backup.addon.ts"],"sourcesContent":["export { LocalBackupService } from './local-backup.js'\nexport type { BackupManifest, BackupConfig } from './local-backup.js'\nexport { LocalBackupAddon } from './local-backup.addon.js'\nexport { LocalBackupAddon as default } from './local-backup.addon.js'\n","import { randomUUID } from 'node:crypto'\nimport { EventCategory } from '@camstack/types'\nimport type { IScopedLogger, IEventBus, BackupManifest } from '@camstack/types'\n\nexport type { BackupManifest }\n\nexport interface BackupConfig {\n readonly backupDir: string\n readonly retentionCount: number\n}\n\nexport class LocalBackupService {\n private manifests: BackupManifest[] = []\n\n constructor(\n private readonly config: BackupConfig,\n private readonly logger: IScopedLogger,\n private readonly eventBus: IEventBus,\n ) {}\n\n /** Create a backup of specified locations */\n async backup(options?: {\n locations?: string[]\n label?: string\n }): Promise<BackupManifest> {\n const id = randomUUID()\n const timestamp = Date.now()\n const locations = options?.locations ?? ['config', 'events', 'logs']\n\n this.logger.info('Starting backup', { meta: { backupId: id, locations } })\n\n const manifest: BackupManifest = {\n id,\n timestamp,\n label: options?.label,\n locations,\n sizeMB: 0,\n path: `${this.config.backupDir}/${id}`,\n }\n\n const updated = [...this.manifests, manifest]\n this.manifests = updated\n\n await this.pruneOldBackups()\n\n this.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'addon', id: 'local-backup' },\n category: EventCategory.BackupCompleted,\n data: { backupId: id, locations: [...locations], sizeMB: manifest.sizeMB },\n })\n\n this.logger.info('Backup completed', { meta: { backupId: id } })\n return manifest\n }\n\n /** Restore from a backup */\n async restore(backupId: string): Promise<void> {\n const manifest = this.manifests.find((m) => m.id === backupId)\n if (!manifest) throw new Error(`Backup ${backupId} not found`)\n\n this.logger.info('Restoring from backup', { meta: { backupId } })\n\n this.eventBus.emit({\n id: randomUUID(),\n timestamp: new Date(),\n source: { type: 'addon', id: 'local-backup' },\n category: EventCategory.BackupRestored,\n data: { backupId },\n })\n }\n\n /** List all backups sorted by timestamp descending */\n list(): readonly BackupManifest[] {\n return [...this.manifests].sort((a, b) => b.timestamp - a.timestamp)\n }\n\n /** Delete a specific backup */\n async delete(backupId: string): Promise<void> {\n this.manifests = this.manifests.filter((m) => m.id !== backupId)\n }\n\n private async pruneOldBackups(): Promise<void> {\n const sorted = [...this.manifests].sort((a, b) => a.timestamp - b.timestamp)\n\n while (sorted.length > this.config.retentionCount) {\n const oldest = sorted.shift()\n if (oldest) {\n this.logger.info('Pruning old backup', { meta: { backupId: oldest.id } })\n this.manifests = this.manifests.filter((m) => m.id !== oldest.id)\n }\n }\n }\n}\n","import * as path from 'node:path'\nimport type { ProviderRegistration } from '@camstack/types'\nimport { BaseAddon, backupCapability } from '@camstack/types'\nimport { LocalBackupService } from './local-backup.js'\nimport type { BackupConfig } from './local-backup.js'\n\ninterface LocalBackupAddonConfig {\n readonly retentionCount: number\n}\n\n/**\n * Local backup addon — snapshot-based backup/restore of camstack data.\n * Settings appear under Cluster → NodeDetail → Settings.\n */\nexport class LocalBackupAddon extends BaseAddon<LocalBackupAddonConfig> {\n private service: LocalBackupService | null = null\n\n constructor() {\n super({ retentionCount: 7 })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const backupConfig: BackupConfig = {\n backupDir: path.join(\n await this.ctx.api.storage.resolve.query({ location: 'data', relativePath: '' }).catch(() => 'camstack-data'),\n 'backups',\n ),\n retentionCount: this.config.retentionCount,\n }\n this.service = new LocalBackupService(backupConfig, this.ctx.logger, this.ctx.eventBus)\n const backupProvider = {\n trigger: (input?: { locations?: string[]; label?: string }) => this.service!.backup(input),\n list: () => this.service!.list(),\n restore: (input: { backupId: string }) => this.service!.restore(input.backupId),\n delete: (input: { backupId: string }) => this.service!.delete(input.backupId),\n }\n this.ctx.logger.info('Local Backup initialized')\n return [{ capability: backupCapability, provider: backupProvider }]\n }\n\n protected async onShutdown(): Promise<void> {\n this.service = null\n }\n\n getService(): LocalBackupService {\n if (!this.service) throw new Error('Local Backup not initialized')\n return this.service\n }\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [{\n id: 'backup-retention',\n title: 'Backup Retention',\n description: 'How many local backup snapshots to keep on disk.',\n columns: 1,\n fields: [\n this.field({\n type: 'number',\n key: 'retentionCount',\n label: 'Retention Count',\n description: 'Number of backup snapshots to keep before deleting the oldest',\n min: 1, max: 100, step: 1, default: 7,\n }),\n ],\n }],\n })\n }\n}\n"],"mappings":"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","names":["import_types"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  LocalBackupAddon,
3
3
  LocalBackupService
4
- } from "../../chunk-SO4LROOT.mjs";
4
+ } from "../../chunk-7FI7SQS7.mjs";
5
5
  export {
6
6
  LocalBackupAddon,
7
7
  LocalBackupService,