@iobroker/db-base 7.2.3 → 8.0.0-alpha.2-20260923-99da17fcc
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.
- package/build/cjs/index.d.ts +1 -1
- package/build/cjs/index.js +3 -3
- package/build/cjs/index.js.map +2 -2
- package/build/cjs/lib/inMemFileDB.d.ts +155 -44
- package/build/cjs/lib/inMemFileDB.js +108 -46
- package/build/cjs/lib/inMemFileDB.js.map +2 -2
- package/build/cjs/lib/redisHandler.d.ts +1 -1
- package/build/cjs/lib/redisHandler.js +11 -11
- package/build/cjs/lib/redisHandler.js.map +2 -2
- package/build/esm/index.d.ts +1 -1
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +1 -1
- package/build/esm/index.js.map +1 -1
- package/build/esm/lib/inMemFileDB.d.ts +155 -44
- package/build/esm/lib/inMemFileDB.d.ts.map +1 -1
- package/build/esm/lib/inMemFileDB.js +107 -49
- package/build/esm/lib/inMemFileDB.js.map +1 -1
- package/build/esm/lib/redisHandler.d.ts +1 -1
- package/build/esm/lib/redisHandler.d.ts.map +1 -1
- package/build/esm/lib/redisHandler.js +7 -11
- package/build/esm/lib/redisHandler.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -6
package/build/cjs/index.d.ts
CHANGED
|
@@ -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
|
package/build/cjs/index.js
CHANGED
|
@@ -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
|
|
20
|
-
__export(
|
|
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(
|
|
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");
|
package/build/cjs/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["export {
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;;;;;;;AAAA,
|
|
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-
|
|
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
|
|
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,
|
|
51
|
+
type ChangeFunction<TObject> = (id: string, obj: TObject) => void;
|
|
52
|
+
/** Status information about the database */
|
|
24
53
|
export interface DbStatus {
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
port
|
|
56
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
protected dataset: Record<string,
|
|
76
|
-
|
|
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
|
-
|
|
132
|
+
protected stateTimer: NodeJS.Timeout | null;
|
|
79
133
|
private callbackSubscriptionClient;
|
|
80
|
-
|
|
134
|
+
protected readonly dataDir: string;
|
|
81
135
|
private readonly datasetName;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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
|
|
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.
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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 (
|
|
161
|
-
this.log.warn(`${this.namespace} Configured backup period ${
|
|
162
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
if (isNaN(
|
|
167
|
-
|
|
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
|
-
|
|
170
|
-
if (isNaN(
|
|
171
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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))
|
|
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))
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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)}_${
|
|
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(
|
|
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
|
-
|
|
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);
|