@uns-kit/core 3.0.27 → 3.0.30
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/MIGRATIONS.md +122 -0
- package/README.md +10 -0
- package/dist/config/app-config.d.ts +14 -0
- package/dist/config/app-config.d.ts.map +1 -1
- package/dist/config/app-config.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/configuration-group-authorization.d.ts +50 -0
- package/dist/runtime/configuration-group-authorization.d.ts.map +1 -0
- package/dist/runtime/configuration-group-authorization.js +113 -0
- package/dist/runtime/configuration-group-authorization.js.map +1 -0
- package/dist/runtime/configuration-group-contract.d.ts +75 -0
- package/dist/runtime/configuration-group-contract.d.ts.map +1 -0
- package/dist/runtime/configuration-group-contract.js +233 -0
- package/dist/runtime/configuration-group-contract.js.map +1 -0
- package/dist/runtime/process-group-authorization.d.ts +6 -0
- package/dist/runtime/process-group-authorization.d.ts.map +1 -1
- package/dist/runtime/process-group-authorization.js +56 -16
- package/dist/runtime/process-group-authorization.js.map +1 -1
- package/dist/runtime/process-group-backup-contract.d.ts +22 -0
- package/dist/runtime/process-group-backup-contract.d.ts.map +1 -0
- package/dist/runtime/process-group-backup-contract.js +20 -0
- package/dist/runtime/process-group-backup-contract.js.map +1 -0
- package/dist/runtime/process-group-backup-contract.json +7 -0
- package/dist/runtime/process-group-configured-upgrade.d.ts +4 -0
- package/dist/runtime/process-group-configured-upgrade.d.ts.map +1 -0
- package/dist/runtime/process-group-configured-upgrade.js +107 -0
- package/dist/runtime/process-group-configured-upgrade.js.map +1 -0
- package/dist/runtime/process-group-generation.d.ts +166 -0
- package/dist/runtime/process-group-generation.d.ts.map +1 -0
- package/dist/runtime/process-group-generation.js +278 -0
- package/dist/runtime/process-group-generation.js.map +1 -0
- package/dist/runtime/process-group-inspection.d.ts +2 -0
- package/dist/runtime/process-group-inspection.d.ts.map +1 -1
- package/dist/runtime/process-group-inspection.js +120 -40
- package/dist/runtime/process-group-inspection.js.map +1 -1
- package/dist/runtime/process-group-resources.d.ts +1 -0
- package/dist/runtime/process-group-resources.d.ts.map +1 -1
- package/dist/runtime/process-group-resources.js +8 -0
- package/dist/runtime/process-group-resources.js.map +1 -1
- package/dist/runtime/process-group-runtime-selection.d.ts +14 -0
- package/dist/runtime/process-group-runtime-selection.d.ts.map +1 -0
- package/dist/runtime/process-group-runtime-selection.js +29 -0
- package/dist/runtime/process-group-runtime-selection.js.map +1 -0
- package/dist/runtime/process-group-snapshot.mjs +60 -8
- package/dist/runtime/process-group-staging.d.ts +2 -2
- package/dist/runtime/process-group-staging.d.ts.map +1 -1
- package/dist/runtime/process-group-staging.js +6 -4
- package/dist/runtime/process-group-staging.js.map +1 -1
- package/dist/runtime/process-group-supervision.d.ts +2 -2
- package/dist/runtime/process-group-supervision.d.ts.map +1 -1
- package/dist/runtime/process-group-supervision.js +5 -3
- package/dist/runtime/process-group-supervision.js.map +1 -1
- package/dist/runtime/process-group-upgrade.d.ts +195 -0
- package/dist/runtime/process-group-upgrade.d.ts.map +1 -0
- package/dist/runtime/process-group-upgrade.js +262 -0
- package/dist/runtime/process-group-upgrade.js.map +1 -0
- package/dist/uns/uns-interfaces.d.ts +3 -0
- package/dist/uns/uns-interfaces.d.ts.map +1 -1
- package/dist/uns/uns-interfaces.js.map +1 -1
- package/dist/uns/uns-proxy-process.d.ts.map +1 -1
- package/dist/uns/uns-proxy-process.js +3 -0
- package/dist/uns/uns-proxy-process.js.map +1 -1
- package/dist/uns-config/config-schema.d.ts +16 -0
- package/dist/uns-config/config-schema.d.ts.map +1 -1
- package/dist/uns-config/mqtt-channel.d.ts +1 -1
- package/dist/uns-config/mqtt-channel.d.ts.map +1 -1
- package/dist/uns-config/mqtt-channel.js +3 -0
- package/dist/uns-config/mqtt-channel.js.map +1 -1
- package/dist/uns-config/uns-core-schema.d.ts +137 -0
- package/dist/uns-config/uns-core-schema.d.ts.map +1 -1
- package/dist/uns-config/uns-core-schema.js +6 -1
- package/dist/uns-config/uns-core-schema.js.map +1 -1
- package/dist/uns-mqtt/mqtt-interfaces.d.ts +5 -0
- package/dist/uns-mqtt/mqtt-interfaces.d.ts.map +1 -1
- package/dist/uns-mqtt/mqtt-interfaces.js.map +1 -1
- package/dist/uns-mqtt/mqtt-proxy.d.ts +3 -0
- package/dist/uns-mqtt/mqtt-proxy.d.ts.map +1 -1
- package/dist/uns-mqtt/mqtt-proxy.js +26 -1
- package/dist/uns-mqtt/mqtt-proxy.js.map +1 -1
- package/dist/uns-mqtt/mqtt-worker.d.ts +3 -1
- package/dist/uns-mqtt/mqtt-worker.d.ts.map +1 -1
- package/dist/uns-mqtt/mqtt-worker.js +45 -8
- package/dist/uns-mqtt/mqtt-worker.js.map +1 -1
- package/dist/uns-mqtt/publish-queue-contract.d.ts +33 -0
- package/dist/uns-mqtt/publish-queue-contract.d.ts.map +1 -0
- package/dist/uns-mqtt/publish-queue-contract.js +18 -0
- package/dist/uns-mqtt/publish-queue-contract.js.map +1 -0
- package/dist/uns-mqtt/publish-queue-store.d.ts +33 -0
- package/dist/uns-mqtt/publish-queue-store.d.ts.map +1 -0
- package/dist/uns-mqtt/publish-queue-store.js +177 -0
- package/dist/uns-mqtt/publish-queue-store.js.map +1 -0
- package/dist/uns-mqtt/publish-queue.d.ts +43 -0
- package/dist/uns-mqtt/publish-queue.d.ts.map +1 -0
- package/dist/uns-mqtt/publish-queue.js +235 -0
- package/dist/uns-mqtt/publish-queue.js.map +1 -0
- package/dist/uns-mqtt/uns-mqtt-proxy.d.ts +7 -0
- package/dist/uns-mqtt/uns-mqtt-proxy.d.ts.map +1 -1
- package/dist/uns-mqtt/uns-mqtt-proxy.js +98 -5
- package/dist/uns-mqtt/uns-mqtt-proxy.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ResolvedMqttPublishQueueOptions } from "./publish-queue-contract.js";
|
|
2
|
+
export type PublishRecord = {
|
|
3
|
+
seq: number;
|
|
4
|
+
id: string;
|
|
5
|
+
topic: string;
|
|
6
|
+
message: string;
|
|
7
|
+
options: {
|
|
8
|
+
qos: 1 | 2;
|
|
9
|
+
retain: boolean;
|
|
10
|
+
};
|
|
11
|
+
bytes: number;
|
|
12
|
+
};
|
|
13
|
+
/** Private, exclusively owned output outbox. It never opens application journals. */
|
|
14
|
+
export declare class MqttPublishQueueStore {
|
|
15
|
+
private config;
|
|
16
|
+
private db;
|
|
17
|
+
private lease;
|
|
18
|
+
private directory;
|
|
19
|
+
private finalSummary?;
|
|
20
|
+
constructor(config: ResolvedMqttPublishQueueOptions, identity: string);
|
|
21
|
+
private transaction;
|
|
22
|
+
summary(): {
|
|
23
|
+
records: number;
|
|
24
|
+
bytes: number;
|
|
25
|
+
lastSeq: number;
|
|
26
|
+
};
|
|
27
|
+
head(): PublishRecord | undefined;
|
|
28
|
+
private physicalBytes;
|
|
29
|
+
put(record: PublishRecord): void;
|
|
30
|
+
complete(record: PublishRecord): void;
|
|
31
|
+
close(): void;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=publish-queue-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-queue-store.d.ts","sourceRoot":"","sources":["../../src/uns-mqtt/publish-queue-store.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,6BAA6B,CAAC;AAEnF,MAAM,MAAM,aAAa,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAGjJ,qFAAqF;AACrF,qBAAa,qBAAqB;IAM9B,OAAO,CAAC,MAAM;IALhB,OAAO,CAAC,EAAE,CAAe;IACzB,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAC,CAAsD;gBAEjE,MAAM,EAAE,+BAA+B,EAC/C,QAAQ,EAAE,MAAM;IAgElB,OAAO,CAAC,WAAW;IAWnB,OAAO,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAK9D,IAAI,IAAI,aAAa,GAAG,SAAS;IAoBjC,OAAO,CAAC,aAAa;IAkBrB,GAAG,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAmBhC,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAIrC,KAAK,IAAI,IAAI;CASd"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, realpathSync, statfsSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
6
|
+
/** Private, exclusively owned output outbox. It never opens application journals. */
|
|
7
|
+
export class MqttPublishQueueStore {
|
|
8
|
+
config;
|
|
9
|
+
db;
|
|
10
|
+
lease;
|
|
11
|
+
directory;
|
|
12
|
+
finalSummary;
|
|
13
|
+
constructor(config, identity) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
if (!path.isAbsolute(config.directory) || path.parse(config.directory).root === config.directory)
|
|
16
|
+
throw Error("MQTT publish queue requires a dedicated absolute directory");
|
|
17
|
+
mkdirSync(config.directory, { recursive: true, mode: 0o700 });
|
|
18
|
+
const dir = lstatSync(config.directory);
|
|
19
|
+
if (!dir.isDirectory() || dir.isSymbolicLink() || dir.mode & 0o077 || (process.getuid && dir.uid !== process.getuid()))
|
|
20
|
+
throw Error("MQTT publish queue directory must be private and owned by this user");
|
|
21
|
+
this.directory = realpathSync(config.directory);
|
|
22
|
+
const file = path.join(this.directory, "mqtt-outbox.sqlite");
|
|
23
|
+
const leaseFile = path.join(this.directory, "mqtt-outbox-owner.sqlite");
|
|
24
|
+
for (const name of [leaseFile, file, file + "-wal", file + "-shm", leaseFile + "-journal"])
|
|
25
|
+
if (existsSync(name)) {
|
|
26
|
+
const stat = lstatSync(name);
|
|
27
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.mode & 0o077 || (process.getuid && stat.uid !== process.getuid()))
|
|
28
|
+
throw Error("Unsafe MQTT publish queue file; preserve and inspect it");
|
|
29
|
+
}
|
|
30
|
+
// node:sqlite is loaded only for the opt-in path; ordinary publishers are unchanged.
|
|
31
|
+
const { DatabaseSync: Sqlite } = createRequire(import.meta.url)("node:sqlite");
|
|
32
|
+
const openPrivate = (name) => {
|
|
33
|
+
if (!existsSync(name))
|
|
34
|
+
closeSync(openSync(name, "wx", 0o600));
|
|
35
|
+
return new Sqlite(name);
|
|
36
|
+
};
|
|
37
|
+
this.lease = openPrivate(leaseFile);
|
|
38
|
+
try {
|
|
39
|
+
this.lease.exec("PRAGMA busy_timeout=0; PRAGMA journal_mode=DELETE; BEGIN EXCLUSIVE");
|
|
40
|
+
const existed = existsSync(file);
|
|
41
|
+
if (existed) {
|
|
42
|
+
const reader = new Sqlite(file, { readOnly: true });
|
|
43
|
+
try {
|
|
44
|
+
if (reader.prepare("PRAGMA page_size").get().page_size !== 4096)
|
|
45
|
+
throw Error("Unsupported MQTT outbox page size; preserve and review storage");
|
|
46
|
+
const meta = reader.prepare("SELECT value FROM metadata WHERE key=?");
|
|
47
|
+
reader.prepare("SELECT seq,id,record,bytes,sha256 FROM pending LIMIT 1").get();
|
|
48
|
+
if (meta.get("schema")?.value !== "1" || meta.get("identity")?.value !== digest(identity))
|
|
49
|
+
throw Error("MQTT outbox identity or schema differs; preserve pending work and use its original destination");
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
reader.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
this.db = openPrivate(file);
|
|
56
|
+
this.db.exec("PRAGMA busy_timeout=1000; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA wal_autocheckpoint=64");
|
|
57
|
+
const pageLimit = Math.floor(config.maxDiskBytes / 4096);
|
|
58
|
+
const selectedPages = Number(this.db.prepare(`PRAGMA max_page_count=${pageLimit}`).get().max_page_count);
|
|
59
|
+
if (selectedPages > pageLimit)
|
|
60
|
+
throw Error("Retained MQTT outbox exceeds maxDiskBytes; preserve and review storage");
|
|
61
|
+
this.db.exec(`CREATE TABLE IF NOT EXISTS metadata(key TEXT PRIMARY KEY,value TEXT NOT NULL);
|
|
62
|
+
CREATE TABLE IF NOT EXISTS pending(seq INTEGER PRIMARY KEY,id TEXT UNIQUE NOT NULL,record TEXT NOT NULL,bytes INTEGER NOT NULL,sha256 TEXT NOT NULL);`);
|
|
63
|
+
if (!existed)
|
|
64
|
+
this.transaction(() => {
|
|
65
|
+
this.db.prepare("INSERT INTO metadata VALUES(?,?)").run("schema", "1");
|
|
66
|
+
this.db.prepare("INSERT INTO metadata VALUES(?,?)").run("identity", digest(identity));
|
|
67
|
+
});
|
|
68
|
+
const fd = openSync(this.directory, "r");
|
|
69
|
+
try {
|
|
70
|
+
fsyncSync(fd);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
closeSync(fd);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
this.db?.close();
|
|
78
|
+
this.lease.close();
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
transaction(run) {
|
|
83
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
84
|
+
try {
|
|
85
|
+
const result = run();
|
|
86
|
+
this.db.exec("COMMIT");
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
this.db.exec("ROLLBACK");
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
summary() {
|
|
95
|
+
if (this.finalSummary)
|
|
96
|
+
return { ...this.finalSummary };
|
|
97
|
+
const row = this.db.prepare("SELECT COUNT(*) AS records,COALESCE(SUM(bytes),0) AS bytes,COALESCE(MAX(seq),0) AS lastSeq FROM pending").get();
|
|
98
|
+
return { records: Number(row.records), bytes: Number(row.bytes), lastSeq: Number(row.lastSeq) };
|
|
99
|
+
}
|
|
100
|
+
head() {
|
|
101
|
+
const row = this.db.prepare("SELECT seq,id,record,bytes,sha256 FROM pending ORDER BY seq LIMIT 1").get();
|
|
102
|
+
if (!row)
|
|
103
|
+
return undefined;
|
|
104
|
+
const serialized = String(row.record);
|
|
105
|
+
if (digest(serialized) !== row.sha256)
|
|
106
|
+
throw Error("MQTT outbox integrity check failed; pending work preserved");
|
|
107
|
+
const value = JSON.parse(serialized);
|
|
108
|
+
if (value.seq !== Number(row.seq) ||
|
|
109
|
+
value.id !== row.id ||
|
|
110
|
+
value.bytes !== Number(row.bytes) ||
|
|
111
|
+
!Number.isSafeInteger(value.seq) ||
|
|
112
|
+
typeof value.id !== "string" ||
|
|
113
|
+
typeof value.topic !== "string" ||
|
|
114
|
+
typeof value.message !== "string" ||
|
|
115
|
+
![1, 2].includes(value.options?.qos) ||
|
|
116
|
+
typeof value.options.retain !== "boolean")
|
|
117
|
+
throw Error("Invalid MQTT outbox record; pending work preserved");
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
physicalBytes() {
|
|
121
|
+
return [
|
|
122
|
+
"mqtt-outbox.sqlite",
|
|
123
|
+
"mqtt-outbox.sqlite-wal",
|
|
124
|
+
"mqtt-outbox.sqlite-shm",
|
|
125
|
+
"mqtt-outbox-owner.sqlite",
|
|
126
|
+
"mqtt-outbox-owner.sqlite-journal",
|
|
127
|
+
].reduce((total, name) => {
|
|
128
|
+
const file = path.join(this.directory, name);
|
|
129
|
+
try {
|
|
130
|
+
return total + lstatSync(file).size;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (error.code === "ENOENT")
|
|
134
|
+
return total;
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}, 0);
|
|
138
|
+
}
|
|
139
|
+
put(record) {
|
|
140
|
+
const space = statfsSync(this.directory, { bigint: true });
|
|
141
|
+
if (space.bavail * space.bsize < BigInt(this.config.minFreeBytes))
|
|
142
|
+
throw Error("MQTT outbox free-space reserve exhausted");
|
|
143
|
+
const serialized = JSON.stringify(record);
|
|
144
|
+
// Account for WAL plus database growth, not just logical pending payloads.
|
|
145
|
+
// Long-lived external readers may prevent truncation: refuse new admission
|
|
146
|
+
// instead of letting their pinned WAL consume the filesystem indefinitely.
|
|
147
|
+
const reserve = 2 * Buffer.byteLength(serialized) + 65536;
|
|
148
|
+
if (this.physicalBytes() + reserve > this.config.maxDiskBytes) {
|
|
149
|
+
this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
150
|
+
if (this.physicalBytes() + reserve > this.config.maxDiskBytes)
|
|
151
|
+
throw Error("MQTT physical disk budget reached; admission rejected");
|
|
152
|
+
}
|
|
153
|
+
this.transaction(() => {
|
|
154
|
+
const total = this.summary();
|
|
155
|
+
if (total.records >= this.config.maxRecords || total.bytes + record.bytes > this.config.maxBytes)
|
|
156
|
+
throw Error("MQTT disk queue capacity reached; admission rejected");
|
|
157
|
+
this.db.prepare("INSERT INTO pending VALUES(?,?,?,?,?)").run(record.seq, record.id, serialized, record.bytes, digest(serialized));
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
complete(record) {
|
|
161
|
+
const result = this.db.prepare("DELETE FROM pending WHERE seq=? AND id=?").run(record.seq, record.id);
|
|
162
|
+
if (Number(result.changes) !== 1)
|
|
163
|
+
throw Error("MQTT outbox checkpoint differs; completion not confirmed");
|
|
164
|
+
}
|
|
165
|
+
close() {
|
|
166
|
+
if (this.finalSummary)
|
|
167
|
+
return;
|
|
168
|
+
this.finalSummary = this.summary();
|
|
169
|
+
try {
|
|
170
|
+
this.db.close();
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
this.lease.close();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=publish-queue-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-queue-store.js","sourceRoot":"","sources":["../../src/uns-mqtt/publish-queue-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, realpathSync, statfsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport path from \"node:path\";\nimport type { DatabaseSync } from \"node:sqlite\";\n\nimport type { ResolvedMqttPublishQueueOptions } from \"./publish-queue-contract.js\";\n\nexport type PublishRecord = { seq: number; id: string; topic: string; message: string; options: { qos: 1 | 2; retain: boolean }; bytes: number };\nconst digest = (value: string) => createHash(\"sha256\").update(value).digest(\"hex\");\n\n/** Private, exclusively owned output outbox. It never opens application journals. */\nexport class MqttPublishQueueStore {\n private db: DatabaseSync;\n private lease: DatabaseSync;\n private directory: string;\n private finalSummary?: { records: number; bytes: number; lastSeq: number };\n constructor(\n private config: ResolvedMqttPublishQueueOptions,\n identity: string,\n ) {\n if (!path.isAbsolute(config.directory) || path.parse(config.directory).root === config.directory)\n throw Error(\"MQTT publish queue requires a dedicated absolute directory\");\n mkdirSync(config.directory, { recursive: true, mode: 0o700 });\n const dir = lstatSync(config.directory);\n if (!dir.isDirectory() || dir.isSymbolicLink() || dir.mode & 0o077 || (process.getuid && dir.uid !== process.getuid()))\n throw Error(\"MQTT publish queue directory must be private and owned by this user\");\n this.directory = realpathSync(config.directory);\n const file = path.join(this.directory, \"mqtt-outbox.sqlite\");\n const leaseFile = path.join(this.directory, \"mqtt-outbox-owner.sqlite\");\n for (const name of [leaseFile, file, file + \"-wal\", file + \"-shm\", leaseFile + \"-journal\"])\n if (existsSync(name)) {\n const stat = lstatSync(name);\n if (!stat.isFile() || stat.isSymbolicLink() || stat.mode & 0o077 || (process.getuid && stat.uid !== process.getuid()))\n throw Error(\"Unsafe MQTT publish queue file; preserve and inspect it\");\n }\n // node:sqlite is loaded only for the opt-in path; ordinary publishers are unchanged.\n const { DatabaseSync: Sqlite } = createRequire(import.meta.url)(\"node:sqlite\") as typeof import(\"node:sqlite\");\n const openPrivate = (name: string) => {\n if (!existsSync(name)) closeSync(openSync(name, \"wx\", 0o600));\n return new Sqlite(name);\n };\n this.lease = openPrivate(leaseFile);\n try {\n this.lease.exec(\"PRAGMA busy_timeout=0; PRAGMA journal_mode=DELETE; BEGIN EXCLUSIVE\");\n const existed = existsSync(file);\n if (existed) {\n const reader = new Sqlite(file, { readOnly: true });\n try {\n if (reader.prepare(\"PRAGMA page_size\").get().page_size !== 4096)\n throw Error(\"Unsupported MQTT outbox page size; preserve and review storage\");\n const meta = reader.prepare(\"SELECT value FROM metadata WHERE key=?\");\n reader.prepare(\"SELECT seq,id,record,bytes,sha256 FROM pending LIMIT 1\").get();\n if (meta.get(\"schema\")?.value !== \"1\" || meta.get(\"identity\")?.value !== digest(identity))\n throw Error(\"MQTT outbox identity or schema differs; preserve pending work and use its original destination\");\n } finally {\n reader.close();\n }\n }\n this.db = openPrivate(file);\n this.db.exec(\"PRAGMA busy_timeout=1000; PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL; PRAGMA wal_autocheckpoint=64\");\n const pageLimit = Math.floor(config.maxDiskBytes / 4096);\n const selectedPages = Number(this.db.prepare(`PRAGMA max_page_count=${pageLimit}`).get().max_page_count);\n if (selectedPages > pageLimit) throw Error(\"Retained MQTT outbox exceeds maxDiskBytes; preserve and review storage\");\n this.db.exec(`CREATE TABLE IF NOT EXISTS metadata(key TEXT PRIMARY KEY,value TEXT NOT NULL);\n CREATE TABLE IF NOT EXISTS pending(seq INTEGER PRIMARY KEY,id TEXT UNIQUE NOT NULL,record TEXT NOT NULL,bytes INTEGER NOT NULL,sha256 TEXT NOT NULL);`);\n if (!existed)\n this.transaction(() => {\n this.db.prepare(\"INSERT INTO metadata VALUES(?,?)\").run(\"schema\", \"1\");\n this.db.prepare(\"INSERT INTO metadata VALUES(?,?)\").run(\"identity\", digest(identity));\n });\n const fd = openSync(this.directory, \"r\");\n try {\n fsyncSync(fd);\n } finally {\n closeSync(fd);\n }\n } catch (error) {\n this.db?.close();\n this.lease.close();\n throw error;\n }\n }\n private transaction<T>(run: () => T): T {\n this.db.exec(\"BEGIN IMMEDIATE\");\n try {\n const result = run();\n this.db.exec(\"COMMIT\");\n return result;\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n }\n summary(): { records: number; bytes: number; lastSeq: number } {\n if (this.finalSummary) return { ...this.finalSummary };\n const row = this.db.prepare(\"SELECT COUNT(*) AS records,COALESCE(SUM(bytes),0) AS bytes,COALESCE(MAX(seq),0) AS lastSeq FROM pending\").get();\n return { records: Number(row.records), bytes: Number(row.bytes), lastSeq: Number(row.lastSeq) };\n }\n head(): PublishRecord | undefined {\n const row = this.db.prepare(\"SELECT seq,id,record,bytes,sha256 FROM pending ORDER BY seq LIMIT 1\").get();\n if (!row) return undefined;\n const serialized = String(row.record);\n if (digest(serialized) !== row.sha256) throw Error(\"MQTT outbox integrity check failed; pending work preserved\");\n const value = JSON.parse(serialized) as PublishRecord;\n if (\n value.seq !== Number(row.seq) ||\n value.id !== row.id ||\n value.bytes !== Number(row.bytes) ||\n !Number.isSafeInteger(value.seq) ||\n typeof value.id !== \"string\" ||\n typeof value.topic !== \"string\" ||\n typeof value.message !== \"string\" ||\n ![1, 2].includes(value.options?.qos) ||\n typeof value.options.retain !== \"boolean\"\n )\n throw Error(\"Invalid MQTT outbox record; pending work preserved\");\n return value;\n }\n private physicalBytes(): number {\n return [\n \"mqtt-outbox.sqlite\",\n \"mqtt-outbox.sqlite-wal\",\n \"mqtt-outbox.sqlite-shm\",\n \"mqtt-outbox-owner.sqlite\",\n \"mqtt-outbox-owner.sqlite-journal\",\n ].reduce((total, name) => {\n const file = path.join(this.directory, name);\n try {\n return total + lstatSync(file).size;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return total;\n throw error;\n }\n }, 0);\n }\n\n put(record: PublishRecord): void {\n const space = statfsSync(this.directory, { bigint: true });\n if (space.bavail * space.bsize < BigInt(this.config.minFreeBytes)) throw Error(\"MQTT outbox free-space reserve exhausted\");\n const serialized = JSON.stringify(record);\n // Account for WAL plus database growth, not just logical pending payloads.\n // Long-lived external readers may prevent truncation: refuse new admission\n // instead of letting their pinned WAL consume the filesystem indefinitely.\n const reserve = 2 * Buffer.byteLength(serialized) + 65536;\n if (this.physicalBytes() + reserve > this.config.maxDiskBytes) {\n this.db.prepare(\"PRAGMA wal_checkpoint(TRUNCATE)\").get();\n if (this.physicalBytes() + reserve > this.config.maxDiskBytes) throw Error(\"MQTT physical disk budget reached; admission rejected\");\n }\n this.transaction(() => {\n const total = this.summary();\n if (total.records >= this.config.maxRecords || total.bytes + record.bytes > this.config.maxBytes)\n throw Error(\"MQTT disk queue capacity reached; admission rejected\");\n this.db.prepare(\"INSERT INTO pending VALUES(?,?,?,?,?)\").run(record.seq, record.id, serialized, record.bytes, digest(serialized));\n });\n }\n complete(record: PublishRecord): void {\n const result = this.db.prepare(\"DELETE FROM pending WHERE seq=? AND id=?\").run(record.seq, record.id);\n if (Number(result.changes) !== 1) throw Error(\"MQTT outbox checkpoint differs; completion not confirmed\");\n }\n close(): void {\n if (this.finalSummary) return;\n this.finalSummary = this.summary();\n try {\n this.db.close();\n } finally {\n this.lease.close();\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { IClientPublishOptions } from "mqtt";
|
|
2
|
+
import { type MqttPublishQueueOptions, type MqttPublishQueueStatus } from "./publish-queue-contract.js";
|
|
3
|
+
import { type PublishRecord } from "./publish-queue-store.js";
|
|
4
|
+
/** MQTT output queue only. Input acknowledgement and business transforms remain caller-owned. */
|
|
5
|
+
export declare class MqttPublishQueue {
|
|
6
|
+
private send;
|
|
7
|
+
private changed;
|
|
8
|
+
private config;
|
|
9
|
+
private store;
|
|
10
|
+
private memory;
|
|
11
|
+
private completions;
|
|
12
|
+
private waiters;
|
|
13
|
+
private seq;
|
|
14
|
+
private active;
|
|
15
|
+
private closed;
|
|
16
|
+
private processing;
|
|
17
|
+
private retryTimer;
|
|
18
|
+
private delivered;
|
|
19
|
+
private retries;
|
|
20
|
+
private lastError;
|
|
21
|
+
private blocked;
|
|
22
|
+
constructor(options: MqttPublishQueueOptions, identity: string, send: (record: PublishRecord) => Promise<void>, active?: boolean, changed?: (status: MqttPublishQueueStatus) => void);
|
|
23
|
+
/** Returns only after RAM admission, or (durable mode) after a FULL SQLite commit. */
|
|
24
|
+
enqueueOrThrow(topic: string, message: string, id: string, options?: IClientPublishOptions): Promise<void>;
|
|
25
|
+
snapshot(): MqttPublishQueueStatus;
|
|
26
|
+
getState(): boolean;
|
|
27
|
+
becomeActive(snapshot?: {
|
|
28
|
+
batchSize?: number;
|
|
29
|
+
referenceHash?: string;
|
|
30
|
+
}): void;
|
|
31
|
+
becomePassive(_n?: number): Promise<{
|
|
32
|
+
batchSize: number;
|
|
33
|
+
referenceHash: string;
|
|
34
|
+
}>;
|
|
35
|
+
waitForIdle(timeoutMs: number): Promise<void>;
|
|
36
|
+
/** Caller must stop its MQTT transport first to release any in-flight publish. */
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
private schedule;
|
|
39
|
+
private run;
|
|
40
|
+
private fail;
|
|
41
|
+
private emit;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=publish-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-queue.d.ts","sourceRoot":"","sources":["../../src/uns-mqtt/publish-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,MAAM,CAAC;AAElD,OAAO,EAAE,KAAK,uBAAuB,EAAE,KAAK,sBAAsB,EAA0B,MAAM,6BAA6B,CAAC;AAChI,OAAO,EAAyB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAErF,iGAAiG;AACjG,qBAAa,gBAAgB;IAmBzB,OAAO,CAAC,IAAI;IAEZ,OAAO,CAAC,OAAO;IApBjB,OAAO,CAAC,MAAM,CAAC;IACf,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAA8E;IACjG,OAAO,CAAC,OAAO,CAA4G;IAC3H,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,MAAM,CAAU;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,UAAU,CAA4C;IAC9D,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,OAAO,CAAoB;gBAGjC,OAAO,EAAE,uBAAuB,EAChC,QAAQ,EAAE,MAAM,EACR,IAAI,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,EACtD,MAAM,UAAO,EACL,OAAO,GAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAe;IActE,sFAAsF;IACtF,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmC1G,QAAQ,IAAI,sBAAsB;IAuBlC,QAAQ,IAAI,OAAO;IAGnB,YAAY,CAAC,QAAQ,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAKvE,aAAa,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAUvF,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB7C,kFAAkF;IAC5E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB5B,OAAO,CAAC,QAAQ;YAcF,GAAG;IAiCjB,OAAO,CAAC,IAAI;IAUZ,OAAO,CAAC,IAAI;CAUb"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { mqttPublishQueueSchema } from "./publish-queue-contract.js";
|
|
2
|
+
import { MqttPublishQueueStore } from "./publish-queue-store.js";
|
|
3
|
+
/** MQTT output queue only. Input acknowledgement and business transforms remain caller-owned. */
|
|
4
|
+
export class MqttPublishQueue {
|
|
5
|
+
send;
|
|
6
|
+
changed;
|
|
7
|
+
config;
|
|
8
|
+
store;
|
|
9
|
+
memory = [];
|
|
10
|
+
completions = new Map();
|
|
11
|
+
waiters = new Set();
|
|
12
|
+
seq;
|
|
13
|
+
active;
|
|
14
|
+
closed = false;
|
|
15
|
+
processing;
|
|
16
|
+
retryTimer;
|
|
17
|
+
delivered = 0;
|
|
18
|
+
retries = 0;
|
|
19
|
+
lastError = null;
|
|
20
|
+
blocked;
|
|
21
|
+
constructor(options, identity, send, active = true, changed = () => { }) {
|
|
22
|
+
this.send = send;
|
|
23
|
+
this.changed = changed;
|
|
24
|
+
this.config = mqttPublishQueueSchema.parse(options);
|
|
25
|
+
this.store = new MqttPublishQueueStore(this.config, identity);
|
|
26
|
+
const saved = this.store.summary();
|
|
27
|
+
if (saved.records > this.config.maxRecords || saved.bytes > this.config.maxBytes) {
|
|
28
|
+
this.store.close();
|
|
29
|
+
throw Error("Retained MQTT outbox exceeds configured limits; preserve and review it");
|
|
30
|
+
}
|
|
31
|
+
this.seq = saved.lastSeq;
|
|
32
|
+
this.active = active;
|
|
33
|
+
this.schedule();
|
|
34
|
+
}
|
|
35
|
+
/** Returns only after RAM admission, or (durable mode) after a FULL SQLite commit. */
|
|
36
|
+
enqueueOrThrow(topic, message, id, options) {
|
|
37
|
+
if (this.closed || this.blocked)
|
|
38
|
+
throw this.blocked ?? Error("MQTT publish queue is closed");
|
|
39
|
+
if (this.completions.has(id))
|
|
40
|
+
throw Error("Duplicate MQTT queue admission id");
|
|
41
|
+
if (!topic ||
|
|
42
|
+
topic.includes("\0") ||
|
|
43
|
+
/[+#]/.test(topic) ||
|
|
44
|
+
Buffer.byteLength(topic) > 65535 ||
|
|
45
|
+
typeof message !== "string" ||
|
|
46
|
+
!id ||
|
|
47
|
+
id.length > 200)
|
|
48
|
+
throw Error("Invalid MQTT publish envelope");
|
|
49
|
+
if (options && Object.keys(options).some((key) => !["qos", "retain"].includes(key)))
|
|
50
|
+
throw Error("Persistent MQTT output currently supports only qos and retain publish options");
|
|
51
|
+
const qos = options?.qos ?? 1;
|
|
52
|
+
if (qos !== 1 && qos !== 2)
|
|
53
|
+
throw Error("Persistent MQTT output requires QoS 1 or 2 broker acknowledgement");
|
|
54
|
+
const bytes = Buffer.byteLength(topic) + Buffer.byteLength(message) + 256;
|
|
55
|
+
if (bytes > this.config.maxMessageBytes)
|
|
56
|
+
throw Error("MQTT publish exceeds maxMessageBytes");
|
|
57
|
+
const total = this.snapshot();
|
|
58
|
+
if (total.pendingRecords >= this.config.maxRecords || total.pendingBytes + bytes > this.config.maxBytes)
|
|
59
|
+
throw Error("MQTT publish queue capacity reached; admission rejected");
|
|
60
|
+
const record = { seq: ++this.seq, id, topic, message, options: { qos, retain: options?.retain ?? false }, bytes };
|
|
61
|
+
if (this.config.mode === "durable" ||
|
|
62
|
+
this.memory.length >= this.config.memoryRecords ||
|
|
63
|
+
this.memory.reduce((sum, r) => sum + r.bytes, 0) + bytes > this.config.memoryBytes)
|
|
64
|
+
this.store.put(record);
|
|
65
|
+
else
|
|
66
|
+
this.memory.push(record);
|
|
67
|
+
const delivered = new Promise((resolve, reject) => this.completions.set(id, { resolve, reject }));
|
|
68
|
+
this.emit();
|
|
69
|
+
this.schedule();
|
|
70
|
+
return delivered;
|
|
71
|
+
}
|
|
72
|
+
snapshot() {
|
|
73
|
+
const disk = this.store.summary();
|
|
74
|
+
return {
|
|
75
|
+
mode: this.config.mode,
|
|
76
|
+
state: this.closed
|
|
77
|
+
? "closed"
|
|
78
|
+
: this.blocked
|
|
79
|
+
? "blocked"
|
|
80
|
+
: this.retryTimer
|
|
81
|
+
? "retrying"
|
|
82
|
+
: disk.records + this.memory.length
|
|
83
|
+
? "pending"
|
|
84
|
+
: "idle",
|
|
85
|
+
pendingRecords: disk.records + this.memory.length,
|
|
86
|
+
pendingBytes: disk.bytes + this.memory.reduce((sum, r) => sum + r.bytes, 0),
|
|
87
|
+
memoryRecords: this.memory.length,
|
|
88
|
+
diskRecords: disk.records,
|
|
89
|
+
inFlight: !!this.processing,
|
|
90
|
+
delivered: this.delivered,
|
|
91
|
+
retries: this.retries,
|
|
92
|
+
lastError: this.lastError,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
getState() {
|
|
96
|
+
return this.active;
|
|
97
|
+
}
|
|
98
|
+
becomeActive(snapshot) {
|
|
99
|
+
if (snapshot?.batchSize)
|
|
100
|
+
throw Error("Persistent MQTT output cannot discard work using legacy handover hashes");
|
|
101
|
+
this.active = true;
|
|
102
|
+
this.schedule();
|
|
103
|
+
}
|
|
104
|
+
async becomePassive(_n) {
|
|
105
|
+
this.active = false;
|
|
106
|
+
if (this.retryTimer) {
|
|
107
|
+
clearTimeout(this.retryTimer);
|
|
108
|
+
this.retryTimer = undefined;
|
|
109
|
+
}
|
|
110
|
+
await this.processing;
|
|
111
|
+
this.emit();
|
|
112
|
+
return { batchSize: 0, referenceHash: "" };
|
|
113
|
+
}
|
|
114
|
+
waitForIdle(timeoutMs) {
|
|
115
|
+
if (this.blocked)
|
|
116
|
+
return Promise.reject(this.blocked);
|
|
117
|
+
if (!this.snapshot().pendingRecords && !this.processing)
|
|
118
|
+
return Promise.resolve();
|
|
119
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
120
|
+
return Promise.reject(Error("MQTT drain timeout must be positive and finite"));
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
const waiter = {
|
|
123
|
+
resolve,
|
|
124
|
+
reject,
|
|
125
|
+
timer: setTimeout(() => {
|
|
126
|
+
this.waiters.delete(waiter);
|
|
127
|
+
reject(Error("MQTT publish queue drain timed out; pending work retained"));
|
|
128
|
+
}, timeoutMs),
|
|
129
|
+
};
|
|
130
|
+
this.waiters.add(waiter);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/** Caller must stop its MQTT transport first to release any in-flight publish. */
|
|
134
|
+
async close() {
|
|
135
|
+
if (this.closed)
|
|
136
|
+
return;
|
|
137
|
+
this.active = false;
|
|
138
|
+
if (this.retryTimer) {
|
|
139
|
+
clearTimeout(this.retryTimer);
|
|
140
|
+
this.retryTimer = undefined;
|
|
141
|
+
}
|
|
142
|
+
await this.processing;
|
|
143
|
+
// Persist all buffered work before releasing the storage lease. Failure leaves it open.
|
|
144
|
+
while (this.memory.length) {
|
|
145
|
+
this.store.put(this.memory[0]);
|
|
146
|
+
this.memory.shift();
|
|
147
|
+
}
|
|
148
|
+
this.closed = true;
|
|
149
|
+
const stopped = Error("MQTT output stopped; undelivered work retained on disk");
|
|
150
|
+
for (const completion of this.completions.values())
|
|
151
|
+
completion.reject(stopped);
|
|
152
|
+
this.completions.clear();
|
|
153
|
+
for (const waiter of this.waiters) {
|
|
154
|
+
clearTimeout(waiter.timer);
|
|
155
|
+
waiter.reject(stopped);
|
|
156
|
+
}
|
|
157
|
+
this.waiters.clear();
|
|
158
|
+
this.emit();
|
|
159
|
+
this.store.close();
|
|
160
|
+
}
|
|
161
|
+
schedule() {
|
|
162
|
+
if (!this.active || this.closed || this.blocked || this.processing || this.retryTimer)
|
|
163
|
+
return;
|
|
164
|
+
// Defer so enqueue acceptance is visible before the delivery callback.
|
|
165
|
+
this.processing = Promise.resolve()
|
|
166
|
+
.then(() => this.run())
|
|
167
|
+
.catch((error) => this.fail(error))
|
|
168
|
+
.finally(() => {
|
|
169
|
+
this.processing = undefined;
|
|
170
|
+
if (!this.closed) {
|
|
171
|
+
this.emit();
|
|
172
|
+
if (this.snapshot().pendingRecords)
|
|
173
|
+
this.schedule();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async run() {
|
|
178
|
+
while (this.active && !this.closed && !this.blocked) {
|
|
179
|
+
const disk = this.store.head();
|
|
180
|
+
const memory = this.memory[0];
|
|
181
|
+
const record = !memory || (disk && disk.seq < memory.seq) ? disk : memory;
|
|
182
|
+
if (!record)
|
|
183
|
+
return;
|
|
184
|
+
try {
|
|
185
|
+
await this.send(record);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// A lost acknowledgement remains uncertain; persist and replay, never mark success.
|
|
189
|
+
// After an observed outage, move all already accepted RAM work to disk.
|
|
190
|
+
while (this.memory.length) {
|
|
191
|
+
this.store.put(this.memory[0]);
|
|
192
|
+
this.memory.shift();
|
|
193
|
+
}
|
|
194
|
+
this.retries++;
|
|
195
|
+
this.lastError = "MQTT delivery unconfirmed; retained for retry";
|
|
196
|
+
if (this.active)
|
|
197
|
+
this.retryTimer = setTimeout(() => {
|
|
198
|
+
this.retryTimer = undefined;
|
|
199
|
+
this.schedule();
|
|
200
|
+
}, this.config.retryIntervalMs);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (record === memory)
|
|
204
|
+
this.memory.shift();
|
|
205
|
+
else
|
|
206
|
+
this.store.complete(record);
|
|
207
|
+
this.delivered++;
|
|
208
|
+
this.lastError = null;
|
|
209
|
+
this.completions.get(record.id)?.resolve();
|
|
210
|
+
this.completions.delete(record.id);
|
|
211
|
+
this.emit();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
fail(reason) {
|
|
215
|
+
this.blocked = reason instanceof Error ? reason : Error("MQTT outbox storage failed");
|
|
216
|
+
this.lastError = "MQTT outbox blocked; preserve storage and inspect local error";
|
|
217
|
+
for (const waiter of this.waiters) {
|
|
218
|
+
clearTimeout(waiter.timer);
|
|
219
|
+
waiter.reject(this.blocked);
|
|
220
|
+
}
|
|
221
|
+
this.waiters.clear();
|
|
222
|
+
this.emit();
|
|
223
|
+
}
|
|
224
|
+
emit() {
|
|
225
|
+
this.changed(this.snapshot());
|
|
226
|
+
if (!this.snapshot().pendingRecords && !this.processing) {
|
|
227
|
+
for (const waiter of this.waiters) {
|
|
228
|
+
clearTimeout(waiter.timer);
|
|
229
|
+
waiter.resolve();
|
|
230
|
+
}
|
|
231
|
+
this.waiters.clear();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=publish-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-queue.js","sourceRoot":"","sources":["../../src/uns-mqtt/publish-queue.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IClientPublishOptions } from \"mqtt\";\n\nimport { type MqttPublishQueueOptions, type MqttPublishQueueStatus, mqttPublishQueueSchema } from \"./publish-queue-contract.js\";\nimport { MqttPublishQueueStore, type PublishRecord } from \"./publish-queue-store.js\";\n\n/** MQTT output queue only. Input acknowledgement and business transforms remain caller-owned. */\nexport class MqttPublishQueue {\n private config;\n private store: MqttPublishQueueStore;\n private memory: PublishRecord[] = [];\n private completions = new Map<string, { resolve: () => void; reject: (error: Error) => void }>();\n private waiters = new Set<{ resolve: () => void; reject: (error: Error) => void; timer: ReturnType<typeof setTimeout> }>();\n private seq: number;\n private active: boolean;\n private closed = false;\n private processing: Promise<void> | undefined;\n private retryTimer: ReturnType<typeof setTimeout> | undefined;\n private delivered = 0;\n private retries = 0;\n private lastError: string | null = null;\n private blocked: Error | undefined;\n\n constructor(\n options: MqttPublishQueueOptions,\n identity: string,\n private send: (record: PublishRecord) => Promise<void>,\n active = true,\n private changed: (status: MqttPublishQueueStatus) => void = () => {},\n ) {\n this.config = mqttPublishQueueSchema.parse(options);\n this.store = new MqttPublishQueueStore(this.config, identity);\n const saved = this.store.summary();\n if (saved.records > this.config.maxRecords || saved.bytes > this.config.maxBytes) {\n this.store.close();\n throw Error(\"Retained MQTT outbox exceeds configured limits; preserve and review it\");\n }\n this.seq = saved.lastSeq;\n this.active = active;\n this.schedule();\n }\n\n /** Returns only after RAM admission, or (durable mode) after a FULL SQLite commit. */\n enqueueOrThrow(topic: string, message: string, id: string, options?: IClientPublishOptions): Promise<void> {\n if (this.closed || this.blocked) throw this.blocked ?? Error(\"MQTT publish queue is closed\");\n if (this.completions.has(id)) throw Error(\"Duplicate MQTT queue admission id\");\n if (\n !topic ||\n topic.includes(\"\\0\") ||\n /[+#]/.test(topic) ||\n Buffer.byteLength(topic) > 65535 ||\n typeof message !== \"string\" ||\n !id ||\n id.length > 200\n )\n throw Error(\"Invalid MQTT publish envelope\");\n if (options && Object.keys(options).some((key) => ![\"qos\", \"retain\"].includes(key)))\n throw Error(\"Persistent MQTT output currently supports only qos and retain publish options\");\n const qos = options?.qos ?? 1;\n if (qos !== 1 && qos !== 2) throw Error(\"Persistent MQTT output requires QoS 1 or 2 broker acknowledgement\");\n const bytes = Buffer.byteLength(topic) + Buffer.byteLength(message) + 256;\n if (bytes > this.config.maxMessageBytes) throw Error(\"MQTT publish exceeds maxMessageBytes\");\n const total = this.snapshot();\n if (total.pendingRecords >= this.config.maxRecords || total.pendingBytes + bytes > this.config.maxBytes)\n throw Error(\"MQTT publish queue capacity reached; admission rejected\");\n const record: PublishRecord = { seq: ++this.seq, id, topic, message, options: { qos, retain: options?.retain ?? false }, bytes };\n if (\n this.config.mode === \"durable\" ||\n this.memory.length >= this.config.memoryRecords ||\n this.memory.reduce((sum, r) => sum + r.bytes, 0) + bytes > this.config.memoryBytes\n )\n this.store.put(record);\n else this.memory.push(record);\n const delivered = new Promise<void>((resolve, reject) => this.completions.set(id, { resolve, reject }));\n this.emit();\n this.schedule();\n return delivered;\n }\n snapshot(): MqttPublishQueueStatus {\n const disk = this.store.summary();\n return {\n mode: this.config.mode,\n state: this.closed\n ? \"closed\"\n : this.blocked\n ? \"blocked\"\n : this.retryTimer\n ? \"retrying\"\n : disk.records + this.memory.length\n ? \"pending\"\n : \"idle\",\n pendingRecords: disk.records + this.memory.length,\n pendingBytes: disk.bytes + this.memory.reduce((sum, r) => sum + r.bytes, 0),\n memoryRecords: this.memory.length,\n diskRecords: disk.records,\n inFlight: !!this.processing,\n delivered: this.delivered,\n retries: this.retries,\n lastError: this.lastError,\n };\n }\n getState(): boolean {\n return this.active;\n }\n becomeActive(snapshot?: { batchSize?: number; referenceHash?: string }): void {\n if (snapshot?.batchSize) throw Error(\"Persistent MQTT output cannot discard work using legacy handover hashes\");\n this.active = true;\n this.schedule();\n }\n async becomePassive(_n?: number): Promise<{ batchSize: number; referenceHash: string }> {\n this.active = false;\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n }\n await this.processing;\n this.emit();\n return { batchSize: 0, referenceHash: \"\" };\n }\n waitForIdle(timeoutMs: number): Promise<void> {\n if (this.blocked) return Promise.reject(this.blocked);\n if (!this.snapshot().pendingRecords && !this.processing) return Promise.resolve();\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return Promise.reject(Error(\"MQTT drain timeout must be positive and finite\"));\n return new Promise((resolve, reject) => {\n const waiter = {\n resolve,\n reject,\n timer: setTimeout(() => {\n this.waiters.delete(waiter);\n reject(Error(\"MQTT publish queue drain timed out; pending work retained\"));\n }, timeoutMs),\n };\n this.waiters.add(waiter);\n });\n }\n /** Caller must stop its MQTT transport first to release any in-flight publish. */\n async close(): Promise<void> {\n if (this.closed) return;\n this.active = false;\n if (this.retryTimer) {\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n }\n await this.processing;\n // Persist all buffered work before releasing the storage lease. Failure leaves it open.\n while (this.memory.length) {\n this.store.put(this.memory[0]);\n this.memory.shift();\n }\n this.closed = true;\n const stopped = Error(\"MQTT output stopped; undelivered work retained on disk\");\n for (const completion of this.completions.values()) completion.reject(stopped);\n this.completions.clear();\n for (const waiter of this.waiters) {\n clearTimeout(waiter.timer);\n waiter.reject(stopped);\n }\n this.waiters.clear();\n this.emit();\n this.store.close();\n }\n private schedule(): void {\n if (!this.active || this.closed || this.blocked || this.processing || this.retryTimer) return;\n // Defer so enqueue acceptance is visible before the delivery callback.\n this.processing = Promise.resolve()\n .then(() => this.run())\n .catch((error) => this.fail(error))\n .finally(() => {\n this.processing = undefined;\n if (!this.closed) {\n this.emit();\n if (this.snapshot().pendingRecords) this.schedule();\n }\n });\n }\n private async run(): Promise<void> {\n while (this.active && !this.closed && !this.blocked) {\n const disk = this.store.head();\n const memory = this.memory[0];\n const record = !memory || (disk && disk.seq < memory.seq) ? disk : memory;\n if (!record) return;\n try {\n await this.send(record);\n } catch {\n // A lost acknowledgement remains uncertain; persist and replay, never mark success.\n // After an observed outage, move all already accepted RAM work to disk.\n while (this.memory.length) {\n this.store.put(this.memory[0]);\n this.memory.shift();\n }\n this.retries++;\n this.lastError = \"MQTT delivery unconfirmed; retained for retry\";\n if (this.active)\n this.retryTimer = setTimeout(() => {\n this.retryTimer = undefined;\n this.schedule();\n }, this.config.retryIntervalMs);\n return;\n }\n if (record === memory) this.memory.shift();\n else this.store.complete(record);\n this.delivered++;\n this.lastError = null;\n this.completions.get(record.id)?.resolve();\n this.completions.delete(record.id);\n this.emit();\n }\n }\n private fail(reason: unknown): void {\n this.blocked = reason instanceof Error ? reason : Error(\"MQTT outbox storage failed\");\n this.lastError = \"MQTT outbox blocked; preserve storage and inspect local error\";\n for (const waiter of this.waiters) {\n clearTimeout(waiter.timer);\n waiter.reject(this.blocked);\n }\n this.waiters.clear();\n this.emit();\n }\n private emit(): void {\n this.changed(this.snapshot());\n if (!this.snapshot().pendingRecords && !this.processing) {\n for (const waiter of this.waiters) {\n clearTimeout(waiter.timer);\n waiter.resolve();\n }\n this.waiters.clear();\n }\n }\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MqttPublishQueueStatus } from "./publish-queue-contract.js";
|
|
1
2
|
import { IClientPublishOptions } from "mqtt";
|
|
2
3
|
import { IMqttPublishRequest, IUnsPacket, IUnsParameters, UnsEvents } from "../uns/uns-interfaces.js";
|
|
3
4
|
import UnsProxy from "../uns/uns-proxy.js";
|
|
@@ -35,6 +36,10 @@ export default class UnsMqttProxy extends UnsProxy {
|
|
|
35
36
|
private lifecycleState;
|
|
36
37
|
private stopPromise;
|
|
37
38
|
private expectedWorkerExit;
|
|
39
|
+
private publishQueueStatus?;
|
|
40
|
+
private admissionBytes;
|
|
41
|
+
private pendingAdmissionBytes;
|
|
42
|
+
private queueRequests;
|
|
38
43
|
constructor(mqttHost: string, processName: string, instanceName: string, unsParameters?: IUnsParameters, publisherActive?: boolean, subscriberActive?: boolean);
|
|
39
44
|
/**
|
|
40
45
|
* Resolve object identity from explicit fields or the tail of the topic path.
|
|
@@ -140,6 +145,8 @@ export default class UnsMqttProxy extends UnsProxy {
|
|
|
140
145
|
*/
|
|
141
146
|
stop(options?: UnsMqttProxyStopOptions): Promise<void>;
|
|
142
147
|
private performStop;
|
|
148
|
+
getPublishQueueStatus(): MqttPublishQueueStatus | undefined;
|
|
149
|
+
private requestPublishQueue;
|
|
143
150
|
private failPendingPublishes;
|
|
144
151
|
private rejectOutstandingPublishes;
|
|
145
152
|
private notifyDrainWaiters;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uns-mqtt-proxy.d.ts","sourceRoot":"","sources":["../../src/uns-mqtt/uns-mqtt-proxy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,MAAM,CAAC;AAM7C,OAAO,EAAE,mBAAmB,EAAsC,UAAU,EAAE,cAAc,EAAmG,SAAS,EAAa,MAAM,0BAA0B,CAAC;AAOtP,OAAO,QAAQ,MAAM,qBAAqB,CAAC;AAa3C,oBAAY,WAAW;IACrB,GAAG,QAAQ,CAAM,iCAAiC;IAClD;;;OAGG;IACH,KAAK,UAAU,CAAE,8BAA8B;IAC/C;;;OAGG;IACH,IAAI,SAAS;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAwCD,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,QAAQ;IAChD,gBAAuB,wBAAwB,SAAU;IACzD,OAAO,CAAC,UAAU,CAA8E;IAChG,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,eAAe,CAAmF;IAC1G,OAAO,CAAC,yBAAyB,CAAqB;IACtD,OAAO,CAAC,aAAa,CAAiB;IACtC,SAAS,CAAC,kBAAkB,EAAE,MAAM,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,iBAAiB,CAAkC;IAC3D,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,0BAA0B,CAAS;IAC3C,OAAO,CAAC,YAAY,CAA8F;IAClH,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,cAAc,CAAyC;IAC/D,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,kBAAkB,CAAS;
|
|
1
|
+
{"version":3,"file":"uns-mqtt-proxy.d.ts","sourceRoot":"","sources":["../../src/uns-mqtt/uns-mqtt-proxy.ts"],"names":[],"mappings":"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"}
|