@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,4 +1,4 @@
1
- export { InMemoryFileDB } from './lib/inMemFileDB.js';
1
+ export { InMemoryFileDB, type ConnectionOptions, type DbStatus, type DbOptions, type FileDbSettings, type MetaObject, } from './lib/inMemFileDB.js';
2
2
  export { RedisHandler } from './lib/redisHandler.js';
3
3
  export { tools } from '@iobroker/js-controller-common-db';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -16,13 +16,13 @@ var __copyProps = (to, from, except, desc) => {
16
16
  return to;
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
- var esm_exports = {};
20
- __export(esm_exports, {
19
+ var index_exports = {};
20
+ __export(index_exports, {
21
21
  InMemoryFileDB: () => import_inMemFileDB.InMemoryFileDB,
22
22
  RedisHandler: () => import_redisHandler.RedisHandler,
23
23
  tools: () => import_js_controller_common_db.tools
24
24
  });
25
- module.exports = __toCommonJS(esm_exports);
25
+ module.exports = __toCommonJS(index_exports);
26
26
  var import_inMemFileDB = require("./lib/inMemFileDB.js");
27
27
  var import_redisHandler = require("./lib/redisHandler.js");
28
28
  var import_js_controller_common_db = require("@iobroker/js-controller-common-db");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["export { InMemoryFileDB } from './lib/inMemFileDB.js';\nexport { RedisHandler } from './lib/redisHandler.js';\nexport { tools } from '@iobroker/js-controller-common-db';\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;;;;;;;AAAA,yBAA+B;AAC/B,0BAA6B;AAC7B,qCAAsB;",
4
+ "sourcesContent": ["export {\n InMemoryFileDB,\n type ConnectionOptions,\n type DbStatus,\n type DbOptions,\n type FileDbSettings,\n type MetaObject,\n} from './lib/inMemFileDB.js';\nexport { RedisHandler } from './lib/redisHandler.js';\nexport { tools } from '@iobroker/js-controller-common-db';\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;;;;;;;AAAA,yBAOO;AACP,0BAA6B;AAC7B,qCAAsB;",
6
6
  "names": []
7
7
  }
@@ -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 {};
@@ -5,6 +5,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
9
  var __export = (target, all) => {
9
10
  for (var name in all)
10
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -36,24 +37,27 @@ var import_node_path = __toESM(require("node:path"), 1);
36
37
  var import_js_controller_common_db = require("@iobroker/js-controller-common-db");
37
38
  var import_node_zlib = require("node:zlib");
38
39
  class InMemoryFileDB {
40
+ static {
41
+ __name(this, "InMemoryFileDB");
42
+ }
39
43
  settings;
40
44
  change;
41
- dataset;
45
+ dataset = {};
42
46
  namespace;
43
- lastSave;
44
- stateTimer;
45
- callbackSubscriptionClient;
47
+ lastSave = null;
48
+ stateTimer = null;
49
+ callbackSubscriptionClient = {};
46
50
  dataDir;
47
51
  datasetName;
48
52
  log;
49
53
  backupDir;
54
+ /**
55
+ * @param settings Settings for the database, the connection and backups
56
+ */
50
57
  constructor(settings) {
51
- this.settings = settings || {};
58
+ this.settings = settings;
52
59
  this.change = this.settings.change;
53
- this.dataset = {};
54
60
  this.namespace = this.settings.namespace || "";
55
- this.lastSave = null;
56
- this.callbackSubscriptionClient = {};
57
61
  this.settings.backup = this.settings.connection.backup || {
58
62
  disabled: false,
59
63
  // deactivates
@@ -66,29 +70,36 @@ class InMemoryFileDB {
66
70
  path: ""
67
71
  // use default path
68
72
  };
73
+ const backup = this.settings.backup;
69
74
  this.dataDir = this.settings.connection.dataDir || import_js_controller_common_db.tools.getDefaultDataDir();
70
75
  if (!import_node_path.default.isAbsolute(this.dataDir)) {
71
76
  this.dataDir = import_node_path.default.normalize(import_node_path.default.join(import_js_controller_common_db.tools.getControllerDir(), this.dataDir));
72
77
  }
73
78
  this.dataDir = this.dataDir.replace(/\\/g, "/");
74
- const fileName = this.settings.jsonlDB ? this.settings.jsonlDB.fileName : this.settings.fileDB.fileName;
79
+ const fileDbOptions = this.settings.fileDB;
80
+ if (!fileDbOptions) {
81
+ throw new Error(`${this.namespace} No fileDB storage options were provided`);
82
+ }
83
+ const fileName = this.settings.jsonlDB ? this.settings.jsonlDB.fileName : fileDbOptions.fileName;
75
84
  this.datasetName = import_node_path.default.join(this.dataDir, fileName);
76
85
  const parts = import_node_path.default.dirname(this.datasetName);
77
86
  import_fs_extra.default.ensureDirSync(parts);
78
- this.stateTimer = null;
79
- this.backupDir = this.settings.backup.path || import_node_path.default.join(this.dataDir, this.settings.fileDB.backupDirName);
87
+ this.backupDir = backup.path || import_node_path.default.join(this.dataDir, fileDbOptions.backupDirName);
80
88
  this.log = import_js_controller_common_db.tools.getLogger(this.settings.logger);
81
- if (!this.settings.backup.disabled) {
89
+ if (!backup.disabled) {
82
90
  try {
83
91
  this.initBackupDir();
84
92
  } catch (e) {
85
93
  this.log.error(`Database backups are disabled, because backup directory could not be initialized: ${e.message}`);
86
94
  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");
87
- this.settings.backup.disabled = true;
95
+ backup.disabled = true;
88
96
  }
89
97
  }
90
98
  this.log.debug(`${this.namespace} Data File: ${this.datasetName}`);
91
99
  }
100
+ /**
101
+ * Open the database by loading the dataset from disk
102
+ */
92
103
  async open() {
93
104
  this.dataset = await this.loadDataset(this.datasetName);
94
105
  }
@@ -151,58 +162,75 @@ class InMemoryFileDB {
151
162
  }
152
163
  return ret;
153
164
  }
165
+ /**
166
+ * Normalize the backup settings and create the backup directory
167
+ */
154
168
  initBackupDir() {
155
- this.settings.backup.period = this.settings.backup.period === void 0 ? 120 : parseInt(String(this.settings.backup.period));
156
- if (isNaN(this.settings.backup.period)) {
157
- this.settings.backup.period = 120;
169
+ import_fs_extra.default.ensureDirSync(this.backupDir);
170
+ const backup = this.settings.backup;
171
+ if (!backup) {
172
+ return;
173
+ }
174
+ backup.period = backup.period === void 0 ? 120 : parseInt(String(backup.period));
175
+ if (isNaN(backup.period)) {
176
+ backup.period = 120;
158
177
  }
159
178
  const maxTimeoutMinutes = Math.floor((2 ** 31 - 1) / 6e4);
160
- if (this.settings.backup.period > maxTimeoutMinutes) {
161
- 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.`);
162
- this.settings.backup.period = 120;
179
+ if (backup.period > maxTimeoutMinutes) {
180
+ this.log.warn(`${this.namespace} Configured backup period ${backup.period} is larger than the supported maximum of ${maxTimeoutMinutes} minutes. Defaulting to 120 minutes.`);
181
+ backup.period = 120;
163
182
  }
164
- this.settings.backup.period *= 6e4;
165
- this.settings.backup.files = this.settings.backup.files === void 0 ? 24 : parseInt(String(this.settings.backup.files));
166
- if (isNaN(this.settings.backup.files)) {
167
- this.settings.backup.files = 24;
183
+ backup.period *= 6e4;
184
+ backup.files = backup.files === void 0 ? 24 : parseInt(String(backup.files));
185
+ if (isNaN(backup.files)) {
186
+ backup.files = 24;
168
187
  }
169
- this.settings.backup.hours = this.settings.backup.hours === void 0 ? 48 : parseInt(String(this.settings.backup.hours));
170
- if (isNaN(this.settings.backup.hours)) {
171
- this.settings.backup.hours = 48;
188
+ backup.hours = backup.hours === void 0 ? 48 : parseInt(String(backup.hours));
189
+ if (isNaN(backup.hours)) {
190
+ backup.hours = 48;
172
191
  }
173
- import_fs_extra.default.ensureDirSync(this.backupDir);
174
192
  }
175
- handleSubscribe(client, type, pattern, options, cb) {
176
- if (typeof options === "function") {
177
- cb = options;
178
- options = void 0;
179
- }
180
- client._subscribe = client._subscribe || {};
181
- client._subscribe[type] = client._subscribe[type] || [];
193
+ /**
194
+ * Subscribe a client to changes matching the given pattern
195
+ *
196
+ * @param client The client to register the subscription for
197
+ * @param type The subscription type (e.g. objects or states)
198
+ * @param pattern One or more patterns to subscribe to
199
+ */
200
+ handleSubscribe(client, type, pattern) {
201
+ client._subscribe ||= {};
202
+ client._subscribe[type] ||= [];
182
203
  const s = client._subscribe[type];
183
204
  if (pattern instanceof Array) {
184
205
  pattern.forEach((pattern2) => {
185
206
  if (s.find((sub) => sub.pattern === pattern2)) {
186
207
  return;
187
208
  }
188
- s.push({ pattern: pattern2, regex: new RegExp(import_js_controller_common_db.tools.pattern2RegEx(pattern2)), options });
209
+ s.push({ pattern: pattern2, regex: new RegExp(import_js_controller_common_db.tools.pattern2RegEx(pattern2)) });
189
210
  });
190
211
  } else {
191
212
  if (!s.find((sub) => sub.pattern === pattern)) {
192
- s.push({ pattern, regex: new RegExp(import_js_controller_common_db.tools.pattern2RegEx(pattern)), options });
213
+ s.push({ pattern, regex: new RegExp(import_js_controller_common_db.tools.pattern2RegEx(pattern)) });
193
214
  }
194
215
  }
195
- typeof cb === "function" && cb();
196
216
  }
217
+ /**
218
+ * Remove a client's subscription for the given pattern
219
+ *
220
+ * @param client The client whose subscription should be removed
221
+ * @param type The subscription type (e.g. objects or states)
222
+ * @param pattern One or more patterns to unsubscribe from
223
+ * @param cb Called once the subscription has been removed
224
+ */
197
225
  handleUnsubscribe(client, type, pattern, cb) {
198
226
  const s = client?._subscribe?.[type];
199
227
  if (s) {
200
- const removeEntry = (p) => {
228
+ const removeEntry = /* @__PURE__ */ __name((p) => {
201
229
  const index = s.findIndex((sub) => sub.pattern === p);
202
230
  if (index > -1) {
203
231
  s.splice(index, 1);
204
232
  }
205
- };
233
+ }, "removeEntry");
206
234
  if (pattern instanceof Array) {
207
235
  pattern.forEach((p) => {
208
236
  removeEntry(p);
@@ -213,15 +241,28 @@ class InMemoryFileDB {
213
241
  }
214
242
  return import_js_controller_common_db.tools.maybeCallback(cb);
215
243
  }
244
+ /**
245
+ * Publish a change to a single client. Must be implemented by a subclass that handles client communication.
246
+ *
247
+ * @param _client The client to publish to
248
+ * @param _type The change type (e.g. objects or states)
249
+ * @param _id The ID of the changed object or state
250
+ * @param _obj The new value of the object or state
251
+ */
216
252
  publishToClients(_client, _type, _id, _obj) {
217
253
  throw new Error("no communication handling implemented");
218
254
  }
255
+ /**
256
+ * Delete outdated backup files according to the configured retention settings
257
+ *
258
+ * @param baseFilename Base file name of the backups that should be pruned
259
+ */
219
260
  deleteOldBackupFiles(baseFilename) {
220
261
  let files = import_fs_extra.default.readdirSync(this.backupDir);
221
262
  files.sort();
222
- const limit = Date.now() - this.settings.backup.hours * 36e5;
263
+ const limit = Date.now() - (this.settings.backup?.hours ?? 48) * 36e5;
223
264
  files = files.filter((f) => f.endsWith(`${baseFilename}.gz`));
224
- while (files.length > this.settings.backup.files) {
265
+ while (files.length > (this.settings.backup?.files ?? 24)) {
225
266
  const file = files.shift();
226
267
  if (!file) {
227
268
  continue;
@@ -236,6 +277,11 @@ class InMemoryFileDB {
236
277
  }
237
278
  }
238
279
  }
280
+ /**
281
+ * Format a timestamp into a "YYYY-MM-DD_HH-MM-SS" string used for backup file names
282
+ *
283
+ * @param date Timestamp in milliseconds
284
+ */
239
285
  getTimeStr(date) {
240
286
  const dateObj = new Date(date);
241
287
  let text = `${dateObj.getFullYear().toString()}-`;
@@ -267,7 +313,7 @@ class InMemoryFileDB {
267
313
  async saveState() {
268
314
  try {
269
315
  const jsonString = await this.saveDataset();
270
- if (!this.settings.backup.disabled && jsonString) {
316
+ if (!this.settings.backup?.disabled && jsonString) {
271
317
  this.saveBackup(jsonString);
272
318
  }
273
319
  } finally {
@@ -332,9 +378,10 @@ class InMemoryFileDB {
332
378
  */
333
379
  saveBackup(jsonString) {
334
380
  const now = Date.now();
335
- if (this.settings.backup.period && (!this.lastSave || now - this.lastSave > this.settings.backup.period)) {
381
+ const fileName = this.settings.fileDB?.fileName;
382
+ if (fileName && this.settings.backup?.period && (!this.lastSave || now - this.lastSave > this.settings.backup.period)) {
336
383
  this.lastSave = now;
337
- const backFileName = import_node_path.default.join(this.backupDir, `${this.getTimeStr(now)}_${this.settings.fileDB.fileName}.gz`);
384
+ const backFileName = import_node_path.default.join(this.backupDir, `${this.getTimeStr(now)}_${fileName}.gz`);
338
385
  try {
339
386
  if (!import_fs_extra.default.existsSync(backFileName)) {
340
387
  const output = import_fs_extra.default.createWriteStream(backFileName);
@@ -345,19 +392,32 @@ class InMemoryFileDB {
345
392
  compress.pipe(output);
346
393
  compress.write(jsonString);
347
394
  compress.end();
348
- this.deleteOldBackupFiles(this.settings.fileDB.fileName);
395
+ this.deleteOldBackupFiles(fileName);
349
396
  }
350
397
  } catch (e) {
351
398
  this.log.error(`${this.namespace} Cannot save backup ${backFileName}: ${e.message}`);
352
399
  }
353
400
  }
354
401
  }
402
+ /**
403
+ * Get the current status of the database
404
+ */
355
405
  getStatus() {
356
406
  return { type: "file", server: true };
357
407
  }
408
+ /**
409
+ * Get the currently connected clients. Subclasses override this to return the real clients.
410
+ */
358
411
  getClients() {
359
412
  return {};
360
413
  }
414
+ /**
415
+ * Publish a change to all connected clients and local subscribers
416
+ *
417
+ * @param type The change type (e.g. objects or states)
418
+ * @param id The ID of the changed object or state
419
+ * @param obj The new value of the object or state
420
+ */
361
421
  publishAll(type, id, obj) {
362
422
  if (id === void 0) {
363
423
  this.log.error(`${this.namespace} Can not publish empty ID`);
@@ -380,7 +440,9 @@ class InMemoryFileDB {
380
440
  }
381
441
  return publishCount;
382
442
  }
383
- // Destructor of the class. Called by shutting down.
443
+ /**
444
+ * Destructor of the class. Called when shutting down to persist state and clear timers.
445
+ */
384
446
  async destroy() {
385
447
  if (this.stateTimer) {
386
448
  clearTimeout(this.stateTimer);