velocious 1.0.299 → 1.0.301
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/src/configuration-types.d.ts +1 -1
- package/build/src/configuration.d.ts +1 -1
- package/build/src/database/drivers/base.d.ts +29 -0
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +25 -1
- package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/index.js +10 -1
- package/build/src/database/drivers/mssql/sql/upsert.d.ts +5 -0
- package/build/src/database/drivers/mssql/sql/upsert.d.ts.map +1 -0
- package/build/src/database/drivers/mssql/sql/upsert.js +19 -0
- package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/index.js +10 -1
- package/build/src/database/drivers/mysql/sql/upsert.d.ts +5 -0
- package/build/src/database/drivers/mysql/sql/upsert.d.ts.map +1 -0
- package/build/src/database/drivers/mysql/sql/upsert.js +11 -0
- package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/index.js +10 -1
- package/build/src/database/drivers/pgsql/sql/upsert.d.ts +5 -0
- package/build/src/database/drivers/pgsql/sql/upsert.d.ts.map +1 -0
- package/build/src/database/drivers/pgsql/sql/upsert.js +12 -0
- package/build/src/database/drivers/sqlite/base.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/base.js +7 -1
- package/build/src/database/drivers/sqlite/sql/upsert.d.ts +5 -0
- package/build/src/database/drivers/sqlite/sql/upsert.d.ts.map +1 -0
- package/build/src/database/drivers/sqlite/sql/upsert.js +12 -0
- package/build/src/database/query/upsert-base.d.ts +59 -0
- package/build/src/database/query/upsert-base.d.ts.map +1 -0
- package/build/src/database/query/upsert-base.js +87 -0
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +8 -3
- package/build/src/frontend-models/websocket-channel.d.ts +9 -1
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +17 -3
- package/build/src/http-client/websocket-client.d.ts +8 -6
- package/build/src/http-client/websocket-client.d.ts.map +1 -1
- package/build/src/http-client/websocket-client.js +17 -8
- package/build/src/http-server/client/websocket-session.d.ts +62 -5
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +129 -14
- package/build/src/http-server/websocket-channel.d.ts +18 -3
- package/build/src/http-server/websocket-channel.d.ts.map +1 -1
- package/build/src/http-server/websocket-channel.js +18 -6
- package/build/src/http-server/websocket-event-log-store.d.ts +198 -0
- package/build/src/http-server/websocket-event-log-store.d.ts.map +1 -0
- package/build/src/http-server/websocket-event-log-store.js +308 -0
- package/build/src/http-server/websocket-events-host.d.ts +14 -0
- package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
- package/build/src/http-server/websocket-events-host.js +38 -4
- package/build/src/http-server/worker-handler/in-process.d.ts +5 -1
- package/build/src/http-server/worker-handler/in-process.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/in-process.js +10 -3
- package/build/src/http-server/worker-handler/index.d.ts +5 -1
- package/build/src/http-server/worker-handler/index.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/index.js +5 -3
- package/build/src/http-server/worker-handler/worker-thread.d.ts +9 -1
- package/build/src/http-server/worker-handler/worker-thread.d.ts.map +1 -1
- package/build/src/http-server/worker-handler/worker-thread.js +16 -6
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -5,13 +5,17 @@ export default class VelociousHttpServerWebsocketChannel {
|
|
|
5
5
|
* @param {import("../configuration.js").default} args.configuration - Configuration instance.
|
|
6
6
|
* @param {import("./client/request.js").default | import("./client/websocket-request.js").default | undefined} args.request - Request instance.
|
|
7
7
|
* @param {import("./client/index.js").default} args.client - Client instance.
|
|
8
|
+
* @param {string} [args.lastEventId] - Last received event id.
|
|
9
|
+
* @param {string} [args.subscriptionChannel] - Client-facing subscription channel.
|
|
8
10
|
* @param {import("./client/websocket-session.js").default} args.websocketSession - Websocket session.
|
|
9
11
|
* @param {Record<string, unknown>} [args.subscriptionParams] - Params from subscribe message.
|
|
10
12
|
*/
|
|
11
|
-
constructor({ configuration, request, client, websocketSession, subscriptionParams }) {
|
|
13
|
+
constructor({ configuration, request, client, lastEventId, subscriptionChannel, websocketSession, subscriptionParams }) {
|
|
12
14
|
this.configuration = configuration;
|
|
13
15
|
this.request = request;
|
|
14
16
|
this.client = client;
|
|
17
|
+
this.lastEventId = lastEventId;
|
|
18
|
+
this.subscriptionChannel = subscriptionChannel;
|
|
15
19
|
this.websocketSession = websocketSession;
|
|
16
20
|
this.subscriptionParams = subscriptionParams;
|
|
17
21
|
this._params = this._buildParams();
|
|
@@ -33,24 +37,32 @@ export default class VelociousHttpServerWebsocketChannel {
|
|
|
33
37
|
/**
|
|
34
38
|
* Subscribe this connection to a broadcast channel.
|
|
35
39
|
* @param {string} channel - Channel name.
|
|
36
|
-
* @param {{acknowledge?: boolean}} [options] - Subscription options.
|
|
40
|
+
* @param {{acknowledge?: boolean, lastEventId?: string}} [options] - Subscription options.
|
|
37
41
|
* @returns {Promise<boolean>} - Whether the subscription succeeded.
|
|
38
42
|
*/
|
|
39
43
|
async streamFrom(channel, options = {}) {
|
|
44
|
+
const lastEventId = options.lastEventId ?? this.lastEventId;
|
|
40
45
|
return await this.websocketSession.subscribeToChannel(channel, {
|
|
41
46
|
acknowledge: options.acknowledge ?? true,
|
|
42
|
-
channelHandler: this
|
|
47
|
+
channelHandler: this,
|
|
48
|
+
lastEventId,
|
|
49
|
+
params: this.subscriptionParams,
|
|
50
|
+
subscriptionChannel: this.subscriptionChannel
|
|
43
51
|
});
|
|
44
52
|
}
|
|
45
53
|
/**
|
|
46
54
|
* Called when a broadcast event is delivered for one of this channel instance's subscriptions.
|
|
47
55
|
* @param {object} args - Event args.
|
|
48
56
|
* @param {string} args.channel - Broadcast channel name.
|
|
57
|
+
* @param {string} [args.createdAt] - Event creation timestamp.
|
|
58
|
+
* @param {string} [args.eventId] - Event id.
|
|
49
59
|
* @param {any} args.payload - Broadcast payload.
|
|
60
|
+
* @param {boolean} [args.replayed] - Whether this event was replayed.
|
|
61
|
+
* @param {number} [args.sequence] - Event sequence.
|
|
50
62
|
* @returns {Promise<void>} - Resolves when complete.
|
|
51
63
|
*/
|
|
52
|
-
async receivedBroadcast({ channel, payload }) {
|
|
53
|
-
this.websocketSession.sendJson({ channel, payload, type: "event" });
|
|
64
|
+
async receivedBroadcast({ channel, createdAt, eventId, payload, replayed, sequence }) {
|
|
65
|
+
this.websocketSession.sendJson({ channel, createdAt, eventId, payload, replayed, sequence, type: "event" });
|
|
54
66
|
}
|
|
55
67
|
/**
|
|
56
68
|
* @returns {Record<string, unknown>} - Parsed params.
|
|
@@ -80,4 +92,4 @@ export default class VelociousHttpServerWebsocketChannel {
|
|
|
80
92
|
return params;
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
95
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2Vic29ja2V0LWNoYW5uZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvaHR0cC1zZXJ2ZXIvd2Vic29ja2V0LWNoYW5uZWwuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWTtBQUVaLE1BQU0sQ0FBQyxPQUFPLE9BQU8sbUNBQW1DO0lBQ3REOzs7Ozs7Ozs7T0FTRztJQUNILFlBQVksRUFBQyxhQUFhLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsbUJBQW1CLEVBQUUsZ0JBQWdCLEVBQUUsa0JBQWtCLEVBQUM7UUFDbEgsSUFBSSxDQUFDLGFBQWEsR0FBRyxhQUFhLENBQUE7UUFDbEMsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUE7UUFDdEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUE7UUFDcEIsSUFBSSxDQUFDLFdBQVcsR0FBRyxXQUFXLENBQUE7UUFDOUIsSUFBSSxDQUFDLG1CQUFtQixHQUFHLG1CQUFtQixDQUFBO1FBQzlDLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxnQkFBZ0IsQ0FBQTtRQUN4QyxJQUFJLENBQUMsa0JBQWtCLEdBQUcsa0JBQWtCLENBQUE7UUFDNUMsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUE7SUFDcEMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsTUFBTSxLQUFLLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQSxDQUFDLENBQUM7SUFFaEM7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLFVBQVUsS0FBSSxDQUFDO0lBRXJCOzs7T0FHRztJQUNILEtBQUssQ0FBQyxZQUFZLEtBQUksQ0FBQztJQUV2Qjs7Ozs7T0FLRztJQUNILEtBQUssQ0FBQyxVQUFVLENBQUMsT0FBTyxFQUFFLE9BQU8sR0FBRyxFQUFFO1FBQ3BDLE1BQU0sV0FBVyxHQUFHLE9BQU8sQ0FBQyxXQUFXLElBQUksSUFBSSxDQUFDLFdBQVcsQ0FBQTtRQUUzRCxPQUFPLE1BQU0sSUFBSSxDQUFDLGdCQUFnQixDQUFDLGtCQUFrQixDQUFDLE9BQU8sRUFBRTtZQUM3RCxXQUFXLEVBQUUsT0FBTyxDQUFDLFdBQVcsSUFBSSxJQUFJO1lBQ3hDLGNBQWMsRUFBRSxJQUFJO1lBQ3BCLFdBQVc7WUFDWCxNQUFNLEVBQUUsSUFBSSxDQUFDLGtCQUFrQjtZQUMvQixtQkFBbUIsRUFBRSxJQUFJLENBQUMsbUJBQW1CO1NBQzlDLENBQUMsQ0FBQTtJQUNKLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0gsS0FBSyxDQUFDLGlCQUFpQixDQUFDLEVBQUMsT0FBTyxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUM7UUFDaEYsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxFQUFDLE9BQU8sRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUMsQ0FBQyxDQUFBO0lBQzNHLENBQUM7SUFFRDs7T0FFRztJQUNILFlBQVk7UUFDVixzQ0FBc0M7UUFDdEMsTUFBTSxNQUFNLEdBQUcsRUFBRSxDQUFBO1FBRWpCLElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsQ0FBQztZQUN6QixNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFBO1lBRTNDLElBQUksYUFBYSxJQUFJLE9BQU8sYUFBYSxLQUFLLFFBQVEsRUFBRSxDQUFDO2dCQUN2RCxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxhQUFhLENBQUMsQ0FBQTtZQUN0QyxDQUFDO1FBQ0gsQ0FBQztRQUVELE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxPQUFPLEVBQUUsSUFBSSxFQUFFLEVBQUUsQ0FBQTtRQUN4QyxNQUFNLEtBQUssR0FBRyxTQUFTLEVBQUUsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBRXRDLElBQUksS0FBSyxFQUFFLENBQUM7WUFDVixNQUFNLFlBQVksR0FBRyxJQUFJLGVBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQTtZQUUvQyxLQUFLLE1BQU0sQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLElBQUksWUFBWSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUM7Z0JBQ2xELElBQUksTUFBTSxDQUFDLEdBQUcsQ0FBQyxLQUFLLFNBQVMsRUFBRSxDQUFDO29CQUM5QixNQUFNLENBQUMsR0FBRyxDQUFDLEdBQUcsS0FBSyxDQUFBO2dCQUNyQixDQUFDO1lBQ0gsQ0FBQztRQUNILENBQUM7UUFFRCxJQUFJLElBQUksQ0FBQyxrQkFBa0IsSUFBSSxPQUFPLElBQUksQ0FBQyxrQkFBa0IsS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUMzRSxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsa0JBQWtCLENBQUMsQ0FBQTtRQUNoRCxDQUFDO1FBRUQsT0FBTyxNQUFNLENBQUE7SUFDZixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgVmVsb2Npb3VzSHR0cFNlcnZlcldlYnNvY2tldENoYW5uZWwge1xuICAvKipcbiAgICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBPcHRpb25zIG9iamVjdC5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLmpzXCIpLmRlZmF1bHR9IGFyZ3MuY29uZmlndXJhdGlvbiAtIENvbmZpZ3VyYXRpb24gaW5zdGFuY2UuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi9jbGllbnQvcmVxdWVzdC5qc1wiKS5kZWZhdWx0IHwgaW1wb3J0KFwiLi9jbGllbnQvd2Vic29ja2V0LXJlcXVlc3QuanNcIikuZGVmYXVsdCB8IHVuZGVmaW5lZH0gYXJncy5yZXF1ZXN0IC0gUmVxdWVzdCBpbnN0YW5jZS5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuL2NsaWVudC9pbmRleC5qc1wiKS5kZWZhdWx0fSBhcmdzLmNsaWVudCAtIENsaWVudCBpbnN0YW5jZS5cbiAgICogQHBhcmFtIHtzdHJpbmd9IFthcmdzLmxhc3RFdmVudElkXSAtIExhc3QgcmVjZWl2ZWQgZXZlbnQgaWQuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBbYXJncy5zdWJzY3JpcHRpb25DaGFubmVsXSAtIENsaWVudC1mYWNpbmcgc3Vic2NyaXB0aW9uIGNoYW5uZWwuXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi9jbGllbnQvd2Vic29ja2V0LXNlc3Npb24uanNcIikuZGVmYXVsdH0gYXJncy53ZWJzb2NrZXRTZXNzaW9uIC0gV2Vic29ja2V0IHNlc3Npb24uXG4gICAqIEBwYXJhbSB7UmVjb3JkPHN0cmluZywgdW5rbm93bj59IFthcmdzLnN1YnNjcmlwdGlvblBhcmFtc10gLSBQYXJhbXMgZnJvbSBzdWJzY3JpYmUgbWVzc2FnZS5cbiAgICovXG4gIGNvbnN0cnVjdG9yKHtjb25maWd1cmF0aW9uLCByZXF1ZXN0LCBjbGllbnQsIGxhc3RFdmVudElkLCBzdWJzY3JpcHRpb25DaGFubmVsLCB3ZWJzb2NrZXRTZXNzaW9uLCBzdWJzY3JpcHRpb25QYXJhbXN9KSB7XG4gICAgdGhpcy5jb25maWd1cmF0aW9uID0gY29uZmlndXJhdGlvblxuICAgIHRoaXMucmVxdWVzdCA9IHJlcXVlc3RcbiAgICB0aGlzLmNsaWVudCA9IGNsaWVudFxuICAgIHRoaXMubGFzdEV2ZW50SWQgPSBsYXN0RXZlbnRJZFxuICAgIHRoaXMuc3Vic2NyaXB0aW9uQ2hhbm5lbCA9IHN1YnNjcmlwdGlvbkNoYW5uZWxcbiAgICB0aGlzLndlYnNvY2tldFNlc3Npb24gPSB3ZWJzb2NrZXRTZXNzaW9uXG4gICAgdGhpcy5zdWJzY3JpcHRpb25QYXJhbXMgPSBzdWJzY3JpcHRpb25QYXJhbXNcbiAgICB0aGlzLl9wYXJhbXMgPSB0aGlzLl9idWlsZFBhcmFtcygpXG4gIH1cblxuICAvKipcbiAgICogQHJldHVybnMge1JlY29yZDxzdHJpbmcsIHVua25vd24+fSAtIFBhcmFtcyBmb3IgdGhlIHdlYnNvY2tldCBjb25uZWN0aW9uLlxuICAgKi9cbiAgcGFyYW1zKCkgeyByZXR1cm4gdGhpcy5fcGFyYW1zIH1cblxuICAvKipcbiAgICogQ2FsbGVkIHdoZW4gdGhlIGNoYW5uZWwgaXMgY3JlYXRlZCBmb3IgYSB3ZWJzb2NrZXQgY29ubmVjdGlvbi5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgd2hlbiBjb21wbGV0ZS5cbiAgICovXG4gIGFzeW5jIHN1YnNjcmliZWQoKSB7fVxuXG4gIC8qKlxuICAgKiBDYWxsZWQgd2hlbiB0aGUgd2Vic29ja2V0IGRpc2Nvbm5lY3RzLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIGNvbXBsZXRlLlxuICAgKi9cbiAgYXN5bmMgdW5zdWJzY3JpYmVkKCkge31cblxuICAvKipcbiAgICogU3Vic2NyaWJlIHRoaXMgY29ubmVjdGlvbiB0byBhIGJyb2FkY2FzdCBjaGFubmVsLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gY2hhbm5lbCAtIENoYW5uZWwgbmFtZS5cbiAgICogQHBhcmFtIHt7YWNrbm93bGVkZ2U/OiBib29sZWFuLCBsYXN0RXZlbnRJZD86IHN0cmluZ319IFtvcHRpb25zXSAtIFN1YnNjcmlwdGlvbiBvcHRpb25zLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxib29sZWFuPn0gLSBXaGV0aGVyIHRoZSBzdWJzY3JpcHRpb24gc3VjY2VlZGVkLlxuICAgKi9cbiAgYXN5bmMgc3RyZWFtRnJvbShjaGFubmVsLCBvcHRpb25zID0ge30pIHtcbiAgICBjb25zdCBsYXN0RXZlbnRJZCA9IG9wdGlvbnMubGFzdEV2ZW50SWQgPz8gdGhpcy5sYXN0RXZlbnRJZFxuXG4gICAgcmV0dXJuIGF3YWl0IHRoaXMud2Vic29ja2V0U2Vzc2lvbi5zdWJzY3JpYmVUb0NoYW5uZWwoY2hhbm5lbCwge1xuICAgICAgYWNrbm93bGVkZ2U6IG9wdGlvbnMuYWNrbm93bGVkZ2UgPz8gdHJ1ZSxcbiAgICAgIGNoYW5uZWxIYW5kbGVyOiB0aGlzLFxuICAgICAgbGFzdEV2ZW50SWQsXG4gICAgICBwYXJhbXM6IHRoaXMuc3Vic2NyaXB0aW9uUGFyYW1zLFxuICAgICAgc3Vic2NyaXB0aW9uQ2hhbm5lbDogdGhpcy5zdWJzY3JpcHRpb25DaGFubmVsXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBDYWxsZWQgd2hlbiBhIGJyb2FkY2FzdCBldmVudCBpcyBkZWxpdmVyZWQgZm9yIG9uZSBvZiB0aGlzIGNoYW5uZWwgaW5zdGFuY2UncyBzdWJzY3JpcHRpb25zLlxuICAgKiBAcGFyYW0ge29iamVjdH0gYXJncyAtIEV2ZW50IGFyZ3MuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhcmdzLmNoYW5uZWwgLSBCcm9hZGNhc3QgY2hhbm5lbCBuYW1lLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gW2FyZ3MuY3JlYXRlZEF0XSAtIEV2ZW50IGNyZWF0aW9uIHRpbWVzdGFtcC5cbiAgICogQHBhcmFtIHtzdHJpbmd9IFthcmdzLmV2ZW50SWRdIC0gRXZlbnQgaWQuXG4gICAqIEBwYXJhbSB7YW55fSBhcmdzLnBheWxvYWQgLSBCcm9hZGNhc3QgcGF5bG9hZC5cbiAgICogQHBhcmFtIHtib29sZWFufSBbYXJncy5yZXBsYXllZF0gLSBXaGV0aGVyIHRoaXMgZXZlbnQgd2FzIHJlcGxheWVkLlxuICAgKiBAcGFyYW0ge251bWJlcn0gW2FyZ3Muc2VxdWVuY2VdIC0gRXZlbnQgc2VxdWVuY2UuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gICAqL1xuICBhc3luYyByZWNlaXZlZEJyb2FkY2FzdCh7Y2hhbm5lbCwgY3JlYXRlZEF0LCBldmVudElkLCBwYXlsb2FkLCByZXBsYXllZCwgc2VxdWVuY2V9KSB7XG4gICAgdGhpcy53ZWJzb2NrZXRTZXNzaW9uLnNlbmRKc29uKHtjaGFubmVsLCBjcmVhdGVkQXQsIGV2ZW50SWQsIHBheWxvYWQsIHJlcGxheWVkLCBzZXF1ZW5jZSwgdHlwZTogXCJldmVudFwifSlcbiAgfVxuXG4gIC8qKlxuICAgKiBAcmV0dXJucyB7UmVjb3JkPHN0cmluZywgdW5rbm93bj59IC0gUGFyc2VkIHBhcmFtcy5cbiAgICovXG4gIF9idWlsZFBhcmFtcygpIHtcbiAgICAvKiogQHR5cGUge1JlY29yZDxzdHJpbmcsIHVua25vd24+fSAqL1xuICAgIGNvbnN0IHBhcmFtcyA9IHt9XG5cbiAgICBpZiAodGhpcy5yZXF1ZXN0Py5wYXJhbXMpIHtcbiAgICAgIGNvbnN0IHJlcXVlc3RQYXJhbXMgPSB0aGlzLnJlcXVlc3QucGFyYW1zKClcblxuICAgICAgaWYgKHJlcXVlc3RQYXJhbXMgJiYgdHlwZW9mIHJlcXVlc3RQYXJhbXMgPT09IFwib2JqZWN0XCIpIHtcbiAgICAgICAgT2JqZWN0LmFzc2lnbihwYXJhbXMsIHJlcXVlc3RQYXJhbXMpXG4gICAgICB9XG4gICAgfVxuXG4gICAgY29uc3QgcGF0aFZhbHVlID0gdGhpcy5yZXF1ZXN0Py5wYXRoPy4oKVxuICAgIGNvbnN0IHF1ZXJ5ID0gcGF0aFZhbHVlPy5zcGxpdChcIj9cIilbMV1cblxuICAgIGlmIChxdWVyeSkge1xuICAgICAgY29uc3Qgc2VhcmNoUGFyYW1zID0gbmV3IFVSTFNlYXJjaFBhcmFtcyhxdWVyeSlcblxuICAgICAgZm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2Ygc2VhcmNoUGFyYW1zLmVudHJpZXMoKSkge1xuICAgICAgICBpZiAocGFyYW1zW2tleV0gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgIHBhcmFtc1trZXldID0gdmFsdWVcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGlmICh0aGlzLnN1YnNjcmlwdGlvblBhcmFtcyAmJiB0eXBlb2YgdGhpcy5zdWJzY3JpcHRpb25QYXJhbXMgPT09IFwib2JqZWN0XCIpIHtcbiAgICAgIE9iamVjdC5hc3NpZ24ocGFyYW1zLCB0aGlzLnN1YnNjcmlwdGlvblBhcmFtcylcbiAgICB9XG5cbiAgICByZXR1cm4gcGFyYW1zXG4gIH1cbn1cbiJdfQ==
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} WebsocketEventRow
|
|
3
|
+
* @property {string} channel - Channel name.
|
|
4
|
+
* @property {Date | string} created_at - Creation time.
|
|
5
|
+
* @property {string} id - Event id.
|
|
6
|
+
* @property {string} payload_json - Serialized payload.
|
|
7
|
+
* @property {number | string} sequence - Sequence number.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} WebsocketReplayChannelRow
|
|
11
|
+
* @property {string} channel - Channel name.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @param {import("../configuration.js").default} configuration - Configuration.
|
|
15
|
+
* @returns {VelociousHttpServerWebsocketEventLogStore} - Shared store instance.
|
|
16
|
+
*/
|
|
17
|
+
export function websocketEventLogStoreForConfiguration(configuration: import("../configuration.js").default): VelociousHttpServerWebsocketEventLogStore;
|
|
18
|
+
export default class VelociousHttpServerWebsocketEventLogStore {
|
|
19
|
+
/**
|
|
20
|
+
* @param {object} args - Options.
|
|
21
|
+
* @param {import("../configuration.js").default} args.configuration - Configuration.
|
|
22
|
+
* @param {string} [args.databaseIdentifier] - Database identifier.
|
|
23
|
+
* @param {number} [args.retentionMs] - Event retention in milliseconds.
|
|
24
|
+
*/
|
|
25
|
+
constructor({ configuration, databaseIdentifier, retentionMs }: {
|
|
26
|
+
configuration: import("../configuration.js").default;
|
|
27
|
+
databaseIdentifier?: string | undefined;
|
|
28
|
+
retentionMs?: number | undefined;
|
|
29
|
+
});
|
|
30
|
+
configuration: import("../configuration.js").default;
|
|
31
|
+
databaseIdentifier: string;
|
|
32
|
+
retentionMs: number;
|
|
33
|
+
logger: Logger;
|
|
34
|
+
_isReady: boolean;
|
|
35
|
+
_readyPromise: Promise<void> | null;
|
|
36
|
+
/**
|
|
37
|
+
* @returns {Promise<void>} - Resolves when ready.
|
|
38
|
+
*/
|
|
39
|
+
ensureReady(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* @param {object} args - Options.
|
|
42
|
+
* @param {string} args.channel - Channel name.
|
|
43
|
+
* @param {unknown} args.payload - Event payload.
|
|
44
|
+
* @returns {Promise<{channel: string, createdAt: string, id: string, payload: unknown}>} - Persisted event row.
|
|
45
|
+
*/
|
|
46
|
+
appendEvent({ channel, payload }: {
|
|
47
|
+
channel: string;
|
|
48
|
+
payload: unknown;
|
|
49
|
+
}): Promise<{
|
|
50
|
+
channel: string;
|
|
51
|
+
createdAt: string;
|
|
52
|
+
id: string;
|
|
53
|
+
payload: unknown;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} channel - Channel name.
|
|
57
|
+
* @returns {Promise<void>} - Resolves when the channel interest was persisted.
|
|
58
|
+
*/
|
|
59
|
+
markChannelInterested(channel: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} channel - Channel name.
|
|
62
|
+
* @returns {Promise<boolean>} - Whether the channel should be persisted for replay.
|
|
63
|
+
*/
|
|
64
|
+
shouldPersistChannel(channel: string): Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* @param {object} args - Options.
|
|
67
|
+
* @param {string} args.channel - Channel name.
|
|
68
|
+
* @param {string} args.id - Event id.
|
|
69
|
+
* @returns {Promise<{channel: string, createdAt: string, id: string, payload: unknown, sequence: number} | null>} - Event row or null.
|
|
70
|
+
*/
|
|
71
|
+
getEventById({ channel, id }: {
|
|
72
|
+
channel: string;
|
|
73
|
+
id: string;
|
|
74
|
+
}): Promise<{
|
|
75
|
+
channel: string;
|
|
76
|
+
createdAt: string;
|
|
77
|
+
id: string;
|
|
78
|
+
payload: unknown;
|
|
79
|
+
sequence: number;
|
|
80
|
+
} | null>;
|
|
81
|
+
/**
|
|
82
|
+
* @param {string} channel - Channel name.
|
|
83
|
+
* @returns {Promise<number | null>} - Latest channel sequence.
|
|
84
|
+
*/
|
|
85
|
+
latestSequence(channel: string): Promise<number | null>;
|
|
86
|
+
/**
|
|
87
|
+
* @param {object} args - Options.
|
|
88
|
+
* @param {string} args.channel - Channel name.
|
|
89
|
+
* @param {number} args.sequence - Lower bound sequence.
|
|
90
|
+
* @param {number | null | undefined} [args.upToSequence] - Inclusive ceiling sequence.
|
|
91
|
+
* @returns {Promise<Array<{channel: string, createdAt: string, id: string, payload: unknown, sequence: number}>>} - Ordered events.
|
|
92
|
+
*/
|
|
93
|
+
getEventsAfter({ channel, sequence, upToSequence }: {
|
|
94
|
+
channel: string;
|
|
95
|
+
sequence: number;
|
|
96
|
+
upToSequence?: number | null | undefined;
|
|
97
|
+
}): Promise<Array<{
|
|
98
|
+
channel: string;
|
|
99
|
+
createdAt: string;
|
|
100
|
+
id: string;
|
|
101
|
+
payload: unknown;
|
|
102
|
+
sequence: number;
|
|
103
|
+
}>>;
|
|
104
|
+
/**
|
|
105
|
+
* @param {object} [args] - Options.
|
|
106
|
+
* @param {Date} [args.now] - Cleanup reference time.
|
|
107
|
+
* @returns {Promise<void>} - Resolves when cleanup completes.
|
|
108
|
+
*/
|
|
109
|
+
cleanupExpired({ now }?: {
|
|
110
|
+
now?: Date | undefined;
|
|
111
|
+
}): Promise<void>;
|
|
112
|
+
_ensureSchema(): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
115
|
+
* @returns {Promise<void>} - Resolves when complete.
|
|
116
|
+
*/
|
|
117
|
+
_ensureEventsTable(db: import("../database/drivers/base.js").default): Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
120
|
+
* @returns {Promise<void>} - Resolves when complete.
|
|
121
|
+
*/
|
|
122
|
+
_ensureReplayChannelsTable(db: import("../database/drivers/base.js").default): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* @param {object} args - Options.
|
|
125
|
+
* @param {string} args.channel - Channel name.
|
|
126
|
+
* @param {import("../database/drivers/base.js").default} args.db - Database connection.
|
|
127
|
+
* @param {string} args.id - Event id.
|
|
128
|
+
* @returns {Promise<{channel: string, createdAt: string, id: string, payload: unknown, sequence: number} | null>} - Event row or null.
|
|
129
|
+
*/
|
|
130
|
+
_getEventById({ channel, db, id }: {
|
|
131
|
+
channel: string;
|
|
132
|
+
db: import("../database/drivers/base.js").default;
|
|
133
|
+
id: string;
|
|
134
|
+
}): Promise<{
|
|
135
|
+
channel: string;
|
|
136
|
+
createdAt: string;
|
|
137
|
+
id: string;
|
|
138
|
+
payload: unknown;
|
|
139
|
+
sequence: number;
|
|
140
|
+
} | null>;
|
|
141
|
+
/**
|
|
142
|
+
* @param {WebsocketEventRow} row - Raw row.
|
|
143
|
+
* @returns {{channel: string, createdAt: string, id: string, payload: unknown, sequence: number}} - Normalized row.
|
|
144
|
+
*/
|
|
145
|
+
_normalizeEventRow(row: WebsocketEventRow): {
|
|
146
|
+
channel: string;
|
|
147
|
+
createdAt: string;
|
|
148
|
+
id: string;
|
|
149
|
+
payload: unknown;
|
|
150
|
+
sequence: number;
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
154
|
+
* @param {object} args - Options.
|
|
155
|
+
* @param {string} args.channel - Channel name.
|
|
156
|
+
* @param {Date} args.interestedUntil - Retention deadline.
|
|
157
|
+
* @returns {Promise<void>} - Resolves when the replay-channel row was upserted.
|
|
158
|
+
*/
|
|
159
|
+
_upsertReplayChannelInterest(db: import("../database/drivers/base.js").default, { channel, interestedUntil }: {
|
|
160
|
+
channel: string;
|
|
161
|
+
interestedUntil: Date;
|
|
162
|
+
}): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* @param {(db: import("../database/drivers/base.js").default) => Promise<any>} callback - Callback.
|
|
165
|
+
* @returns {Promise<any>} - Callback result.
|
|
166
|
+
*/
|
|
167
|
+
_withDb(callback: (db: import("../database/drivers/base.js").default) => Promise<any>): Promise<any>;
|
|
168
|
+
}
|
|
169
|
+
export type WebsocketEventRow = {
|
|
170
|
+
/**
|
|
171
|
+
* - Channel name.
|
|
172
|
+
*/
|
|
173
|
+
channel: string;
|
|
174
|
+
/**
|
|
175
|
+
* - Creation time.
|
|
176
|
+
*/
|
|
177
|
+
created_at: Date | string;
|
|
178
|
+
/**
|
|
179
|
+
* - Event id.
|
|
180
|
+
*/
|
|
181
|
+
id: string;
|
|
182
|
+
/**
|
|
183
|
+
* - Serialized payload.
|
|
184
|
+
*/
|
|
185
|
+
payload_json: string;
|
|
186
|
+
/**
|
|
187
|
+
* - Sequence number.
|
|
188
|
+
*/
|
|
189
|
+
sequence: number | string;
|
|
190
|
+
};
|
|
191
|
+
export type WebsocketReplayChannelRow = {
|
|
192
|
+
/**
|
|
193
|
+
* - Channel name.
|
|
194
|
+
*/
|
|
195
|
+
channel: string;
|
|
196
|
+
};
|
|
197
|
+
import Logger from "../logger.js";
|
|
198
|
+
//# sourceMappingURL=websocket-event-log-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket-event-log-store.d.ts","sourceRoot":"","sources":["../../../src/http-server/websocket-event-log-store.js"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import TableData from "../database/table-data/index.js";
|
|
4
|
+
import Logger from "../logger.js";
|
|
5
|
+
const EVENTS_TABLE = "websocket_channel_events";
|
|
6
|
+
const REPLAY_CHANNELS_TABLE = "websocket_replay_channels";
|
|
7
|
+
const DEFAULT_RETENTION_MS = 10 * 60 * 1000;
|
|
8
|
+
const stores = new WeakMap();
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} WebsocketEventRow
|
|
11
|
+
* @property {string} channel - Channel name.
|
|
12
|
+
* @property {Date | string} created_at - Creation time.
|
|
13
|
+
* @property {string} id - Event id.
|
|
14
|
+
* @property {string} payload_json - Serialized payload.
|
|
15
|
+
* @property {number | string} sequence - Sequence number.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {object} WebsocketReplayChannelRow
|
|
19
|
+
* @property {string} channel - Channel name.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* @param {import("../configuration.js").default} configuration - Configuration.
|
|
23
|
+
* @returns {VelociousHttpServerWebsocketEventLogStore} - Shared store instance.
|
|
24
|
+
*/
|
|
25
|
+
export function websocketEventLogStoreForConfiguration(configuration) {
|
|
26
|
+
let store = stores.get(configuration);
|
|
27
|
+
if (!store) {
|
|
28
|
+
store = new VelociousHttpServerWebsocketEventLogStore({ configuration });
|
|
29
|
+
stores.set(configuration, store);
|
|
30
|
+
}
|
|
31
|
+
return store;
|
|
32
|
+
}
|
|
33
|
+
export default class VelociousHttpServerWebsocketEventLogStore {
|
|
34
|
+
/**
|
|
35
|
+
* @param {object} args - Options.
|
|
36
|
+
* @param {import("../configuration.js").default} args.configuration - Configuration.
|
|
37
|
+
* @param {string} [args.databaseIdentifier] - Database identifier.
|
|
38
|
+
* @param {number} [args.retentionMs] - Event retention in milliseconds.
|
|
39
|
+
*/
|
|
40
|
+
constructor({ configuration, databaseIdentifier = "default", retentionMs = DEFAULT_RETENTION_MS }) {
|
|
41
|
+
this.configuration = configuration;
|
|
42
|
+
this.databaseIdentifier = databaseIdentifier;
|
|
43
|
+
this.retentionMs = retentionMs;
|
|
44
|
+
this.logger = new Logger(this);
|
|
45
|
+
this._isReady = false;
|
|
46
|
+
this._readyPromise = null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* @returns {Promise<void>} - Resolves when ready.
|
|
50
|
+
*/
|
|
51
|
+
async ensureReady() {
|
|
52
|
+
if (this._isReady)
|
|
53
|
+
return;
|
|
54
|
+
if (this._readyPromise)
|
|
55
|
+
return await this._readyPromise;
|
|
56
|
+
this._readyPromise = (async () => {
|
|
57
|
+
this.configuration.setCurrent();
|
|
58
|
+
await this._ensureSchema();
|
|
59
|
+
this._isReady = true;
|
|
60
|
+
})();
|
|
61
|
+
try {
|
|
62
|
+
await this._readyPromise;
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
if (!this._isReady) {
|
|
66
|
+
this._readyPromise = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @param {object} args - Options.
|
|
72
|
+
* @param {string} args.channel - Channel name.
|
|
73
|
+
* @param {unknown} args.payload - Event payload.
|
|
74
|
+
* @returns {Promise<{channel: string, createdAt: string, id: string, payload: unknown}>} - Persisted event row.
|
|
75
|
+
*/
|
|
76
|
+
async appendEvent({ channel, payload }) {
|
|
77
|
+
await this.ensureReady();
|
|
78
|
+
const id = randomUUID();
|
|
79
|
+
const createdAt = new Date();
|
|
80
|
+
return await this._withDb(async (db) => {
|
|
81
|
+
await db.insert({
|
|
82
|
+
tableName: EVENTS_TABLE,
|
|
83
|
+
data: {
|
|
84
|
+
channel,
|
|
85
|
+
created_at: createdAt,
|
|
86
|
+
id,
|
|
87
|
+
payload_json: JSON.stringify(payload)
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return { channel, createdAt: createdAt.toISOString(), id, payload };
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} channel - Channel name.
|
|
95
|
+
* @returns {Promise<void>} - Resolves when the channel interest was persisted.
|
|
96
|
+
*/
|
|
97
|
+
async markChannelInterested(channel) {
|
|
98
|
+
await this.ensureReady();
|
|
99
|
+
const interestedUntil = new Date(Date.now() + this.retentionMs);
|
|
100
|
+
await this._withDb(async (db) => {
|
|
101
|
+
await this._upsertReplayChannelInterest(db, { channel, interestedUntil });
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} channel - Channel name.
|
|
106
|
+
* @returns {Promise<boolean>} - Whether the channel should be persisted for replay.
|
|
107
|
+
*/
|
|
108
|
+
async shouldPersistChannel(channel) {
|
|
109
|
+
await this.ensureReady();
|
|
110
|
+
return await this._withDb(async (db) => {
|
|
111
|
+
const rows = await db
|
|
112
|
+
.newQuery()
|
|
113
|
+
.from(REPLAY_CHANNELS_TABLE)
|
|
114
|
+
.where({ channel })
|
|
115
|
+
.where(`interested_until > ${db.quote(new Date())}`)
|
|
116
|
+
.limit(1)
|
|
117
|
+
.results();
|
|
118
|
+
return rows.length > 0;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* @param {object} args - Options.
|
|
123
|
+
* @param {string} args.channel - Channel name.
|
|
124
|
+
* @param {string} args.id - Event id.
|
|
125
|
+
* @returns {Promise<{channel: string, createdAt: string, id: string, payload: unknown, sequence: number} | null>} - Event row or null.
|
|
126
|
+
*/
|
|
127
|
+
async getEventById({ channel, id }) {
|
|
128
|
+
await this.ensureReady();
|
|
129
|
+
return await this._withDb(async (db) => {
|
|
130
|
+
return await this._getEventById({ channel, db, id });
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* @param {string} channel - Channel name.
|
|
135
|
+
* @returns {Promise<number | null>} - Latest channel sequence.
|
|
136
|
+
*/
|
|
137
|
+
async latestSequence(channel) {
|
|
138
|
+
await this.ensureReady();
|
|
139
|
+
return await this._withDb(async (db) => {
|
|
140
|
+
const rows = await db
|
|
141
|
+
.newQuery()
|
|
142
|
+
.from(EVENTS_TABLE)
|
|
143
|
+
.where({ channel })
|
|
144
|
+
.order("sequence DESC")
|
|
145
|
+
.limit(1)
|
|
146
|
+
.results();
|
|
147
|
+
const row = /** @type {Record<string, any> | undefined} */ (rows[0]);
|
|
148
|
+
if (!row)
|
|
149
|
+
return null;
|
|
150
|
+
return Number(row.sequence);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* @param {object} args - Options.
|
|
155
|
+
* @param {string} args.channel - Channel name.
|
|
156
|
+
* @param {number} args.sequence - Lower bound sequence.
|
|
157
|
+
* @param {number | null | undefined} [args.upToSequence] - Inclusive ceiling sequence.
|
|
158
|
+
* @returns {Promise<Array<{channel: string, createdAt: string, id: string, payload: unknown, sequence: number}>>} - Ordered events.
|
|
159
|
+
*/
|
|
160
|
+
async getEventsAfter({ channel, sequence, upToSequence }) {
|
|
161
|
+
await this.ensureReady();
|
|
162
|
+
return await this._withDb(async (db) => {
|
|
163
|
+
const query = db
|
|
164
|
+
.newQuery()
|
|
165
|
+
.from(EVENTS_TABLE)
|
|
166
|
+
.where({ channel })
|
|
167
|
+
.where(`sequence > ${db.quote(sequence)}`)
|
|
168
|
+
.order("sequence ASC");
|
|
169
|
+
if (typeof upToSequence === "number") {
|
|
170
|
+
query.where(`sequence <= ${db.quote(upToSequence)}`);
|
|
171
|
+
}
|
|
172
|
+
const rows = /** @type {WebsocketEventRow[]} */ (await query.results());
|
|
173
|
+
return rows.map((row) => this._normalizeEventRow(row));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* @param {object} [args] - Options.
|
|
178
|
+
* @param {Date} [args.now] - Cleanup reference time.
|
|
179
|
+
* @returns {Promise<void>} - Resolves when cleanup completes.
|
|
180
|
+
*/
|
|
181
|
+
async cleanupExpired({ now = new Date() } = {}) {
|
|
182
|
+
await this.ensureReady();
|
|
183
|
+
const cutoff = new Date(now.getTime() - this.retentionMs);
|
|
184
|
+
await this._withDb(async (db) => {
|
|
185
|
+
const expiredEventRows = /** @type {Array<{id: string}>} */ (await db
|
|
186
|
+
.newQuery()
|
|
187
|
+
.from(EVENTS_TABLE)
|
|
188
|
+
.where(`created_at <= ${db.quote(cutoff)}`)
|
|
189
|
+
.results());
|
|
190
|
+
const expiredReplayChannelRows = /** @type {WebsocketReplayChannelRow[]} */ (await db
|
|
191
|
+
.newQuery()
|
|
192
|
+
.from(REPLAY_CHANNELS_TABLE)
|
|
193
|
+
.where(`interested_until <= ${db.quote(now)}`)
|
|
194
|
+
.results());
|
|
195
|
+
for (const expiredEventRow of expiredEventRows) {
|
|
196
|
+
await db.delete({
|
|
197
|
+
tableName: EVENTS_TABLE,
|
|
198
|
+
conditions: { id: expiredEventRow.id }
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
for (const expiredReplayChannelRow of expiredReplayChannelRows) {
|
|
202
|
+
await db.delete({
|
|
203
|
+
tableName: REPLAY_CHANNELS_TABLE,
|
|
204
|
+
conditions: { channel: expiredReplayChannelRow.channel }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
async _ensureSchema() {
|
|
210
|
+
await this._withDb(async (db) => {
|
|
211
|
+
await this._ensureEventsTable(db);
|
|
212
|
+
await this._ensureReplayChannelsTable(db);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
217
|
+
* @returns {Promise<void>} - Resolves when complete.
|
|
218
|
+
*/
|
|
219
|
+
async _ensureEventsTable(db) {
|
|
220
|
+
this.logger.info("Applying websocket event-log schema");
|
|
221
|
+
if (await db.tableExists(EVENTS_TABLE)) {
|
|
222
|
+
this.logger.info("Websocket event-log table already exists - skipping create");
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const eventTable = new TableData(EVENTS_TABLE, { ifNotExists: true });
|
|
226
|
+
eventTable.integer("sequence", { autoIncrement: true, null: false, primaryKey: true });
|
|
227
|
+
eventTable.string("id", { index: true, null: false });
|
|
228
|
+
eventTable.string("channel", { index: true, null: false });
|
|
229
|
+
eventTable.text("payload_json", { null: false });
|
|
230
|
+
eventTable.datetime("created_at", { index: true, null: false });
|
|
231
|
+
await db.createTable(eventTable);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
235
|
+
* @returns {Promise<void>} - Resolves when complete.
|
|
236
|
+
*/
|
|
237
|
+
async _ensureReplayChannelsTable(db) {
|
|
238
|
+
if (await db.tableExists(REPLAY_CHANNELS_TABLE))
|
|
239
|
+
return;
|
|
240
|
+
const replayChannelTable = new TableData(REPLAY_CHANNELS_TABLE, { ifNotExists: true });
|
|
241
|
+
replayChannelTable.string("channel", { null: false, primaryKey: true });
|
|
242
|
+
replayChannelTable.datetime("interested_until", { index: true, null: false });
|
|
243
|
+
await db.createTable(replayChannelTable);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* @param {object} args - Options.
|
|
247
|
+
* @param {string} args.channel - Channel name.
|
|
248
|
+
* @param {import("../database/drivers/base.js").default} args.db - Database connection.
|
|
249
|
+
* @param {string} args.id - Event id.
|
|
250
|
+
* @returns {Promise<{channel: string, createdAt: string, id: string, payload: unknown, sequence: number} | null>} - Event row or null.
|
|
251
|
+
*/
|
|
252
|
+
async _getEventById({ channel, db, id }) {
|
|
253
|
+
const rows = /** @type {WebsocketEventRow[]} */ (await db
|
|
254
|
+
.newQuery()
|
|
255
|
+
.from(EVENTS_TABLE)
|
|
256
|
+
.where({ channel, id })
|
|
257
|
+
.limit(1)
|
|
258
|
+
.results());
|
|
259
|
+
if (!rows[0])
|
|
260
|
+
return null;
|
|
261
|
+
return this._normalizeEventRow(rows[0]);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* @param {WebsocketEventRow} row - Raw row.
|
|
265
|
+
* @returns {{channel: string, createdAt: string, id: string, payload: unknown, sequence: number}} - Normalized row.
|
|
266
|
+
*/
|
|
267
|
+
_normalizeEventRow(row) {
|
|
268
|
+
const createdAtValue = row.created_at;
|
|
269
|
+
return {
|
|
270
|
+
channel: row.channel,
|
|
271
|
+
createdAt: createdAtValue instanceof Date ? createdAtValue.toISOString() : new Date(createdAtValue).toISOString(),
|
|
272
|
+
id: row.id,
|
|
273
|
+
payload: JSON.parse(row.payload_json),
|
|
274
|
+
sequence: Number(row.sequence)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
279
|
+
* @param {object} args - Options.
|
|
280
|
+
* @param {string} args.channel - Channel name.
|
|
281
|
+
* @param {Date} args.interestedUntil - Retention deadline.
|
|
282
|
+
* @returns {Promise<void>} - Resolves when the replay-channel row was upserted.
|
|
283
|
+
*/
|
|
284
|
+
async _upsertReplayChannelInterest(db, { channel, interestedUntil }) {
|
|
285
|
+
await db.upsert({
|
|
286
|
+
conflictColumns: ["channel"],
|
|
287
|
+
data: {
|
|
288
|
+
channel,
|
|
289
|
+
interested_until: interestedUntil
|
|
290
|
+
},
|
|
291
|
+
tableName: REPLAY_CHANNELS_TABLE,
|
|
292
|
+
updateColumns: ["interested_until"]
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* @param {(db: import("../database/drivers/base.js").default) => Promise<any>} callback - Callback.
|
|
297
|
+
* @returns {Promise<any>} - Callback result.
|
|
298
|
+
*/
|
|
299
|
+
async _withDb(callback) {
|
|
300
|
+
return await this.configuration.ensureConnections(async (dbs) => {
|
|
301
|
+
const db = dbs[this.databaseIdentifier];
|
|
302
|
+
if (!db)
|
|
303
|
+
throw new Error(`No database connection available for identifier: ${this.databaseIdentifier}`);
|
|
304
|
+
return await callback(db);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2Vic29ja2V0LWV2ZW50LWxvZy1zdG9yZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9odHRwLXNlcnZlci93ZWJzb2NrZXQtZXZlbnQtbG9nLXN0b3JlLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFlBQVk7QUFFWixPQUFPLEVBQUMsVUFBVSxFQUFDLE1BQU0sUUFBUSxDQUFBO0FBQ2pDLE9BQU8sU0FBUyxNQUFNLGlDQUFpQyxDQUFBO0FBQ3ZELE9BQU8sTUFBTSxNQUFNLGNBQWMsQ0FBQTtBQUVqQyxNQUFNLFlBQVksR0FBRywwQkFBMEIsQ0FBQTtBQUMvQyxNQUFNLHFCQUFxQixHQUFHLDJCQUEyQixDQUFBO0FBQ3pELE1BQU0sb0JBQW9CLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUE7QUFDM0MsTUFBTSxNQUFNLEdBQUcsSUFBSSxPQUFPLEVBQUUsQ0FBQTtBQUU1Qjs7Ozs7OztHQU9HO0FBRUg7OztHQUdHO0FBRUg7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLHNDQUFzQyxDQUFDLGFBQWE7SUFDbEUsSUFBSSxLQUFLLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUMsQ0FBQTtJQUVyQyxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDWCxLQUFLLEdBQUcsSUFBSSx5Q0FBeUMsQ0FBQyxFQUFDLGFBQWEsRUFBQyxDQUFDLENBQUE7UUFDdEUsTUFBTSxDQUFDLEdBQUcsQ0FBQyxhQUFhLEVBQUUsS0FBSyxDQUFDLENBQUE7SUFDbEMsQ0FBQztJQUVELE9BQU8sS0FBSyxDQUFBO0FBQ2QsQ0FBQztBQUVELE1BQU0sQ0FBQyxPQUFPLE9BQU8seUNBQXlDO0lBQzVEOzs7OztPQUtHO0lBQ0gsWUFBWSxFQUFDLGFBQWEsRUFBRSxrQkFBa0IsR0FBRyxTQUFTLEVBQUUsV0FBVyxHQUFHLG9CQUFvQixFQUFDO1FBQzdGLElBQUksQ0FBQyxhQUFhLEdBQUcsYUFBYSxDQUFBO1FBQ2xD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QUFJLENBQUMsRUFBRTtnQkFBRSxNQUFNLElBQUksS0FBSyxDQUFDLG9EQUFvRCxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQyxDQUFBO1lBRXZHLE9BQU8sTUFBTSxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUE7UUFDM0IsQ0FBQyxDQUFDLENBQUE7SUFDSixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBAdHMtY2hlY2tcblxuaW1wb3J0IHtyYW5kb21VVUlEfSBmcm9tIFwiY3J5cHRvXCJcbmltcG9ydCBUYWJsZURhdGEgZnJvbSBcIi4uL2RhdGFiYXNlL3RhYmxlLWRhdGEvaW5kZXguanNcIlxuaW1wb3J0IExvZ2dlciBmcm9tIFwiLi4vbG9nZ2VyLmpzXCJcblxuY29uc3QgRVZFTlRTX1RBQkxFID0gXCJ3ZWJzb2NrZXRfY2hhbm5lbF9ldmVudHNcIlxuY29uc3QgUkVQTEFZX0NIQU5ORUxTX1RBQkxFID0gXCJ3ZWJzb2NrZXRfcmVwbGF5X2NoYW5uZWxzXCJcbmNvbnN0IERFRkFVTFRfUkVURU5USU9OX01TID0gMTAgKiA2MCAqIDEwMDBcbmNvbnN0IHN0b3JlcyA9IG5ldyBXZWFrTWFwKClcblxuLyoqXG4gKiBAdHlwZWRlZiB7b2JqZWN0fSBXZWJzb2NrZXRFdmVudFJvd1xuICogQHByb3BlcnR5IHtzdHJpbmd9IGNoYW5uZWwgLSBDaGFubmVsIG5hbWUuXG4gKiBAcHJvcGVydHkge0RhdGUgfCBzdHJpbmd9IGNyZWF0ZWRfYXQgLSBDcmVhdGlvbiB0aW1lLlxuICogQHByb3BlcnR5IHtzdHJpbmd9IGlkIC0gRXZlbnQgaWQuXG4gKiBAcHJvcGVydHkge3N0cmluZ30gcGF5bG9hZF9qc29uIC0gU2VyaWFsaXplZCBwYXlsb2FkLlxuICogQHByb3BlcnR5IHtudW1iZXIgfCBzdHJpbmd9IHNlcXVlbmNlIC0gU2VxdWVuY2UgbnVtYmVyLlxuICovXG5cbi8qKlxuICogQHR5cGVkZWYge29iamVjdH0gV2Vic29ja2V0UmVwbGF5Q2hhbm5lbFJvd1xuICogQHByb3BlcnR5IHtzdHJpbmd9IGNoYW5uZWwgLSBDaGFubmVsIG5hbWUuXG4gKi9cblxuLyoqXG4gKiBAcGFyYW0ge2ltcG9ydChcIi4uL2NvbmZpZ3VyYXRpb24uanNcIikuZGVmYXVsdH0gY29uZmlndXJhdGlvbiAtIENvbmZpZ3VyYXRpb24uXG4gKiBAcmV0dXJucyB7VmVsb2Npb3VzSHR0cFNlcnZlcldlYnNvY2tldEV2ZW50TG9nU3RvcmV9IC0gU2hhcmVkIHN0b3JlIGluc3RhbmNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gd2Vic29ja2V0RXZlbnRMb2dTdG9yZUZvckNvbmZpZ3VyYXRpb24oY29uZmlndXJhdGlvbikge1xuICBsZXQgc3RvcmUgPSBzdG9yZXMuZ2V0KGNvbmZpZ3VyYXRpb24pXG5cbiAgaWYgKCFzdG9yZSkge1xuICAgIHN0b3JlID0gbmV3IFZlbG9jaW91c0h0dHBTZXJ2ZXJXZWJzb2NrZXRFdmVudExvZ1N0b3JlKHtjb25maWd1cmF0aW9ufSlcbiAgICBzdG9yZXMuc2V0KGNvbmZpZ3VyYXRpb24sIHN0b3JlKVxuICB9XG5cbiAgcmV0dXJuIHN0b3JlXG59XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFZlbG9jaW91c0h0dHBTZXJ2ZXJXZWJzb2NrZXRFdmVudExvZ1N0b3JlIHtcbiAgLyoqXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gT3B0aW9ucy5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9jb25maWd1cmF0aW9uLmpzXCIpLmRlZmF1bHR9IGFyZ3MuY29uZmlndXJhdGlvbiAtIENvbmZpZ3VyYXRpb24uXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBbYXJncy5kYXRhYmFzZUlkZW50aWZpZXJdIC0gRGF0YWJhc2UgaWRlbnRpZmllci5cbiAgICogQHBhcmFtIHtudW1iZXJ9IFthcmdzLnJldGVudGlvbk1zXSAtIEV2ZW50IHJldGVudGlvbiBpbiBtaWxsaXNlY29uZHMuXG4gICAqL1xuICBjb25zdHJ1Y3Rvcih7Y29uZmlndXJhdGlvbiwgZGF0YWJhc2VJZGVudGlmaWVyID0gXCJkZWZhdWx0XCIsIHJldGVudGlvbk1zID0gREVGQVVMVF9SRVRFTlRJT05fTVN9KSB7XG4gICAgdGhpcy5jb25maWd1cmF0aW9uID0gY29uZmlndXJhdGlvblxuICAgIHRoaXMuZGF0YWJhc2VJZGVudGlmaWVyID0gZGF0YWJhc2VJZGVudGlmaWVyXG4gICAgdGhpcy5yZXRlbnRpb25NcyA9IHJldGVudGlvbk1zXG4gICAgdGhpcy5sb2dnZXIgPSBuZXcgTG9nZ2VyKHRoaXMpXG4gICAgdGhpcy5faXNSZWFkeSA9IGZhbHNlXG4gICAgdGhpcy5fcmVhZHlQcm9taXNlID0gbnVsbFxuICB9XG5cbiAgLyoqXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gcmVhZHkuXG4gICAqL1xuICBhc3luYyBlbnN1cmVSZWFkeSgpIHtcbiAgICBpZiAodGhpcy5faXNSZWFkeSkgcmV0dXJuXG4gICAgaWYgKHRoaXMuX3JlYWR5UHJvbWlzZSkgcmV0dXJuIGF3YWl0IHRoaXMuX3JlYWR5UHJvbWlzZVxuXG4gICAgdGhpcy5fcmVhZHlQcm9taXNlID0gKGFzeW5jICgpID0+IHtcbiAgICAgIHRoaXMuY29uZmlndXJhdGlvbi5zZXRDdXJyZW50KClcbiAgICAgIGF3YWl0IHRoaXMuX2Vuc3VyZVNjaGVtYSgpXG4gICAgICB0aGlzLl9pc1JlYWR5ID0gdHJ1ZVxuICAgIH0pKClcblxuICAgIHRyeSB7XG4gICAgICBhd2FpdCB0aGlzLl9yZWFkeVByb21pc2VcbiAgICB9IGZpbmFsbHkge1xuICAgICAgaWYgKCF0aGlzLl9pc1JlYWR5KSB7XG4gICAgICAgIHRoaXMuX3JlYWR5UHJvbWlzZSA9IG51bGxcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBPcHRpb25zLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXJncy5jaGFubmVsIC0gQ2hhbm5lbCBuYW1lLlxuICAgKiBAcGFyYW0ge3Vua25vd259IGFyZ3MucGF5bG9hZCAtIEV2ZW50IHBheWxvYWQuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHtjaGFubmVsOiBzdHJpbmcsIGNyZWF0ZWRBdDogc3RyaW5nLCBpZDogc3RyaW5nLCBwYXlsb2FkOiB1bmtub3dufT59IC0gUGVyc2lzdGVkIGV2ZW50IHJvdy5cbiAgICovXG4gIGFzeW5jIGFwcGVuZEV2ZW50KHtjaGFubmVsLCBwYXlsb2FkfSkge1xuICAgIGF3YWl0IHRoaXMuZW5zdXJlUmVhZHkoKVxuXG4gICAgY29uc3QgaWQgPSByYW5kb21VVUlEKClcbiAgICBjb25zdCBjcmVhdGVkQXQgPSBuZXcgRGF0ZSgpXG5cbiAgICByZXR1cm4gYXdhaXQgdGhpcy5fd2l0aERiKGFzeW5jIChkYikgPT4ge1xuICAgICAgYXdhaXQgZGIuaW5zZXJ0KHtcbiAgICAgICAgdGFibGVOYW1lOiBFVkVOVFNfVEFCTEUsXG4gICAgICAgIGRhdGE6IHtcbiAgICAgICAgICBjaGFubmVsLFxuICAgICAgICAgIGNyZWF0ZWRfYXQ6IGNyZWF0ZWRBdCxcbiAgICAgICAgICBpZCxcbiAgICAgICAgICBwYXlsb2FkX2pzb246IEpTT04uc3RyaW5naWZ5KHBheWxvYWQpXG4gICAgICAgIH1cbiAgICAgIH0pXG4gICAgICByZXR1cm4ge2NoYW5uZWwsIGNyZWF0ZWRBdDogY3JlYXRlZEF0LnRvSVNPU3RyaW5nKCksIGlkLCBwYXlsb2FkfVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtzdHJpbmd9IGNoYW5uZWwgLSBDaGFubmVsIG5hbWUuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gdGhlIGNoYW5uZWwgaW50ZXJlc3Qgd2FzIHBlcnNpc3RlZC5cbiAgICovXG4gIGFzeW5jIG1hcmtDaGFubmVsSW50ZXJlc3RlZChjaGFubmVsKSB7XG4gICAgYXdhaXQgdGhpcy5lbnN1cmVSZWFkeSgpXG5cbiAgICBjb25zdCBpbnRlcmVzdGVkVW50aWwgPSBuZXcgRGF0ZShEYXRlLm5vdygpICsgdGhpcy5yZXRlbnRpb25NcylcblxuICAgIGF3YWl0IHRoaXMuX3dpdGhEYihhc3luYyAoZGIpID0+IHtcbiAgICAgIGF3YWl0IHRoaXMuX3Vwc2VydFJlcGxheUNoYW5uZWxJbnRlcmVzdChkYiwge2NoYW5uZWwsIGludGVyZXN0ZWRVbnRpbH0pXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge3N0cmluZ30gY2hhbm5lbCAtIENoYW5uZWwgbmFtZS5cbiAgICogQHJldHVybnMge1Byb21pc2U8Ym9vbGVhbj59IC0gV2hldGhlciB0aGUgY2hhbm5lbCBzaG91bGQgYmUgcGVyc2lzdGVkIGZvciByZXBsYXkuXG4gICAqL1xuICBhc3luYyBzaG91bGRQZXJzaXN0Q2hhbm5lbChjaGFubmVsKSB7XG4gICAgYXdhaXQgdGhpcy5lbnN1cmVSZWFkeSgpXG5cbiAgICByZXR1cm4gYXdhaXQgdGhpcy5fd2l0aERiKGFzeW5jIChkYikgPT4ge1xuICAgICAgY29uc3Qgcm93cyA9IGF3YWl0IGRiXG4gICAgICAgIC5uZXdRdWVyeSgpXG4gICAgICAgIC5mcm9tKFJFUExBWV9DSEFOTkVMU19UQUJMRSlcbiAgICAgICAgLndoZXJlKHtjaGFubmVsfSlcbiAgICAgICAgLndoZXJlKGBpbnRlcmVzdGVkX3VudGlsID4gJHtkYi5xdW90ZShuZXcgRGF0ZSgpKX1gKVxuICAgICAgICAubGltaXQoMSlcbiAgICAgICAgLnJlc3VsdHMoKVxuXG4gICAgICByZXR1cm4gcm93cy5sZW5ndGggPiAwXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge29iamVjdH0gYXJncyAtIE9wdGlvbnMuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhcmdzLmNoYW5uZWwgLSBDaGFubmVsIG5hbWUuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhcmdzLmlkIC0gRXZlbnQgaWQuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHtjaGFubmVsOiBzdHJpbmcsIGNyZWF0ZWRBdDogc3RyaW5nLCBpZDogc3RyaW5nLCBwYXlsb2FkOiB1bmtub3duLCBzZXF1ZW5jZTogbnVtYmVyfSB8IG51bGw+fSAtIEV2ZW50IHJvdyBvciBudWxsLlxuICAgKi9cbiAgYXN5bmMgZ2V0RXZlbnRCeUlkKHtjaGFubmVsLCBpZH0pIHtcbiAgICBhd2FpdCB0aGlzLmVuc3VyZVJlYWR5KClcblxuICAgIHJldHVybiBhd2FpdCB0aGlzLl93aXRoRGIoYXN5bmMgKGRiKSA9PiB7XG4gICAgICByZXR1cm4gYXdhaXQgdGhpcy5fZ2V0RXZlbnRCeUlkKHtjaGFubmVsLCBkYiwgaWR9KVxuICAgIH0pXG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtzdHJpbmd9IGNoYW5uZWwgLSBDaGFubmVsIG5hbWUuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPG51bWJlciB8IG51bGw+fSAtIExhdGVzdCBjaGFubmVsIHNlcXVlbmNlLlxuICAgKi9cbiAgYXN5bmMgbGF0ZXN0U2VxdWVuY2UoY2hhbm5lbCkge1xuICAgIGF3YWl0IHRoaXMuZW5zdXJlUmVhZHkoKVxuXG4gICAgcmV0dXJuIGF3YWl0IHRoaXMuX3dpdGhEYihhc3luYyAoZGIpID0+IHtcbiAgICAgIGNvbnN0IHJvd3MgPSBhd2FpdCBkYlxuICAgICAgICAubmV3UXVlcnkoKVxuICAgICAgICAuZnJvbShFVkVOVFNfVEFCTEUpXG4gICAgICAgIC53aGVyZSh7Y2hhbm5lbH0pXG4gICAgICAgIC5vcmRlcihcInNlcXVlbmNlIERFU0NcIilcbiAgICAgICAgLmxpbWl0KDEpXG4gICAgICAgIC5yZXN1bHRzKClcbiAgICAgIGNvbnN0IHJvdyA9IC8qKiBAdHlwZSB7UmVjb3JkPHN0cmluZywgYW55PiB8IHVuZGVmaW5lZH0gKi8gKHJvd3NbMF0pXG5cbiAgICAgIGlmICghcm93KSByZXR1cm4gbnVsbFxuXG4gICAgICByZXR1cm4gTnVtYmVyKHJvdy5zZXF1ZW5jZSlcbiAgICB9KVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gT3B0aW9ucy5cbiAgICogQHBhcmFtIHtzdHJpbmd9IGFyZ3MuY2hhbm5lbCAtIENoYW5uZWwgbmFtZS5cbiAgICogQHBhcmFtIHtudW1iZXJ9IGFyZ3Muc2VxdWVuY2UgLSBMb3dlciBib3VuZCBzZXF1ZW5jZS5cbiAgICogQHBhcmFtIHtudW1iZXIgfCBudWxsIHwgdW5kZWZpbmVkfSBbYXJncy51cFRvU2VxdWVuY2VdIC0gSW5jbHVzaXZlIGNlaWxpbmcgc2VxdWVuY2UuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPEFycmF5PHtjaGFubmVsOiBzdHJpbmcsIGNyZWF0ZWRBdDogc3RyaW5nLCBpZDogc3RyaW5nLCBwYXlsb2FkOiB1bmtub3duLCBzZXF1ZW5jZTogbnVtYmVyfT4+fSAtIE9yZGVyZWQgZXZlbnRzLlxuICAgKi9cbiAgYXN5bmMgZ2V0RXZlbnRzQWZ0ZXIoe2NoYW5uZWwsIHNlcXVlbmNlLCB1cFRvU2VxdWVuY2V9KSB7XG4gICAgYXdhaXQgdGhpcy5lbnN1cmVSZWFkeSgpXG5cbiAgICByZXR1cm4gYXdhaXQgdGhpcy5fd2l0aERiKGFzeW5jIChkYikgPT4ge1xuICAgICAgY29uc3QgcXVlcnkgPSBkYlxuICAgICAgICAubmV3UXVlcnkoKVxuICAgICAgICAuZnJvbShFVkVOVFNfVEFCTEUpXG4gICAgICAgIC53aGVyZSh7Y2hhbm5lbH0pXG4gICAgICAgIC53aGVyZShgc2VxdWVuY2UgPiAke2RiLnF1b3RlKHNlcXVlbmNlKX1gKVxuICAgICAgICAub3JkZXIoXCJzZXF1ZW5jZSBBU0NcIilcblxuICAgICAgaWYgKHR5cGVvZiB1cFRvU2VxdWVuY2UgPT09IFwibnVtYmVyXCIpIHtcbiAgICAgICAgcXVlcnkud2hlcmUoYHNlcXVlbmNlIDw9ICR7ZGIucXVvdGUodXBUb1NlcXVlbmNlKX1gKVxuICAgICAgfVxuXG4gICAgICBjb25zdCByb3dzID0gLyoqIEB0eXBlIHtXZWJzb2NrZXRFdmVudFJvd1tdfSAqLyAoYXdhaXQgcXVlcnkucmVzdWx0cygpKVxuXG4gICAgICByZXR1cm4gcm93cy5tYXAoKHJvdykgPT4gdGhpcy5fbm9ybWFsaXplRXZlbnRSb3cocm93KSlcbiAgICB9KVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBbYXJnc10gLSBPcHRpb25zLlxuICAgKiBAcGFyYW0ge0RhdGV9IFthcmdzLm5vd10gLSBDbGVhbnVwIHJlZmVyZW5jZSB0aW1lLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIGNsZWFudXAgY29tcGxldGVzLlxuICAgKi9cbiAgYXN5bmMgY2xlYW51cEV4cGlyZWQoe25vdyA9IG5ldyBEYXRlKCl9ID0ge30pIHtcbiAgICBhd2FpdCB0aGlzLmVuc3VyZVJlYWR5KClcblxuICAgIGNvbnN0IGN1dG9mZiA9IG5ldyBEYXRlKG5vdy5nZXRUaW1lKCkgLSB0aGlzLnJldGVudGlvbk1zKVxuXG4gICAgYXdhaXQgdGhpcy5fd2l0aERiKGFzeW5jIChkYikgPT4ge1xuICAgICAgY29uc3QgZXhwaXJlZEV2ZW50Um93cyA9IC8qKiBAdHlwZSB7QXJyYXk8e2lkOiBzdHJpbmd9Pn0gKi8gKGF3YWl0IGRiXG4gICAgICAgIC5uZXdRdWVyeSgpXG4gICAgICAgIC5mcm9tKEVWRU5UU19UQUJMRSlcbiAgICAgICAgLndoZXJlKGBjcmVhdGVkX2F0IDw9ICR7ZGIucXVvdGUoY3V0b2ZmKX1gKVxuICAgICAgICAucmVzdWx0cygpKVxuICAgICAgY29uc3QgZXhwaXJlZFJlcGxheUNoYW5uZWxSb3dzID0gLyoqIEB0eXBlIHtXZWJzb2NrZXRSZXBsYXlDaGFubmVsUm93W119ICovIChhd2FpdCBkYlxuICAgICAgICAubmV3UXVlcnkoKVxuICAgICAgICAuZnJvbShSRVBMQVlfQ0hBTk5FTFNfVEFCTEUpXG4gICAgICAgIC53aGVyZShgaW50ZXJlc3RlZF91bnRpbCA8PSAke2RiLnF1b3RlKG5vdyl9YClcbiAgICAgICAgLnJlc3VsdHMoKSlcblxuICAgICAgZm9yIChjb25zdCBleHBpcmVkRXZlbnRSb3cgb2YgZXhwaXJlZEV2ZW50Um93cykge1xuICAgICAgICBhd2FpdCBkYi5kZWxldGUoe1xuICAgICAgICAgIHRhYmxlTmFtZTogRVZFTlRTX1RBQkxFLFxuICAgICAgICAgIGNvbmRpdGlvbnM6IHtpZDogZXhwaXJlZEV2ZW50Um93LmlkfVxuICAgICAgICB9KVxuICAgICAgfVxuXG4gICAgICBmb3IgKGNvbnN0IGV4cGlyZWRSZXBsYXlDaGFubmVsUm93IG9mIGV4cGlyZWRSZXBsYXlDaGFubmVsUm93cykge1xuICAgICAgICBhd2FpdCBkYi5kZWxldGUoe1xuICAgICAgICAgIHRhYmxlTmFtZTogUkVQTEFZX0NIQU5ORUxTX1RBQkxFLFxuICAgICAgICAgIGNvbmRpdGlvbnM6IHtjaGFubmVsOiBleHBpcmVkUmVwbGF5Q2hhbm5lbFJvdy5jaGFubmVsfVxuICAgICAgICB9KVxuICAgICAgfVxuICAgIH0pXG4gIH1cblxuICBhc3luYyBfZW5zdXJlU2NoZW1hKCkge1xuICAgIGF3YWl0IHRoaXMuX3dpdGhEYihhc3luYyAoZGIpID0+IHtcbiAgICAgIGF3YWl0IHRoaXMuX2Vuc3VyZUV2ZW50c1RhYmxlKGRiKVxuICAgICAgYXdhaXQgdGhpcy5fZW5zdXJlUmVwbGF5Q2hhbm5lbHNUYWJsZShkYilcbiAgICB9KVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vZGF0YWJhc2UvZHJpdmVycy9iYXNlLmpzXCIpLmRlZmF1bHR9IGRiIC0gRGF0YWJhc2UgY29ubmVjdGlvbi5cbiAgICogQHJldHVybnMge1Byb21pc2U8dm9pZD59IC0gUmVzb2x2ZXMgd2hlbiBjb21wbGV0ZS5cbiAgICovXG4gIGFzeW5jIF9lbnN1cmVFdmVudHNUYWJsZShkYikge1xuICAgIHRoaXMubG9nZ2VyLmluZm8oXCJBcHBseWluZyB3ZWJzb2NrZXQgZXZlbnQtbG9nIHNjaGVtYVwiKVxuXG4gICAgaWYgKGF3YWl0IGRiLnRhYmxlRXhpc3RzKEVWRU5UU19UQUJMRSkpIHtcbiAgICAgIHRoaXMubG9nZ2VyLmluZm8oXCJXZWJzb2NrZXQgZXZlbnQtbG9nIHRhYmxlIGFscmVhZHkgZXhpc3RzIC0gc2tpcHBpbmcgY3JlYXRlXCIpXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBjb25zdCBldmVudFRhYmxlID0gbmV3IFRhYmxlRGF0YShFVkVOVFNfVEFCTEUsIHtpZk5vdEV4aXN0czogdHJ1ZX0pXG5cbiAgICBldmVudFRhYmxlLmludGVnZXIoXCJzZXF1ZW5jZVwiLCB7YXV0b0luY3JlbWVudDogdHJ1ZSwgbnVsbDogZmFsc2UsIHByaW1hcnlLZXk6IHRydWV9KVxuICAgIGV2ZW50VGFibGUuc3RyaW5nKFwiaWRcIiwge2luZGV4OiB0cnVlLCBudWxsOiBmYWxzZX0pXG4gICAgZXZlbnRUYWJsZS5zdHJpbmcoXCJjaGFubmVsXCIsIHtpbmRleDogdHJ1ZSwgbnVsbDogZmFsc2V9KVxuICAgIGV2ZW50VGFibGUudGV4dChcInBheWxvYWRfanNvblwiLCB7bnVsbDogZmFsc2V9KVxuICAgIGV2ZW50VGFibGUuZGF0ZXRpbWUoXCJjcmVhdGVkX2F0XCIsIHtpbmRleDogdHJ1ZSwgbnVsbDogZmFsc2V9KVxuXG4gICAgYXdhaXQgZGIuY3JlYXRlVGFibGUoZXZlbnRUYWJsZSlcbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0ge2ltcG9ydChcIi4uL2RhdGFiYXNlL2RyaXZlcnMvYmFzZS5qc1wiKS5kZWZhdWx0fSBkYiAtIERhdGFiYXNlIGNvbm5lY3Rpb24uXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fSAtIFJlc29sdmVzIHdoZW4gY29tcGxldGUuXG4gICAqL1xuICBhc3luYyBfZW5zdXJlUmVwbGF5Q2hhbm5lbHNUYWJsZShkYikge1xuICAgIGlmIChhd2FpdCBkYi50YWJsZUV4aXN0cyhSRVBMQVlfQ0hBTk5FTFNfVEFCTEUpKSByZXR1cm5cblxuICAgIGNvbnN0IHJlcGxheUNoYW5uZWxUYWJsZSA9IG5ldyBUYWJsZURhdGEoUkVQTEFZX0NIQU5ORUxTX1RBQkxFLCB7aWZOb3RFeGlzdHM6IHRydWV9KVxuXG4gICAgcmVwbGF5Q2hhbm5lbFRhYmxlLnN0cmluZyhcImNoYW5uZWxcIiwge251bGw6IGZhbHNlLCBwcmltYXJ5S2V5OiB0cnVlfSlcbiAgICByZXBsYXlDaGFubmVsVGFibGUuZGF0ZXRpbWUoXCJpbnRlcmVzdGVkX3VudGlsXCIsIHtpbmRleDogdHJ1ZSwgbnVsbDogZmFsc2V9KVxuXG4gICAgYXdhaXQgZGIuY3JlYXRlVGFibGUocmVwbGF5Q2hhbm5lbFRhYmxlKVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7b2JqZWN0fSBhcmdzIC0gT3B0aW9ucy5cbiAgICogQHBhcmFtIHtzdHJpbmd9IGFyZ3MuY2hhbm5lbCAtIENoYW5uZWwgbmFtZS5cbiAgICogQHBhcmFtIHtpbXBvcnQoXCIuLi9kYXRhYmFzZS9kcml2ZXJzL2Jhc2UuanNcIikuZGVmYXVsdH0gYXJncy5kYiAtIERhdGFiYXNlIGNvbm5lY3Rpb24uXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhcmdzLmlkIC0gRXZlbnQgaWQuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHtjaGFubmVsOiBzdHJpbmcsIGNyZWF0ZWRBdDogc3RyaW5nLCBpZDogc3RyaW5nLCBwYXlsb2FkOiB1bmtub3duLCBzZXF1ZW5jZTogbnVtYmVyfSB8IG51bGw+fSAtIEV2ZW50IHJvdyBvciBudWxsLlxuICAgKi9cbiAgYXN5bmMgX2dldEV2ZW50QnlJZCh7Y2hhbm5lbCwgZGIsIGlkfSkge1xuICAgIGNvbnN0IHJvd3MgPSAvKiogQHR5cGUge1dlYnNvY2tldEV2ZW50Um93W119ICovIChhd2FpdCBkYlxuICAgICAgLm5ld1F1ZXJ5KClcbiAgICAgIC5mcm9tKEVWRU5UU19UQUJMRSlcbiAgICAgIC53aGVyZSh7Y2hhbm5lbCwgaWR9KVxuICAgICAgLmxpbWl0KDEpXG4gICAgICAucmVzdWx0cygpKVxuXG4gICAgaWYgKCFyb3dzWzBdKSByZXR1cm4gbnVsbFxuXG4gICAgcmV0dXJuIHRoaXMuX25vcm1hbGl6ZUV2ZW50Um93KHJvd3NbMF0pXG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIHtXZWJzb2NrZXRFdmVudFJvd30gcm93IC0gUmF3IHJvdy5cbiAgICogQHJldHVybnMge3tjaGFubmVsOiBzdHJpbmcsIGNyZWF0ZWRBdDogc3RyaW5nLCBpZDogc3RyaW5nLCBwYXlsb2FkOiB1bmtub3duLCBzZXF1ZW5jZTogbnVtYmVyfX0gLSBOb3JtYWxpemVkIHJvdy5cbiAgICovXG4gIF9ub3JtYWxpemVFdmVudFJvdyhyb3cpIHtcbiAgICBjb25zdCBjcmVhdGVkQXRWYWx1ZSA9IHJvdy5jcmVhdGVkX2F0XG5cbiAgICByZXR1cm4ge1xuICAgICAgY2hhbm5lbDogcm93LmNoYW5uZWwsXG4gICAgICBjcmVhdGVkQXQ6IGNyZWF0ZWRBdFZhbHVlIGluc3RhbmNlb2YgRGF0ZSA/IGNyZWF0ZWRBdFZhbHVlLnRvSVNPU3RyaW5nKCkgOiBuZXcgRGF0ZShjcmVhdGVkQXRWYWx1ZSkudG9JU09TdHJpbmcoKSxcbiAgICAgIGlkOiByb3cuaWQsXG4gICAgICBwYXlsb2FkOiBKU09OLnBhcnNlKHJvdy5wYXlsb2FkX2pzb24pLFxuICAgICAgc2VxdWVuY2U6IE51bWJlcihyb3cuc2VxdWVuY2UpXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7aW1wb3J0KFwiLi4vZGF0YWJhc2UvZHJpdmVycy9iYXNlLmpzXCIpLmRlZmF1bHR9IGRiIC0gRGF0YWJhc2UgY29ubmVjdGlvbi5cbiAgICogQHBhcmFtIHtvYmplY3R9IGFyZ3MgLSBPcHRpb25zLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXJncy5jaGFubmVsIC0gQ2hhbm5lbCBuYW1lLlxuICAgKiBAcGFyYW0ge0RhdGV9IGFyZ3MuaW50ZXJlc3RlZFVudGlsIC0gUmV0ZW50aW9uIGRlYWRsaW5lLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTx2b2lkPn0gLSBSZXNvbHZlcyB3aGVuIHRoZSByZXBsYXktY2hhbm5lbCByb3cgd2FzIHVwc2VydGVkLlxuICAgKi9cbiAgYXN5bmMgX3Vwc2VydFJlcGxheUNoYW5uZWxJbnRlcmVzdChkYiwge2NoYW5uZWwsIGludGVyZXN0ZWRVbnRpbH0pIHtcbiAgICBhd2FpdCBkYi51cHNlcnQoe1xuICAgICAgY29uZmxpY3RDb2x1bW5zOiBbXCJjaGFubmVsXCJdLFxuICAgICAgZGF0YToge1xuICAgICAgICBjaGFubmVsLFxuICAgICAgICBpbnRlcmVzdGVkX3VudGlsOiBpbnRlcmVzdGVkVW50aWxcbiAgICAgIH0sXG4gICAgICB0YWJsZU5hbWU6IFJFUExBWV9DSEFOTkVMU19UQUJMRSxcbiAgICAgIHVwZGF0ZUNvbHVtbnM6IFtcImludGVyZXN0ZWRfdW50aWxcIl1cbiAgICB9KVxuICB9XG5cbiAgLyoqXG4gICAqIEBwYXJhbSB7KGRiOiBpbXBvcnQoXCIuLi9kYXRhYmFzZS9kcml2ZXJzL2Jhc2UuanNcIikuZGVmYXVsdCkgPT4gUHJvbWlzZTxhbnk+fSBjYWxsYmFjayAtIENhbGxiYWNrLlxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxhbnk+fSAtIENhbGxiYWNrIHJlc3VsdC5cbiAgICovXG4gIGFzeW5jIF93aXRoRGIoY2FsbGJhY2spIHtcbiAgICByZXR1cm4gYXdhaXQgdGhpcy5jb25maWd1cmF0aW9uLmVuc3VyZUNvbm5lY3Rpb25zKGFzeW5jIChkYnMpID0+IHtcbiAgICAgIGNvbnN0IGRiID0gZGJzW3RoaXMuZGF0YWJhc2VJZGVudGlmaWVyXVxuXG4gICAgICBpZiAoIWRiKSB0aHJvdyBuZXcgRXJyb3IoYE5vIGRhdGFiYXNlIGNvbm5lY3Rpb24gYXZhaWxhYmxlIGZvciBpZGVudGlmaWVyOiAke3RoaXMuZGF0YWJhc2VJZGVudGlmaWVyfWApXG5cbiAgICAgIHJldHVybiBhd2FpdCBjYWxsYmFjayhkYilcbiAgICB9KVxuICB9XG59XG4iXX0=
|