@camstack/core 0.1.2 → 0.1.4

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 (41) hide show
  1. package/dist/builtins/local-backup/index.mjs +10 -3
  2. package/dist/builtins/local-backup/index.mjs.map +1 -1
  3. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +5 -3
  4. package/dist/builtins/sqlite-storage/index.js +197 -95
  5. package/dist/builtins/sqlite-storage/index.js.map +1 -1
  6. package/dist/builtins/sqlite-storage/index.mjs +26 -7
  7. package/dist/builtins/sqlite-storage/index.mjs.map +1 -1
  8. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +4 -2
  9. package/dist/builtins/winston-logging/index.mjs +10 -3
  10. package/dist/builtins/winston-logging/index.mjs.map +1 -1
  11. package/dist/{chunk-QEMJH3KY.mjs → chunk-4JEXNFZZ.mjs} +11 -2
  12. package/dist/chunk-4YD6WMO6.mjs +207 -0
  13. package/dist/{chunk-SPA4JBKN.mjs.map → chunk-4YD6WMO6.mjs.map} +1 -1
  14. package/dist/chunk-CHFIH4G6.mjs +314 -0
  15. package/dist/{chunk-YXNXYYHL.mjs.map → chunk-CHFIH4G6.mjs.map} +1 -1
  16. package/dist/chunk-EFQ25JFE.mjs +689 -0
  17. package/dist/chunk-EFQ25JFE.mjs.map +1 -0
  18. package/dist/chunk-GBWW3JU4.mjs +180 -0
  19. package/dist/{chunk-SO4LROOT.mjs.map → chunk-GBWW3JU4.mjs.map} +1 -1
  20. package/dist/chunk-XSLBW5C2.mjs +177 -0
  21. package/dist/{chunk-LQFPAEQF.mjs.map → chunk-XSLBW5C2.mjs.map} +1 -1
  22. package/dist/index.js +14872 -11586
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +16119 -5933
  25. package/dist/index.mjs.map +1 -1
  26. package/package.json +2 -1
  27. package/dist/chunk-2F3XZYRW.mjs +0 -89
  28. package/dist/chunk-2F3XZYRW.mjs.map +0 -1
  29. package/dist/chunk-LQFPAEQF.mjs +0 -147
  30. package/dist/chunk-R3DIIBBX.mjs +0 -532
  31. package/dist/chunk-R3DIIBBX.mjs.map +0 -1
  32. package/dist/chunk-SO4LROOT.mjs +0 -150
  33. package/dist/chunk-SPA4JBKN.mjs +0 -175
  34. package/dist/chunk-YXNXYYHL.mjs +0 -282
  35. package/dist/dist-N7SR63RN.mjs +0 -3515
  36. package/dist/dist-N7SR63RN.mjs.map +0 -1
  37. package/dist/storage-location-manager-UQRGHTCA.mjs +0 -8
  38. package/dist/storage-location-manager-UQRGHTCA.mjs.map +0 -1
  39. package/dist/wrapper-Y55ADNM5.mjs +0 -3652
  40. package/dist/wrapper-Y55ADNM5.mjs.map +0 -1
  41. /package/dist/{chunk-QEMJH3KY.mjs.map → chunk-4JEXNFZZ.mjs.map} +0 -0
@@ -0,0 +1,207 @@
1
+ import {
2
+ __esm,
3
+ __export
4
+ } from "./chunk-4JEXNFZZ.mjs";
5
+
6
+ // src/builtins/sqlite-storage/filesystem-storage-provider.ts
7
+ var filesystem_storage_provider_exports = {};
8
+ __export(filesystem_storage_provider_exports, {
9
+ FilesystemStorageProvider: () => FilesystemStorageProvider,
10
+ default: () => filesystem_storage_provider_default
11
+ });
12
+ import * as fs from "fs";
13
+ import * as path from "path";
14
+ var STORAGE_LOCATION_TYPES, DEFAULT_LOCATION_SUBDIRS, FilesystemStorageProvider, filesystem_storage_provider_default;
15
+ var init_filesystem_storage_provider = __esm({
16
+ "src/builtins/sqlite-storage/filesystem-storage-provider.ts"() {
17
+ "use strict";
18
+ STORAGE_LOCATION_TYPES = [
19
+ "recordings-high",
20
+ "recordings-low",
21
+ "recordings-clips",
22
+ "event-images",
23
+ "models",
24
+ "addons-data",
25
+ "cache",
26
+ "logs"
27
+ ];
28
+ DEFAULT_LOCATION_SUBDIRS = {
29
+ "recordings-high": "recordings-high",
30
+ "recordings-low": "recordings-low",
31
+ "recordings-clips": "recordings-clips",
32
+ "event-images": "event-images",
33
+ "models": "models",
34
+ "addons-data": "addons-data",
35
+ "cache": "/tmp/camstack-cache",
36
+ "logs": "logs"
37
+ };
38
+ FilesystemStorageProvider = class {
39
+ id = "local";
40
+ name = "Local Filesystem";
41
+ supportedLocations = [...STORAGE_LOCATION_TYPES];
42
+ rootPath;
43
+ locationPaths;
44
+ constructor(rootPath, overrides) {
45
+ this.rootPath = path.resolve(rootPath);
46
+ this.locationPaths = /* @__PURE__ */ new Map();
47
+ for (const loc of STORAGE_LOCATION_TYPES) {
48
+ const override = overrides?.[loc];
49
+ if (override) {
50
+ this.locationPaths.set(loc, path.resolve(override));
51
+ } else {
52
+ const subdir = DEFAULT_LOCATION_SUBDIRS[loc];
53
+ this.locationPaths.set(
54
+ loc,
55
+ path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir)
56
+ );
57
+ }
58
+ }
59
+ }
60
+ resolve(location, relativePath) {
61
+ const base = this.locationPaths.get(location);
62
+ if (!base) throw new Error(`Unknown storage location: ${location}`);
63
+ return path.join(base, relativePath);
64
+ }
65
+ async write(location, relativePath, data) {
66
+ const filePath = this.resolve(location, relativePath);
67
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
68
+ if (Buffer.isBuffer(data)) {
69
+ await fs.promises.writeFile(filePath, data);
70
+ } else {
71
+ const writeStream = fs.createWriteStream(filePath);
72
+ await new Promise((resolve2, reject) => {
73
+ data.pipe(writeStream);
74
+ writeStream.on("finish", resolve2);
75
+ writeStream.on("error", reject);
76
+ });
77
+ }
78
+ }
79
+ async read(location, relativePath) {
80
+ return fs.promises.readFile(this.resolve(location, relativePath));
81
+ }
82
+ async exists(location, relativePath) {
83
+ try {
84
+ await fs.promises.access(this.resolve(location, relativePath));
85
+ return true;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+ async list(location, prefix) {
91
+ const base = this.locationPaths.get(location);
92
+ if (!base) return [];
93
+ const dir = prefix ? path.join(base, prefix) : base;
94
+ try {
95
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
96
+ return entries.map((e) => prefix ? `${prefix}/${e.name}` : e.name);
97
+ } catch {
98
+ return [];
99
+ }
100
+ }
101
+ async delete(location, relativePath) {
102
+ const filePath = this.resolve(location, relativePath);
103
+ await fs.promises.rm(filePath, { force: true });
104
+ }
105
+ async getAvailableSpace(location) {
106
+ const base = this.locationPaths.get(location);
107
+ if (!base) return null;
108
+ try {
109
+ const stats = await fs.promises.statfs(base);
110
+ return stats.bavail * stats.bsize;
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+ async initialize() {
116
+ for (const [, dirPath] of this.locationPaths) {
117
+ await fs.promises.mkdir(dirPath, { recursive: true });
118
+ }
119
+ }
120
+ async shutdown() {
121
+ }
122
+ /** Get the resolved path for a location type */
123
+ getLocationPath(location) {
124
+ const p = this.locationPaths.get(location);
125
+ if (!p) throw new Error(`Unknown storage location: ${location}`);
126
+ return p;
127
+ }
128
+ /** Get the root path */
129
+ getRootPath() {
130
+ return this.rootPath;
131
+ }
132
+ };
133
+ filesystem_storage_provider_default = FilesystemStorageProvider;
134
+ }
135
+ });
136
+
137
+ // src/builtins/sqlite-storage/filesystem-storage.addon.ts
138
+ var filesystem_storage_addon_exports = {};
139
+ __export(filesystem_storage_addon_exports, {
140
+ FilesystemStorageAddon: () => FilesystemStorageAddon,
141
+ default: () => filesystem_storage_addon_default
142
+ });
143
+ var FilesystemStorageAddon, filesystem_storage_addon_default;
144
+ var init_filesystem_storage_addon = __esm({
145
+ "src/builtins/sqlite-storage/filesystem-storage.addon.ts"() {
146
+ init_filesystem_storage_provider();
147
+ FilesystemStorageAddon = class {
148
+ manifest = {
149
+ id: "filesystem-storage",
150
+ name: "Local Filesystem Storage",
151
+ version: "1.0.0",
152
+ capabilities: [{ name: "storage", mode: "collection" }]
153
+ };
154
+ provider = null;
155
+ currentConfig = {
156
+ rootPath: "camstack-data"
157
+ };
158
+ async initialize(context) {
159
+ const rootPath = context.addonConfig.rootPath ?? this.currentConfig.rootPath;
160
+ this.currentConfig.rootPath = rootPath;
161
+ const overrides = {};
162
+ for (const key of Object.keys(context.addonConfig)) {
163
+ if (key.startsWith("override.")) {
164
+ const loc = key.slice("override.".length);
165
+ overrides[loc] = context.addonConfig[key];
166
+ }
167
+ }
168
+ this.provider = new FilesystemStorageProvider(rootPath, overrides);
169
+ await this.provider.initialize();
170
+ context.logger.info(`Filesystem storage initialized at ${this.provider.getRootPath()}`);
171
+ }
172
+ async shutdown() {
173
+ await this.provider?.shutdown();
174
+ }
175
+ getCapabilityProvider(name) {
176
+ if (name === "storage" && this.provider) {
177
+ return this.provider;
178
+ }
179
+ return null;
180
+ }
181
+ getProvider() {
182
+ return this.provider;
183
+ }
184
+ getConfigSchema() {
185
+ return { sections: [] };
186
+ }
187
+ getConfig() {
188
+ return { ...this.currentConfig };
189
+ }
190
+ async onConfigChange(config) {
191
+ this.currentConfig.rootPath = config.rootPath ?? this.currentConfig.rootPath;
192
+ }
193
+ };
194
+ filesystem_storage_addon_default = FilesystemStorageAddon;
195
+ }
196
+ });
197
+
198
+ export {
199
+ FilesystemStorageProvider,
200
+ filesystem_storage_provider_exports,
201
+ init_filesystem_storage_provider,
202
+ FilesystemStorageAddon,
203
+ filesystem_storage_addon_default,
204
+ filesystem_storage_addon_exports,
205
+ init_filesystem_storage_addon
206
+ };
207
+ //# sourceMappingURL=chunk-4YD6WMO6.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/builtins/sqlite-storage/filesystem-storage-provider.ts","../src/builtins/sqlite-storage/filesystem-storage.addon.ts"],"sourcesContent":["import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type {\n IStorageProvider as INewStorageProvider,\n StorageLocationType,\n} from '@camstack/types'\n\n// Inline constants to avoid runtime dependency on @camstack/types\n// (types is external in tsup bundle, may not be resolvable from data/addons/)\nconst STORAGE_LOCATION_TYPES: readonly StorageLocationType[] = [\n 'recordings-high', 'recordings-low', 'recordings-clips', 'event-images',\n 'models', 'addons-data', 'cache', 'logs',\n]\n\nconst DEFAULT_LOCATION_SUBDIRS: Readonly<Record<StorageLocationType, string>> = {\n 'recordings-high': 'recordings-high',\n 'recordings-low': 'recordings-low',\n 'recordings-clips': 'recordings-clips',\n 'event-images': 'event-images',\n 'models': 'models',\n 'addons-data': 'addons-data',\n 'cache': '/tmp/camstack-cache',\n 'logs': 'logs',\n}\n\n/**\n * Filesystem storage provider — serves all location types from a local directory tree.\n *\n * Default layout:\n * {rootPath}/recordings-high/\n * {rootPath}/recordings-low/\n * {rootPath}/recordings-clips/\n * {rootPath}/event-images/\n * {rootPath}/models/\n * {rootPath}/addons-data/\n * {rootPath}/logs/\n * /tmp/camstack-cache/ (cache is always local)\n *\n * Individual location paths can be overridden.\n */\nexport class FilesystemStorageProvider implements INewStorageProvider {\n readonly id = 'local'\n readonly name = 'Local Filesystem'\n readonly supportedLocations: readonly StorageLocationType[] = [...STORAGE_LOCATION_TYPES]\n\n private readonly rootPath: string\n private readonly locationPaths: Map<StorageLocationType, string>\n\n constructor(rootPath: string, overrides?: Partial<Record<StorageLocationType, string>>) {\n this.rootPath = path.resolve(rootPath)\n this.locationPaths = new Map()\n\n for (const loc of STORAGE_LOCATION_TYPES) {\n const override = overrides?.[loc]\n if (override) {\n this.locationPaths.set(loc, path.resolve(override))\n } else {\n const subdir = DEFAULT_LOCATION_SUBDIRS[loc]\n // Absolute paths (like /tmp/camstack-cache) are used as-is\n this.locationPaths.set(\n loc,\n path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir),\n )\n }\n }\n }\n\n resolve(location: StorageLocationType, relativePath: string): string {\n const base = this.locationPaths.get(location)\n if (!base) throw new Error(`Unknown storage location: ${location}`)\n return path.join(base, relativePath)\n }\n\n async write(location: StorageLocationType, relativePath: string, data: Buffer | NodeJS.ReadableStream): Promise<void> {\n const filePath = this.resolve(location, relativePath)\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true })\n\n if (Buffer.isBuffer(data)) {\n await fs.promises.writeFile(filePath, data)\n } else {\n // Stream\n const writeStream = fs.createWriteStream(filePath)\n await new Promise<void>((resolve, reject) => {\n (data as NodeJS.ReadableStream).pipe(writeStream)\n writeStream.on('finish', resolve)\n writeStream.on('error', reject)\n })\n }\n }\n\n async read(location: StorageLocationType, relativePath: string): Promise<Buffer> {\n return fs.promises.readFile(this.resolve(location, relativePath))\n }\n\n async exists(location: StorageLocationType, relativePath: string): Promise<boolean> {\n try {\n await fs.promises.access(this.resolve(location, relativePath))\n return true\n } catch {\n return false\n }\n }\n\n async list(location: StorageLocationType, prefix?: string): Promise<readonly string[]> {\n const base = this.locationPaths.get(location)\n if (!base) return []\n const dir = prefix ? path.join(base, prefix) : base\n try {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true })\n return entries.map(e => (prefix ? `${prefix}/${e.name}` : e.name))\n } catch {\n return []\n }\n }\n\n async delete(location: StorageLocationType, relativePath: string): Promise<void> {\n const filePath = this.resolve(location, relativePath)\n await fs.promises.rm(filePath, { force: true })\n }\n\n async getAvailableSpace(location: StorageLocationType): Promise<number | null> {\n const base = this.locationPaths.get(location)\n if (!base) return null\n try {\n const stats = await fs.promises.statfs(base)\n return stats.bavail * stats.bsize\n } catch {\n return null\n }\n }\n\n async initialize(): Promise<void> {\n for (const [, dirPath] of this.locationPaths) {\n await fs.promises.mkdir(dirPath, { recursive: true })\n }\n }\n\n async shutdown(): Promise<void> {\n // Nothing to clean up for filesystem\n }\n\n /** Get the resolved path for a location type */\n getLocationPath(location: StorageLocationType): string {\n const p = this.locationPaths.get(location)\n if (!p) throw new Error(`Unknown storage location: ${location}`)\n return p\n }\n\n /** Get the root path */\n getRootPath(): string {\n return this.rootPath\n }\n}\n\nexport default FilesystemStorageProvider\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n} from '@camstack/types'\nimport { FilesystemStorageProvider } from './filesystem-storage-provider'\n\n/**\n * Filesystem Storage addon — provides local disk storage for all location types.\n * Capability: 'storage' (collection)\n */\nexport class FilesystemStorageAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'filesystem-storage',\n name: 'Local Filesystem Storage',\n version: '1.0.0',\n capabilities: [{ name: 'storage', mode: 'collection' }],\n }\n\n private provider: FilesystemStorageProvider | null = null\n private currentConfig = {\n rootPath: 'camstack-data',\n }\n\n async initialize(context: AddonContext): Promise<void> {\n const rootPath = (context.addonConfig.rootPath as string) ?? this.currentConfig.rootPath\n this.currentConfig.rootPath = rootPath\n\n // Read location overrides from config\n const overrides: Partial<Record<string, string>> = {}\n for (const key of Object.keys(context.addonConfig)) {\n if (key.startsWith('override.')) {\n const loc = key.slice('override.'.length)\n overrides[loc] = context.addonConfig[key] as string\n }\n }\n\n this.provider = new FilesystemStorageProvider(rootPath, overrides as any)\n await this.provider.initialize()\n context.logger.info(`Filesystem storage initialized at ${this.provider.getRootPath()}`)\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.shutdown()\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'storage' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getProvider(): FilesystemStorageProvider | null {\n return this.provider\n }\n\n getConfigSchema(): ConfigUISchema {\n return { sections: [] }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentConfig.rootPath = (config.rootPath as string) ?? this.currentConfig.rootPath\n }\n}\n\nexport default FilesystemStorageAddon\n"],"mappings":"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","names":["resolve"]}
1
+ {"version":3,"sources":["../src/builtins/sqlite-storage/filesystem-storage-provider.ts","../src/builtins/sqlite-storage/filesystem-storage.addon.ts"],"sourcesContent":["import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type {\n IStorageProvider as INewStorageProvider,\n StorageLocationType,\n} from '@camstack/types'\n\n// Inline constants to avoid runtime dependency on @camstack/types\n// (types is external in tsup bundle, may not be resolvable from data/addons/)\nconst STORAGE_LOCATION_TYPES: readonly StorageLocationType[] = [\n 'recordings-high', 'recordings-low', 'recordings-clips', 'event-images',\n 'models', 'addons-data', 'cache', 'logs',\n]\n\nconst DEFAULT_LOCATION_SUBDIRS: Readonly<Record<StorageLocationType, string>> = {\n 'recordings-high': 'recordings-high',\n 'recordings-low': 'recordings-low',\n 'recordings-clips': 'recordings-clips',\n 'event-images': 'event-images',\n 'models': 'models',\n 'addons-data': 'addons-data',\n 'cache': '/tmp/camstack-cache',\n 'logs': 'logs',\n}\n\n/**\n * Filesystem storage provider — serves all location types from a local directory tree.\n *\n * Default layout:\n * {rootPath}/recordings-high/\n * {rootPath}/recordings-low/\n * {rootPath}/recordings-clips/\n * {rootPath}/event-images/\n * {rootPath}/models/\n * {rootPath}/addons-data/\n * {rootPath}/logs/\n * /tmp/camstack-cache/ (cache is always local)\n *\n * Individual location paths can be overridden.\n */\nexport class FilesystemStorageProvider implements INewStorageProvider {\n readonly id = 'local'\n readonly name = 'Local Filesystem'\n readonly supportedLocations: readonly StorageLocationType[] = [...STORAGE_LOCATION_TYPES]\n\n private readonly rootPath: string\n private readonly locationPaths: Map<StorageLocationType, string>\n\n constructor(rootPath: string, overrides?: Partial<Record<StorageLocationType, string>>) {\n this.rootPath = path.resolve(rootPath)\n this.locationPaths = new Map()\n\n for (const loc of STORAGE_LOCATION_TYPES) {\n const override = overrides?.[loc]\n if (override) {\n this.locationPaths.set(loc, path.resolve(override))\n } else {\n const subdir = DEFAULT_LOCATION_SUBDIRS[loc]\n // Absolute paths (like /tmp/camstack-cache) are used as-is\n this.locationPaths.set(\n loc,\n path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir),\n )\n }\n }\n }\n\n resolve(location: StorageLocationType, relativePath: string): string {\n const base = this.locationPaths.get(location)\n if (!base) throw new Error(`Unknown storage location: ${location}`)\n return path.join(base, relativePath)\n }\n\n async write(location: StorageLocationType, relativePath: string, data: Buffer | NodeJS.ReadableStream): Promise<void> {\n const filePath = this.resolve(location, relativePath)\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true })\n\n if (Buffer.isBuffer(data)) {\n await fs.promises.writeFile(filePath, data)\n } else {\n // Stream\n const writeStream = fs.createWriteStream(filePath)\n await new Promise<void>((resolve, reject) => {\n (data as NodeJS.ReadableStream).pipe(writeStream)\n writeStream.on('finish', resolve)\n writeStream.on('error', reject)\n })\n }\n }\n\n async read(location: StorageLocationType, relativePath: string): Promise<Buffer> {\n return fs.promises.readFile(this.resolve(location, relativePath))\n }\n\n async exists(location: StorageLocationType, relativePath: string): Promise<boolean> {\n try {\n await fs.promises.access(this.resolve(location, relativePath))\n return true\n } catch {\n return false\n }\n }\n\n async list(location: StorageLocationType, prefix?: string): Promise<readonly string[]> {\n const base = this.locationPaths.get(location)\n if (!base) return []\n const dir = prefix ? path.join(base, prefix) : base\n try {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true })\n return entries.map(e => (prefix ? `${prefix}/${e.name}` : e.name))\n } catch {\n return []\n }\n }\n\n async delete(location: StorageLocationType, relativePath: string): Promise<void> {\n const filePath = this.resolve(location, relativePath)\n await fs.promises.rm(filePath, { force: true })\n }\n\n async getAvailableSpace(location: StorageLocationType): Promise<number | null> {\n const base = this.locationPaths.get(location)\n if (!base) return null\n try {\n const stats = await fs.promises.statfs(base)\n return stats.bavail * stats.bsize\n } catch {\n return null\n }\n }\n\n async initialize(): Promise<void> {\n for (const [, dirPath] of this.locationPaths) {\n await fs.promises.mkdir(dirPath, { recursive: true })\n }\n }\n\n async shutdown(): Promise<void> {\n // Nothing to clean up for filesystem\n }\n\n /** Get the resolved path for a location type */\n getLocationPath(location: StorageLocationType): string {\n const p = this.locationPaths.get(location)\n if (!p) throw new Error(`Unknown storage location: ${location}`)\n return p\n }\n\n /** Get the root path */\n getRootPath(): string {\n return this.rootPath\n }\n}\n\nexport default FilesystemStorageProvider\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n} from '@camstack/types'\nimport { FilesystemStorageProvider } from './filesystem-storage-provider'\n\n/**\n * Filesystem Storage addon — provides local disk storage for all location types.\n * Capability: 'storage' (collection)\n */\nexport class FilesystemStorageAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'filesystem-storage',\n name: 'Local Filesystem Storage',\n version: '1.0.0',\n capabilities: [{ name: 'storage', mode: 'collection' }],\n }\n\n private provider: FilesystemStorageProvider | null = null\n private currentConfig = {\n rootPath: 'camstack-data',\n }\n\n async initialize(context: AddonContext): Promise<void> {\n const rootPath = (context.addonConfig.rootPath as string) ?? this.currentConfig.rootPath\n this.currentConfig.rootPath = rootPath\n\n // Read location overrides from config\n const overrides: Partial<Record<string, string>> = {}\n for (const key of Object.keys(context.addonConfig)) {\n if (key.startsWith('override.')) {\n const loc = key.slice('override.'.length)\n overrides[loc] = context.addonConfig[key] as string\n }\n }\n\n this.provider = new FilesystemStorageProvider(rootPath, overrides as any)\n await this.provider.initialize()\n context.logger.info(`Filesystem storage initialized at ${this.provider.getRootPath()}`)\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.shutdown()\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'storage' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getProvider(): FilesystemStorageProvider | null {\n return this.provider\n }\n\n getConfigSchema(): ConfigUISchema {\n return { sections: [] }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentConfig.rootPath = (config.rootPath as string) ?? this.currentConfig.rootPath\n }\n}\n\nexport default FilesystemStorageAddon\n"],"mappings":"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","names":["resolve"]}
@@ -0,0 +1,314 @@
1
+ import {
2
+ __esm,
3
+ __export
4
+ } from "./chunk-4JEXNFZZ.mjs";
5
+
6
+ // src/builtins/sqlite-storage/sqlite-settings-backend.ts
7
+ var sqlite_settings_backend_exports = {};
8
+ __export(sqlite_settings_backend_exports, {
9
+ SqliteSettingsBackend: () => SqliteSettingsBackend,
10
+ default: () => sqlite_settings_backend_default
11
+ });
12
+ import Database from "better-sqlite3";
13
+ import { randomUUID } from "crypto";
14
+ var SqliteSettingsBackend, sqlite_settings_backend_default;
15
+ var init_sqlite_settings_backend = __esm({
16
+ "src/builtins/sqlite-storage/sqlite-settings-backend.ts"() {
17
+ "use strict";
18
+ SqliteSettingsBackend = class {
19
+ constructor(dbPath, runtimeDefaults) {
20
+ this.dbPath = dbPath;
21
+ this.runtimeDefaults = runtimeDefaults ?? {};
22
+ }
23
+ db = null;
24
+ ensuredTables = /* @__PURE__ */ new Set();
25
+ runtimeDefaults;
26
+ async initialize() {
27
+ const dir = this.dbPath.substring(0, this.dbPath.lastIndexOf("/"));
28
+ if (dir) {
29
+ const fs = await import("fs");
30
+ fs.mkdirSync(dir, { recursive: true });
31
+ }
32
+ this.db = new Database(this.dbPath);
33
+ this.db.pragma("journal_mode = WAL");
34
+ this.db.pragma("foreign_keys = ON");
35
+ const isEmpty = await this.isEmpty("system-settings");
36
+ if (isEmpty) {
37
+ await this.seedDefaults();
38
+ }
39
+ }
40
+ async shutdown() {
41
+ this.db?.close();
42
+ this.db = null;
43
+ }
44
+ // ---------------------------------------------------------------------------
45
+ // ISettingsBackend implementation
46
+ // ---------------------------------------------------------------------------
47
+ async get(collection, key) {
48
+ this.ensureTable(collection);
49
+ const row = this.getDb().prepare(`SELECT data FROM "${collection}" WHERE id = ?`).get(key);
50
+ if (!row) return void 0;
51
+ return JSON.parse(row.data);
52
+ }
53
+ async set(collection, key, value) {
54
+ this.ensureTable(collection);
55
+ this.getDb().prepare(`INSERT INTO "${collection}" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`).run(key, JSON.stringify(value));
56
+ }
57
+ async query(collection, filter) {
58
+ this.ensureTable(collection);
59
+ let sql = `SELECT id, data FROM "${collection}"`;
60
+ const params = [];
61
+ const whereClauses = [];
62
+ if (filter?.where) {
63
+ for (const [field, value] of Object.entries(filter.where)) {
64
+ if (field === "id") {
65
+ whereClauses.push("id = ?");
66
+ params.push(value);
67
+ } else {
68
+ whereClauses.push(`json_extract(data, '$.${field}') = ?`);
69
+ params.push(typeof value === "object" ? JSON.stringify(value) : value);
70
+ }
71
+ }
72
+ }
73
+ if (filter?.whereIn) {
74
+ for (const [field, values] of Object.entries(filter.whereIn)) {
75
+ const placeholders = values.map(() => "?").join(", ");
76
+ if (field === "id") {
77
+ whereClauses.push(`id IN (${placeholders})`);
78
+ } else {
79
+ whereClauses.push(`json_extract(data, '$.${field}') IN (${placeholders})`);
80
+ }
81
+ params.push(...values);
82
+ }
83
+ }
84
+ if (filter?.whereBetween) {
85
+ for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {
86
+ if (field === "id") {
87
+ whereClauses.push("id BETWEEN ? AND ?");
88
+ } else {
89
+ whereClauses.push(`json_extract(data, '$.${field}') BETWEEN ? AND ?`);
90
+ }
91
+ params.push(low, high);
92
+ }
93
+ }
94
+ if (whereClauses.length > 0) {
95
+ sql += ` WHERE ${whereClauses.join(" AND ")}`;
96
+ }
97
+ if (filter?.orderBy) {
98
+ const dir = filter.orderBy.direction === "desc" ? "DESC" : "ASC";
99
+ if (filter.orderBy.field === "id") {
100
+ sql += ` ORDER BY id ${dir}`;
101
+ } else {
102
+ sql += ` ORDER BY json_extract(data, '$.${filter.orderBy.field}') ${dir}`;
103
+ }
104
+ }
105
+ if (filter?.limit) {
106
+ sql += ` LIMIT ?`;
107
+ params.push(filter.limit);
108
+ }
109
+ if (filter?.offset) {
110
+ sql += ` OFFSET ?`;
111
+ params.push(filter.offset);
112
+ }
113
+ const rows = this.getDb().prepare(sql).all(...params);
114
+ return rows.map((row) => ({
115
+ id: row.id,
116
+ data: JSON.parse(row.data)
117
+ }));
118
+ }
119
+ async insert(collection, record) {
120
+ this.ensureTable(collection);
121
+ const id = record.id || randomUUID();
122
+ this.getDb().prepare(`INSERT INTO "${collection}" (id, data) VALUES (?, ?)`).run(id, JSON.stringify(record.data));
123
+ }
124
+ async update(collection, id, data) {
125
+ this.ensureTable(collection);
126
+ this.getDb().prepare(`UPDATE "${collection}" SET data = ? WHERE id = ?`).run(JSON.stringify(data), id);
127
+ }
128
+ async delete(collection, key) {
129
+ this.ensureTable(collection);
130
+ this.getDb().prepare(`DELETE FROM "${collection}" WHERE id = ?`).run(key);
131
+ }
132
+ async count(collection, filter) {
133
+ this.ensureTable(collection);
134
+ if (!filter) {
135
+ const row = this.getDb().prepare(`SELECT COUNT(*) AS cnt FROM "${collection}"`).get();
136
+ return row?.cnt ?? 0;
137
+ }
138
+ const rows = await this.query(collection, { ...filter, limit: void 0, offset: void 0 });
139
+ return rows.length;
140
+ }
141
+ async isEmpty(collection) {
142
+ this.ensureTable(collection);
143
+ const row = this.getDb().prepare(`SELECT COUNT(*) AS cnt FROM "${collection}"`).get();
144
+ return (row?.cnt ?? 0) === 0;
145
+ }
146
+ // ---------------------------------------------------------------------------
147
+ // Legacy SettingsStore compatibility (used by ConfigManager.setSettingsStore)
148
+ // ---------------------------------------------------------------------------
149
+ /** Get a system setting by dot-notation key */
150
+ getSystem(key) {
151
+ this.ensureTable("system-settings");
152
+ const row = this.getDb().prepare('SELECT data FROM "system-settings" WHERE id = ?').get(key);
153
+ if (!row) return void 0;
154
+ return JSON.parse(row.data);
155
+ }
156
+ /** Set a system setting */
157
+ setSystem(key, value) {
158
+ this.ensureTable("system-settings");
159
+ this.getDb().prepare('INSERT INTO "system-settings" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data').run(key, JSON.stringify(value));
160
+ }
161
+ /** Get all system settings as flat key-value */
162
+ getAllSystem() {
163
+ this.ensureTable("system-settings");
164
+ const rows = this.getDb().prepare('SELECT id, data FROM "system-settings"').all();
165
+ return Object.fromEntries(rows.map((r) => [r.id, JSON.parse(r.data)]));
166
+ }
167
+ /** Get all settings for an addon */
168
+ getAllAddon(addonId) {
169
+ this.ensureTable("addon-settings");
170
+ const rows = this.getDb().prepare(`SELECT id, data FROM "addon-settings" WHERE json_extract(data, '$.addonId') = ?`).all(addonId);
171
+ if (rows.length === 0) return {};
172
+ const result = {};
173
+ for (const row of rows) {
174
+ const parsed = JSON.parse(row.data);
175
+ const key = row.id.startsWith(`${addonId}.`) ? row.id.slice(addonId.length + 1) : row.id;
176
+ result[key] = parsed.value ?? parsed;
177
+ }
178
+ return result;
179
+ }
180
+ /** Bulk-set all settings for an addon */
181
+ setAllAddon(addonId, config) {
182
+ this.ensureTable("addon-settings");
183
+ const db = this.getDb();
184
+ const deleteStmt = db.prepare(`DELETE FROM "addon-settings" WHERE id LIKE ? || '%'`);
185
+ const insertStmt = db.prepare('INSERT INTO "addon-settings" (id, data) VALUES (?, ?)');
186
+ db.transaction(() => {
187
+ deleteStmt.run(`${addonId}.`);
188
+ for (const [key, value] of Object.entries(config)) {
189
+ insertStmt.run(`${addonId}.${key}`, JSON.stringify({ addonId, key, value }));
190
+ }
191
+ })();
192
+ }
193
+ /** Get all settings for a provider */
194
+ getAllProvider(providerId) {
195
+ return this.getAllScoped("provider-settings", providerId);
196
+ }
197
+ /** Set a provider setting */
198
+ setProvider(providerId, key, value) {
199
+ this.setScopedKey("provider-settings", providerId, key, value);
200
+ }
201
+ /** Get all settings for a device */
202
+ getAllDevice(deviceId) {
203
+ return this.getAllScoped("device-settings", deviceId);
204
+ }
205
+ /** Set a device setting */
206
+ setDevice(deviceId, key, value) {
207
+ this.setScopedKey("device-settings", deviceId, key, value);
208
+ }
209
+ /** Seed system-settings with runtime defaults (first boot) */
210
+ async seedDefaults() {
211
+ this.ensureTable("system-settings");
212
+ const insert = this.getDb().prepare(
213
+ 'INSERT OR IGNORE INTO "system-settings" (id, data) VALUES (?, ?)'
214
+ );
215
+ this.getDb().transaction(() => {
216
+ for (const [key, value] of Object.entries(this.runtimeDefaults)) {
217
+ insert.run(key, JSON.stringify(value));
218
+ }
219
+ })();
220
+ }
221
+ // ---------------------------------------------------------------------------
222
+ // Private helpers
223
+ // ---------------------------------------------------------------------------
224
+ getDb() {
225
+ if (!this.db) throw new Error("SqliteSettingsBackend not initialized \u2014 call initialize() first");
226
+ return this.db;
227
+ }
228
+ ensureTable(collection) {
229
+ if (this.ensuredTables.has(collection)) return;
230
+ this.getDb().exec(
231
+ `CREATE TABLE IF NOT EXISTS "${collection}" (id TEXT PRIMARY KEY, data TEXT NOT NULL)`
232
+ );
233
+ this.ensuredTables.add(collection);
234
+ }
235
+ getAllScoped(collection, scopeId) {
236
+ this.ensureTable(collection);
237
+ const rows = this.getDb().prepare(`SELECT id, data FROM "${collection}" WHERE id LIKE ? || '.%'`).all(scopeId);
238
+ const result = {};
239
+ for (const row of rows) {
240
+ const key = row.id.slice(scopeId.length + 1);
241
+ const parsed = JSON.parse(row.data);
242
+ result[key] = parsed.value ?? parsed;
243
+ }
244
+ return result;
245
+ }
246
+ setScopedKey(collection, scopeId, key, value) {
247
+ this.ensureTable(collection);
248
+ this.getDb().prepare(`INSERT INTO "${collection}" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`).run(`${scopeId}.${key}`, JSON.stringify({ scopeId, key, value }));
249
+ }
250
+ };
251
+ sqlite_settings_backend_default = SqliteSettingsBackend;
252
+ }
253
+ });
254
+
255
+ // src/builtins/sqlite-storage/sqlite-settings.addon.ts
256
+ var sqlite_settings_addon_exports = {};
257
+ __export(sqlite_settings_addon_exports, {
258
+ SqliteSettingsAddon: () => SqliteSettingsAddon,
259
+ default: () => sqlite_settings_addon_default
260
+ });
261
+ var SqliteSettingsAddon, sqlite_settings_addon_default;
262
+ var init_sqlite_settings_addon = __esm({
263
+ "src/builtins/sqlite-storage/sqlite-settings.addon.ts"() {
264
+ init_sqlite_settings_backend();
265
+ SqliteSettingsAddon = class {
266
+ manifest = {
267
+ id: "sqlite-settings",
268
+ name: "SQLite Settings",
269
+ version: "1.0.0",
270
+ capabilities: [{ name: "settings-store", mode: "singleton" }]
271
+ };
272
+ backend = null;
273
+ async initialize(context) {
274
+ const dbPath = context.storageProvider ? context.storageProvider.resolve("addons-data", `${context.id.replace("addon:", "")}/camstack.db`) : context.dataDir ? `${context.dataDir}/camstack.db` : "camstack-data/addons-data/sqlite-settings/camstack.db";
275
+ const runtimeDefaults = context.addonConfig._runtimeDefaults ?? {};
276
+ this.backend = new SqliteSettingsBackend(dbPath, runtimeDefaults);
277
+ await this.backend.initialize();
278
+ context.logger.info(`SQLite settings initialized at ${dbPath}`);
279
+ }
280
+ async shutdown() {
281
+ await this.backend?.shutdown();
282
+ }
283
+ getBackend() {
284
+ return this.backend;
285
+ }
286
+ getCapabilityProvider(name) {
287
+ if (name === "settings-store" && this.backend) {
288
+ return this.backend;
289
+ }
290
+ return null;
291
+ }
292
+ getConfigSchema() {
293
+ return { sections: [] };
294
+ }
295
+ getConfig() {
296
+ return {};
297
+ }
298
+ async onConfigChange(_config) {
299
+ }
300
+ };
301
+ sqlite_settings_addon_default = SqliteSettingsAddon;
302
+ }
303
+ });
304
+
305
+ export {
306
+ SqliteSettingsBackend,
307
+ sqlite_settings_backend_exports,
308
+ init_sqlite_settings_backend,
309
+ SqliteSettingsAddon,
310
+ sqlite_settings_addon_default,
311
+ sqlite_settings_addon_exports,
312
+ init_sqlite_settings_addon
313
+ };
314
+ //# sourceMappingURL=chunk-CHFIH4G6.mjs.map