@iobroker/db-base 7.2.3 → 8.0.0-alpha.1-20260921-e5941ca8a

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.
@@ -1,66 +1,120 @@
1
1
  /**
2
2
  * States DB in memory - Server
3
3
  *
4
- * Copyright 2013-2024 bluefox <dogafox@gmail.com>
4
+ * Copyright 2013-2026 bluefox <dogafox@gmail.com>
5
5
  *
6
6
  * MIT License
7
7
  *
8
8
  */
9
9
  import type { InternalLogger } from '@iobroker/js-controller-common-db/tools';
10
+ interface BackupOptions {
11
+ /** deactivates backup if true */
12
+ disabled: boolean;
13
+ /** minimum number of files */
14
+ files: number;
15
+ hours: number;
16
+ /** minutes */
17
+ period: number;
18
+ path: string;
19
+ }
20
+ export type MetaObject = Record<string, string>;
21
+ export type FileSize = number;
22
+ /** Options describing how to connect to the database server */
10
23
  export interface ConnectionOptions {
24
+ /** Password for authentication, if required */
11
25
  pass?: string;
26
+ /** Name of the sentinel master to connect to */
12
27
  sentinelName?: string;
13
28
  /** array on sentinel */
14
29
  host: string | string[];
15
30
  /** array on sentinel */
16
31
  port: number | number[];
17
- options: Record<string, any>;
32
+ /** Additional connection options passed to the database driver */
33
+ options: ioBroker.ObjectsDatabaseOptions['options'];
34
+ /** Options forwarded to the JSONL database backend */
35
+ jsonlOptions?: ioBroker.ObjectsDatabaseOptions['jsonlOptions'];
36
+ /** Redis key prefix used by the in-memory server */
37
+ redisNamespace?: string;
38
+ /** Enable more verbose connection logging */
18
39
  enhancedLogging?: boolean;
40
+ /** Disable the in-memory file cache and always read files from disk */
41
+ noFileCache?: boolean;
42
+ /** Whether the connection should be secured via TLS */
43
+ secure?: boolean;
44
+ /** Backup configuration */
19
45
  backup?: BackupOptions;
20
46
  /** relative path to the data dir */
21
47
  dataDir?: string;
48
+ /** Interval in milliseconds in which the in-memory states are persisted to disk */
49
+ writeFileInterval?: number;
22
50
  }
23
- type ChangeFunction = (id: string, state: any) => void;
51
+ type ChangeFunction<TObject> = (id: string, obj: TObject) => void;
52
+ /** Status information about the database */
24
53
  export interface DbStatus {
25
- type: string;
54
+ /** Type of the database backend (e.g. file, jsonl, redis) */
55
+ type: 'file' | 'redis' | 'jsonl';
56
+ /** Whether this process runs the database server */
26
57
  server: boolean;
27
58
  }
28
- interface BackupOptions {
29
- /** deactivates backup if true */
30
- disabled: boolean;
31
- /** minimum number of files */
32
- files: number;
33
- hours: number;
34
- /** minutes */
35
- period: number;
36
- path: string;
37
- }
59
+ /** Options describing where the database stores its files */
38
60
  export interface DbOptions {
61
+ /** Name of the directory used for backups */
39
62
  backupDirName: string;
63
+ /** Name of the database file */
40
64
  fileName: string;
41
65
  }
42
- interface FileDbSettings {
43
- fileDB: DbOptions;
44
- jsonlDB: DbOptions;
45
- backup: BackupOptions;
46
- change?: ChangeFunction;
47
- connected: (nameOfServer: string) => void;
48
- logger: InternalLogger;
66
+ /** Settings accepted by the in-memory database classes and their subclasses */
67
+ export interface FileDbSettings<TObject> {
68
+ /** File DB storage options (filled in by the subclass constructor before `super()`) */
69
+ fileDB?: DbOptions;
70
+ /** JSONL DB storage options (only set by the JSONL backend; only the file name is used) */
71
+ jsonlDB?: Pick<DbOptions, 'fileName'>;
72
+ /** Backup options (derived from `connection.backup` by the base constructor) */
73
+ backup?: BackupOptions;
74
+ /** Callback invoked when a value in the database changes */
75
+ change?: ChangeFunction<TObject>;
76
+ /** Callback invoked once the database server is ready */
77
+ connected: (nameOfServer?: string) => void;
78
+ /** Logger instance (or logger configuration) used for all database logging */
79
+ logger?: InternalLogger;
80
+ /** Options describing how to connect to the database server */
49
81
  connection: ConnectionOptions;
50
82
  /** unused */
51
83
  auth?: null;
52
- secure: boolean;
53
- /** as required by createServer TODO: if createServer is typed, add type */
54
- certificates: any;
55
- port: number;
56
- host: string;
84
+ /** Whether the server connection should be secured via TLS */
85
+ secure?: boolean;
86
+ /** Port the in-memory server listens on */
87
+ port?: number;
88
+ /** Host the in-memory server binds to */
89
+ host?: string;
57
90
  /** logging namespace */
58
91
  namespace?: string;
92
+ /** Host name used as a fallback for the logging namespace */
93
+ hostname?: string;
94
+ /** Default ACL applied to newly created objects */
95
+ defaultNewAcl?: {
96
+ object: number;
97
+ state: number;
98
+ file: number;
99
+ owner: ioBroker.ObjectIDs.User;
100
+ ownerGroup: ioBroker.ObjectIDs.Group;
101
+ };
102
+ /** Redis key prefix used by the in-memory server (defaults to "io") */
103
+ redisNamespace?: string;
104
+ /** Namespace used for the message box */
105
+ namespaceMsg?: string;
106
+ /** Namespace used for log messages */
107
+ namespaceLog?: string;
108
+ /** Namespace used for sessions */
109
+ namespaceSession?: string;
110
+ /** Namespace used for meta information */
111
+ metaNamespace?: string;
112
+ /** Redis key prefix used for object meta information (defaults to "meta") */
113
+ namespaceMeta?: string;
59
114
  }
60
115
  interface Subscription {
61
116
  pattern: string;
62
117
  regex: RegExp;
63
- options: any;
64
118
  }
65
119
  interface SubscriptionClient {
66
120
  _subscribe?: Record<string, Subscription[]>;
@@ -69,19 +123,25 @@ interface SubscriptionClient {
69
123
  * The parent of the class structure, which provides basic JSON storage
70
124
  * and general subscription and publish functionality
71
125
  */
72
- export declare class InMemoryFileDB {
73
- private settings;
74
- private readonly change;
75
- protected dataset: Record<string, any>;
76
- private readonly namespace;
126
+ export declare class InMemoryFileDB<TObject, THandler extends SubscriptionClient = SubscriptionClient> {
127
+ protected settings: FileDbSettings<TObject>;
128
+ protected change: ChangeFunction<TObject> | undefined;
129
+ protected dataset: Record<string, TObject | MetaObject>;
130
+ protected namespace: string;
77
131
  private lastSave;
78
- private stateTimer;
132
+ protected stateTimer: NodeJS.Timeout | null;
79
133
  private callbackSubscriptionClient;
80
- private readonly dataDir;
134
+ protected readonly dataDir: string;
81
135
  private readonly datasetName;
82
- private log;
83
- private readonly backupDir;
84
- constructor(settings: FileDbSettings);
136
+ protected log: InternalLogger;
137
+ protected readonly backupDir: string;
138
+ /**
139
+ * @param settings Settings for the database, the connection and backups
140
+ */
141
+ constructor(settings: FileDbSettings<TObject>);
142
+ /**
143
+ * Open the database by loading the dataset from disk
144
+ */
85
145
  open(): Promise<void>;
86
146
  /**
87
147
  * Loads a dataset file
@@ -89,20 +149,55 @@ export declare class InMemoryFileDB {
89
149
  * @param datasetName Filename of the file to load
90
150
  * @returns obj read data, normally as object
91
151
  */
92
- loadDatasetFile(datasetName: string): Promise<Record<string, any>>;
152
+ loadDatasetFile(datasetName: string): Promise<Record<string, TObject>>;
93
153
  /**
94
154
  * Loads the dataset including pot. Fallback handling
95
155
  *
96
156
  * @param datasetName Filename of the file to load
97
157
  * @returns obj dataset read as object
98
158
  */
99
- loadDataset(datasetName: string): Promise<Record<string, any>>;
159
+ loadDataset(datasetName: string): Promise<Record<string, TObject>>;
160
+ /**
161
+ * Normalize the backup settings and create the backup directory
162
+ */
100
163
  initBackupDir(): void;
101
- handleSubscribe(client: SubscriptionClient, type: string, pattern: string | string[], cb?: () => void): void;
102
- handleSubscribe(client: SubscriptionClient, type: string, pattern: string | string[], options: any, cb?: () => void): void;
164
+ /**
165
+ * Subscribe a client to changes matching the given pattern
166
+ *
167
+ * @param client The client to register the subscription for
168
+ * @param type The subscription type (e.g. objects or states)
169
+ * @param pattern One or more patterns to subscribe to
170
+ */
171
+ handleSubscribe(client: SubscriptionClient, type: string, pattern: string | string[]): void;
172
+ /**
173
+ * Remove a client's subscription for the given pattern
174
+ *
175
+ * @param client The client whose subscription should be removed
176
+ * @param type The subscription type (e.g. objects or states)
177
+ * @param pattern One or more patterns to unsubscribe from
178
+ * @param cb Called once the subscription has been removed
179
+ */
103
180
  handleUnsubscribe(client: SubscriptionClient, type: string, pattern: string | string[], cb?: () => void): void | Promise<void>;
104
- publishToClients(_client: SubscriptionClient, _type: string, _id: string, _obj: any): number;
181
+ /**
182
+ * Publish a change to a single client. Must be implemented by a subclass that handles client communication.
183
+ *
184
+ * @param _client The client to publish to
185
+ * @param _type The change type (e.g. objects or states)
186
+ * @param _id The ID of the changed object or state
187
+ * @param _obj The new value of the object or state
188
+ */
189
+ publishToClients(_client: SubscriptionClient, _type: string, _id: string, _obj: TObject | string | null | FileSize | MetaObject): number;
190
+ /**
191
+ * Delete outdated backup files according to the configured retention settings
192
+ *
193
+ * @param baseFilename Base file name of the backups that should be pruned
194
+ */
105
195
  deleteOldBackupFiles(baseFilename: string): void;
196
+ /**
197
+ * Format a timestamp into a "YYYY-MM-DD_HH-MM-SS" string used for backup file names
198
+ *
199
+ * @param date Timestamp in milliseconds
200
+ */
106
201
  getTimeStr(date: number): string;
107
202
  /**
108
203
  * Handle saving the dataset incl. backups
@@ -120,9 +215,25 @@ export declare class InMemoryFileDB {
120
215
  * @param jsonString JSON string of the complete dataset to also be stored into a compressed backup file
121
216
  */
122
217
  saveBackup(jsonString: string): void;
218
+ /**
219
+ * Get the current status of the database
220
+ */
123
221
  getStatus(): DbStatus;
124
- getClients(): Record<string, any>;
125
- publishAll(type: string, id: string, obj: any): number;
222
+ /**
223
+ * Get the currently connected clients. Subclasses override this to return the real clients.
224
+ */
225
+ getClients(): Record<string, THandler>;
226
+ /**
227
+ * Publish a change to all connected clients and local subscribers
228
+ *
229
+ * @param type The change type (e.g. objects or states)
230
+ * @param id The ID of the changed object or state
231
+ * @param obj The new value of the object or state
232
+ */
233
+ publishAll(type: string, id: string, obj: TObject | string | null | FileSize | MetaObject): number;
234
+ /**
235
+ * Destructor of the class. Called when shutting down to persist state and clear timers.
236
+ */
126
237
  destroy(): Promise<void>;
127
238
  }
128
239
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"inMemFileDB.d.ts","sourceRoot":"","sources":["../../../src/lib/inMemFileDB.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"inMemFileDB.d.ts","sourceRoot":"","sources":["../../../src/lib/inMemFileDB.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * States DB in memory - Server
3
3
  *
4
- * Copyright 2013-2024 bluefox <dogafox@gmail.com>
4
+ * Copyright 2013-2026 bluefox <dogafox@gmail.com>
5
5
  *
6
6
  * MIT License
7
7
  *
8
8
  */
9
+ /// <reference types="@iobroker/types-dev" />
9
10
  import fs from 'fs-extra';
10
11
  import path from 'node:path';
11
12
  import { tools } from '@iobroker/js-controller-common-db';
@@ -17,22 +18,22 @@ import { createGzip } from 'node:zlib';
17
18
  export class InMemoryFileDB {
18
19
  settings;
19
20
  change;
20
- dataset;
21
+ dataset = {};
21
22
  namespace;
22
- lastSave;
23
- stateTimer;
24
- callbackSubscriptionClient;
23
+ lastSave = null;
24
+ stateTimer = null;
25
+ callbackSubscriptionClient = {};
25
26
  dataDir;
26
27
  datasetName;
27
28
  log;
28
29
  backupDir;
30
+ /**
31
+ * @param settings Settings for the database, the connection and backups
32
+ */
29
33
  constructor(settings) {
30
- this.settings = settings || {};
34
+ this.settings = settings;
31
35
  this.change = this.settings.change;
32
- this.dataset = {};
33
36
  this.namespace = this.settings.namespace || '';
34
- this.lastSave = null;
35
- this.callbackSubscriptionClient = {};
36
37
  this.settings.backup = this.settings.connection.backup || {
37
38
  disabled: false, // deactivates
38
39
  files: 24, // minimum number of files
@@ -40,30 +41,37 @@ export class InMemoryFileDB {
40
41
  period: 120, // minutes
41
42
  path: '', // use default path
42
43
  };
44
+ const backup = this.settings.backup;
43
45
  this.dataDir = this.settings.connection.dataDir || tools.getDefaultDataDir();
44
46
  if (!path.isAbsolute(this.dataDir)) {
45
47
  this.dataDir = path.normalize(path.join(tools.getControllerDir(), this.dataDir));
46
48
  }
47
49
  this.dataDir = this.dataDir.replace(/\\/g, '/');
48
- const fileName = this.settings.jsonlDB ? this.settings.jsonlDB.fileName : this.settings.fileDB.fileName;
50
+ const fileDbOptions = this.settings.fileDB;
51
+ if (!fileDbOptions) {
52
+ throw new Error(`${this.namespace} No fileDB storage options were provided`);
53
+ }
54
+ const fileName = this.settings.jsonlDB ? this.settings.jsonlDB.fileName : fileDbOptions.fileName;
49
55
  this.datasetName = path.join(this.dataDir, fileName);
50
56
  const parts = path.dirname(this.datasetName);
51
57
  fs.ensureDirSync(parts);
52
- this.stateTimer = null;
53
- this.backupDir = this.settings.backup.path || path.join(this.dataDir, this.settings.fileDB.backupDirName);
58
+ this.backupDir = backup.path || path.join(this.dataDir, fileDbOptions.backupDirName);
54
59
  this.log = tools.getLogger(this.settings.logger);
55
- if (!this.settings.backup.disabled) {
60
+ if (!backup.disabled) {
56
61
  try {
57
62
  this.initBackupDir();
58
63
  }
59
64
  catch (e) {
60
65
  this.log.error(`Database backups are disabled, because backup directory could not be initialized: ${e.message}`);
61
66
  this.log.error('This leads to an increased risk of data loss, please check that the configured backup directory is available and restart the controller');
62
- this.settings.backup.disabled = true;
67
+ backup.disabled = true;
63
68
  }
64
69
  }
65
70
  this.log.debug(`${this.namespace} Data File: ${this.datasetName}`);
66
71
  }
72
+ /**
73
+ * Open the database by loading the dataset from disk
74
+ */
67
75
  async open() {
68
76
  // load values from file
69
77
  this.dataset = await this.loadDataset(this.datasetName);
@@ -137,57 +145,71 @@ export class InMemoryFileDB {
137
145
  }
138
146
  return ret;
139
147
  }
148
+ /**
149
+ * Normalize the backup settings and create the backup directory
150
+ */
140
151
  initBackupDir() {
152
+ // Create backup directory
153
+ fs.ensureDirSync(this.backupDir);
154
+ const backup = this.settings.backup;
155
+ if (!backup) {
156
+ return;
157
+ }
141
158
  // Interval in minutes => to milliseconds
142
- this.settings.backup.period =
143
- this.settings.backup.period === undefined ? 120 : parseInt(String(this.settings.backup.period));
144
- if (isNaN(this.settings.backup.period)) {
145
- this.settings.backup.period = 120;
159
+ backup.period = backup.period === undefined ? 120 : parseInt(String(backup.period));
160
+ if (isNaN(backup.period)) {
161
+ backup.period = 120;
146
162
  }
147
163
  // Node.js timeouts overflow after roughly 24 days, defaulting to 1 millisecond, which causes chaos.
148
164
  // If a user configured the backup this way, we use our default of 120 minutes instead.
149
165
  const maxTimeoutMinutes = Math.floor((2 ** 31 - 1) / 60000);
150
- if (this.settings.backup.period > maxTimeoutMinutes) {
151
- this.log.warn(`${this.namespace} Configured backup period ${this.settings.backup.period} is larger than the supported maximum of ${maxTimeoutMinutes} minutes. Defaulting to 120 minutes.`);
152
- this.settings.backup.period = 120;
166
+ if (backup.period > maxTimeoutMinutes) {
167
+ this.log.warn(`${this.namespace} Configured backup period ${backup.period} is larger than the supported maximum of ${maxTimeoutMinutes} minutes. Defaulting to 120 minutes.`);
168
+ backup.period = 120;
153
169
  }
154
- this.settings.backup.period *= 60_000;
155
- this.settings.backup.files =
156
- this.settings.backup.files === undefined ? 24 : parseInt(String(this.settings.backup.files));
157
- if (isNaN(this.settings.backup.files)) {
158
- this.settings.backup.files = 24;
170
+ backup.period *= 60_000;
171
+ backup.files = backup.files === undefined ? 24 : parseInt(String(backup.files));
172
+ if (isNaN(backup.files)) {
173
+ backup.files = 24;
159
174
  }
160
- this.settings.backup.hours =
161
- this.settings.backup.hours === undefined ? 48 : parseInt(String(this.settings.backup.hours));
162
- if (isNaN(this.settings.backup.hours)) {
163
- this.settings.backup.hours = 48;
175
+ backup.hours = backup.hours === undefined ? 48 : parseInt(String(backup.hours));
176
+ if (isNaN(backup.hours)) {
177
+ backup.hours = 48;
164
178
  }
165
- // Create backup directory
166
- fs.ensureDirSync(this.backupDir);
167
179
  }
168
- handleSubscribe(client, type, pattern, options, cb) {
169
- if (typeof options === 'function') {
170
- cb = options;
171
- options = undefined;
172
- }
173
- client._subscribe = client._subscribe || {};
174
- client._subscribe[type] = client._subscribe[type] || [];
180
+ /**
181
+ * Subscribe a client to changes matching the given pattern
182
+ *
183
+ * @param client The client to register the subscription for
184
+ * @param type The subscription type (e.g. objects or states)
185
+ * @param pattern One or more patterns to subscribe to
186
+ */
187
+ handleSubscribe(client, type, pattern) {
188
+ client._subscribe ||= {};
189
+ client._subscribe[type] ||= [];
175
190
  const s = client._subscribe[type];
176
191
  if (pattern instanceof Array) {
177
192
  pattern.forEach(pattern => {
178
193
  if (s.find(sub => sub.pattern === pattern)) {
179
194
  return;
180
195
  }
181
- s.push({ pattern, regex: new RegExp(tools.pattern2RegEx(pattern)), options });
196
+ s.push({ pattern, regex: new RegExp(tools.pattern2RegEx(pattern)) });
182
197
  });
183
198
  }
184
199
  else {
185
200
  if (!s.find(sub => sub.pattern === pattern)) {
186
- s.push({ pattern, regex: new RegExp(tools.pattern2RegEx(pattern)), options });
201
+ s.push({ pattern, regex: new RegExp(tools.pattern2RegEx(pattern)) });
187
202
  }
188
203
  }
189
- typeof cb === 'function' && cb();
190
204
  }
205
+ /**
206
+ * Remove a client's subscription for the given pattern
207
+ *
208
+ * @param client The client whose subscription should be removed
209
+ * @param type The subscription type (e.g. objects or states)
210
+ * @param pattern One or more patterns to unsubscribe from
211
+ * @param cb Called once the subscription has been removed
212
+ */
191
213
  handleUnsubscribe(client, type, pattern, cb) {
192
214
  const s = client?._subscribe?.[type];
193
215
  if (s) {
@@ -208,16 +230,29 @@ export class InMemoryFileDB {
208
230
  }
209
231
  return tools.maybeCallback(cb);
210
232
  }
233
+ /**
234
+ * Publish a change to a single client. Must be implemented by a subclass that handles client communication.
235
+ *
236
+ * @param _client The client to publish to
237
+ * @param _type The change type (e.g. objects or states)
238
+ * @param _id The ID of the changed object or state
239
+ * @param _obj The new value of the object or state
240
+ */
211
241
  publishToClients(_client, _type, _id, _obj) {
212
242
  throw new Error('no communication handling implemented');
213
243
  }
244
+ /**
245
+ * Delete outdated backup files according to the configured retention settings
246
+ *
247
+ * @param baseFilename Base file name of the backups that should be pruned
248
+ */
214
249
  deleteOldBackupFiles(baseFilename) {
215
250
  // delete files only if settings.backupNumber is not 0
216
251
  let files = fs.readdirSync(this.backupDir);
217
252
  files.sort();
218
- const limit = Date.now() - this.settings.backup.hours * 3600000;
253
+ const limit = Date.now() - (this.settings.backup?.hours ?? 48) * 3600000;
219
254
  files = files.filter(f => f.endsWith(`${baseFilename}.gz`));
220
- while (files.length > this.settings.backup.files) {
255
+ while (files.length > (this.settings.backup?.files ?? 24)) {
221
256
  const file = files.shift();
222
257
  if (!file) {
223
258
  continue;
@@ -234,6 +269,11 @@ export class InMemoryFileDB {
234
269
  }
235
270
  }
236
271
  }
272
+ /**
273
+ * Format a timestamp into a "YYYY-MM-DD_HH-MM-SS" string used for backup file names
274
+ *
275
+ * @param date Timestamp in milliseconds
276
+ */
237
277
  getTimeStr(date) {
238
278
  const dateObj = new Date(date);
239
279
  let text = `${dateObj.getFullYear().toString()}-`;
@@ -265,7 +305,7 @@ export class InMemoryFileDB {
265
305
  async saveState() {
266
306
  try {
267
307
  const jsonString = await this.saveDataset();
268
- if (!this.settings.backup.disabled && jsonString) {
308
+ if (!this.settings.backup?.disabled && jsonString) {
269
309
  this.saveBackup(jsonString);
270
310
  }
271
311
  }
@@ -342,9 +382,12 @@ export class InMemoryFileDB {
342
382
  // save files for the last x hours
343
383
  const now = Date.now();
344
384
  // makes backups only if settings.backupInterval is not 0
345
- if (this.settings.backup.period && (!this.lastSave || now - this.lastSave > this.settings.backup.period)) {
385
+ const fileName = this.settings.fileDB?.fileName;
386
+ if (fileName &&
387
+ this.settings.backup?.period &&
388
+ (!this.lastSave || now - this.lastSave > this.settings.backup.period)) {
346
389
  this.lastSave = now;
347
- const backFileName = path.join(this.backupDir, `${this.getTimeStr(now)}_${this.settings.fileDB.fileName}.gz`);
390
+ const backFileName = path.join(this.backupDir, `${this.getTimeStr(now)}_${fileName}.gz`);
348
391
  try {
349
392
  if (!fs.existsSync(backFileName)) {
350
393
  const output = fs.createWriteStream(backFileName);
@@ -359,7 +402,7 @@ export class InMemoryFileDB {
359
402
  compress.write(jsonString);
360
403
  compress.end();
361
404
  // analyse older files
362
- this.deleteOldBackupFiles(this.settings.fileDB.fileName);
405
+ this.deleteOldBackupFiles(fileName);
363
406
  }
364
407
  }
365
408
  catch (e) {
@@ -367,12 +410,25 @@ export class InMemoryFileDB {
367
410
  }
368
411
  }
369
412
  }
413
+ /**
414
+ * Get the current status of the database
415
+ */
370
416
  getStatus() {
371
417
  return { type: 'file', server: true };
372
418
  }
419
+ /**
420
+ * Get the currently connected clients. Subclasses override this to return the real clients.
421
+ */
373
422
  getClients() {
374
423
  return {};
375
424
  }
425
+ /**
426
+ * Publish a change to all connected clients and local subscribers
427
+ *
428
+ * @param type The change type (e.g. objects or states)
429
+ * @param id The ID of the changed object or state
430
+ * @param obj The new value of the object or state
431
+ */
376
432
  publishAll(type, id, obj) {
377
433
  if (id === undefined) {
378
434
  this.log.error(`${this.namespace} Can not publish empty ID`);
@@ -399,7 +455,9 @@ export class InMemoryFileDB {
399
455
  }
400
456
  return publishCount;
401
457
  }
402
- // Destructor of the class. Called by shutting down.
458
+ /**
459
+ * Destructor of the class. Called when shutting down to persist state and clear timers.
460
+ */
403
461
  async destroy() {
404
462
  if (this.stateTimer) {
405
463
  clearTimeout(this.stateTimer);