@blokjs/trigger-pubsub 0.2.2 → 0.6.1
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/__tests__/integration/gcp-pubsub.real-emulator.test.ts +235 -0
- package/__tests__/integration/kafka-pubsub.real-kafka.test.ts +269 -0
- package/__tests__/integration/nats-pubsub.real-nats.test.ts +138 -0
- package/dist/PubSubTrigger.d.ts +43 -4
- package/dist/PubSubTrigger.js +74 -21
- package/dist/adapters/AWSSNSAdapter.d.ts +16 -0
- package/dist/adapters/AWSSNSAdapter.js +52 -9
- package/dist/adapters/AzureServiceBusAdapter.d.ts +15 -0
- package/dist/adapters/AzureServiceBusAdapter.js +44 -11
- package/dist/adapters/GCPPubSubAdapter.d.ts +16 -0
- package/dist/adapters/GCPPubSubAdapter.js +42 -8
- package/dist/adapters/KafkaPubSubAdapter.d.ts +53 -0
- package/dist/adapters/KafkaPubSubAdapter.js +168 -0
- package/dist/adapters/NATSPubSubAdapter.d.ts +52 -0
- package/dist/adapters/NATSPubSubAdapter.js +260 -0
- package/dist/adapters/RedisStreamsPubSubAdapter.d.ts +49 -0
- package/dist/adapters/RedisStreamsPubSubAdapter.js +193 -0
- package/dist/adapters/factory.d.ts +22 -0
- package/dist/adapters/factory.js +80 -0
- package/dist/index.d.ts +36 -45
- package/dist/index.js +39 -46
- package/package.json +22 -10
- package/src/PubSubTrigger.ts +89 -24
- package/src/adapters/AWSSNSAdapter.ts +76 -12
- package/src/adapters/AzureServiceBusAdapter.ts +57 -14
- package/src/adapters/GCPPubSubAdapter.ts +50 -10
- package/src/adapters/KafkaPubSubAdapter.ts +194 -0
- package/src/adapters/NATSPubSubAdapter.ts +326 -0
- package/src/adapters/RedisStreamsPubSubAdapter.ts +225 -0
- package/src/adapters/factory.test.ts +87 -0
- package/src/adapters/factory.ts +88 -0
- package/src/adapters/new-adapters.test.ts +108 -0
- package/src/index.ts +40 -41
- package/template/package.json +6 -6
- package/template/src/runner/PubSubServer.ts +2 -2
- package/template/src/workflows/messages/on-message.ts +38 -34
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NATSPubSubAdapter — v0.7 PR 6 — Pub/Sub adapter backed by NATS.
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* - **Fan-out** (default, when `consumerGroup` is absent): every
|
|
6
|
+
* subscriber receives every message on the subject. NATS Core
|
|
7
|
+
* publish/subscribe semantics — cheapest path, no persistence.
|
|
8
|
+
* - **Competing-consumer** (when `consumerGroup` is set): NATS
|
|
9
|
+
* Queue Group — exactly one subscriber in the named group
|
|
10
|
+
* receives each message. Pure NATS Core.
|
|
11
|
+
* - **Durable** (when `durable: true`): subscribe via NATS
|
|
12
|
+
* JetStream consumer so the subscription survives restarts and
|
|
13
|
+
* replays missed messages from `startFrom`. Required for the
|
|
14
|
+
* `{seq}` / `{timestamp}` replay cursors.
|
|
15
|
+
*
|
|
16
|
+
* Subject wildcards (`orders.*.created`, `orders.>`) are honored by
|
|
17
|
+
* NATS natively in both modes.
|
|
18
|
+
*
|
|
19
|
+
* Requires `nats` as a peer dependency:
|
|
20
|
+
*
|
|
21
|
+
* bun add nats
|
|
22
|
+
*
|
|
23
|
+
* Environment variables:
|
|
24
|
+
* - `NATS_SERVERS` — comma-separated URLs (default `localhost:4222`).
|
|
25
|
+
* - `NATS_TOKEN` — bearer token authentication.
|
|
26
|
+
* - `NATS_USER` / `NATS_PASS` — userpass authentication.
|
|
27
|
+
*/
|
|
28
|
+
import type { PubSubTriggerOpts } from "@blokjs/helper";
|
|
29
|
+
import type { PubSubAdapter, PubSubMessage } from "../PubSubTrigger";
|
|
30
|
+
export interface NATSPubSubConfig {
|
|
31
|
+
servers: string[];
|
|
32
|
+
token?: string;
|
|
33
|
+
user?: string;
|
|
34
|
+
pass?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare class NATSPubSubAdapter implements PubSubAdapter {
|
|
37
|
+
readonly provider: "nats";
|
|
38
|
+
private readonly config;
|
|
39
|
+
private conn;
|
|
40
|
+
private subscriptions;
|
|
41
|
+
private connected;
|
|
42
|
+
constructor(config?: Partial<NATSPubSubConfig>);
|
|
43
|
+
connect(): Promise<void>;
|
|
44
|
+
disconnect(): Promise<void>;
|
|
45
|
+
subscribe(config: PubSubTriggerOpts, handler: (message: PubSubMessage) => Promise<void>): Promise<void>;
|
|
46
|
+
private dispatchJsMessage;
|
|
47
|
+
private dispatchCoreMessage;
|
|
48
|
+
unsubscribe(subscription: string): Promise<void>;
|
|
49
|
+
publish(topic: string, payload: unknown): Promise<void>;
|
|
50
|
+
isConnected(): boolean;
|
|
51
|
+
healthCheck(): Promise<boolean>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NATSPubSubAdapter — v0.7 PR 6 — Pub/Sub adapter backed by NATS.
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* - **Fan-out** (default, when `consumerGroup` is absent): every
|
|
6
|
+
* subscriber receives every message on the subject. NATS Core
|
|
7
|
+
* publish/subscribe semantics — cheapest path, no persistence.
|
|
8
|
+
* - **Competing-consumer** (when `consumerGroup` is set): NATS
|
|
9
|
+
* Queue Group — exactly one subscriber in the named group
|
|
10
|
+
* receives each message. Pure NATS Core.
|
|
11
|
+
* - **Durable** (when `durable: true`): subscribe via NATS
|
|
12
|
+
* JetStream consumer so the subscription survives restarts and
|
|
13
|
+
* replays missed messages from `startFrom`. Required for the
|
|
14
|
+
* `{seq}` / `{timestamp}` replay cursors.
|
|
15
|
+
*
|
|
16
|
+
* Subject wildcards (`orders.*.created`, `orders.>`) are honored by
|
|
17
|
+
* NATS natively in both modes.
|
|
18
|
+
*
|
|
19
|
+
* Requires `nats` as a peer dependency:
|
|
20
|
+
*
|
|
21
|
+
* bun add nats
|
|
22
|
+
*
|
|
23
|
+
* Environment variables:
|
|
24
|
+
* - `NATS_SERVERS` — comma-separated URLs (default `localhost:4222`).
|
|
25
|
+
* - `NATS_TOKEN` — bearer token authentication.
|
|
26
|
+
* - `NATS_USER` / `NATS_PASS` — userpass authentication.
|
|
27
|
+
*/
|
|
28
|
+
import { v4 as uuid } from "uuid";
|
|
29
|
+
const TEXT_DECODER = new TextDecoder();
|
|
30
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
31
|
+
export class NATSPubSubAdapter {
|
|
32
|
+
provider = "nats";
|
|
33
|
+
config;
|
|
34
|
+
conn = null;
|
|
35
|
+
subscriptions = new Map();
|
|
36
|
+
connected = false;
|
|
37
|
+
constructor(config) {
|
|
38
|
+
this.config = {
|
|
39
|
+
servers: config?.servers ?? (process.env.NATS_SERVERS ?? "localhost:4222").split(",").map((s) => s.trim()),
|
|
40
|
+
token: config?.token ?? process.env.NATS_TOKEN,
|
|
41
|
+
user: config?.user ?? process.env.NATS_USER,
|
|
42
|
+
pass: config?.pass ?? process.env.NATS_PASS,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async connect() {
|
|
46
|
+
if (this.connected)
|
|
47
|
+
return;
|
|
48
|
+
try {
|
|
49
|
+
// biome-ignore lint/suspicious/noExplicitAny: nats is a runtime peer dep.
|
|
50
|
+
const nats = await import("nats");
|
|
51
|
+
this.conn = (await nats.connect({
|
|
52
|
+
servers: this.config.servers,
|
|
53
|
+
token: this.config.token,
|
|
54
|
+
user: this.config.user,
|
|
55
|
+
pass: this.config.pass,
|
|
56
|
+
}));
|
|
57
|
+
this.connected = true;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
throw new Error(`[blok][pubsub-nats] connect failed: ${err.message}. Install nats as a peer dependency: bun add nats`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async disconnect() {
|
|
64
|
+
if (!this.connected)
|
|
65
|
+
return;
|
|
66
|
+
for (const sub of this.subscriptions.values()) {
|
|
67
|
+
try {
|
|
68
|
+
const result = sub.unsubscribe();
|
|
69
|
+
if (result instanceof Promise)
|
|
70
|
+
await result;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* ignore */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
this.subscriptions.clear();
|
|
77
|
+
try {
|
|
78
|
+
await this.conn?.drain();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* ignore */
|
|
82
|
+
}
|
|
83
|
+
this.conn = null;
|
|
84
|
+
this.connected = false;
|
|
85
|
+
}
|
|
86
|
+
async subscribe(config, handler) {
|
|
87
|
+
if (!this.connected || !this.conn)
|
|
88
|
+
throw new Error("[blok][pubsub-nats] not connected. Call connect() first.");
|
|
89
|
+
const subKey = `${config.topic}#${config.consumerGroup ?? ""}`;
|
|
90
|
+
if (config.durable === true) {
|
|
91
|
+
// JetStream durable subscription — survives restarts.
|
|
92
|
+
if (!this.conn.jetstream) {
|
|
93
|
+
throw new Error("[blok][pubsub-nats] durable subscriptions require JetStream support in the nats client");
|
|
94
|
+
}
|
|
95
|
+
const jsm = await this.conn.jetstreamManager?.();
|
|
96
|
+
if (jsm) {
|
|
97
|
+
// Auto-create a stream covering the subject if one doesn't
|
|
98
|
+
// exist. Production deployments should pre-provision via
|
|
99
|
+
// `nats stream add` to control retention.
|
|
100
|
+
try {
|
|
101
|
+
await jsm.streams.add({
|
|
102
|
+
name: `blok-${(config.topic ?? "").replace(/[^a-zA-Z0-9_]/g, "_")}`,
|
|
103
|
+
subjects: [config.topic],
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* stream already exists — ignore */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const js = this.conn.jetstream();
|
|
111
|
+
const startSeq = typeof config.startFrom === "object" && config.startFrom && "seq" in config.startFrom
|
|
112
|
+
? config.startFrom.seq
|
|
113
|
+
: undefined;
|
|
114
|
+
const deliverPolicy = config.startFrom === "earliest"
|
|
115
|
+
? "all"
|
|
116
|
+
: config.startFrom === "latest"
|
|
117
|
+
? "new"
|
|
118
|
+
: startSeq !== undefined
|
|
119
|
+
? "by_start_sequence"
|
|
120
|
+
: "all";
|
|
121
|
+
const sub = await js.subscribe(config.topic, {
|
|
122
|
+
config: {
|
|
123
|
+
durable_name: config.consumerGroup ??
|
|
124
|
+
`blok-${(config.subscription ?? config.topic ?? "default").replace(/[^a-zA-Z0-9_]/g, "_")}`,
|
|
125
|
+
deliver_policy: deliverPolicy,
|
|
126
|
+
opt_start_seq: startSeq,
|
|
127
|
+
ack_policy: config.ack === false ? "none" : "explicit",
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
this.subscriptions.set(subKey, { unsubscribe: () => sub.unsubscribe() });
|
|
131
|
+
// Drive the async iterator in a background loop.
|
|
132
|
+
void (async () => {
|
|
133
|
+
try {
|
|
134
|
+
for await (const msg of sub) {
|
|
135
|
+
await this.dispatchJsMessage(msg, config, handler);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
// Subscription closed or connection lost — let the trigger
|
|
140
|
+
// re-listen via HMR/reconnect logic. Log for visibility.
|
|
141
|
+
console.error(`[blok][pubsub-nats] subscription error: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
})();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Core NATS subscription — fire-and-forget, with optional queue
|
|
147
|
+
// group for competing-consumer semantics.
|
|
148
|
+
const sub = this.conn.subscribe(config.topic, {
|
|
149
|
+
queue: config.consumerGroup,
|
|
150
|
+
callback: (err, msg) => {
|
|
151
|
+
if (err) {
|
|
152
|
+
console.error(`[blok][pubsub-nats] subscribe error: ${err.message}`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
void this.dispatchCoreMessage(msg, config, handler);
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
this.subscriptions.set(subKey, sub);
|
|
159
|
+
}
|
|
160
|
+
async dispatchJsMessage(msg, config, handler) {
|
|
161
|
+
const text = TEXT_DECODER.decode(msg.data);
|
|
162
|
+
let body = text;
|
|
163
|
+
try {
|
|
164
|
+
body = text.length > 0 ? JSON.parse(text) : null;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
/* leave as text */
|
|
168
|
+
}
|
|
169
|
+
const message = {
|
|
170
|
+
id: `${msg.info.stream}:${msg.seq}`,
|
|
171
|
+
body,
|
|
172
|
+
attributes: { subject: msg.subject },
|
|
173
|
+
raw: msg,
|
|
174
|
+
topic: msg.subject,
|
|
175
|
+
subscription: msg.info.consumer,
|
|
176
|
+
publishTime: msg.info.timestampNanos ? new Date(msg.info.timestampNanos / 1e6) : undefined,
|
|
177
|
+
ack: async () => {
|
|
178
|
+
msg.ack();
|
|
179
|
+
},
|
|
180
|
+
nack: async () => {
|
|
181
|
+
msg.nak();
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
try {
|
|
185
|
+
await handler(message);
|
|
186
|
+
if (config.ack !== false)
|
|
187
|
+
msg.ack();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
msg.nak();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async dispatchCoreMessage(msg, _config, handler) {
|
|
194
|
+
const text = TEXT_DECODER.decode(msg.data);
|
|
195
|
+
let body = text;
|
|
196
|
+
try {
|
|
197
|
+
body = text.length > 0 ? JSON.parse(text) : null;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
/* leave as text */
|
|
201
|
+
}
|
|
202
|
+
const message = {
|
|
203
|
+
id: `${msg.subject}:${msg.sid}:${uuid()}`,
|
|
204
|
+
body,
|
|
205
|
+
attributes: { subject: msg.subject },
|
|
206
|
+
raw: msg,
|
|
207
|
+
topic: msg.subject,
|
|
208
|
+
ack: async () => {
|
|
209
|
+
/* core NATS has no explicit ack */
|
|
210
|
+
},
|
|
211
|
+
nack: async () => {
|
|
212
|
+
/* core NATS has no explicit nack */
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
try {
|
|
216
|
+
await handler(message);
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
console.error(`[blok][pubsub-nats] handler error: ${err.message}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async unsubscribe(subscription) {
|
|
223
|
+
const sub = this.subscriptions.get(subscription);
|
|
224
|
+
if (!sub)
|
|
225
|
+
return;
|
|
226
|
+
try {
|
|
227
|
+
const result = sub.unsubscribe();
|
|
228
|
+
if (result instanceof Promise)
|
|
229
|
+
await result;
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
/* ignore */
|
|
233
|
+
}
|
|
234
|
+
this.subscriptions.delete(subscription);
|
|
235
|
+
}
|
|
236
|
+
async publish(topic, payload) {
|
|
237
|
+
if (!this.connected || !this.conn)
|
|
238
|
+
throw new Error("[blok][pubsub-nats] not connected. Call connect() first.");
|
|
239
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
240
|
+
const data = TEXT_ENCODER.encode(body);
|
|
241
|
+
// Use core NATS publish. When a durable subscriber has been
|
|
242
|
+
// installed for this subject, the subscribe() path created a
|
|
243
|
+
// JetStream stream with a subject filter that captures any
|
|
244
|
+
// publish to `topic` — so durable consumers see the message via
|
|
245
|
+
// the stream while core subscribers see it directly. The earlier
|
|
246
|
+
// "try js.publish first, fall back to core" logic caused
|
|
247
|
+
// **double-delivery**: js.publish to a subject covered by a
|
|
248
|
+
// stream goes to BOTH the stream and core subscribers, then the
|
|
249
|
+
// fallback would publish AGAIN if js.publish timed out (vs
|
|
250
|
+
// returning 503 fast). Sticking to core publish avoids the race.
|
|
251
|
+
this.conn.publish(topic, data);
|
|
252
|
+
}
|
|
253
|
+
isConnected() {
|
|
254
|
+
return this.connected;
|
|
255
|
+
}
|
|
256
|
+
async healthCheck() {
|
|
257
|
+
return this.connected;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTkFUU1B1YlN1YkFkYXB0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYWRhcHRlcnMvTkFUU1B1YlN1YkFkYXB0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBMEJHO0FBR0gsT0FBTyxFQUFFLEVBQUUsSUFBSSxJQUFJLEVBQUUsTUFBTSxNQUFNLENBQUM7QUEwRGxDLE1BQU0sWUFBWSxHQUFHLElBQUksV0FBVyxFQUFFLENBQUM7QUFDdkMsTUFBTSxZQUFZLEdBQUcsSUFBSSxXQUFXLEVBQUUsQ0FBQztBQUV2QyxNQUFNLE9BQU8saUJBQWlCO0lBQ3BCLFFBQVEsR0FBRyxNQUFlLENBQUM7SUFDbkIsTUFBTSxDQUFtQjtJQUNsQyxJQUFJLEdBQTBCLElBQUksQ0FBQztJQUNuQyxhQUFhLEdBQWdGLElBQUksR0FBRyxFQUFFLENBQUM7SUFDdkcsU0FBUyxHQUFHLEtBQUssQ0FBQztJQUUxQixZQUFZLE1BQWtDO1FBQzdDLElBQUksQ0FBQyxNQUFNLEdBQUc7WUFDYixPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxJQUFJLGdCQUFnQixDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO1lBQzFHLEtBQUssRUFBRSxNQUFNLEVBQUUsS0FBSyxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVTtZQUM5QyxJQUFJLEVBQUUsTUFBTSxFQUFFLElBQUksSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLFNBQVM7WUFDM0MsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTO1NBQzNDLENBQUM7SUFDSCxDQUFDO0lBRUQsS0FBSyxDQUFDLE9BQU87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QztJQUNoQyxDQUFDO0lBRUQsV0FBVztRQUNWLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQztJQUN2QixDQUFDO0lBRUQsS0FBSyxDQUFDLFdBQVc7UUFDaEIsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDO0lBQ3ZCLENBQUM7Q0FDRCIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogTkFUU1B1YlN1YkFkYXB0ZXIg4oCUIHYwLjcgUFIgNiDigJQgUHViL1N1YiBhZGFwdGVyIGJhY2tlZCBieSBOQVRTLlxuICpcbiAqIFR3byBtb2RlczpcbiAqICAgLSAqKkZhbi1vdXQqKiAoZGVmYXVsdCwgd2hlbiBgY29uc3VtZXJHcm91cGAgaXMgYWJzZW50KTogZXZlcnlcbiAqICAgICBzdWJzY3JpYmVyIHJlY2VpdmVzIGV2ZXJ5IG1lc3NhZ2Ugb24gdGhlIHN1YmplY3QuIE5BVFMgQ29yZVxuICogICAgIHB1Ymxpc2gvc3Vic2NyaWJlIHNlbWFudGljcyDigJQgY2hlYXBlc3QgcGF0aCwgbm8gcGVyc2lzdGVuY2UuXG4gKiAgIC0gKipDb21wZXRpbmctY29uc3VtZXIqKiAod2hlbiBgY29uc3VtZXJHcm91cGAgaXMgc2V0KTogTkFUU1xuICogICAgIFF1ZXVlIEdyb3VwIOKAlCBleGFjdGx5IG9uZSBzdWJzY3JpYmVyIGluIHRoZSBuYW1lZCBncm91cFxuICogICAgIHJlY2VpdmVzIGVhY2ggbWVzc2FnZS4gUHVyZSBOQVRTIENvcmUuXG4gKiAgIC0gKipEdXJhYmxlKiogKHdoZW4gYGR1cmFibGU6IHRydWVgKTogc3Vic2NyaWJlIHZpYSBOQVRTXG4gKiAgICAgSmV0U3RyZWFtIGNvbnN1bWVyIHNvIHRoZSBzdWJzY3JpcHRpb24gc3Vydml2ZXMgcmVzdGFydHMgYW5kXG4gKiAgICAgcmVwbGF5cyBtaXNzZWQgbWVzc2FnZXMgZnJvbSBgc3RhcnRGcm9tYC4gUmVxdWlyZWQgZm9yIHRoZVxuICogICAgIGB7c2VxfWAgLyBge3RpbWVzdGFtcH1gIHJlcGxheSBjdXJzb3JzLlxuICpcbiAqIFN1YmplY3Qgd2lsZGNhcmRzIChgb3JkZXJzLiouY3JlYXRlZGAsIGBvcmRlcnMuPmApIGFyZSBob25vcmVkIGJ5XG4gKiBOQVRTIG5hdGl2ZWx5IGluIGJvdGggbW9kZXMuXG4gKlxuICogUmVxdWlyZXMgYG5hdHNgIGFzIGEgcGVlciBkZXBlbmRlbmN5OlxuICpcbiAqICAgICBidW4gYWRkIG5hdHNcbiAqXG4gKiBFbnZpcm9ubWVudCB2YXJpYWJsZXM6XG4gKiAgIC0gYE5BVFNfU0VSVkVSU2AgICDigJQgY29tbWEtc2VwYXJhdGVkIFVSTHMgKGRlZmF1bHQgYGxvY2FsaG9zdDo0MjIyYCkuXG4gKiAgIC0gYE5BVFNfVE9LRU5gICAgICDigJQgYmVhcmVyIHRva2VuIGF1dGhlbnRpY2F0aW9uLlxuICogICAtIGBOQVRTX1VTRVJgIC8gYE5BVFNfUEFTU2AgIOKAlCB1c2VycGFzcyBhdXRoZW50aWNhdGlvbi5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IFB1YlN1YlRyaWdnZXJPcHRzIH0gZnJvbSBcIkBibG9ranMvaGVscGVyXCI7XG5pbXBvcnQgeyB2NCBhcyB1dWlkIH0gZnJvbSBcInV1aWRcIjtcbmltcG9ydCB0eXBlIHsgUHViU3ViQWRhcHRlciwgUHViU3ViTWVzc2FnZSB9IGZyb20gXCIuLi9QdWJTdWJUcmlnZ2VyXCI7XG5cbmV4cG9ydCBpbnRlcmZhY2UgTkFUU1B1YlN1YkNvbmZpZyB7XG5cdHNlcnZlcnM6IHN0cmluZ1tdO1xuXHR0b2tlbj86IHN0cmluZztcblx0dXNlcj86IHN0cmluZztcblx0cGFzcz86IHN0cmluZztcbn1cblxuaW50ZXJmYWNlIE5hdHNTdWJzY3JpcHRpb24ge1xuXHR1bnN1YnNjcmliZTogKCkgPT4gdm9pZCB8IFByb21pc2U8dm9pZD47XG59XG5cbmludGVyZmFjZSBOYXRzQ29ubmVjdGlvbiB7XG5cdGNsb3NlOiAoKSA9PiBQcm9taXNlPHZvaWQ+O1xuXHRkcmFpbjogKCkgPT4gUHJvbWlzZTx2b2lkPjtcblx0c3Vic2NyaWJlOiAoXG5cdFx0c3ViamVjdDogc3RyaW5nLFxuXHRcdG9wdHM/OiB7IHF1ZXVlPzogc3RyaW5nOyBjYWxsYmFjaz86IChlcnI6IEVycm9yIHwgbnVsbCwgbXNnOiBOYXRzTXNnKSA9PiB2b2lkIH0sXG5cdCkgPT4gTmF0c1N1YnNjcmlwdGlvbjtcblx0cHVibGlzaDogKHN1YmplY3Q6IHN0cmluZywgcGF5bG9hZDogVWludDhBcnJheSkgPT4gdm9pZDtcblx0amV0c3RyZWFtPzogKCkgPT4gTmF0c0pldFN0cmVhbTtcblx0amV0c3RyZWFtTWFuYWdlcj86ICgpID0+IFByb21pc2U8TmF0c0pldFN0cmVhbU1hbmFnZXI+O1xufVxuXG5pbnRlcmZhY2UgTmF0c0pldFN0cmVhbSB7XG5cdHN1YnNjcmliZTogKFxuXHRcdHN1YmplY3Q6IHN0cmluZyxcblx0XHRvcHRzPzogdW5rbm93bixcblx0KSA9PiBQcm9taXNlPHtcblx0XHRbU3ltYm9sLmFzeW5jSXRlcmF0b3JdOiAoKSA9PiBBc3luY0l0ZXJhdG9yPE5hdHNKc01zZz47XG5cdFx0dW5zdWJzY3JpYmU6ICgpID0+IFByb21pc2U8dm9pZD4gfCB2b2lkO1xuXHR9Pjtcblx0cHVibGlzaDogKHN1YmplY3Q6IHN0cmluZywgcGF5bG9hZDogVWludDhBcnJheSkgPT4gUHJvbWlzZTx1bmtub3duPjtcbn1cblxuaW50ZXJmYWNlIE5hdHNKZXRTdHJlYW1NYW5hZ2VyIHtcblx0c3RyZWFtczogeyBhZGQ6IChjb25maWc6IHVua25vd24pID0+IFByb21pc2U8dW5rbm93bj47IGluZm86IChuYW1lOiBzdHJpbmcpID0+IFByb21pc2U8dW5rbm93bj4gfTtcblx0Y29uc3VtZXJzOiB7IGFkZDogKHN0cmVhbTogc3RyaW5nLCBjb25maWc6IHVua25vd24pID0+IFByb21pc2U8dW5rbm93bj4gfTtcbn1cblxuaW50ZXJmYWNlIE5hdHNNc2cge1xuXHRzdWJqZWN0OiBzdHJpbmc7XG5cdGRhdGE6IFVpbnQ4QXJyYXk7XG5cdHNpZDogbnVtYmVyO1xuXHRyZXNwb25kPzogKGRhdGE/OiBVaW50OEFycmF5KSA9PiBib29sZWFuO1xufVxuXG5pbnRlcmZhY2UgTmF0c0pzTXNnIHtcblx0c3ViamVjdDogc3RyaW5nO1xuXHRkYXRhOiBVaW50OEFycmF5O1xuXHRzZXE6IG51bWJlcjtcblx0YWNrOiAoKSA9PiB2b2lkO1xuXHRuYWs6IChtaWxsaXM/OiBudW1iZXIpID0+IHZvaWQ7XG5cdGluZm86IHsgc3RyZWFtOiBzdHJpbmc7IGNvbnN1bWVyOiBzdHJpbmc7IHJlZGVsaXZlcnlDb3VudDogbnVtYmVyOyB0aW1lc3RhbXBOYW5vcz86IG51bWJlciB9O1xufVxuXG5jb25zdCBURVhUX0RFQ09ERVIgPSBuZXcgVGV4dERlY29kZXIoKTtcbmNvbnN0IFRFWFRfRU5DT0RFUiA9IG5ldyBUZXh0RW5jb2RlcigpO1xuXG5leHBvcnQgY2xhc3MgTkFUU1B1YlN1YkFkYXB0ZXIgaW1wbGVtZW50cyBQdWJTdWJBZGFwdGVyIHtcblx0cmVhZG9ubHkgcHJvdmlkZXIgPSBcIm5hdHNcIiBhcyBjb25zdDtcblx0cHJpdmF0ZSByZWFkb25seSBjb25maWc6IE5BVFNQdWJTdWJDb25maWc7XG5cdHByaXZhdGUgY29ubjogTmF0c0Nvbm5lY3Rpb24gfCBudWxsID0gbnVsbDtcblx0cHJpdmF0ZSBzdWJzY3JpcHRpb25zOiBNYXA8c3RyaW5nLCBOYXRzU3Vic2NyaXB0aW9uIHwgeyB1bnN1YnNjcmliZTogKCkgPT4gUHJvbWlzZTx2b2lkPiB8IHZvaWQgfT4gPSBuZXcgTWFwKCk7XG5cdHByaXZhdGUgY29ubmVjdGVkID0gZmFsc2U7XG5cblx0Y29uc3RydWN0b3IoY29uZmlnPzogUGFydGlhbDxOQVRTUHViU3ViQ29uZmlnPikge1xuXHRcdHRoaXMuY29uZmlnID0ge1xuXHRcdFx0c2VydmVyczogY29uZmlnPy5zZXJ2ZXJzID8/IChwcm9jZXNzLmVudi5OQVRTX1NFUlZFUlMgPz8gXCJsb2NhbGhvc3Q6NDIyMlwiKS5zcGxpdChcIixcIikubWFwKChzKSA9PiBzLnRyaW0oKSksXG5cdFx0XHR0b2tlbjogY29uZmlnPy50b2tlbiA/PyBwcm9jZXNzLmVudi5OQVRTX1RPS0VOLFxuXHRcdFx0dXNlcjogY29uZmlnPy51c2VyID8/IHByb2Nlc3MuZW52Lk5BVFNfVVNFUixcblx0XHRcdHBhc3M6IGNvbmZpZz8ucGFzcyA/PyBwcm9jZXNzLmVudi5OQVRTX1BBU1MsXG5cdFx0fTtcblx0fVxuXG5cdGFzeW5jIGNvbm5lY3QoKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0aWYgKHRoaXMuY29ubmVjdGVkKSByZXR1cm47XG5cdFx0dHJ5IHtcblx0XHRcdC8vIGJpb21lLWlnbm9yZSBsaW50L3N1c3BpY2lvdXMvbm9FeHBsaWNpdEFueTogbmF0cyBpcyBhIHJ1bnRpbWUgcGVlciBkZXAuXG5cdFx0XHRjb25zdCBuYXRzOiBhbnkgPSBhd2FpdCBpbXBvcnQoXCJuYXRzXCIpO1xuXHRcdFx0dGhpcy5jb25uID0gKGF3YWl0IG5hdHMuY29ubmVjdCh7XG5cdFx0XHRcdHNlcnZlcnM6IHRoaXMuY29uZmlnLnNlcnZlcnMsXG5cdFx0XHRcdHRva2VuOiB0aGlzLmNvbmZpZy50b2tlbixcblx0XHRcdFx0dXNlcjogdGhpcy5jb25maWcudXNlcixcblx0XHRcdFx0cGFzczogdGhpcy5jb25maWcucGFzcyxcblx0XHRcdH0pKSBhcyBOYXRzQ29ubmVjdGlvbjtcblx0XHRcdHRoaXMuY29ubmVjdGVkID0gdHJ1ZTtcblx0XHR9IGNhdGNoIChlcnIpIHtcblx0XHRcdHRocm93IG5ldyBFcnJvcihcblx0XHRcdFx0YFtibG9rXVtwdWJzdWItbmF0c10gY29ubmVjdCBmYWlsZWQ6ICR7KGVyciBhcyBFcnJvcikubWVzc2FnZX0uIEluc3RhbGwgbmF0cyBhcyBhIHBlZXIgZGVwZW5kZW5jeTogYnVuIGFkZCBuYXRzYCxcblx0XHRcdCk7XG5cdFx0fVxuXHR9XG5cblx0YXN5bmMgZGlzY29ubmVjdCgpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRpZiAoIXRoaXMuY29ubmVjdGVkKSByZXR1cm47XG5cdFx0Zm9yIChjb25zdCBzdWIgb2YgdGhpcy5zdWJzY3JpcHRpb25zLnZhbHVlcygpKSB7XG5cdFx0XHR0cnkge1xuXHRcdFx0XHRjb25zdCByZXN1bHQgPSBzdWIudW5zdWJzY3JpYmUoKTtcblx0XHRcdFx0aWYgKHJlc3VsdCBpbnN0YW5jZW9mIFByb21pc2UpIGF3YWl0IHJlc3VsdDtcblx0XHRcdH0gY2F0Y2gge1xuXHRcdFx0XHQvKiBpZ25vcmUgKi9cblx0XHRcdH1cblx0XHR9XG5cdFx0dGhpcy5zdWJzY3JpcHRpb25zLmNsZWFyKCk7XG5cdFx0dHJ5IHtcblx0XHRcdGF3YWl0IHRoaXMuY29ubj8uZHJhaW4oKTtcblx0XHR9IGNhdGNoIHtcblx0XHRcdC8qIGlnbm9yZSAqL1xuXHRcdH1cblx0XHR0aGlzLmNvbm4gPSBudWxsO1xuXHRcdHRoaXMuY29ubmVjdGVkID0gZmFsc2U7XG5cdH1cblxuXHRhc3luYyBzdWJzY3JpYmUoY29uZmlnOiBQdWJTdWJUcmlnZ2VyT3B0cywgaGFuZGxlcjogKG1lc3NhZ2U6IFB1YlN1Yk1lc3NhZ2UpID0+IFByb21pc2U8dm9pZD4pOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRpZiAoIXRoaXMuY29ubmVjdGVkIHx8ICF0aGlzLmNvbm4pIHRocm93IG5ldyBFcnJvcihcIltibG9rXVtwdWJzdWItbmF0c10gbm90IGNvbm5lY3RlZC4gQ2FsbCBjb25uZWN0KCkgZmlyc3QuXCIpO1xuXHRcdGNvbnN0IHN1YktleSA9IGAke2NvbmZpZy50b3BpY30jJHtjb25maWcuY29uc3VtZXJHcm91cCA/PyBcIlwifWA7XG5cblx0XHRpZiAoY29uZmlnLmR1cmFibGUgPT09IHRydWUpIHtcblx0XHRcdC8vIEpldFN0cmVhbSBkdXJhYmxlIHN1YnNjcmlwdGlvbiDigJQgc3Vydml2ZXMgcmVzdGFydHMuXG5cdFx0XHRpZiAoIXRoaXMuY29ubi5qZXRzdHJlYW0pIHtcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKFwiW2Jsb2tdW3B1YnN1Yi1uYXRzXSBkdXJhYmxlIHN1YnNjcmlwdGlvbnMgcmVxdWlyZSBKZXRTdHJlYW0gc3VwcG9ydCBpbiB0aGUgbmF0cyBjbGllbnRcIik7XG5cdFx0XHR9XG5cdFx0XHRjb25zdCBqc20gPSBhd2FpdCB0aGlzLmNvbm4uamV0c3RyZWFtTWFuYWdlcj8uKCk7XG5cdFx0XHRpZiAoanNtKSB7XG5cdFx0XHRcdC8vIEF1dG8tY3JlYXRlIGEgc3RyZWFtIGNvdmVyaW5nIHRoZSBzdWJqZWN0IGlmIG9uZSBkb2Vzbid0XG5cdFx0XHRcdC8vIGV4aXN0LiBQcm9kdWN0aW9uIGRlcGxveW1lbnRzIHNob3VsZCBwcmUtcHJvdmlzaW9uIHZpYVxuXHRcdFx0XHQvLyBgbmF0cyBzdHJlYW0gYWRkYCB0byBjb250cm9sIHJldGVudGlvbi5cblx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRhd2FpdCBqc20uc3RyZWFtcy5hZGQoe1xuXHRcdFx0XHRcdFx0bmFtZTogYGJsb2stJHsoY29uZmlnLnRvcGljID8/IFwiXCIpLnJlcGxhY2UoL1teYS16QS1aMC05X10vZywgXCJfXCIpfWAsXG5cdFx0XHRcdFx0XHRzdWJqZWN0czogW2NvbmZpZy50b3BpY10sXG5cdFx0XHRcdFx0fSk7XG5cdFx0XHRcdH0gY2F0Y2gge1xuXHRcdFx0XHRcdC8qIHN0cmVhbSBhbHJlYWR5IGV4aXN0cyDigJQgaWdub3JlICovXG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHRcdGNvbnN0IGpzID0gdGhpcy5jb25uLmpldHN0cmVhbSgpO1xuXHRcdFx0Y29uc3Qgc3RhcnRTZXEgPVxuXHRcdFx0XHR0eXBlb2YgY29uZmlnLnN0YXJ0RnJvbSA9PT0gXCJvYmplY3RcIiAmJiBjb25maWcuc3RhcnRGcm9tICYmIFwic2VxXCIgaW4gY29uZmlnLnN0YXJ0RnJvbVxuXHRcdFx0XHRcdD8gY29uZmlnLnN0YXJ0RnJvbS5zZXFcblx0XHRcdFx0XHQ6IHVuZGVmaW5lZDtcblx0XHRcdGNvbnN0IGRlbGl2ZXJQb2xpY3kgPVxuXHRcdFx0XHRjb25maWcuc3RhcnRGcm9tID09PSBcImVhcmxpZXN0XCJcblx0XHRcdFx0XHQ/IFwiYWxsXCJcblx0XHRcdFx0XHQ6IGNvbmZpZy5zdGFydEZyb20gPT09IFwibGF0ZXN0XCJcblx0XHRcdFx0XHRcdD8gXCJuZXdcIlxuXHRcdFx0XHRcdFx0OiBzdGFydFNlcSAhPT0gdW5kZWZpbmVkXG5cdFx0XHRcdFx0XHRcdD8gXCJieV9zdGFydF9zZXF1ZW5jZVwiXG5cdFx0XHRcdFx0XHRcdDogXCJhbGxcIjtcblx0XHRcdGNvbnN0IHN1YiA9IGF3YWl0IGpzLnN1YnNjcmliZShjb25maWcudG9waWMsIHtcblx0XHRcdFx0Y29uZmlnOiB7XG5cdFx0XHRcdFx0ZHVyYWJsZV9uYW1lOlxuXHRcdFx0XHRcdFx0Y29uZmlnLmNvbnN1bWVyR3JvdXAgPz9cblx0XHRcdFx0XHRcdGBibG9rLSR7KGNvbmZpZy5zdWJzY3JpcHRpb24gPz8gY29uZmlnLnRvcGljID8/IFwiZGVmYXVsdFwiKS5yZXBsYWNlKC9bXmEtekEtWjAtOV9dL2csIFwiX1wiKX1gLFxuXHRcdFx0XHRcdGRlbGl2ZXJfcG9saWN5OiBkZWxpdmVyUG9saWN5LFxuXHRcdFx0XHRcdG9wdF9zdGFydF9zZXE6IHN0YXJ0U2VxLFxuXHRcdFx0XHRcdGFja19wb2xpY3k6IGNvbmZpZy5hY2sgPT09IGZhbHNlID8gXCJub25lXCIgOiBcImV4cGxpY2l0XCIsXG5cdFx0XHRcdH0sXG5cdFx0XHR9KTtcblx0XHRcdHRoaXMuc3Vic2NyaXB0aW9ucy5zZXQoc3ViS2V5LCB7IHVuc3Vic2NyaWJlOiAoKSA9PiBzdWIudW5zdWJzY3JpYmUoKSB9KTtcblx0XHRcdC8vIERyaXZlIHRoZSBhc3luYyBpdGVyYXRvciBpbiBhIGJhY2tncm91bmQgbG9vcC5cblx0XHRcdHZvaWQgKGFzeW5jICgpID0+IHtcblx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRmb3IgYXdhaXQgKGNvbnN0IG1zZyBvZiBzdWIgYXMgdW5rbm93biBhcyBBc3luY0l0ZXJhYmxlPE5hdHNKc01zZz4pIHtcblx0XHRcdFx0XHRcdGF3YWl0IHRoaXMuZGlzcGF0Y2hKc01lc3NhZ2UobXNnLCBjb25maWcsIGhhbmRsZXIpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSBjYXRjaCAoZXJyKSB7XG5cdFx0XHRcdFx0Ly8gU3Vic2NyaXB0aW9uIGNsb3NlZCBvciBjb25uZWN0aW9uIGxvc3Qg4oCUIGxldCB0aGUgdHJpZ2dlclxuXHRcdFx0XHRcdC8vIHJlLWxpc3RlbiB2aWEgSE1SL3JlY29ubmVjdCBsb2dpYy4gTG9nIGZvciB2aXNpYmlsaXR5LlxuXHRcdFx0XHRcdGNvbnNvbGUuZXJyb3IoYFtibG9rXVtwdWJzdWItbmF0c10gc3Vic2NyaXB0aW9uIGVycm9yOiAkeyhlcnIgYXMgRXJyb3IpLm1lc3NhZ2V9YCk7XG5cdFx0XHRcdH1cblx0XHRcdH0pKCk7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cdFx0Ly8gQ29yZSBOQVRTIHN1YnNjcmlwdGlvbiDigJQgZmlyZS1hbmQtZm9yZ2V0LCB3aXRoIG9wdGlvbmFsIHF1ZXVlXG5cdFx0Ly8gZ3JvdXAgZm9yIGNvbXBldGluZy1jb25zdW1lciBzZW1hbnRpY3MuXG5cdFx0Y29uc3Qgc3ViID0gdGhpcy5jb25uLnN1YnNjcmliZShjb25maWcudG9waWMsIHtcblx0XHRcdHF1ZXVlOiBjb25maWcuY29uc3VtZXJHcm91cCxcblx0XHRcdGNhbGxiYWNrOiAoZXJyLCBtc2cpID0+IHtcblx0XHRcdFx0aWYgKGVycikge1xuXHRcdFx0XHRcdGNvbnNvbGUuZXJyb3IoYFtibG9rXVtwdWJzdWItbmF0c10gc3Vic2NyaWJlIGVycm9yOiAke2Vyci5tZXNzYWdlfWApO1xuXHRcdFx0XHRcdHJldHVybjtcblx0XHRcdFx0fVxuXHRcdFx0XHR2b2lkIHRoaXMuZGlzcGF0Y2hDb3JlTWVzc2FnZShtc2csIGNvbmZpZywgaGFuZGxlcik7XG5cdFx0XHR9LFxuXHRcdH0pO1xuXHRcdHRoaXMuc3Vic2NyaXB0aW9ucy5zZXQoc3ViS2V5LCBzdWIpO1xuXHR9XG5cblx0cHJpdmF0ZSBhc3luYyBkaXNwYXRjaEpzTWVzc2FnZShcblx0XHRtc2c6IE5hdHNKc01zZyxcblx0XHRjb25maWc6IFB1YlN1YlRyaWdnZXJPcHRzLFxuXHRcdGhhbmRsZXI6IChtZXNzYWdlOiBQdWJTdWJNZXNzYWdlKSA9PiBQcm9taXNlPHZvaWQ+LFxuXHQpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRjb25zdCB0ZXh0ID0gVEVYVF9ERUNPREVSLmRlY29kZShtc2cuZGF0YSk7XG5cdFx0bGV0IGJvZHk6IHVua25vd24gPSB0ZXh0O1xuXHRcdHRyeSB7XG5cdFx0XHRib2R5ID0gdGV4dC5sZW5ndGggPiAwID8gSlNPTi5wYXJzZSh0ZXh0KSA6IG51bGw7XG5cdFx0fSBjYXRjaCB7XG5cdFx0XHQvKiBsZWF2ZSBhcyB0ZXh0ICovXG5cdFx0fVxuXHRcdGNvbnN0IG1lc3NhZ2U6IFB1YlN1Yk1lc3NhZ2UgPSB7XG5cdFx0XHRpZDogYCR7bXNnLmluZm8uc3RyZWFtfToke21zZy5zZXF9YCxcblx0XHRcdGJvZHksXG5cdFx0XHRhdHRyaWJ1dGVzOiB7IHN1YmplY3Q6IG1zZy5zdWJqZWN0IH0sXG5cdFx0XHRyYXc6IG1zZyxcblx0XHRcdHRvcGljOiBtc2cuc3ViamVjdCxcblx0XHRcdHN1YnNjcmlwdGlvbjogbXNnLmluZm8uY29uc3VtZXIsXG5cdFx0XHRwdWJsaXNoVGltZTogbXNnLmluZm8udGltZXN0YW1wTmFub3MgPyBuZXcgRGF0ZShtc2cuaW5mby50aW1lc3RhbXBOYW5vcyAvIDFlNikgOiB1bmRlZmluZWQsXG5cdFx0XHRhY2s6IGFzeW5jICgpID0+IHtcblx0XHRcdFx0bXNnLmFjaygpO1xuXHRcdFx0fSxcblx0XHRcdG5hY2s6IGFzeW5jICgpID0+IHtcblx0XHRcdFx0bXNnLm5haygpO1xuXHRcdFx0fSxcblx0XHR9O1xuXHRcdHRyeSB7XG5cdFx0XHRhd2FpdCBoYW5kbGVyKG1lc3NhZ2UpO1xuXHRcdFx0aWYgKGNvbmZpZy5hY2sgIT09IGZhbHNlKSBtc2cuYWNrKCk7XG5cdFx0fSBjYXRjaCB7XG5cdFx0XHRtc2cubmFrKCk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBhc3luYyBkaXNwYXRjaENvcmVNZXNzYWdlKFxuXHRcdG1zZzogTmF0c01zZyxcblx0XHRfY29uZmlnOiBQdWJTdWJUcmlnZ2VyT3B0cyxcblx0XHRoYW5kbGVyOiAobWVzc2FnZTogUHViU3ViTWVzc2FnZSkgPT4gUHJvbWlzZTx2b2lkPixcblx0KTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0Y29uc3QgdGV4dCA9IFRFWFRfREVDT0RFUi5kZWNvZGUobXNnLmRhdGEpO1xuXHRcdGxldCBib2R5OiB1bmtub3duID0gdGV4dDtcblx0XHR0cnkge1xuXHRcdFx0Ym9keSA9IHRleHQubGVuZ3RoID4gMCA/IEpTT04ucGFyc2UodGV4dCkgOiBudWxsO1xuXHRcdH0gY2F0Y2gge1xuXHRcdFx0LyogbGVhdmUgYXMgdGV4dCAqL1xuXHRcdH1cblx0XHRjb25zdCBtZXNzYWdlOiBQdWJTdWJNZXNzYWdlID0ge1xuXHRcdFx0aWQ6IGAke21zZy5zdWJqZWN0fToke21zZy5zaWR9OiR7dXVpZCgpfWAsXG5cdFx0XHRib2R5LFxuXHRcdFx0YXR0cmlidXRlczogeyBzdWJqZWN0OiBtc2cuc3ViamVjdCB9LFxuXHRcdFx0cmF3OiBtc2csXG5cdFx0XHR0b3BpYzogbXNnLnN1YmplY3QsXG5cdFx0XHRhY2s6IGFzeW5jICgpID0+IHtcblx0XHRcdFx0LyogY29yZSBOQVRTIGhhcyBubyBleHBsaWNpdCBhY2sgKi9cblx0XHRcdH0sXG5cdFx0XHRuYWNrOiBhc3luYyAoKSA9PiB7XG5cdFx0XHRcdC8qIGNvcmUgTkFUUyBoYXMgbm8gZXhwbGljaXQgbmFjayAqL1xuXHRcdFx0fSxcblx0XHR9O1xuXHRcdHRyeSB7XG5cdFx0XHRhd2FpdCBoYW5kbGVyKG1lc3NhZ2UpO1xuXHRcdH0gY2F0Y2ggKGVycikge1xuXHRcdFx0Y29uc29sZS5lcnJvcihgW2Jsb2tdW3B1YnN1Yi1uYXRzXSBoYW5kbGVyIGVycm9yOiAkeyhlcnIgYXMgRXJyb3IpLm1lc3NhZ2V9YCk7XG5cdFx0fVxuXHR9XG5cblx0YXN5bmMgdW5zdWJzY3JpYmUoc3Vic2NyaXB0aW9uOiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRjb25zdCBzdWIgPSB0aGlzLnN1YnNjcmlwdGlvbnMuZ2V0KHN1YnNjcmlwdGlvbik7XG5cdFx0aWYgKCFzdWIpIHJldHVybjtcblx0XHR0cnkge1xuXHRcdFx0Y29uc3QgcmVzdWx0ID0gc3ViLnVuc3Vic2NyaWJlKCk7XG5cdFx0XHRpZiAocmVzdWx0IGluc3RhbmNlb2YgUHJvbWlzZSkgYXdhaXQgcmVzdWx0O1xuXHRcdH0gY2F0Y2gge1xuXHRcdFx0LyogaWdub3JlICovXG5cdFx0fVxuXHRcdHRoaXMuc3Vic2NyaXB0aW9ucy5kZWxldGUoc3Vic2NyaXB0aW9uKTtcblx0fVxuXG5cdGFzeW5jIHB1Ymxpc2godG9waWM6IHN0cmluZywgcGF5bG9hZDogdW5rbm93bik6IFByb21pc2U8dm9pZD4ge1xuXHRcdGlmICghdGhpcy5jb25uZWN0ZWQgfHwgIXRoaXMuY29ubikgdGhyb3cgbmV3IEVycm9yKFwiW2Jsb2tdW3B1YnN1Yi1uYXRzXSBub3QgY29ubmVjdGVkLiBDYWxsIGNvbm5lY3QoKSBmaXJzdC5cIik7XG5cdFx0Y29uc3QgYm9keSA9IHR5cGVvZiBwYXlsb2FkID09PSBcInN0cmluZ1wiID8gcGF5bG9hZCA6IEpTT04uc3RyaW5naWZ5KHBheWxvYWQpO1xuXHRcdGNvbnN0IGRhdGEgPSBURVhUX0VOQ09ERVIuZW5jb2RlKGJvZHkpO1xuXHRcdC8vIFVzZSBjb3JlIE5BVFMgcHVibGlzaC4gV2hlbiBhIGR1cmFibGUgc3Vic2NyaWJlciBoYXMgYmVlblxuXHRcdC8vIGluc3RhbGxlZCBmb3IgdGhpcyBzdWJqZWN0LCB0aGUgc3Vic2NyaWJlKCkgcGF0aCBjcmVhdGVkIGFcblx0XHQvLyBKZXRTdHJlYW0gc3RyZWFtIHdpdGggYSBzdWJqZWN0IGZpbHRlciB0aGF0IGNhcHR1cmVzIGFueVxuXHRcdC8vIHB1Ymxpc2ggdG8gYHRvcGljYCDigJQgc28gZHVyYWJsZSBjb25zdW1lcnMgc2VlIHRoZSBtZXNzYWdlIHZpYVxuXHRcdC8vIHRoZSBzdHJlYW0gd2hpbGUgY29yZSBzdWJzY3JpYmVycyBzZWUgaXQgZGlyZWN0bHkuIFRoZSBlYXJsaWVyXG5cdFx0Ly8gXCJ0cnkganMucHVibGlzaCBmaXJzdCwgZmFsbCBiYWNrIHRvIGNvcmVcIiBsb2dpYyBjYXVzZWRcblx0XHQvLyAqKmRvdWJsZS1kZWxpdmVyeSoqOiBqcy5wdWJsaXNoIHRvIGEgc3ViamVjdCBjb3ZlcmVkIGJ5IGFcblx0XHQvLyBzdHJlYW0gZ29lcyB0byBCT1RIIHRoZSBzdHJlYW0gYW5kIGNvcmUgc3Vic2NyaWJlcnMsIHRoZW4gdGhlXG5cdFx0Ly8gZmFsbGJhY2sgd291bGQgcHVibGlzaCBBR0FJTiBpZiBqcy5wdWJsaXNoIHRpbWVkIG91dCAodnNcblx0XHQvLyByZXR1cm5pbmcgNTAzIGZhc3QpLiBTdGlja2luZyB0byBjb3JlIHB1Ymxpc2ggYXZvaWRzIHRoZSByYWNlLlxuXHRcdHRoaXMuY29ubi5wdWJsaXNoKHRvcGljLCBkYXRhKTtcblx0fVxuXG5cdGlzQ29ubmVjdGVkKCk6IGJvb2xlYW4ge1xuXHRcdHJldHVybiB0aGlzLmNvbm5lY3RlZDtcblx0fVxuXG5cdGFzeW5jIGhlYWx0aENoZWNrKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuXHRcdHJldHVybiB0aGlzLmNvbm5lY3RlZDtcblx0fVxufVxuIl19
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedisStreamsPubSubAdapter — v0.7 PR 6 — Pub/Sub adapter backed by
|
|
3
|
+
* Redis Streams via `ioredis`.
|
|
4
|
+
*
|
|
5
|
+
* Pub/Sub vs Worker semantics: this adapter uses **distinct consumer
|
|
6
|
+
* groups per subscriber** so multiple subscribers each receive every
|
|
7
|
+
* message (fan-out). When `consumerGroup` is explicitly set on the
|
|
8
|
+
* workflow, all subscribers in the group compete (1 of N gets each).
|
|
9
|
+
*
|
|
10
|
+
* Replay cursors:
|
|
11
|
+
* - `"earliest"` / unset → `$` (only new messages by default).
|
|
12
|
+
* - `"earliest"` with explicit intent → `0` (replay full stream).
|
|
13
|
+
* - `{seq: N}` → resume from stream id `N-0`.
|
|
14
|
+
*
|
|
15
|
+
* Requires `ioredis` as a peer dependency.
|
|
16
|
+
*
|
|
17
|
+
* Environment variables:
|
|
18
|
+
* - `REDIS_HOST` (default `localhost`).
|
|
19
|
+
* - `REDIS_PORT` (default `6379`).
|
|
20
|
+
* - `REDIS_PASSWORD`.
|
|
21
|
+
* - `REDIS_DB`.
|
|
22
|
+
*/
|
|
23
|
+
import type { PubSubTriggerOpts } from "@blokjs/helper";
|
|
24
|
+
import type { PubSubAdapter, PubSubMessage } from "../PubSubTrigger";
|
|
25
|
+
export interface RedisStreamsPubSubConfig {
|
|
26
|
+
host: string;
|
|
27
|
+
port: number;
|
|
28
|
+
password?: string;
|
|
29
|
+
db?: number;
|
|
30
|
+
blockMs: number;
|
|
31
|
+
count: number;
|
|
32
|
+
}
|
|
33
|
+
export declare class RedisStreamsPubSubAdapter implements PubSubAdapter {
|
|
34
|
+
readonly provider: "redis-streams";
|
|
35
|
+
private readonly config;
|
|
36
|
+
private client;
|
|
37
|
+
private subscriptions;
|
|
38
|
+
private connected;
|
|
39
|
+
private consumerName;
|
|
40
|
+
constructor(config?: Partial<RedisStreamsPubSubConfig>);
|
|
41
|
+
connect(): Promise<void>;
|
|
42
|
+
disconnect(): Promise<void>;
|
|
43
|
+
subscribe(config: PubSubTriggerOpts, handler: (message: PubSubMessage) => Promise<void>): Promise<void>;
|
|
44
|
+
private fieldsToObject;
|
|
45
|
+
unsubscribe(subscription: string): Promise<void>;
|
|
46
|
+
publish(topic: string, payload: unknown): Promise<void>;
|
|
47
|
+
isConnected(): boolean;
|
|
48
|
+
healthCheck(): Promise<boolean>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedisStreamsPubSubAdapter — v0.7 PR 6 — Pub/Sub adapter backed by
|
|
3
|
+
* Redis Streams via `ioredis`.
|
|
4
|
+
*
|
|
5
|
+
* Pub/Sub vs Worker semantics: this adapter uses **distinct consumer
|
|
6
|
+
* groups per subscriber** so multiple subscribers each receive every
|
|
7
|
+
* message (fan-out). When `consumerGroup` is explicitly set on the
|
|
8
|
+
* workflow, all subscribers in the group compete (1 of N gets each).
|
|
9
|
+
*
|
|
10
|
+
* Replay cursors:
|
|
11
|
+
* - `"earliest"` / unset → `$` (only new messages by default).
|
|
12
|
+
* - `"earliest"` with explicit intent → `0` (replay full stream).
|
|
13
|
+
* - `{seq: N}` → resume from stream id `N-0`.
|
|
14
|
+
*
|
|
15
|
+
* Requires `ioredis` as a peer dependency.
|
|
16
|
+
*
|
|
17
|
+
* Environment variables:
|
|
18
|
+
* - `REDIS_HOST` (default `localhost`).
|
|
19
|
+
* - `REDIS_PORT` (default `6379`).
|
|
20
|
+
* - `REDIS_PASSWORD`.
|
|
21
|
+
* - `REDIS_DB`.
|
|
22
|
+
*/
|
|
23
|
+
import { v4 as uuid } from "uuid";
|
|
24
|
+
export class RedisStreamsPubSubAdapter {
|
|
25
|
+
provider = "redis-streams";
|
|
26
|
+
config;
|
|
27
|
+
client = null;
|
|
28
|
+
subscriptions = new Map();
|
|
29
|
+
connected = false;
|
|
30
|
+
consumerName = `blok-pubsub-${uuid().slice(0, 8)}`;
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.config = {
|
|
33
|
+
host: config?.host ?? process.env.REDIS_HOST ?? "localhost",
|
|
34
|
+
port: config?.port ?? Number.parseInt(process.env.REDIS_PORT ?? "6379", 10),
|
|
35
|
+
password: config?.password ?? process.env.REDIS_PASSWORD,
|
|
36
|
+
db: config?.db ?? Number.parseInt(process.env.REDIS_DB ?? "0", 10),
|
|
37
|
+
blockMs: config?.blockMs ?? 5000,
|
|
38
|
+
count: config?.count ?? 10,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async connect() {
|
|
42
|
+
if (this.connected)
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
// biome-ignore lint/suspicious/noExplicitAny: ioredis is a runtime peer dep.
|
|
46
|
+
const ioredis = await import("ioredis");
|
|
47
|
+
const IORedis = ioredis.default ?? ioredis.Redis ?? ioredis;
|
|
48
|
+
this.client = new IORedis({
|
|
49
|
+
host: this.config.host,
|
|
50
|
+
port: this.config.port,
|
|
51
|
+
password: this.config.password,
|
|
52
|
+
db: this.config.db,
|
|
53
|
+
});
|
|
54
|
+
await this.client.ping();
|
|
55
|
+
this.connected = true;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
throw new Error(`[blok][pubsub-redis] connect failed: ${err.message}. Install ioredis as a peer dependency: bun add ioredis`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async disconnect() {
|
|
62
|
+
if (!this.connected)
|
|
63
|
+
return;
|
|
64
|
+
for (const sub of this.subscriptions.values())
|
|
65
|
+
sub.stop();
|
|
66
|
+
this.subscriptions.clear();
|
|
67
|
+
try {
|
|
68
|
+
await this.client?.quit();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* ignore */
|
|
72
|
+
}
|
|
73
|
+
this.client = null;
|
|
74
|
+
this.connected = false;
|
|
75
|
+
}
|
|
76
|
+
async subscribe(config, handler) {
|
|
77
|
+
if (!this.connected || !this.client)
|
|
78
|
+
throw new Error("[blok][pubsub-redis] not connected. Call connect() first.");
|
|
79
|
+
const client = this.client;
|
|
80
|
+
const stream = config.topic;
|
|
81
|
+
// Fan-out: each subscriber gets its own group (unique per instance).
|
|
82
|
+
// Competing-consumer: explicit `consumerGroup` makes all subscribers
|
|
83
|
+
// share work.
|
|
84
|
+
const group = config.consumerGroup ?? `blok-fanout-${this.consumerName}-${stream.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
85
|
+
const startId = config.startFrom === "earliest"
|
|
86
|
+
? "0"
|
|
87
|
+
: config.startFrom === "latest" || config.startFrom === undefined
|
|
88
|
+
? "$"
|
|
89
|
+
: typeof config.startFrom === "object" && "seq" in config.startFrom
|
|
90
|
+
? `${config.startFrom.seq}-0`
|
|
91
|
+
: "$";
|
|
92
|
+
try {
|
|
93
|
+
await client.xgroup("CREATE", stream, group, startId, "MKSTREAM");
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
if (!/BUSYGROUP/i.test(err.message))
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
let stopped = false;
|
|
100
|
+
const sub = {
|
|
101
|
+
stop: () => {
|
|
102
|
+
stopped = true;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
this.subscriptions.set(`${stream}#${group}`, sub);
|
|
106
|
+
void (async () => {
|
|
107
|
+
while (!stopped) {
|
|
108
|
+
let entries = null;
|
|
109
|
+
try {
|
|
110
|
+
entries = await client.xreadgroup("GROUP", group, this.consumerName, "COUNT", String(this.config.count), "BLOCK", String(this.config.blockMs), "STREAMS", stream, ">");
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!entries)
|
|
117
|
+
continue;
|
|
118
|
+
for (const [, msgs] of entries) {
|
|
119
|
+
for (const [id, fields] of msgs) {
|
|
120
|
+
if (stopped)
|
|
121
|
+
break;
|
|
122
|
+
const payload = this.fieldsToObject(fields);
|
|
123
|
+
const dataString = typeof payload.data === "string" ? payload.data : "";
|
|
124
|
+
let body;
|
|
125
|
+
try {
|
|
126
|
+
body = dataString.length > 0 ? JSON.parse(dataString) : null;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
body = dataString;
|
|
130
|
+
}
|
|
131
|
+
const message = {
|
|
132
|
+
id,
|
|
133
|
+
body,
|
|
134
|
+
attributes: payload,
|
|
135
|
+
raw: { id, fields },
|
|
136
|
+
topic: stream,
|
|
137
|
+
subscription: group,
|
|
138
|
+
publishTime: new Date(Number.parseInt(id.split("-")[0] ?? String(Date.now()), 10)),
|
|
139
|
+
ack: async () => {
|
|
140
|
+
await client.xack(stream, group, id);
|
|
141
|
+
},
|
|
142
|
+
nack: async () => {
|
|
143
|
+
/* leave unacked — picked up by XAUTOCLAIM */
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
await handler(message);
|
|
148
|
+
if (config.ack !== false)
|
|
149
|
+
await client.xack(stream, group, id);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Leave unacked — pending entries are visible in XPENDING.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
})();
|
|
158
|
+
}
|
|
159
|
+
fieldsToObject(fields) {
|
|
160
|
+
const out = {};
|
|
161
|
+
for (let i = 0; i < fields.length; i += 2)
|
|
162
|
+
out[fields[i]] = fields[i + 1];
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
async unsubscribe(subscription) {
|
|
166
|
+
const sub = this.subscriptions.get(subscription);
|
|
167
|
+
if (!sub)
|
|
168
|
+
return;
|
|
169
|
+
sub.stop();
|
|
170
|
+
this.subscriptions.delete(subscription);
|
|
171
|
+
}
|
|
172
|
+
async publish(topic, payload) {
|
|
173
|
+
if (!this.connected || !this.client)
|
|
174
|
+
throw new Error("[blok][pubsub-redis] not connected. Call connect() first.");
|
|
175
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
176
|
+
await this.client.xadd(topic, "*", "data", body);
|
|
177
|
+
}
|
|
178
|
+
isConnected() {
|
|
179
|
+
return this.connected;
|
|
180
|
+
}
|
|
181
|
+
async healthCheck() {
|
|
182
|
+
if (!this.connected || !this.client)
|
|
183
|
+
return false;
|
|
184
|
+
try {
|
|
185
|
+
const pong = await this.client.ping();
|
|
186
|
+
return pong === "PONG";
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmVkaXNTdHJlYW1zUHViU3ViQWRhcHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hZGFwdGVycy9SZWRpc1N0cmVhbXNQdWJTdWJBZGFwdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FxQkc7QUFHSCxPQUFPLEVBQUUsRUFBRSxJQUFJLElBQUksRUFBRSxNQUFNLE1BQU0sQ0FBQztBQXlCbEMsTUFBTSxPQUFPLHlCQUF5QjtJQUM1QixRQUFRLEdBQUcsZUFBd0IsQ0FBQztJQUM1QixNQUFNLENBQTJCO0lBQzFDLE1BQU0sR0FBdUIsSUFBSSxDQUFDO0lBQ2xDLGFBQWEsR0FBb0MsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUMzRCxTQUFTLEdBQUcsS0FBSyxDQUFDO0lBQ2xCLFlBQVksR0FBRyxlQUFlLElBQUksRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUUzRCxZQUFZLE1BQTBDO1FBQ3JELElBQUksQ0FBQyxNQUFNLEdBQUc7WUFDYixJQUFJLEVBQUUsTUFBTSxFQUFFLElBQUksSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsSUFBSSxXQUFXO1lBQzNELElBQUksRUFBRSxNQUFNLEVBQUUsSUFBSSxJQUFJLE1BQU0sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLElBQUksTUFBTSxFQUFFLEVBQUUsQ0FBQztZQUMzRSxRQUFRLEVBQUUsTUFBTSxFQUFFLFFBQVEsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLGNBQWM7WUFDeEQsRUFBRSxFQUFFLE1BQU0sRUFBRSxFQUFFLElBQUksTUFBTSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsSUFBSSxHQUFHLEVBQUUsRUFBRSxDQUFDO1lBQ2xFLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxJQUFJLElBQUk7WUFDaEMs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QUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNO1lBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQywyREFBMkQsQ0FBQyxDQUFDO1FBQ2xILE1BQU0sSUFBSSxHQUFHLE9BQU8sT0FBTyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQzdFLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDbEQsQ0FBQztJQUVELFdBQVc7UUFDVixPQUFPLElBQUksQ0FBQyxTQUFTLENBQUM7SUFDdkIsQ0FBQztJQUVELEtBQUssQ0FBQyxXQUFXO1FBQ2hCLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU07WUFBRSxPQUFPLEtBQUssQ0FBQztRQUNsRCxJQUFJLENBQUM7WUFDSixNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDdEMsT0FBTyxJQUFJLEtBQUssTUFBTSxDQUFDO1FBQ3hCLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUixPQUFPLEtBQUssQ0FBQztRQUNkLENBQUM7SUFDRixDQUFDO0NBQ0QiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFJlZGlzU3RyZWFtc1B1YlN1YkFkYXB0ZXIg4oCUIHYwLjcgUFIgNiDigJQgUHViL1N1YiBhZGFwdGVyIGJhY2tlZCBieVxuICogUmVkaXMgU3RyZWFtcyB2aWEgYGlvcmVkaXNgLlxuICpcbiAqIFB1Yi9TdWIgdnMgV29ya2VyIHNlbWFudGljczogdGhpcyBhZGFwdGVyIHVzZXMgKipkaXN0aW5jdCBjb25zdW1lclxuICogZ3JvdXBzIHBlciBzdWJzY3JpYmVyKiogc28gbXVsdGlwbGUgc3Vic2NyaWJlcnMgZWFjaCByZWNlaXZlIGV2ZXJ5XG4gKiBtZXNzYWdlIChmYW4tb3V0KS4gV2hlbiBgY29uc3VtZXJHcm91cGAgaXMgZXhwbGljaXRseSBzZXQgb24gdGhlXG4gKiB3b3JrZmxvdywgYWxsIHN1YnNjcmliZXJzIGluIHRoZSBncm91cCBjb21wZXRlICgxIG9mIE4gZ2V0cyBlYWNoKS5cbiAqXG4gKiBSZXBsYXkgY3Vyc29yczpcbiAqICAgLSBgXCJlYXJsaWVzdFwiYCAvIHVuc2V0IOKGkiBgJGAgKG9ubHkgbmV3IG1lc3NhZ2VzIGJ5IGRlZmF1bHQpLlxuICogICAtIGBcImVhcmxpZXN0XCJgIHdpdGggZXhwbGljaXQgaW50ZW50IOKGkiBgMGAgKHJlcGxheSBmdWxsIHN0cmVhbSkuXG4gKiAgIC0gYHtzZXE6IE59YCDihpIgcmVzdW1lIGZyb20gc3RyZWFtIGlkIGBOLTBgLlxuICpcbiAqIFJlcXVpcmVzIGBpb3JlZGlzYCBhcyBhIHBlZXIgZGVwZW5kZW5jeS5cbiAqXG4gKiBFbnZpcm9ubWVudCB2YXJpYWJsZXM6XG4gKiAgIC0gYFJFRElTX0hPU1RgIChkZWZhdWx0IGBsb2NhbGhvc3RgKS5cbiAqICAgLSBgUkVESVNfUE9SVGAgKGRlZmF1bHQgYDYzNzlgKS5cbiAqICAgLSBgUkVESVNfUEFTU1dPUkRgLlxuICogICAtIGBSRURJU19EQmAuXG4gKi9cblxuaW1wb3J0IHR5cGUgeyBQdWJTdWJUcmlnZ2VyT3B0cyB9IGZyb20gXCJAYmxva2pzL2hlbHBlclwiO1xuaW1wb3J0IHsgdjQgYXMgdXVpZCB9IGZyb20gXCJ1dWlkXCI7XG5pbXBvcnQgdHlwZSB7IFB1YlN1YkFkYXB0ZXIsIFB1YlN1Yk1lc3NhZ2UgfSBmcm9tIFwiLi4vUHViU3ViVHJpZ2dlclwiO1xuXG5leHBvcnQgaW50ZXJmYWNlIFJlZGlzU3RyZWFtc1B1YlN1YkNvbmZpZyB7XG5cdGhvc3Q6IHN0cmluZztcblx0cG9ydDogbnVtYmVyO1xuXHRwYXNzd29yZD86IHN0cmluZztcblx0ZGI/OiBudW1iZXI7XG5cdGJsb2NrTXM6IG51bWJlcjtcblx0Y291bnQ6IG51bWJlcjtcbn1cblxuaW50ZXJmYWNlIFJlZGlzQ2xpZW50IHtcblx0eGFkZChzdHJlYW06IHN0cmluZywgLi4uYXJnczogc3RyaW5nW10pOiBQcm9taXNlPHN0cmluZz47XG5cdHhyZWFkZ3JvdXAoLi4uYXJnczogc3RyaW5nW10pOiBQcm9taXNlPEFycmF5PFtzdHJpbmcsIEFycmF5PFtzdHJpbmcsIHN0cmluZ1tdXT5dPiB8IG51bGw+O1xuXHR4Z3JvdXAoLi4uYXJnczogc3RyaW5nW10pOiBQcm9taXNlPHN0cmluZz47XG5cdHhhY2soc3RyZWFtOiBzdHJpbmcsIGdyb3VwOiBzdHJpbmcsIC4uLmlkczogc3RyaW5nW10pOiBQcm9taXNlPG51bWJlcj47XG5cdHBpbmcoKTogUHJvbWlzZTxzdHJpbmc+O1xuXHRxdWl0KCk6IFByb21pc2U8c3RyaW5nPjtcbn1cblxuaW50ZXJmYWNlIEFjdGl2ZVN1YnNjcmlwdGlvbiB7XG5cdHN0b3A6ICgpID0+IHZvaWQ7XG59XG5cbmV4cG9ydCBjbGFzcyBSZWRpc1N0cmVhbXNQdWJTdWJBZGFwdGVyIGltcGxlbWVudHMgUHViU3ViQWRhcHRlciB7XG5cdHJlYWRvbmx5IHByb3ZpZGVyID0gXCJyZWRpcy1zdHJlYW1zXCIgYXMgY29uc3Q7XG5cdHByaXZhdGUgcmVhZG9ubHkgY29uZmlnOiBSZWRpc1N0cmVhbXNQdWJTdWJDb25maWc7XG5cdHByaXZhdGUgY2xpZW50OiBSZWRpc0NsaWVudCB8IG51bGwgPSBudWxsO1xuXHRwcml2YXRlIHN1YnNjcmlwdGlvbnM6IE1hcDxzdHJpbmcsIEFjdGl2ZVN1YnNjcmlwdGlvbj4gPSBuZXcgTWFwKCk7XG5cdHByaXZhdGUgY29ubmVjdGVkID0gZmFsc2U7XG5cdHByaXZhdGUgY29uc3VtZXJOYW1lID0gYGJsb2stcHVic3ViLSR7dXVpZCgpLnNsaWNlKDAsIDgpfWA7XG5cblx0Y29uc3RydWN0b3IoY29uZmlnPzogUGFydGlhbDxSZWRpc1N0cmVhbXNQdWJTdWJDb25maWc+KSB7XG5cdFx0dGhpcy5jb25maWcgPSB7XG5cdFx0XHRob3N0OiBjb25maWc/Lmhvc3QgPz8gcHJvY2Vzcy5lbnYuUkVESVNfSE9TVCA/PyBcImxvY2FsaG9zdFwiLFxuXHRcdFx0cG9ydDogY29uZmlnPy5wb3J0ID8/IE51bWJlci5wYXJzZUludChwcm9jZXNzLmVudi5SRURJU19QT1JUID8/IFwiNjM3OVwiLCAxMCksXG5cdFx0XHRwYXNzd29yZDogY29uZmlnPy5wYXNzd29yZCA/PyBwcm9jZXNzLmVudi5SRURJU19QQVNTV09SRCxcblx0XHRcdGRiOiBjb25maWc/LmRiID8/IE51bWJlci5wYXJzZUludChwcm9jZXNzLmVudi5SRURJU19EQiA/PyBcIjBcIiwgMTApLFxuXHRcdFx0YmxvY2tNczogY29uZmlnPy5ibG9ja01zID8/IDUwMDAsXG5cdFx0XHRjb3VudDogY29uZmlnPy5jb3VudCA/PyAxMCxcblx0XHR9O1xuXHR9XG5cblx0YXN5bmMgY29ubmVjdCgpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRpZiAodGhpcy5jb25uZWN0ZWQpIHJldHVybjtcblx0XHR0cnkge1xuXHRcdFx0Ly8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiBpb3JlZGlzIGlzIGEgcnVudGltZSBwZWVyIGRlcC5cblx0XHRcdGNvbnN0IGlvcmVkaXM6IGFueSA9IGF3YWl0IGltcG9ydChcImlvcmVkaXNcIik7XG5cdFx0XHRjb25zdCBJT1JlZGlzID0gaW9yZWRpcy5kZWZhdWx0ID8/IGlvcmVkaXMuUmVkaXMgPz8gaW9yZWRpcztcblx0XHRcdHRoaXMuY2xpZW50ID0gbmV3IElPUmVkaXMoe1xuXHRcdFx0XHRob3N0OiB0aGlzLmNvbmZpZy5ob3N0LFxuXHRcdFx0XHRwb3J0OiB0aGlzLmNvbmZpZy5wb3J0LFxuXHRcdFx0XHRwYXNzd29yZDogdGhpcy5jb25maWcucGFzc3dvcmQsXG5cdFx0XHRcdGRiOiB0aGlzLmNvbmZpZy5kYixcblx0XHRcdH0pIGFzIFJlZGlzQ2xpZW50O1xuXHRcdFx0YXdhaXQgdGhpcy5jbGllbnQucGluZygpO1xuXHRcdFx0dGhpcy5jb25uZWN0ZWQgPSB0cnVlO1xuXHRcdH0gY2F0Y2ggKGVycikge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRgW2Jsb2tdW3B1YnN1Yi1yZWRpc10gY29ubmVjdCBmYWlsZWQ6ICR7KGVyciBhcyBFcnJvcikubWVzc2FnZX0uIEluc3RhbGwgaW9yZWRpcyBhcyBhIHBlZXIgZGVwZW5kZW5jeTogYnVuIGFkZCBpb3JlZGlzYCxcblx0XHRcdCk7XG5cdFx0fVxuXHR9XG5cblx0YXN5bmMgZGlzY29ubmVjdCgpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRpZiAoIXRoaXMuY29ubmVjdGVkKSByZXR1cm47XG5cdFx0Zm9yIChjb25zdCBzdWIgb2YgdGhpcy5zdWJzY3JpcHRpb25zLnZhbHVlcygpKSBzdWIuc3RvcCgpO1xuXHRcdHRoaXMuc3Vic2NyaXB0aW9ucy5jbGVhcigpO1xuXHRcdHRyeSB7XG5cdFx0XHRhd2FpdCB0aGlzLmNsaWVudD8ucXVpdCgpO1xuXHRcdH0gY2F0Y2gge1xuXHRcdFx0LyogaWdub3JlICovXG5cdFx0fVxuXHRcdHRoaXMuY2xpZW50ID0gbnVsbDtcblx0XHR0aGlzLmNvbm5lY3RlZCA9IGZhbHNlO1xuXHR9XG5cblx0YXN5bmMgc3Vic2NyaWJlKGNvbmZpZzogUHViU3ViVHJpZ2dlck9wdHMsIGhhbmRsZXI6IChtZXNzYWdlOiBQdWJTdWJNZXNzYWdlKSA9PiBQcm9taXNlPHZvaWQ+KTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0aWYgKCF0aGlzLmNvbm5lY3RlZCB8fCAhdGhpcy5jbGllbnQpIHRocm93IG5ldyBFcnJvcihcIltibG9rXVtwdWJzdWItcmVkaXNdIG5vdCBjb25uZWN0ZWQuIENhbGwgY29ubmVjdCgpIGZpcnN0LlwiKTtcblx0XHRjb25zdCBjbGllbnQgPSB0aGlzLmNsaWVudDtcblx0XHRjb25zdCBzdHJlYW0gPSBjb25maWcudG9waWM7XG5cdFx0Ly8gRmFuLW91dDogZWFjaCBzdWJzY3JpYmVyIGdldHMgaXRzIG93biBncm91cCAodW5pcXVlIHBlciBpbnN0YW5jZSkuXG5cdFx0Ly8gQ29tcGV0aW5nLWNvbnN1bWVyOiBleHBsaWNpdCBgY29uc3VtZXJHcm91cGAgbWFrZXMgYWxsIHN1YnNjcmliZXJzXG5cdFx0Ly8gc2hhcmUgd29yay5cblx0XHRjb25zdCBncm91cCA9IGNvbmZpZy5jb25zdW1lckdyb3VwID8/IGBibG9rLWZhbm91dC0ke3RoaXMuY29uc3VtZXJOYW1lfS0ke3N0cmVhbS5yZXBsYWNlKC9bXmEtekEtWjAtOV9dL2csIFwiX1wiKX1gO1xuXHRcdGNvbnN0IHN0YXJ0SWQgPVxuXHRcdFx0Y29uZmlnLnN0YXJ0RnJvbSA9PT0gXCJlYXJsaWVzdFwiXG5cdFx0XHRcdD8gXCIwXCJcblx0XHRcdFx0OiBjb25maWcuc3RhcnRGcm9tID09PSBcImxhdGVzdFwiIHx8IGNvbmZpZy5zdGFydEZyb20gPT09IHVuZGVmaW5lZFxuXHRcdFx0XHRcdD8gXCIkXCJcblx0XHRcdFx0XHQ6IHR5cGVvZiBjb25maWcuc3RhcnRGcm9tID09PSBcIm9iamVjdFwiICYmIFwic2VxXCIgaW4gY29uZmlnLnN0YXJ0RnJvbVxuXHRcdFx0XHRcdFx0PyBgJHtjb25maWcuc3RhcnRGcm9tLnNlcX0tMGBcblx0XHRcdFx0XHRcdDogXCIkXCI7XG5cblx0XHR0cnkge1xuXHRcdFx0YXdhaXQgY2xpZW50Lnhncm91cChcIkNSRUFURVwiLCBzdHJlYW0sIGdyb3VwLCBzdGFydElkLCBcIk1LU1RSRUFNXCIpO1xuXHRcdH0gY2F0Y2ggKGVycikge1xuXHRcdFx0aWYgKCEvQlVTWUdST1VQL2kudGVzdCgoZXJyIGFzIEVycm9yKS5tZXNzYWdlKSkgdGhyb3cgZXJyO1xuXHRcdH1cblxuXHRcdGxldCBzdG9wcGVkID0gZmFsc2U7XG5cdFx0Y29uc3Qgc3ViOiBBY3RpdmVTdWJzY3JpcHRpb24gPSB7XG5cdFx0XHRzdG9wOiAoKSA9PiB7XG5cdFx0XHRcdHN0b3BwZWQgPSB0cnVlO1xuXHRcdFx0fSxcblx0XHR9O1xuXHRcdHRoaXMuc3Vic2NyaXB0aW9ucy5zZXQoYCR7c3RyZWFtfSMke2dyb3VwfWAsIHN1Yik7XG5cblx0XHR2b2lkIChhc3luYyAoKSA9PiB7XG5cdFx0XHR3aGlsZSAoIXN0b3BwZWQpIHtcblx0XHRcdFx0bGV0IGVudHJpZXM6IEFycmF5PFtzdHJpbmcsIEFycmF5PFtzdHJpbmcsIHN0cmluZ1tdXT5dPiB8IG51bGwgPSBudWxsO1xuXHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdGVudHJpZXMgPSBhd2FpdCBjbGllbnQueHJlYWRncm91cChcblx0XHRcdFx0XHRcdFwiR1JPVVBcIixcblx0XHRcdFx0XHRcdGdyb3VwLFxuXHRcdFx0XHRcdFx0dGhpcy5jb25zdW1lck5hbWUsXG5cdFx0XHRcdFx0XHRcIkNPVU5UXCIsXG5cdFx0XHRcdFx0XHRTdHJpbmcodGhpcy5jb25maWcuY291bnQpLFxuXHRcdFx0XHRcdFx0XCJCTE9DS1wiLFxuXHRcdFx0XHRcdFx0U3RyaW5nKHRoaXMuY29uZmlnLmJsb2NrTXMpLFxuXHRcdFx0XHRcdFx0XCJTVFJFQU1TXCIsXG5cdFx0XHRcdFx0XHRzdHJlYW0sXG5cdFx0XHRcdFx0XHRcIj5cIixcblx0XHRcdFx0XHQpO1xuXHRcdFx0XHR9IGNhdGNoIHtcblx0XHRcdFx0XHRhd2FpdCBuZXcgUHJvbWlzZSgocikgPT4gc2V0VGltZW91dChyLCAxMDAwKSk7XG5cdFx0XHRcdFx0Y29udGludWU7XG5cdFx0XHRcdH1cblx0XHRcdFx0aWYgKCFlbnRyaWVzKSBjb250aW51ZTtcblx0XHRcdFx0Zm9yIChjb25zdCBbLCBtc2dzXSBvZiBlbnRyaWVzKSB7XG5cdFx0XHRcdFx0Zm9yIChjb25zdCBbaWQsIGZpZWxkc10gb2YgbXNncykge1xuXHRcdFx0XHRcdFx0aWYgKHN0b3BwZWQpIGJyZWFrO1xuXHRcdFx0XHRcdFx0Y29uc3QgcGF5bG9hZCA9IHRoaXMuZmllbGRzVG9PYmplY3QoZmllbGRzKTtcblx0XHRcdFx0XHRcdGNvbnN0IGRhdGFTdHJpbmcgPSB0eXBlb2YgcGF5bG9hZC5kYXRhID09PSBcInN0cmluZ1wiID8gcGF5bG9hZC5kYXRhIDogXCJcIjtcblx0XHRcdFx0XHRcdGxldCBib2R5OiB1bmtub3duO1xuXHRcdFx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRcdFx0Ym9keSA9IGRhdGFTdHJpbmcubGVuZ3RoID4gMCA/IEpTT04ucGFyc2UoZGF0YVN0cmluZykgOiBudWxsO1xuXHRcdFx0XHRcdFx0fSBjYXRjaCB7XG5cdFx0XHRcdFx0XHRcdGJvZHkgPSBkYXRhU3RyaW5nO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0Y29uc3QgbWVzc2FnZTogUHViU3ViTWVzc2FnZSA9IHtcblx0XHRcdFx0XHRcdFx0aWQsXG5cdFx0XHRcdFx0XHRcdGJvZHksXG5cdFx0XHRcdFx0XHRcdGF0dHJpYnV0ZXM6IHBheWxvYWQsXG5cdFx0XHRcdFx0XHRcdHJhdzogeyBpZCwgZmllbGRzIH0sXG5cdFx0XHRcdFx0XHRcdHRvcGljOiBzdHJlYW0sXG5cdFx0XHRcdFx0XHRcdHN1YnNjcmlwdGlvbjogZ3JvdXAsXG5cdFx0XHRcdFx0XHRcdHB1Ymxpc2hUaW1lOiBuZXcgRGF0ZShOdW1iZXIucGFyc2VJbnQoaWQuc3BsaXQoXCItXCIpWzBdID8/IFN0cmluZyhEYXRlLm5vdygpKSwgMTApKSxcblx0XHRcdFx0XHRcdFx0YWNrOiBhc3luYyAoKSA9PiB7XG5cdFx0XHRcdFx0XHRcdFx0YXdhaXQgY2xpZW50LnhhY2soc3RyZWFtLCBncm91cCwgaWQpO1xuXHRcdFx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdFx0XHRuYWNrOiBhc3luYyAoKSA9PiB7XG5cdFx0XHRcdFx0XHRcdFx0LyogbGVhdmUgdW5hY2tlZCDigJQgcGlja2VkIHVwIGJ5IFhBVVRPQ0xBSU0gKi9cblx0XHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdH07XG5cdFx0XHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdFx0XHRhd2FpdCBoYW5kbGVyKG1lc3NhZ2UpO1xuXHRcdFx0XHRcdFx0XHRpZiAoY29uZmlnLmFjayAhPT0gZmFsc2UpIGF3YWl0IGNsaWVudC54YWNrKHN0cmVhbSwgZ3JvdXAsIGlkKTtcblx0XHRcdFx0XHRcdH0gY2F0Y2gge1xuXHRcdFx0XHRcdFx0XHQvLyBMZWF2ZSB1bmFja2VkIOKAlCBwZW5kaW5nIGVudHJpZXMgYXJlIHZpc2libGUgaW4gWFBFTkRJTkcuXG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fSkoKTtcblx0fVxuXG5cdHByaXZhdGUgZmllbGRzVG9PYmplY3QoZmllbGRzOiBzdHJpbmdbXSk6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4ge1xuXHRcdGNvbnN0IG91dDogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHt9O1xuXHRcdGZvciAobGV0IGkgPSAwOyBpIDwgZmllbGRzLmxlbmd0aDsgaSArPSAyKSBvdXRbZmllbGRzW2ldXSA9IGZpZWxkc1tpICsgMV07XG5cdFx0cmV0dXJuIG91dDtcblx0fVxuXG5cdGFzeW5jIHVuc3Vic2NyaWJlKHN1YnNjcmlwdGlvbjogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0Y29uc3Qgc3ViID0gdGhpcy5zdWJzY3JpcHRpb25zLmdldChzdWJzY3JpcHRpb24pO1xuXHRcdGlmICghc3ViKSByZXR1cm47XG5cdFx0c3ViLnN0b3AoKTtcblx0XHR0aGlzLnN1YnNjcmlwdGlvbnMuZGVsZXRlKHN1YnNjcmlwdGlvbik7XG5cdH1cblxuXHRhc3luYyBwdWJsaXNoKHRvcGljOiBzdHJpbmcsIHBheWxvYWQ6IHVua25vd24pOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRpZiAoIXRoaXMuY29ubmVjdGVkIHx8ICF0aGlzLmNsaWVudCkgdGhyb3cgbmV3IEVycm9yKFwiW2Jsb2tdW3B1YnN1Yi1yZWRpc10gbm90IGNvbm5lY3RlZC4gQ2FsbCBjb25uZWN0KCkgZmlyc3QuXCIpO1xuXHRcdGNvbnN0IGJvZHkgPSB0eXBlb2YgcGF5bG9hZCA9PT0gXCJzdHJpbmdcIiA/IHBheWxvYWQgOiBKU09OLnN0cmluZ2lmeShwYXlsb2FkKTtcblx0XHRhd2FpdCB0aGlzLmNsaWVudC54YWRkKHRvcGljLCBcIipcIiwgXCJkYXRhXCIsIGJvZHkpO1xuXHR9XG5cblx0aXNDb25uZWN0ZWQoKTogYm9vbGVhbiB7XG5cdFx0cmV0dXJuIHRoaXMuY29ubmVjdGVkO1xuXHR9XG5cblx0YXN5bmMgaGVhbHRoQ2hlY2soKTogUHJvbWlzZTxib29sZWFuPiB7XG5cdFx0aWYgKCF0aGlzLmNvbm5lY3RlZCB8fCAhdGhpcy5jbGllbnQpIHJldHVybiBmYWxzZTtcblx0XHR0cnkge1xuXHRcdFx0Y29uc3QgcG9uZyA9IGF3YWl0IHRoaXMuY2xpZW50LnBpbmcoKTtcblx0XHRcdHJldHVybiBwb25nID09PSBcIlBPTkdcIjtcblx0XHR9IGNhdGNoIHtcblx0XHRcdHJldHVybiBmYWxzZTtcblx0XHR9XG5cdH1cbn1cbiJdfQ==
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.7 PR 6 — pub/sub adapter factory.
|
|
3
|
+
*
|
|
4
|
+
* Resolves a `provider` string to a concrete `PubSubAdapter` instance.
|
|
5
|
+
* Used by `PubSubTrigger` (per-workflow provider dispatch) and by the
|
|
6
|
+
* `@blokjs/pubsub-publish` helper.
|
|
7
|
+
*
|
|
8
|
+
* Provider resolution order:
|
|
9
|
+
* 1. Explicit `provider` field on the workflow.
|
|
10
|
+
* 2. `BLOK_PUBSUB_ADAPTER` env var.
|
|
11
|
+
* 3. `"nats"` fallback (cheapest infra; matches the v0.7 plan's
|
|
12
|
+
* "default for pub/sub" recommendation).
|
|
13
|
+
*
|
|
14
|
+
* Each adapter lazy-imports its broker SDK on first use; workflows
|
|
15
|
+
* that don't use a given provider don't pay the install cost.
|
|
16
|
+
*/
|
|
17
|
+
import type { PubSubProvider } from "@blokjs/helper";
|
|
18
|
+
import type { PubSubAdapter } from "../PubSubTrigger";
|
|
19
|
+
export declare function resolveProvider(provider?: PubSubProvider): PubSubProvider;
|
|
20
|
+
export declare function createPubSubAdapter(provider: PubSubProvider): PubSubAdapter;
|
|
21
|
+
export declare function getOrCreateAdapter(provider: PubSubProvider): PubSubAdapter;
|
|
22
|
+
export declare function _resetAdapterPoolForTests(): void;
|