@pipeline-builder/api-core 0.0.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.
Files changed (184) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/lib/constants/ai-providers.d.ts +52 -0
  4. package/lib/constants/ai-providers.js +129 -0
  5. package/lib/constants/http-status.d.ts +20 -0
  6. package/lib/constants/http-status.js +26 -0
  7. package/lib/constants/index.d.ts +3 -0
  8. package/lib/constants/index.js +6 -0
  9. package/lib/constants/time.d.ts +10 -0
  10. package/lib/constants/time.js +13 -0
  11. package/lib/errors/app-errors.d.ts +30 -0
  12. package/lib/errors/app-errors.js +53 -0
  13. package/lib/errors/index.d.ts +1 -0
  14. package/lib/errors/index.js +4 -0
  15. package/lib/helpers/access-helpers.d.ts +63 -0
  16. package/lib/helpers/access-helpers.js +82 -0
  17. package/lib/helpers/crud-helpers.d.ts +9 -0
  18. package/lib/helpers/crud-helpers.js +23 -0
  19. package/lib/helpers/index.d.ts +6 -0
  20. package/lib/helpers/index.js +9 -0
  21. package/lib/helpers/org-hierarchy-http.d.ts +74 -0
  22. package/lib/helpers/org-hierarchy-http.js +144 -0
  23. package/lib/helpers/org-hierarchy.d.ts +57 -0
  24. package/lib/helpers/org-hierarchy.js +110 -0
  25. package/lib/helpers/restore-helpers.d.ts +49 -0
  26. package/lib/helpers/restore-helpers.js +78 -0
  27. package/lib/helpers/sse-helpers.d.ts +20 -0
  28. package/lib/helpers/sse-helpers.js +45 -0
  29. package/lib/index.d.ts +58 -0
  30. package/lib/index.js +71 -0
  31. package/lib/middleware/auth.d.ts +208 -0
  32. package/lib/middleware/auth.js +588 -0
  33. package/lib/middleware/index.d.ts +3 -0
  34. package/lib/middleware/index.js +6 -0
  35. package/lib/middleware/mongo-sanitize.d.ts +32 -0
  36. package/lib/middleware/mongo-sanitize.js +100 -0
  37. package/lib/middleware/step-up.d.ts +45 -0
  38. package/lib/middleware/step-up.js +138 -0
  39. package/lib/openapi/extend-zod.d.ts +1 -0
  40. package/lib/openapi/extend-zod.js +6 -0
  41. package/lib/openapi/index.d.ts +2 -0
  42. package/lib/openapi/index.js +5 -0
  43. package/lib/openapi/registry.d.ts +17 -0
  44. package/lib/openapi/registry.js +38 -0
  45. package/lib/openapi/routes/ask-routes.d.ts +7 -0
  46. package/lib/openapi/routes/ask-routes.js +62 -0
  47. package/lib/openapi/routes/billing-routes.d.ts +1 -0
  48. package/lib/openapi/routes/billing-routes.js +66 -0
  49. package/lib/openapi/routes/image-registry-routes.d.ts +7 -0
  50. package/lib/openapi/routes/image-registry-routes.js +98 -0
  51. package/lib/openapi/routes/index.d.ts +5 -0
  52. package/lib/openapi/routes/index.js +28 -0
  53. package/lib/openapi/routes/message-routes.d.ts +1 -0
  54. package/lib/openapi/routes/message-routes.js +105 -0
  55. package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
  56. package/lib/openapi/routes/pipeline-routes.js +105 -0
  57. package/lib/openapi/routes/pipeline-template-routes.d.ts +1 -0
  58. package/lib/openapi/routes/pipeline-template-routes.js +61 -0
  59. package/lib/openapi/routes/plugin-routes.d.ts +1 -0
  60. package/lib/openapi/routes/plugin-routes.js +96 -0
  61. package/lib/openapi/routes/quota-routes.d.ts +1 -0
  62. package/lib/openapi/routes/quota-routes.js +62 -0
  63. package/lib/openapi/schema-registry.d.ts +25 -0
  64. package/lib/openapi/schema-registry.js +91 -0
  65. package/lib/routes/health.d.ts +41 -0
  66. package/lib/routes/health.js +82 -0
  67. package/lib/routes/index.d.ts +1 -0
  68. package/lib/routes/index.js +4 -0
  69. package/lib/services/audit-spool.d.ts +80 -0
  70. package/lib/services/audit-spool.js +172 -0
  71. package/lib/services/cache-service.d.ts +124 -0
  72. package/lib/services/cache-service.js +276 -0
  73. package/lib/services/circuit-breaker.d.ts +45 -0
  74. package/lib/services/circuit-breaker.js +138 -0
  75. package/lib/services/compliance-client.d.ts +46 -0
  76. package/lib/services/compliance-client.js +128 -0
  77. package/lib/services/compliance-event-subscriber.d.ts +16 -0
  78. package/lib/services/compliance-event-subscriber.js +72 -0
  79. package/lib/services/durable-event-bus.d.ts +96 -0
  80. package/lib/services/durable-event-bus.js +204 -0
  81. package/lib/services/entity-events.d.ts +48 -0
  82. package/lib/services/entity-events.js +79 -0
  83. package/lib/services/env-redis.d.ts +44 -0
  84. package/lib/services/env-redis.js +94 -0
  85. package/lib/services/http-client.d.ts +143 -0
  86. package/lib/services/http-client.js +335 -0
  87. package/lib/services/index.d.ts +18 -0
  88. package/lib/services/index.js +20 -0
  89. package/lib/services/leader-lock.d.ts +45 -0
  90. package/lib/services/leader-lock.js +85 -0
  91. package/lib/services/quota.d.ts +130 -0
  92. package/lib/services/quota.js +264 -0
  93. package/lib/services/remote-audit-client.d.ts +160 -0
  94. package/lib/services/remote-audit-client.js +390 -0
  95. package/lib/services/retry-strategy.d.ts +74 -0
  96. package/lib/services/retry-strategy.js +118 -0
  97. package/lib/services/scheduler.d.ts +41 -0
  98. package/lib/services/scheduler.js +86 -0
  99. package/lib/services/service-boot.d.ts +20 -0
  100. package/lib/services/service-boot.js +28 -0
  101. package/lib/services/sse-ticket-store.d.ts +51 -0
  102. package/lib/services/sse-ticket-store.js +130 -0
  103. package/lib/services/token-revocation.d.ts +52 -0
  104. package/lib/services/token-revocation.js +120 -0
  105. package/lib/testing/tier-mock.d.ts +44 -0
  106. package/lib/testing/tier-mock.js +65 -0
  107. package/lib/types/audit-events.d.ts +69 -0
  108. package/lib/types/audit-events.js +4 -0
  109. package/lib/types/catalog-metadata.d.ts +36 -0
  110. package/lib/types/catalog-metadata.js +21 -0
  111. package/lib/types/common.d.ts +255 -0
  112. package/lib/types/common.js +41 -0
  113. package/lib/types/error-codes.d.ts +54 -0
  114. package/lib/types/error-codes.js +107 -0
  115. package/lib/types/feature-flags.d.ts +36 -0
  116. package/lib/types/feature-flags.js +128 -0
  117. package/lib/types/http.d.ts +27 -0
  118. package/lib/types/http.js +4 -0
  119. package/lib/types/index.d.ts +11 -0
  120. package/lib/types/index.js +14 -0
  121. package/lib/types/permissions.d.ts +65 -0
  122. package/lib/types/permissions.js +131 -0
  123. package/lib/types/pipeline-template.d.ts +30 -0
  124. package/lib/types/pipeline-template.js +13 -0
  125. package/lib/types/pipeline.d.ts +59 -0
  126. package/lib/types/pipeline.js +31 -0
  127. package/lib/types/quota-tiers.d.ts +79 -0
  128. package/lib/types/quota-tiers.js +221 -0
  129. package/lib/types/visibility.d.ts +20 -0
  130. package/lib/types/visibility.js +22 -0
  131. package/lib/utils/alias-resolver.d.ts +37 -0
  132. package/lib/utils/alias-resolver.js +68 -0
  133. package/lib/utils/audit.d.ts +15 -0
  134. package/lib/utils/audit.js +39 -0
  135. package/lib/utils/aws-scrub.d.ts +11 -0
  136. package/lib/utils/aws-scrub.js +67 -0
  137. package/lib/utils/compliance-attributes.d.ts +6 -0
  138. package/lib/utils/compliance-attributes.js +69 -0
  139. package/lib/utils/concurrency.d.ts +9 -0
  140. package/lib/utils/concurrency.js +29 -0
  141. package/lib/utils/crash-handlers.d.ts +18 -0
  142. package/lib/utils/crash-handlers.js +36 -0
  143. package/lib/utils/env.d.ts +28 -0
  144. package/lib/utils/env.js +54 -0
  145. package/lib/utils/headers.d.ts +18 -0
  146. package/lib/utils/headers.js +21 -0
  147. package/lib/utils/identity.d.ts +40 -0
  148. package/lib/utils/identity.js +51 -0
  149. package/lib/utils/index.d.ts +17 -0
  150. package/lib/utils/index.js +20 -0
  151. package/lib/utils/logger.d.ts +38 -0
  152. package/lib/utils/logger.js +174 -0
  153. package/lib/utils/metric-emitter.d.ts +36 -0
  154. package/lib/utils/metric-emitter.js +32 -0
  155. package/lib/utils/object.d.ts +13 -0
  156. package/lib/utils/object.js +18 -0
  157. package/lib/utils/params.d.ts +150 -0
  158. package/lib/utils/params.js +206 -0
  159. package/lib/utils/response.d.ts +117 -0
  160. package/lib/utils/response.js +223 -0
  161. package/lib/utils/safe-require.d.ts +13 -0
  162. package/lib/utils/safe-require.js +19 -0
  163. package/lib/utils/secret-encryption.d.ts +176 -0
  164. package/lib/utils/secret-encryption.js +374 -0
  165. package/lib/utils/ssrf.d.ts +30 -0
  166. package/lib/utils/ssrf.js +106 -0
  167. package/lib/validation/ai-schemas.d.ts +56 -0
  168. package/lib/validation/ai-schemas.js +78 -0
  169. package/lib/validation/common-schemas.d.ts +133 -0
  170. package/lib/validation/common-schemas.js +94 -0
  171. package/lib/validation/index.d.ts +7 -0
  172. package/lib/validation/index.js +10 -0
  173. package/lib/validation/message-schemas.d.ts +160 -0
  174. package/lib/validation/message-schemas.js +156 -0
  175. package/lib/validation/middleware.d.ts +72 -0
  176. package/lib/validation/middleware.js +65 -0
  177. package/lib/validation/pipeline-schemas.d.ts +181 -0
  178. package/lib/validation/pipeline-schemas.js +101 -0
  179. package/lib/validation/pipeline-template-schemas.d.ts +155 -0
  180. package/lib/validation/pipeline-template-schemas.js +66 -0
  181. package/lib/validation/plugin-schemas.d.ts +171 -0
  182. package/lib/validation/plugin-schemas.js +81 -0
  183. package/openapi.yaml +292 -0
  184. package/package.json +167 -0
@@ -0,0 +1,204 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { createEnvRedisClient } from './env-redis.js';
4
+ import { createLogger } from '../utils/logger.js';
5
+ import { emitCounter } from '../utils/metric-emitter.js';
6
+ const logger = createLogger('durable-event-bus');
7
+ const DEFAULT_MAXLEN = 10_000;
8
+ const DEFAULT_BATCH = 16;
9
+ const DEFAULT_BLOCK_MS = 5_000;
10
+ const DEFAULT_MIN_IDLE_MS = 60_000;
11
+ function errMsg(err) {
12
+ return err instanceof Error ? err.message : String(err);
13
+ }
14
+ function streamKey(topic) {
15
+ return `evt:${topic}`;
16
+ }
17
+ /** True when the error is Redis' "group already exists" (idempotent create). */
18
+ function isBusyGroup(err) {
19
+ return errMsg(err).includes('BUSYGROUP');
20
+ }
21
+ /**
22
+ * Parse the nested array shape XREADGROUP / XAUTOCLAIM return into flat envelopes.
23
+ * XREADGROUP: `[[streamKey, [[id, [f, v, ...]], ...]]]`.
24
+ * XAUTOCLAIM: `[nextCursor, [[id, [f, v, ...]], ...], deletedIds]`.
25
+ * We store the JSON payload under a single field `d`.
26
+ */
27
+ function parseEntries(topic, entries) {
28
+ const out = [];
29
+ if (!Array.isArray(entries))
30
+ return out;
31
+ for (const entry of entries) {
32
+ if (!Array.isArray(entry) || entry.length < 2)
33
+ continue;
34
+ const id = String(entry[0]);
35
+ const fields = entry[1];
36
+ if (!Array.isArray(fields))
37
+ continue;
38
+ // fields is [name, value, name, value, ...]; find 'd'.
39
+ let raw;
40
+ for (let i = 0; i + 1 < fields.length; i += 2) {
41
+ if (String(fields[i]) === 'd') {
42
+ raw = String(fields[i + 1]);
43
+ break;
44
+ }
45
+ }
46
+ // A missing `d` field (an entry not written by publish()) OR a corrupt JSON
47
+ // payload both yield an undefined-payload envelope so `deliver()` ACKs it —
48
+ // a poison message must never wedge the group by staying perpetually pending.
49
+ if (raw === undefined) {
50
+ out.push({ id, topic, payload: undefined });
51
+ continue;
52
+ }
53
+ try {
54
+ out.push({ id, topic, payload: JSON.parse(raw) });
55
+ }
56
+ catch {
57
+ out.push({ id, topic, payload: undefined });
58
+ }
59
+ }
60
+ return out;
61
+ }
62
+ /** Build a durable event bus over a Redis Streams client. */
63
+ export function createRedisDurableEventBus(redis, opts = {}) {
64
+ const maxLen = Math.max(1, opts.maxLen ?? DEFAULT_MAXLEN);
65
+ return {
66
+ async publish(topic, payload) {
67
+ const key = streamKey(topic);
68
+ try {
69
+ // MAXLEN ~ <n> bounds the stream approximately (cheap); `*` = server id.
70
+ const id = await redis.xadd(key, 'MAXLEN', '~', maxLen, '*', 'd', JSON.stringify(payload));
71
+ emitCounter('event_bus_published_total', { topic });
72
+ return id;
73
+ }
74
+ catch (err) {
75
+ emitCounter('event_bus_publish_failed_total', { topic });
76
+ logger.warn('Event bus publish failed (event dropped)', { topic, error: errMsg(err) });
77
+ return null;
78
+ }
79
+ },
80
+ subscribe(subOpts) {
81
+ const { topic, group, consumer, handler } = subOpts;
82
+ const key = streamKey(topic);
83
+ const batch = Math.max(1, subOpts.batchSize ?? DEFAULT_BATCH);
84
+ const blockMs = Math.max(0, subOpts.blockMs ?? DEFAULT_BLOCK_MS);
85
+ const minIdle = Math.max(0, subOpts.minIdleMs ?? DEFAULT_MIN_IDLE_MS);
86
+ let stopped = false;
87
+ let started = false;
88
+ const deliver = async (envs) => {
89
+ for (const env of envs) {
90
+ try {
91
+ if (env.payload !== undefined)
92
+ await handler(env);
93
+ // Ack on success OR on an undefined (corrupt) payload — a poison
94
+ // message must not block the group forever.
95
+ await redis.xack(key, group, env.id);
96
+ emitCounter('event_bus_delivered_total', { topic, group });
97
+ }
98
+ catch (err) {
99
+ // Leave it pending (no XACK) — XAUTOCLAIM redelivers it after minIdle.
100
+ emitCounter('event_bus_handler_failed_total', { topic, group });
101
+ logger.warn('Event handler failed; leaving message pending for redelivery', {
102
+ topic, group, id: env.id, error: errMsg(err),
103
+ });
104
+ }
105
+ }
106
+ };
107
+ const ensureGroup = async () => {
108
+ try {
109
+ // MKSTREAM so the stream need not exist yet; '$' = only new messages.
110
+ await redis.xgroup('CREATE', key, group, '$', 'MKSTREAM');
111
+ }
112
+ catch (err) {
113
+ if (!isBusyGroup(err)) {
114
+ logger.warn('Event bus group create failed', { topic, group, error: errMsg(err) });
115
+ }
116
+ }
117
+ };
118
+ const loop = async () => {
119
+ await ensureGroup();
120
+ while (!stopped) {
121
+ try {
122
+ // 1) Reclaim messages stranded by a crashed consumer in this group.
123
+ const claimed = await redis.xautoclaim(key, group, consumer, minIdle, '0', 'COUNT', batch);
124
+ // XAUTOCLAIM reply: [nextCursor, entries, deletedIds]
125
+ if (Array.isArray(claimed) && claimed.length >= 2) {
126
+ await deliver(parseEntries(topic, claimed[1]));
127
+ }
128
+ if (stopped)
129
+ break;
130
+ // 2) Read new, never-delivered messages for this group ('>').
131
+ const res = await redis.xreadgroup('GROUP', group, consumer, 'COUNT', batch, 'BLOCK', blockMs, 'STREAMS', key, '>');
132
+ // XREADGROUP reply: [[streamKey, entries]] | null (on block timeout)
133
+ if (Array.isArray(res) && res.length > 0 && Array.isArray(res[0]) && res[0].length >= 2) {
134
+ await deliver(parseEntries(topic, res[0][1]));
135
+ }
136
+ }
137
+ catch (err) {
138
+ if (stopped)
139
+ break;
140
+ logger.warn('Event bus consumer loop error; backing off', { topic, group, error: errMsg(err) });
141
+ // Back off so a persistent Redis error doesn't hot-spin.
142
+ await new Promise((r) => setTimeout(r, 1000));
143
+ }
144
+ }
145
+ };
146
+ let loopPromise = Promise.resolve();
147
+ if (!started) {
148
+ started = true;
149
+ loopPromise = loop();
150
+ logger.info('Event bus consumer started', { topic, group, consumer });
151
+ }
152
+ return {
153
+ async stop() {
154
+ stopped = true;
155
+ // Await the in-flight read/handler so a caller (e.g. a service shutting
156
+ // down its DB) doesn't race a message still being processed. Bounded by
157
+ // the XREADGROUP BLOCK window. Never rejects — the loop swallows errors.
158
+ await loopPromise.catch(() => undefined);
159
+ },
160
+ };
161
+ },
162
+ };
163
+ }
164
+ /**
165
+ * Build a durable event bus from the ambient Redis env via the shared
166
+ * `createEnvRedisClient`. Returns `null` when Redis is not configured — the
167
+ * caller then runs without a bus (today's HTTP/reconciler behavior), never
168
+ * crashing. Opt in with a single boot line where a service wants durable events.
169
+ */
170
+ export function createEnvRedisDurableEventBus(opts = {}) {
171
+ const inst = createEnvRedisClient('event-bus');
172
+ if (!inst)
173
+ return null;
174
+ logger.info('Redis durable event bus initialized');
175
+ return createRedisDurableEventBus(inst, opts);
176
+ }
177
+ // ---------------------------------------------------------------------------
178
+ // Entity-event bridge (additive, opt-in)
179
+ //
180
+ // Lets a service give its in-process {@link entityEvents} emitter a DURABLE,
181
+ // cross-pod delivery leg WITHOUT changing existing behavior: register the
182
+ // returned subscriber and every entity mutation is ALSO published to the bus.
183
+ // A consumer elsewhere (`bus.subscribe`) then reacts at-least-once across pods
184
+ // and restarts. The in-process subscribers keep firing exactly as before, so
185
+ // this is purely additive — no double-processing unless a service intentionally
186
+ // runs a bus consumer for the same reaction. Kept here (not in entity-events.ts)
187
+ // so that module stays infrastructure-free per its own contract.
188
+ // ---------------------------------------------------------------------------
189
+ /** Topic the entity-event bridge publishes to. */
190
+ export const ENTITY_EVENT_TOPIC = 'entity-events';
191
+ /**
192
+ * An {@link EntityEventSubscriber} that forwards each entity event to the durable
193
+ * bus. `bus.publish` is fail-safe (drops-with-metric, never throws), matching the
194
+ * emitter's fire-and-forget contract. Note `EntityEvent.timestamp` (a Date)
195
+ * serializes to an ISO string on the wire — a consumer re-hydrates as needed.
196
+ */
197
+ export function createEntityEventBusPublisher(bus) {
198
+ return {
199
+ async onEntityEvent(event) {
200
+ await bus.publish(ENTITY_EVENT_TOPIC, event);
201
+ },
202
+ };
203
+ }
204
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHVyYWJsZS1ldmVudC1idXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvZHVyYWJsZS1ldmVudC1idXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQWdDdEMsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDdEQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUV6RCxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsbUJBQW1CLENBQUMsQ0FBQztBQW9EakQsTUFBTSxjQUFjLEdBQUcsTUFBTSxDQUFDO0FBQzlCLE1BQU0sYUFBYSxHQUFHLEVBQUUsQ0FBQztBQUN6QixNQUFNLGdCQUFnQixHQUFHLEtBQUssQ0FBQztBQUMvQixNQUFNLG1CQUFtQixHQUFHLE1BQU0sQ0FBQztBQUVuQyxTQUFTLE1BQU0sQ0FBQyxHQUFZO0lBQzFCLE9BQU8sR0FBRyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQzFELENBQUM7QUFFRCxTQUFTLFNBQVMsQ0FBQyxLQUFhO0lBQzlCLE9BQU8sT0FBTyxLQUFLLEVBQUUsQ0FBQztBQUN4QixDQUFDO0FBRUQsZ0ZBQWdGO0FBQ2hGLFNBQVMsV0FBVyxDQUFDLEdBQVk7SUFDL0IsT0FBTyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0FBQzNDLENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILFNBQVMsWUFBWSxDQUFJLEtBQWEsRUFBRSxPQUFnQjtJQUN0RCxNQUFNLEdBQUcsR0FBdUIsRUFBRSxDQUFDO0lBQ25DLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLE9B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QUMsTUFBTSxrQkFBa0IsR0FBRyxlQUFlLENBQUM7QUFFbEQ7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsNkJBQTZCLENBQUMsR0FBb0I7SUFDaEUsT0FBTztRQUNMLEtBQUssQ0FBQyxhQUFhLENBQUMsS0FBa0I7WUFDcEMsTUFBTSxHQUFHLENBQUMsT0FBTyxDQUFDLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQy9DLENBQUM7S0FDRixDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIER1cmFibGUsIGF0LWxlYXN0LW9uY2UgZXZlbnQgYmFja2JvbmUgKFJlZGlzIFN0cmVhbXMpLlxuICpcbiAqIFRoZSBwbGF0Zm9ybSdzIGNyb3NzLXNlcnZpY2Ugc2lnbmFsbGluZyB0b2RheSBpcyBwb2ludC10by1wb2ludCBIVFRQIHBsdXNcbiAqIHBvbGxpbmcgcmVjb25jaWxlcnMgcGx1cyBhbiBJTi1QUk9DRVNTIGJlc3QtZWZmb3J0IGVtaXR0ZXIgKHtAbGluayBlbnRpdHlFdmVudHN9KS5cbiAqIFRoZXJlIGlzIG5vIGR1cmFibGUgYnVzLCBzbyBhIHNpZ25hbCBwcm9kdWNlZCBvbiBvbmUgcG9kIGlzIGVpdGhlciBkZWxpdmVyZWRcbiAqIHN5bmNocm9ub3VzbHkgb3ZlciBIVFRQIChjb3VwbGluZyArIHJldHJ5LXN0b3JtIHJpc2spIG9yIGxvc3QgaWYgdGhlIGNvbnN1bWVyXG4gKiBpcyBtb21lbnRhcmlseSBkb3duICh0aGUgcmVjb25jaWxlcnMgZXhpc3QgdG8gcGFwZXIgb3ZlciBleGFjdGx5IHRoYXQpLlxuICpcbiAqIFRoaXMgaXMgdGhhdCBtaXNzaW5nIGJ1cywgYXMgYSByZXVzYWJsZSBwcmltaXRpdmU6XG4gKiAgLSAqKnB1Ymxpc2godG9waWMsIHBheWxvYWQpKiog4oaSIGBYQUREYCBvbnRvIGEgYm91bmRlZCAoYE1BWExFTiB+YCkgcGVyLXRvcGljXG4gKiAgICBzdHJlYW0uIER1cmFibGUgYWNyb3NzIHJlc3RhcnRzLCBzaGFyZWQgYnkgYWxsIHJlcGxpY2FzLlxuICogIC0gKipzdWJzY3JpYmUoe3RvcGljLCBncm91cCwgY29uc3VtZXIsIGhhbmRsZXJ9KSoqIOKGkiBhIGBYUkVBREdST1VQYCBjb25zdW1lci5cbiAqICAgIENvbnN1bWVyIEdST1VQUyBnaXZlIGF0LWxlYXN0LW9uY2U6IGVhY2ggZ3JvdXAgZ2V0cyBldmVyeSBtZXNzYWdlIG9uY2UsIGFcbiAqICAgIG1lc3NhZ2Ugc3RheXMgcGVuZGluZyB1bnRpbCB0aGUgaGFuZGxlciBgWEFDS2BzIGl0LCBhbmQgYFhBVVRPQ0xBSU1gXG4gKiAgICByZWNsYWltcyBtZXNzYWdlcyBzdHJhbmRlZCBieSBhIGNyYXNoZWQgY29uc3VtZXIuIE11bHRpcGxlIGdyb3VwcyBvbiBvbmVcbiAqICAgIHRvcGljID0gaW5kZXBlbmRlbnQgZmFuLW91dCAoZS5nLiBxdW90YS1zeW5jIEFORCBhdWRpdCBib3RoIHJlYWN0KS5cbiAqXG4gKiBGQUlMLVNBRkUgKyBPUFQtSU4sIGV4YWN0bHkgbGlrZSB0aGUgYXVkaXQgc3Bvb2w6IHtAbGluayBjcmVhdGVFbnZSZWRpc0R1cmFibGVFdmVudEJ1c31cbiAqIHJldHVybnMgYG51bGxgIHdoZW4gUmVkaXMgaXNuJ3QgY29uZmlndXJlZCwgc28gYSBzZXJ2aWNlIHdpdGhvdXQgaXQgc2ltcGx5XG4gKiBkb2Vzbid0IHVzZSB0aGUgYnVzIChrZWVwcyB0b2RheSdzIGJlaGF2aW9yKS4gQSBwdWJsaXNoIGZhaWx1cmUgaXMgZHJvcHBlZC13aXRoLVxuICogLW1ldHJpYywgbmV2ZXIgdGhyb3duOyBhIGhhbmRsZXIgdGhyb3cgbGVhdmVzIHRoZSBtZXNzYWdlIHVuLWFja2VkIGZvciByZWRlbGl2ZXJ5LlxuICpcbiAqIE1pZ3JhdGluZyBhIHNwZWNpZmljIHJlY29uY2lsZXIgKGUuZy4gYmlsbGluZ+KGknF1b3RhIGVudGl0bGVtZW50IHN5bmMpIG9udG8gdGhpc1xuICogaXMgZGVsaWJlcmF0ZWx5IGZvbGxvdy1vbiB3b3JrIOKAlCB0aGlzIGxhbmRzIHRoZSBiYWNrYm9uZSArIGFuIGFkZGl0aXZlIGVudGl0eS1cbiAqIGV2ZW50IGJyaWRnZTsgdGhlIHJlY29uY2lsZXJzIHN0YXkgYXMgdGhlIGJlbHQtYW5kLXN1c3BlbmRlcnMgYmFja3N0b3AgdW50aWwgYVxuICogZmxvdyBpcyBmdWxseSBjdXQgb3Zlci5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IEVudGl0eUV2ZW50LCBFbnRpdHlFdmVudFN1YnNjcmliZXIgfSBmcm9tICcuL2VudGl0eS1ldmVudHMuanMnO1xuaW1wb3J0IHsgY3JlYXRlRW52UmVkaXNDbGllbnQgfSBmcm9tICcuL2Vudi1yZWRpcy5qcyc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIgfSBmcm9tICcuLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHsgZW1pdENvdW50ZXIgfSBmcm9tICcuLi91dGlscy9tZXRyaWMtZW1pdHRlci5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignZHVyYWJsZS1ldmVudC1idXMnKTtcblxuLyoqIEEgZGVsaXZlcmVkIGV2ZW50OiBpdHMgc3RyZWFtIGlkLCB0b3BpYywgYW5kIGRlY29kZWQgcGF5bG9hZC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRXZlbnRFbnZlbG9wZTxUID0gdW5rbm93bj4ge1xuICAvKiogUmVkaXMgU3RyZWFtIGVudHJ5IGlkIChgPG1zPi08c2VxPmApIOKAlCBhbHNvIHRoZSBpZGVtcG90ZW5jeSBoYW5kbGUuICovXG4gIGlkOiBzdHJpbmc7XG4gIHRvcGljOiBzdHJpbmc7XG4gIHBheWxvYWQ6IFQ7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgU3Vic2NyaWJlT3B0aW9uczxUID0gdW5rbm93bj4ge1xuICAvKiogVG9waWMgKHN0cmVhbSkgdG8gY29uc3VtZS4gKi9cbiAgdG9waWM6IHN0cmluZztcbiAgLyoqIENvbnN1bWVyIGdyb3VwIOKAlCBvbmUgZGVsaXZlcnkgcGVyIGdyb3VwOyBkaXN0aW5jdCBncm91cHMgZmFuIG91dC4gKi9cbiAgZ3JvdXA6IHN0cmluZztcbiAgLyoqIFRoaXMgY29uc3VtZXIncyBpZCB3aXRoaW4gdGhlIGdyb3VwIChlLmcuIGAke3NlcnZpY2V9LSR7cGlkfWApLiAqL1xuICBjb25zdW1lcjogc3RyaW5nO1xuICAvKiogQXN5bmMgaGFuZGxlci4gUmVzb2x2ZXMg4oeSIHRoZSBtZXNzYWdlIGlzIGFja2VkOyB0aHJvd3Mg4oeSIGxlZnQgcGVuZGluZyBmb3IgcmVkZWxpdmVyeS4gKi9cbiAgaGFuZGxlcjogKGVudjogRXZlbnRFbnZlbG9wZTxUPikgPT4gUHJvbWlzZTx2b2lkPjtcbiAgLyoqIE1heCBtZXNzYWdlcyBwZXIgcmVhZCAoZGVmYXVsdCAxNikuICovXG4gIGJhdGNoU2l6ZT86IG51bWJlcjtcbiAgLyoqIFhSRUFER1JPVVAgYmxvY2sgdGltZSBpbiBtcyAoZGVmYXVsdCA1MDAwKS4gKi9cbiAgYmxvY2tNcz86IG51bWJlcjtcbiAgLyoqIEEgcGVuZGluZyBtZXNzYWdlIGlkbGUgdGhpcyBsb25nIChtcykgaXMgcmVjbGFpbWVkIGZyb20gYSBkZWFkIGNvbnN1bWVyIChkZWZhdWx0IDYwMDAwKS4gKi9cbiAgbWluSWRsZU1zPzogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEV2ZW50U3Vic2NyaXB0aW9uIHtcbiAgLyoqIFN0b3AgdGhlIGNvbnN1bWVyIGxvb3AuIFJlc29sdmVzIG9uY2UgdGhlIGluLWZsaWdodCByZWFkIHJldHVybnMuICovXG4gIHN0b3AoKTogUHJvbWlzZTx2b2lkPjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBEdXJhYmxlRXZlbnRCdXMge1xuICAvKiogUHVibGlzaCBhIHBheWxvYWQgdG8gYSB0b3BpYy4gUmV0dXJucyB0aGUgc3RyZWFtIGlkLCBvciBudWxsIG9uIGZhaWx1cmUgKGRyb3BwZWQsIG5ldmVyIHRocm93cykuICovXG4gIHB1Ymxpc2g8VD4odG9waWM6IHN0cmluZywgcGF5bG9hZDogVCk6IFByb21pc2U8c3RyaW5nIHwgbnVsbD47XG4gIC8qKiBTdGFydCBhIGNvbnN1bWVyLWdyb3VwIHJlYWRlciBmb3IgYSB0b3BpYy4gRmlyZS1hbmQtZm9yZ2V0IGxvb3A7IGNhbGwgYHN0b3AoKWAgdG8gZW5kIGl0LiAqL1xuICBzdWJzY3JpYmU8VD4ob3B0czogU3Vic2NyaWJlT3B0aW9uczxUPik6IEV2ZW50U3Vic2NyaXB0aW9uO1xufVxuXG4vKipcbiAqIFRoZSBzdWJzZXQgb2YgaW9yZWRpcycgU3RyZWFtIEFQSSB0aGUgYnVzIHVzZXMuIEtlcHQgbWluaW1hbCBzbyBhIHRlc3QgY2FuXG4gKiBzdXBwbHkgYW4gaW4tbWVtb3J5IGZha2UgYW5kIHNvIG1lcmVseSBpbXBvcnRpbmcgdGhpcyBuZXZlciBoYXJkLWRlcGVuZHMgb25cbiAqIGlvcmVkaXMgKGNvbnN0cnVjdGVkIHZpYSB0aGUgc2hhcmVkIGVudiBoZWxwZXIpLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlZGlzU3RyZWFtQ2xpZW50IHtcbiAgeGFkZChrZXk6IHN0cmluZywgLi4uYXJnczogKHN0cmluZyB8IG51bWJlcilbXSk6IFByb21pc2U8c3RyaW5nIHwgbnVsbD47XG4gIHhncm91cCguLi5hcmdzOiAoc3RyaW5nIHwgbnVtYmVyKVtdKTogUHJvbWlzZTx1bmtub3duPjtcbiAgeHJlYWRncm91cCguLi5hcmdzOiAoc3RyaW5nIHwgbnVtYmVyKVtdKTogUHJvbWlzZTx1bmtub3duPjtcbiAgeGFjayhrZXk6IHN0cmluZywgZ3JvdXA6IHN0cmluZywgLi4uaWRzOiBzdHJpbmdbXSk6IFByb21pc2U8bnVtYmVyPjtcbiAgeGF1dG9jbGFpbSguLi5hcmdzOiAoc3RyaW5nIHwgbnVtYmVyKVtdKTogUHJvbWlzZTx1bmtub3duPjtcbn1cblxuY29uc3QgREVGQVVMVF9NQVhMRU4gPSAxMF8wMDA7XG5jb25zdCBERUZBVUxUX0JBVENIID0gMTY7XG5jb25zdCBERUZBVUxUX0JMT0NLX01TID0gNV8wMDA7XG5jb25zdCBERUZBVUxUX01JTl9JRExFX01TID0gNjBfMDAwO1xuXG5mdW5jdGlvbiBlcnJNc2coZXJyOiB1bmtub3duKTogc3RyaW5nIHtcbiAgcmV0dXJuIGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKTtcbn1cblxuZnVuY3Rpb24gc3RyZWFtS2V5KHRvcGljOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gYGV2dDoke3RvcGljfWA7XG59XG5cbi8qKiBUcnVlIHdoZW4gdGhlIGVycm9yIGlzIFJlZGlzJyBcImdyb3VwIGFscmVhZHkgZXhpc3RzXCIgKGlkZW1wb3RlbnQgY3JlYXRlKS4gKi9cbmZ1bmN0aW9uIGlzQnVzeUdyb3VwKGVycjogdW5rbm93bik6IGJvb2xlYW4ge1xuICByZXR1cm4gZXJyTXNnKGVycikuaW5jbHVkZXMoJ0JVU1lHUk9VUCcpO1xufVxuXG4vKipcbiAqIFBhcnNlIHRoZSBuZXN0ZWQgYXJyYXkgc2hhcGUgWFJFQURHUk9VUCAvIFhBVVRPQ0xBSU0gcmV0dXJuIGludG8gZmxhdCBlbnZlbG9wZXMuXG4gKiBYUkVBREdST1VQOiBgW1tzdHJlYW1LZXksIFtbaWQsIFtmLCB2LCAuLi5dXSwgLi4uXV1dYC5cbiAqIFhBVVRPQ0xBSU06IGBbbmV4dEN1cnNvciwgW1tpZCwgW2YsIHYsIC4uLl1dLCAuLi5dLCBkZWxldGVkSWRzXWAuXG4gKiBXZSBzdG9yZSB0aGUgSlNPTiBwYXlsb2FkIHVuZGVyIGEgc2luZ2xlIGZpZWxkIGBkYC5cbiAqL1xuZnVuY3Rpb24gcGFyc2VFbnRyaWVzPFQ+KHRvcGljOiBzdHJpbmcsIGVudHJpZXM6IHVua25vd24pOiBFdmVudEVudmVsb3BlPFQ+W10ge1xuICBjb25zdCBvdXQ6IEV2ZW50RW52ZWxvcGU8VD5bXSA9IFtdO1xuICBpZiAoIUFycmF5LmlzQXJyYXkoZW50cmllcykpIHJldHVybiBvdXQ7XG4gIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgIGlmICghQXJyYXkuaXNBcnJheShlbnRyeSkgfHwgZW50cnkubGVuZ3RoIDwgMikgY29udGludWU7XG4gICAgY29uc3QgaWQgPSBTdHJpbmcoZW50cnlbMF0pO1xuICAgIGNvbnN0IGZpZWxkcyA9IGVudHJ5WzFdO1xuICAgIGlmICghQXJyYXkuaXNBcnJheShmaWVsZHMpKSBjb250aW51ZTtcbiAgICAvLyBmaWVsZHMgaXMgW25hbWUsIHZhbHVlLCBuYW1lLCB2YWx1ZSwgLi4uXTsgZmluZCAnZCcuXG4gICAgbGV0IHJhdzogc3RyaW5nIHwgdW5kZWZpbmVkO1xuICAgIGZvciAobGV0IGkgPSAwOyBpICsgMSA8IGZpZWxkcy5sZW5ndGg7IGkgKz0gMikge1xuICAgICAgaWYgKFN0cmluZyhmaWVsZHNbaV0pID09PSAnZCcpIHsgcmF3ID0gU3RyaW5nKGZpZWxkc1tpICsgMV0pOyBicmVhazsgfVxuICAgIH1cbiAgICAvLyBBIG1pc3NpbmcgYGRgIGZpZWxkIChhbiBlbnRyeSBub3Qgd3JpdHRlbiBieSBwdWJsaXNoKCkpIE9SIGEgY29ycnVwdCBKU09OXG4gICAgLy8gcGF5bG9hZCBib3RoIHlpZWxkIGFuIHVuZGVmaW5lZC1wYXlsb2FkIGVudmVsb3BlIHNvIGBkZWxpdmVyKClgIEFDS3MgaXQg4oCUXG4gICAgLy8gYSBwb2lzb24gbWVzc2FnZSBtdXN0IG5ldmVyIHdlZGdlIHRoZSBncm91cCBieSBzdGF5aW5nIHBlcnBldHVhbGx5IHBlbmRpbmcuXG4gICAgaWYgKHJhdyA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICBvdXQucHVzaCh7IGlkLCB0b3BpYywgcGF5bG9hZDogdW5kZWZpbmVkIGFzIHVua25vd24gYXMgVCB9KTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICB0cnkge1xuICAgICAgb3V0LnB1c2goeyBpZCwgdG9waWMsIHBheWxvYWQ6IEpTT04ucGFyc2UocmF3KSBhcyBUIH0pO1xuICAgIH0gY2F0Y2gge1xuICAgICAgb3V0LnB1c2goeyBpZCwgdG9waWMsIHBheWxvYWQ6IHVuZGVmaW5lZCBhcyB1bmtub3duIGFzIFQgfSk7XG4gICAgfVxuICB9XG4gIHJldHVybiBvdXQ7XG59XG5cbi8qKiBCdWlsZCBhIGR1cmFibGUgZXZlbnQgYnVzIG92ZXIgYSBSZWRpcyBTdHJlYW1zIGNsaWVudC4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVSZWRpc0R1cmFibGVFdmVudEJ1cyhcbiAgcmVkaXM6IFJlZGlzU3RyZWFtQ2xpZW50LFxuICBvcHRzOiB7IG1heExlbj86IG51bWJlciB9ID0ge30sXG4pOiBEdXJhYmxlRXZlbnRCdXMge1xuICBjb25zdCBtYXhMZW4gPSBNYXRoLm1heCgxLCBvcHRzLm1heExlbiA/PyBERUZBVUxUX01BWExFTik7XG5cbiAgcmV0dXJuIHtcbiAgICBhc3luYyBwdWJsaXNoPFQ+KHRvcGljOiBzdHJpbmcsIHBheWxvYWQ6IFQpOiBQcm9taXNlPHN0cmluZyB8IG51bGw+IHtcbiAgICAgIGNvbnN0IGtleSA9IHN0cmVhbUtleSh0b3BpYyk7XG4gICAgICB0cnkge1xuICAgICAgICAvLyBNQVhMRU4gfiA8bj4gYm91bmRzIHRoZSBzdHJlYW0gYXBwcm94aW1hdGVseSAoY2hlYXApOyBgKmAgPSBzZXJ2ZXIgaWQuXG4gICAgICAgIGNvbnN0IGlkID0gYXdhaXQgcmVkaXMueGFkZChrZXksICdNQVhMRU4nLCAnficsIG1heExlbiwgJyonLCAnZCcsIEpTT04uc3RyaW5naWZ5KHBheWxvYWQpKTtcbiAgICAgICAgZW1pdENvdW50ZXIoJ2V2ZW50X2J1c19wdWJsaXNoZWRfdG90YWwnLCB7IHRvcGljIH0pO1xuICAgICAgICByZXR1cm4gaWQ7XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgZW1pdENvdW50ZXIoJ2V2ZW50X2J1c19wdWJsaXNoX2ZhaWxlZF90b3RhbCcsIHsgdG9waWMgfSk7XG4gICAgICAgIGxvZ2dlci53YXJuKCdFdmVudCBidXMgcHVibGlzaCBmYWlsZWQgKGV2ZW50IGRyb3BwZWQpJywgeyB0b3BpYywgZXJyb3I6IGVyck1zZyhlcnIpIH0pO1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICB9LFxuXG4gICAgc3Vic2NyaWJlPFQ+KHN1Yk9wdHM6IFN1YnNjcmliZU9wdGlvbnM8VD4pOiBFdmVudFN1YnNjcmlwdGlvbiB7XG4gICAgICBjb25zdCB7IHRvcGljLCBncm91cCwgY29uc3VtZXIsIGhhbmRsZXIgfSA9IHN1Yk9wdHM7XG4gICAgICBjb25zdCBrZXkgPSBzdHJlYW1LZXkodG9waWMpO1xuICAgICAgY29uc3QgYmF0Y2ggPSBNYXRoLm1heCgxLCBzdWJPcHRzLmJhdGNoU2l6ZSA/PyBERUZBVUxUX0JBVENIKTtcbiAgICAgIGNvbnN0IGJsb2NrTXMgPSBNYXRoLm1heCgwLCBzdWJPcHRzLmJsb2NrTXMgPz8gREVGQVVMVF9CTE9DS19NUyk7XG4gICAgICBjb25zdCBtaW5JZGxlID0gTWF0aC5tYXgoMCwgc3ViT3B0cy5taW5JZGxlTXMgPz8gREVGQVVMVF9NSU5fSURMRV9NUyk7XG5cbiAgICAgIGxldCBzdG9wcGVkID0gZmFsc2U7XG4gICAgICBsZXQgc3RhcnRlZCA9IGZhbHNlO1xuXG4gICAgICBjb25zdCBkZWxpdmVyID0gYXN5bmMgKGVudnM6IEV2ZW50RW52ZWxvcGU8VD5bXSk6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgICAgICBmb3IgKGNvbnN0IGVudiBvZiBlbnZzKSB7XG4gICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGlmIChlbnYucGF5bG9hZCAhPT0gdW5kZWZpbmVkKSBhd2FpdCBoYW5kbGVyKGVudik7XG4gICAgICAgICAgICAvLyBBY2sgb24gc3VjY2VzcyBPUiBvbiBhbiB1bmRlZmluZWQgKGNvcnJ1cHQpIHBheWxvYWQg4oCUIGEgcG9pc29uXG4gICAgICAgICAgICAvLyBtZXNzYWdlIG11c3Qgbm90IGJsb2NrIHRoZSBncm91cCBmb3JldmVyLlxuICAgICAgICAgICAgYXdhaXQgcmVkaXMueGFjayhrZXksIGdyb3VwLCBlbnYuaWQpO1xuICAgICAgICAgICAgZW1pdENvdW50ZXIoJ2V2ZW50X2J1c19kZWxpdmVyZWRfdG90YWwnLCB7IHRvcGljLCBncm91cCB9KTtcbiAgICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgIC8vIExlYXZlIGl0IHBlbmRpbmcgKG5vIFhBQ0spIOKAlCBYQVVUT0NMQUlNIHJlZGVsaXZlcnMgaXQgYWZ0ZXIgbWluSWRsZS5cbiAgICAgICAgICAgIGVtaXRDb3VudGVyKCdldmVudF9idXNfaGFuZGxlcl9mYWlsZWRfdG90YWwnLCB7IHRvcGljLCBncm91cCB9KTtcbiAgICAgICAgICAgIGxvZ2dlci53YXJuKCdFdmVudCBoYW5kbGVyIGZhaWxlZDsgbGVhdmluZyBtZXNzYWdlIHBlbmRpbmcgZm9yIHJlZGVsaXZlcnknLCB7XG4gICAgICAgICAgICAgIHRvcGljLCBncm91cCwgaWQ6IGVudi5pZCwgZXJyb3I6IGVyck1zZyhlcnIpLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9O1xuXG4gICAgICBjb25zdCBlbnN1cmVHcm91cCA9IGFzeW5jICgpOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAvLyBNS1NUUkVBTSBzbyB0aGUgc3RyZWFtIG5lZWQgbm90IGV4aXN0IHlldDsgJyQnID0gb25seSBuZXcgbWVzc2FnZXMuXG4gICAgICAgICAgYXdhaXQgcmVkaXMueGdyb3VwKCdDUkVBVEUnLCBrZXksIGdyb3VwLCAnJCcsICdNS1NUUkVBTScpO1xuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICBpZiAoIWlzQnVzeUdyb3VwKGVycikpIHtcbiAgICAgICAgICAgIGxvZ2dlci53YXJuKCdFdmVudCBidXMgZ3JvdXAgY3JlYXRlIGZhaWxlZCcsIHsgdG9waWMsIGdyb3VwLCBlcnJvcjogZXJyTXNnKGVycikgfSk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9O1xuXG4gICAgICBjb25zdCBsb29wID0gYXN5bmMgKCk6IFByb21pc2U8dm9pZD4gPT4ge1xuICAgICAgICBhd2FpdCBlbnN1cmVHcm91cCgpO1xuICAgICAgICB3aGlsZSAoIXN0b3BwZWQpIHtcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgLy8gMSkgUmVjbGFpbSBtZXNzYWdlcyBzdHJhbmRlZCBieSBhIGNyYXNoZWQgY29uc3VtZXIgaW4gdGhpcyBncm91cC5cbiAgICAgICAgICAgIGNvbnN0IGNsYWltZWQgPSBhd2FpdCByZWRpcy54YXV0b2NsYWltKGtleSwgZ3JvdXAsIGNvbnN1bWVyLCBtaW5JZGxlLCAnMCcsICdDT1VOVCcsIGJhdGNoKTtcbiAgICAgICAgICAgIC8vIFhBVVRPQ0xBSU0gcmVwbHk6IFtuZXh0Q3Vyc29yLCBlbnRyaWVzLCBkZWxldGVkSWRzXVxuICAgICAgICAgICAgaWYgKEFycmF5LmlzQXJyYXkoY2xhaW1lZCkgJiYgY2xhaW1lZC5sZW5ndGggPj0gMikge1xuICAgICAgICAgICAgICBhd2FpdCBkZWxpdmVyKHBhcnNlRW50cmllczxUPih0b3BpYywgY2xhaW1lZFsxXSkpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKHN0b3BwZWQpIGJyZWFrO1xuXG4gICAgICAgICAgICAvLyAyKSBSZWFkIG5ldywgbmV2ZXItZGVsaXZlcmVkIG1lc3NhZ2VzIGZvciB0aGlzIGdyb3VwICgnPicpLlxuICAgICAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgcmVkaXMueHJlYWRncm91cChcbiAgICAgICAgICAgICAgJ0dST1VQJywgZ3JvdXAsIGNvbnN1bWVyLCAnQ09VTlQnLCBiYXRjaCwgJ0JMT0NLJywgYmxvY2tNcywgJ1NUUkVBTVMnLCBrZXksICc+JyxcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICAvLyBYUkVBREdST1VQIHJlcGx5OiBbW3N0cmVhbUtleSwgZW50cmllc11dIHwgbnVsbCAob24gYmxvY2sgdGltZW91dClcbiAgICAgICAgICAgIGlmIChBcnJheS5pc0FycmF5KHJlcykgJiYgcmVzLmxlbmd0aCA+IDAgJiYgQXJyYXkuaXNBcnJheShyZXNbMF0pICYmIHJlc1swXS5sZW5ndGggPj0gMikge1xuICAgICAgICAgICAgICBhd2FpdCBkZWxpdmVyKHBhcnNlRW50cmllczxUPih0b3BpYywgcmVzWzBdWzFdKSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICBpZiAoc3RvcHBlZCkgYnJlYWs7XG4gICAgICAgICAgICBsb2dnZXIud2FybignRXZlbnQgYnVzIGNvbnN1bWVyIGxvb3AgZXJyb3I7IGJhY2tpbmcgb2ZmJywgeyB0b3BpYywgZ3JvdXAsIGVycm9yOiBlcnJNc2coZXJyKSB9KTtcbiAgICAgICAgICAgIC8vIEJhY2sgb2ZmIHNvIGEgcGVyc2lzdGVudCBSZWRpcyBlcnJvciBkb2Vzbid0IGhvdC1zcGluLlxuICAgICAgICAgICAgYXdhaXQgbmV3IFByb21pc2UoKHIpID0+IHNldFRpbWVvdXQociwgMTAwMCkpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfTtcblxuICAgICAgbGV0IGxvb3BQcm9taXNlID0gUHJvbWlzZS5yZXNvbHZlKCk7XG4gICAgICBpZiAoIXN0YXJ0ZWQpIHtcbiAgICAgICAgc3RhcnRlZCA9IHRydWU7XG4gICAgICAgIGxvb3BQcm9taXNlID0gbG9vcCgpO1xuICAgICAgICBsb2dnZXIuaW5mbygnRXZlbnQgYnVzIGNvbnN1bWVyIHN0YXJ0ZWQnLCB7IHRvcGljLCBncm91cCwgY29uc3VtZXIgfSk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGFzeW5jIHN0b3AoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgICAgc3RvcHBlZCA9IHRydWU7XG4gICAgICAgICAgLy8gQXdhaXQgdGhlIGluLWZsaWdodCByZWFkL2hhbmRsZXIgc28gYSBjYWxsZXIgKGUuZy4gYSBzZXJ2aWNlIHNodXR0aW5nXG4gICAgICAgICAgLy8gZG93biBpdHMgREIpIGRvZXNuJ3QgcmFjZSBhIG1lc3NhZ2Ugc3RpbGwgYmVpbmcgcHJvY2Vzc2VkLiBCb3VuZGVkIGJ5XG4gICAgICAgICAgLy8gdGhlIFhSRUFER1JPVVAgQkxPQ0sgd2luZG93LiBOZXZlciByZWplY3RzIOKAlCB0aGUgbG9vcCBzd2FsbG93cyBlcnJvcnMuXG4gICAgICAgICAgYXdhaXQgbG9vcFByb21pc2UuY2F0Y2goKCkgPT4gdW5kZWZpbmVkKTtcbiAgICAgICAgfSxcbiAgICAgIH07XG4gICAgfSxcbiAgfTtcbn1cblxuLyoqXG4gKiBCdWlsZCBhIGR1cmFibGUgZXZlbnQgYnVzIGZyb20gdGhlIGFtYmllbnQgUmVkaXMgZW52IHZpYSB0aGUgc2hhcmVkXG4gKiBgY3JlYXRlRW52UmVkaXNDbGllbnRgLiBSZXR1cm5zIGBudWxsYCB3aGVuIFJlZGlzIGlzIG5vdCBjb25maWd1cmVkIOKAlCB0aGVcbiAqIGNhbGxlciB0aGVuIHJ1bnMgd2l0aG91dCBhIGJ1cyAodG9kYXkncyBIVFRQL3JlY29uY2lsZXIgYmVoYXZpb3IpLCBuZXZlclxuICogY3Jhc2hpbmcuIE9wdCBpbiB3aXRoIGEgc2luZ2xlIGJvb3QgbGluZSB3aGVyZSBhIHNlcnZpY2Ugd2FudHMgZHVyYWJsZSBldmVudHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVFbnZSZWRpc0R1cmFibGVFdmVudEJ1cyhvcHRzOiB7IG1heExlbj86IG51bWJlciB9ID0ge30pOiBEdXJhYmxlRXZlbnRCdXMgfCBudWxsIHtcbiAgY29uc3QgaW5zdCA9IGNyZWF0ZUVudlJlZGlzQ2xpZW50PFJlZGlzU3RyZWFtQ2xpZW50PignZXZlbnQtYnVzJyk7XG4gIGlmICghaW5zdCkgcmV0dXJuIG51bGw7XG4gIGxvZ2dlci5pbmZvKCdSZWRpcyBkdXJhYmxlIGV2ZW50IGJ1cyBpbml0aWFsaXplZCcpO1xuICByZXR1cm4gY3JlYXRlUmVkaXNEdXJhYmxlRXZlbnRCdXMoaW5zdCwgb3B0cyk7XG59XG5cbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gRW50aXR5LWV2ZW50IGJyaWRnZSAoYWRkaXRpdmUsIG9wdC1pbilcbi8vXG4vLyBMZXRzIGEgc2VydmljZSBnaXZlIGl0cyBpbi1wcm9jZXNzIHtAbGluayBlbnRpdHlFdmVudHN9IGVtaXR0ZXIgYSBEVVJBQkxFLFxuLy8gY3Jvc3MtcG9kIGRlbGl2ZXJ5IGxlZyBXSVRIT1VUIGNoYW5naW5nIGV4aXN0aW5nIGJlaGF2aW9yOiByZWdpc3RlciB0aGVcbi8vIHJldHVybmVkIHN1YnNjcmliZXIgYW5kIGV2ZXJ5IGVudGl0eSBtdXRhdGlvbiBpcyBBTFNPIHB1Ymxpc2hlZCB0byB0aGUgYnVzLlxuLy8gQSBjb25zdW1lciBlbHNld2hlcmUgKGBidXMuc3Vic2NyaWJlYCkgdGhlbiByZWFjdHMgYXQtbGVhc3Qtb25jZSBhY3Jvc3MgcG9kc1xuLy8gYW5kIHJlc3RhcnRzLiBUaGUgaW4tcHJvY2VzcyBzdWJzY3JpYmVycyBrZWVwIGZpcmluZyBleGFjdGx5IGFzIGJlZm9yZSwgc29cbi8vIHRoaXMgaXMgcHVyZWx5IGFkZGl0aXZlIOKAlCBubyBkb3VibGUtcHJvY2Vzc2luZyB1bmxlc3MgYSBzZXJ2aWNlIGludGVudGlvbmFsbHlcbi8vIHJ1bnMgYSBidXMgY29uc3VtZXIgZm9yIHRoZSBzYW1lIHJlYWN0aW9uLiBLZXB0IGhlcmUgKG5vdCBpbiBlbnRpdHktZXZlbnRzLnRzKVxuLy8gc28gdGhhdCBtb2R1bGUgc3RheXMgaW5mcmFzdHJ1Y3R1cmUtZnJlZSBwZXIgaXRzIG93biBjb250cmFjdC5cbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG4vKiogVG9waWMgdGhlIGVudGl0eS1ldmVudCBicmlkZ2UgcHVibGlzaGVzIHRvLiAqL1xuZXhwb3J0IGNvbnN0IEVOVElUWV9FVkVOVF9UT1BJQyA9ICdlbnRpdHktZXZlbnRzJztcblxuLyoqXG4gKiBBbiB7QGxpbmsgRW50aXR5RXZlbnRTdWJzY3JpYmVyfSB0aGF0IGZvcndhcmRzIGVhY2ggZW50aXR5IGV2ZW50IHRvIHRoZSBkdXJhYmxlXG4gKiBidXMuIGBidXMucHVibGlzaGAgaXMgZmFpbC1zYWZlIChkcm9wcy13aXRoLW1ldHJpYywgbmV2ZXIgdGhyb3dzKSwgbWF0Y2hpbmcgdGhlXG4gKiBlbWl0dGVyJ3MgZmlyZS1hbmQtZm9yZ2V0IGNvbnRyYWN0LiBOb3RlIGBFbnRpdHlFdmVudC50aW1lc3RhbXBgIChhIERhdGUpXG4gKiBzZXJpYWxpemVzIHRvIGFuIElTTyBzdHJpbmcgb24gdGhlIHdpcmUg4oCUIGEgY29uc3VtZXIgcmUtaHlkcmF0ZXMgYXMgbmVlZGVkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlRW50aXR5RXZlbnRCdXNQdWJsaXNoZXIoYnVzOiBEdXJhYmxlRXZlbnRCdXMpOiBFbnRpdHlFdmVudFN1YnNjcmliZXIge1xuICByZXR1cm4ge1xuICAgIGFzeW5jIG9uRW50aXR5RXZlbnQoZXZlbnQ6IEVudGl0eUV2ZW50KTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICBhd2FpdCBidXMucHVibGlzaChFTlRJVFlfRVZFTlRfVE9QSUMsIGV2ZW50KTtcbiAgICB9LFxuICB9O1xufVxuIl19
@@ -0,0 +1,48 @@
1
+ export type EntityEventType = 'created' | 'updated' | 'deleted';
2
+ export type EntityTarget = 'plugin' | 'pipeline' | 'message' | 'organization' | 'user';
3
+ export interface EntityEvent {
4
+ /** The type of mutation that occurred */
5
+ eventType: EntityEventType;
6
+ /** The entity type that was mutated */
7
+ target: EntityTarget;
8
+ /** The entity's unique ID */
9
+ entityId: string;
10
+ /** The organization that owns this entity */
11
+ orgId: string;
12
+ /** Org → team hierarchy: the owning org's parent (present only when `orgId` is
13
+ * a team). Lets compliance evaluate parent `propagateToChildren` rules on the
14
+ * async event path, matching live validation. */
15
+ parentOrgId?: string;
16
+ /** The user who performed the mutation */
17
+ userId: string;
18
+ /** When the event occurred */
19
+ timestamp: Date;
20
+ /** Snapshot of entity attributes (for compliance evaluation) */
21
+ attributes: Record<string, unknown>;
22
+ }
23
+ export interface EntityEventSubscriber {
24
+ onEntityEvent(event: EntityEvent): Promise<void>;
25
+ }
26
+ declare class EntityEventEmitter {
27
+ private subscribers;
28
+ /**
29
+ * Register a subscriber to receive entity events.
30
+ */
31
+ subscribe(subscriber: EntityEventSubscriber): void;
32
+ /**
33
+ * Remove a subscriber.
34
+ */
35
+ unsubscribe(subscriber: EntityEventSubscriber): void;
36
+ /**
37
+ * Emit an entity event to all subscribers.
38
+ * Fire-and-forget: errors are caught and logged, never thrown.
39
+ */
40
+ emit(event: EntityEvent): void;
41
+ /**
42
+ * Get the current subscriber count (for testing/diagnostics).
43
+ */
44
+ get subscriberCount(): number;
45
+ }
46
+ /** Singleton entity event emitter — shared across the process */
47
+ export declare const entityEvents: EntityEventEmitter;
48
+ export {};
@@ -0,0 +1,79 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Lightweight in-process event emitter for entity lifecycle events.
5
+ *
6
+ * Services emit events after mutations (create/update/delete) via CrudService hooks.
7
+ * Subscribers react asynchronously to entity changes.
8
+ *
9
+ * Active subscribers:
10
+ * - **Compliance event subscriber** (`registerComplianceEventSubscriber()`) — forwards
11
+ * entity events to the compliance service for post-mutation rule evaluation.
12
+ * Registered at startup in plugin and pipeline service index.ts.
13
+ *
14
+ * Design:
15
+ * - Fire-and-forget: emit() never throws, subscriber errors are logged
16
+ * - In-process only: no network, no Redis, no infrastructure
17
+ * - Subscribers run async: never block the original request
18
+ *
19
+ * Durability contract (deliberately BEST-EFFORT):
20
+ * - The post-mutation compliance evaluation this feeds is NOT the enforcement
21
+ * gate — LIVE validation on the mutation path already enforces compliance
22
+ * before the write commits. This async fan-out is a convenience/reporting
23
+ * signal, so a lost delivery (subscriber throw, process crash mid-callback)
24
+ * downgrades timeliness, not correctness. Backing it with the durable Redis
25
+ * spool would pull network/infra into a module whose whole point is to have
26
+ * none, for no enforcement benefit — so instead a dropped delivery is made
27
+ * OBSERVABLE via the `entity_event_subscriber_failed_total` counter (alert on
28
+ * a sustained rate) rather than silently swallowed at debug level.
29
+ */
30
+ import { createLogger } from '../utils/logger.js';
31
+ import { emitCounter } from '../utils/metric-emitter.js';
32
+ const logger = createLogger('entity-events');
33
+ class EntityEventEmitter {
34
+ subscribers = [];
35
+ /**
36
+ * Register a subscriber to receive entity events.
37
+ */
38
+ subscribe(subscriber) {
39
+ this.subscribers.push(subscriber);
40
+ }
41
+ /**
42
+ * Remove a subscriber.
43
+ */
44
+ unsubscribe(subscriber) {
45
+ this.subscribers = this.subscribers.filter((s) => s !== subscriber);
46
+ }
47
+ /**
48
+ * Emit an entity event to all subscribers.
49
+ * Fire-and-forget: errors are caught and logged, never thrown.
50
+ */
51
+ emit(event) {
52
+ for (const subscriber of this.subscribers) {
53
+ subscriber.onEntityEvent(event).catch((err) => {
54
+ // Make the drop observable — a lost async compliance eval is best-effort
55
+ // (live validation already enforced), but a sustained failure rate is an
56
+ // operational signal, not something to bury at debug level.
57
+ emitCounter('entity_event_subscriber_failed_total', {
58
+ target: event.target,
59
+ eventType: event.eventType,
60
+ });
61
+ logger.debug('Entity event subscriber failed', {
62
+ target: event.target,
63
+ eventType: event.eventType,
64
+ entityId: event.entityId,
65
+ error: err instanceof Error ? err.message : String(err),
66
+ });
67
+ });
68
+ }
69
+ }
70
+ /**
71
+ * Get the current subscriber count (for testing/diagnostics).
72
+ */
73
+ get subscriberCount() {
74
+ return this.subscribers.length;
75
+ }
76
+ }
77
+ /** Singleton entity event emitter — shared across the process */
78
+ export const entityEvents = new EntityEventEmitter();
79
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50aXR5LWV2ZW50cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2aWNlcy9lbnRpdHktZXZlbnRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBMEJHO0FBRUgsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUV6RCxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsZUFBZSxDQUFDLENBQUM7QUE4QjdDLE1BQU0sa0JBQWtCO0lBQ2QsV0FBVyxHQUE0QixFQUFFLENBQUM7SUFFbEQ7O09BRUc7SUFDSCxTQUFTLENBQUMsVUFBaUM7UUFDekMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDcEMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsV0FBVyxDQUFDLFVBQWlDO1FBQzNDLElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxVQUFVLENBQUMsQ0FBQztJQUN0RSxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsSUFBSSxDQUFDLEtBQWtCO1FBQ3JCLEtBQUssTUFBTSxVQUFVLElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQzFDLFVBQVUsQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUU7Z0JBQzVDLHlFQUF5RTtnQkFDekUseUVBQXlFO2dCQUN6RSw0REFBNEQ7Z0JBQzVELFdBQVcsQ0FBQyxzQ0FBc0MsRUFBRTtvQkFDbEQsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNO29CQUNwQixTQUFTLEVBQUUsS0FBSyxDQUFDLFNBQVM7aUJBQzNCLENBQUMsQ0FBQztnQkFDSCxNQUFNLENBQUMsS0FBSyxDQUFDLGdDQUFnQyxFQUFFO29CQUM3QyxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07b0JBQ3BCLFNBQVMsRUFBRSxLQUFLLENBQUMsU0FBUztvQkFDMUIsUUFBUSxFQUFFLEtBQUssQ0FBQyxRQUFRO29CQUN4QixLQUFLLEVBQUUsR0FBRyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQztpQkFDeEQsQ0FBQyxDQUFDO1lBQ0wsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0gsSUFBSSxlQUFlO1FBQ2pCLE9BQU8sSUFBSSxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUM7SUFDakMsQ0FBQztDQUNGO0FBRUQsaUVBQWlFO0FBQ2pFLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxJQUFJLGtCQUFrQixFQUFFLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBMaWdodHdlaWdodCBpbi1wcm9jZXNzIGV2ZW50IGVtaXR0ZXIgZm9yIGVudGl0eSBsaWZlY3ljbGUgZXZlbnRzLlxuICpcbiAqIFNlcnZpY2VzIGVtaXQgZXZlbnRzIGFmdGVyIG11dGF0aW9ucyAoY3JlYXRlL3VwZGF0ZS9kZWxldGUpIHZpYSBDcnVkU2VydmljZSBob29rcy5cbiAqIFN1YnNjcmliZXJzIHJlYWN0IGFzeW5jaHJvbm91c2x5IHRvIGVudGl0eSBjaGFuZ2VzLlxuICpcbiAqIEFjdGl2ZSBzdWJzY3JpYmVyczpcbiAqIC0gKipDb21wbGlhbmNlIGV2ZW50IHN1YnNjcmliZXIqKiAoYHJlZ2lzdGVyQ29tcGxpYW5jZUV2ZW50U3Vic2NyaWJlcigpYCkg4oCUIGZvcndhcmRzXG4gKiAgIGVudGl0eSBldmVudHMgdG8gdGhlIGNvbXBsaWFuY2Ugc2VydmljZSBmb3IgcG9zdC1tdXRhdGlvbiBydWxlIGV2YWx1YXRpb24uXG4gKiAgIFJlZ2lzdGVyZWQgYXQgc3RhcnR1cCBpbiBwbHVnaW4gYW5kIHBpcGVsaW5lIHNlcnZpY2UgaW5kZXgudHMuXG4gKlxuICogRGVzaWduOlxuICogLSBGaXJlLWFuZC1mb3JnZXQ6IGVtaXQoKSBuZXZlciB0aHJvd3MsIHN1YnNjcmliZXIgZXJyb3JzIGFyZSBsb2dnZWRcbiAqIC0gSW4tcHJvY2VzcyBvbmx5OiBubyBuZXR3b3JrLCBubyBSZWRpcywgbm8gaW5mcmFzdHJ1Y3R1cmVcbiAqIC0gU3Vic2NyaWJlcnMgcnVuIGFzeW5jOiBuZXZlciBibG9jayB0aGUgb3JpZ2luYWwgcmVxdWVzdFxuICpcbiAqIER1cmFiaWxpdHkgY29udHJhY3QgKGRlbGliZXJhdGVseSBCRVNULUVGRk9SVCk6XG4gKiAtIFRoZSBwb3N0LW11dGF0aW9uIGNvbXBsaWFuY2UgZXZhbHVhdGlvbiB0aGlzIGZlZWRzIGlzIE5PVCB0aGUgZW5mb3JjZW1lbnRcbiAqICAgZ2F0ZSDigJQgTElWRSB2YWxpZGF0aW9uIG9uIHRoZSBtdXRhdGlvbiBwYXRoIGFscmVhZHkgZW5mb3JjZXMgY29tcGxpYW5jZVxuICogICBiZWZvcmUgdGhlIHdyaXRlIGNvbW1pdHMuIFRoaXMgYXN5bmMgZmFuLW91dCBpcyBhIGNvbnZlbmllbmNlL3JlcG9ydGluZ1xuICogICBzaWduYWwsIHNvIGEgbG9zdCBkZWxpdmVyeSAoc3Vic2NyaWJlciB0aHJvdywgcHJvY2VzcyBjcmFzaCBtaWQtY2FsbGJhY2spXG4gKiAgIGRvd25ncmFkZXMgdGltZWxpbmVzcywgbm90IGNvcnJlY3RuZXNzLiBCYWNraW5nIGl0IHdpdGggdGhlIGR1cmFibGUgUmVkaXNcbiAqICAgc3Bvb2wgd291bGQgcHVsbCBuZXR3b3JrL2luZnJhIGludG8gYSBtb2R1bGUgd2hvc2Ugd2hvbGUgcG9pbnQgaXMgdG8gaGF2ZVxuICogICBub25lLCBmb3Igbm8gZW5mb3JjZW1lbnQgYmVuZWZpdCDigJQgc28gaW5zdGVhZCBhIGRyb3BwZWQgZGVsaXZlcnkgaXMgbWFkZVxuICogICBPQlNFUlZBQkxFIHZpYSB0aGUgYGVudGl0eV9ldmVudF9zdWJzY3JpYmVyX2ZhaWxlZF90b3RhbGAgY291bnRlciAoYWxlcnQgb25cbiAqICAgYSBzdXN0YWluZWQgcmF0ZSkgcmF0aGVyIHRoYW4gc2lsZW50bHkgc3dhbGxvd2VkIGF0IGRlYnVnIGxldmVsLlxuICovXG5cbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgeyBlbWl0Q291bnRlciB9IGZyb20gJy4uL3V0aWxzL21ldHJpYy1lbWl0dGVyLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdlbnRpdHktZXZlbnRzJyk7XG5cbmV4cG9ydCB0eXBlIEVudGl0eUV2ZW50VHlwZSA9ICdjcmVhdGVkJyB8ICd1cGRhdGVkJyB8ICdkZWxldGVkJztcbmV4cG9ydCB0eXBlIEVudGl0eVRhcmdldCA9ICdwbHVnaW4nIHwgJ3BpcGVsaW5lJyB8ICdtZXNzYWdlJyB8ICdvcmdhbml6YXRpb24nIHwgJ3VzZXInO1xuXG5leHBvcnQgaW50ZXJmYWNlIEVudGl0eUV2ZW50IHtcbiAgLyoqIFRoZSB0eXBlIG9mIG11dGF0aW9uIHRoYXQgb2NjdXJyZWQgKi9cbiAgZXZlbnRUeXBlOiBFbnRpdHlFdmVudFR5cGU7XG4gIC8qKiBUaGUgZW50aXR5IHR5cGUgdGhhdCB3YXMgbXV0YXRlZCAqL1xuICB0YXJnZXQ6IEVudGl0eVRhcmdldDtcbiAgLyoqIFRoZSBlbnRpdHkncyB1bmlxdWUgSUQgKi9cbiAgZW50aXR5SWQ6IHN0cmluZztcbiAgLyoqIFRoZSBvcmdhbml6YXRpb24gdGhhdCBvd25zIHRoaXMgZW50aXR5ICovXG4gIG9yZ0lkOiBzdHJpbmc7XG4gIC8qKiBPcmcg4oaSIHRlYW0gaGllcmFyY2h5OiB0aGUgb3duaW5nIG9yZydzIHBhcmVudCAocHJlc2VudCBvbmx5IHdoZW4gYG9yZ0lkYCBpc1xuICAgKiAgYSB0ZWFtKS4gTGV0cyBjb21wbGlhbmNlIGV2YWx1YXRlIHBhcmVudCBgcHJvcGFnYXRlVG9DaGlsZHJlbmAgcnVsZXMgb24gdGhlXG4gICAqICBhc3luYyBldmVudCBwYXRoLCBtYXRjaGluZyBsaXZlIHZhbGlkYXRpb24uICovXG4gIHBhcmVudE9yZ0lkPzogc3RyaW5nO1xuICAvKiogVGhlIHVzZXIgd2hvIHBlcmZvcm1lZCB0aGUgbXV0YXRpb24gKi9cbiAgdXNlcklkOiBzdHJpbmc7XG4gIC8qKiBXaGVuIHRoZSBldmVudCBvY2N1cnJlZCAqL1xuICB0aW1lc3RhbXA6IERhdGU7XG4gIC8qKiBTbmFwc2hvdCBvZiBlbnRpdHkgYXR0cmlidXRlcyAoZm9yIGNvbXBsaWFuY2UgZXZhbHVhdGlvbikgKi9cbiAgYXR0cmlidXRlczogUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgRW50aXR5RXZlbnRTdWJzY3JpYmVyIHtcbiAgb25FbnRpdHlFdmVudChldmVudDogRW50aXR5RXZlbnQpOiBQcm9taXNlPHZvaWQ+O1xufVxuXG5jbGFzcyBFbnRpdHlFdmVudEVtaXR0ZXIge1xuICBwcml2YXRlIHN1YnNjcmliZXJzOiBFbnRpdHlFdmVudFN1YnNjcmliZXJbXSA9IFtdO1xuXG4gIC8qKlxuICAgKiBSZWdpc3RlciBhIHN1YnNjcmliZXIgdG8gcmVjZWl2ZSBlbnRpdHkgZXZlbnRzLlxuICAgKi9cbiAgc3Vic2NyaWJlKHN1YnNjcmliZXI6IEVudGl0eUV2ZW50U3Vic2NyaWJlcik6IHZvaWQge1xuICAgIHRoaXMuc3Vic2NyaWJlcnMucHVzaChzdWJzY3JpYmVyKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZW1vdmUgYSBzdWJzY3JpYmVyLlxuICAgKi9cbiAgdW5zdWJzY3JpYmUoc3Vic2NyaWJlcjogRW50aXR5RXZlbnRTdWJzY3JpYmVyKTogdm9pZCB7XG4gICAgdGhpcy5zdWJzY3JpYmVycyA9IHRoaXMuc3Vic2NyaWJlcnMuZmlsdGVyKChzKSA9PiBzICE9PSBzdWJzY3JpYmVyKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBFbWl0IGFuIGVudGl0eSBldmVudCB0byBhbGwgc3Vic2NyaWJlcnMuXG4gICAqIEZpcmUtYW5kLWZvcmdldDogZXJyb3JzIGFyZSBjYXVnaHQgYW5kIGxvZ2dlZCwgbmV2ZXIgdGhyb3duLlxuICAgKi9cbiAgZW1pdChldmVudDogRW50aXR5RXZlbnQpOiB2b2lkIHtcbiAgICBmb3IgKGNvbnN0IHN1YnNjcmliZXIgb2YgdGhpcy5zdWJzY3JpYmVycykge1xuICAgICAgc3Vic2NyaWJlci5vbkVudGl0eUV2ZW50KGV2ZW50KS5jYXRjaCgoZXJyKSA9PiB7XG4gICAgICAgIC8vIE1ha2UgdGhlIGRyb3Agb2JzZXJ2YWJsZSDigJQgYSBsb3N0IGFzeW5jIGNvbXBsaWFuY2UgZXZhbCBpcyBiZXN0LWVmZm9ydFxuICAgICAgICAvLyAobGl2ZSB2YWxpZGF0aW9uIGFscmVhZHkgZW5mb3JjZWQpLCBidXQgYSBzdXN0YWluZWQgZmFpbHVyZSByYXRlIGlzIGFuXG4gICAgICAgIC8vIG9wZXJhdGlvbmFsIHNpZ25hbCwgbm90IHNvbWV0aGluZyB0byBidXJ5IGF0IGRlYnVnIGxldmVsLlxuICAgICAgICBlbWl0Q291bnRlcignZW50aXR5X2V2ZW50X3N1YnNjcmliZXJfZmFpbGVkX3RvdGFsJywge1xuICAgICAgICAgIHRhcmdldDogZXZlbnQudGFyZ2V0LFxuICAgICAgICAgIGV2ZW50VHlwZTogZXZlbnQuZXZlbnRUeXBlLFxuICAgICAgICB9KTtcbiAgICAgICAgbG9nZ2VyLmRlYnVnKCdFbnRpdHkgZXZlbnQgc3Vic2NyaWJlciBmYWlsZWQnLCB7XG4gICAgICAgICAgdGFyZ2V0OiBldmVudC50YXJnZXQsXG4gICAgICAgICAgZXZlbnRUeXBlOiBldmVudC5ldmVudFR5cGUsXG4gICAgICAgICAgZW50aXR5SWQ6IGV2ZW50LmVudGl0eUlkLFxuICAgICAgICAgIGVycm9yOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVyciksXG4gICAgICAgIH0pO1xuICAgICAgfSk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgY3VycmVudCBzdWJzY3JpYmVyIGNvdW50IChmb3IgdGVzdGluZy9kaWFnbm9zdGljcykuXG4gICAqL1xuICBnZXQgc3Vic2NyaWJlckNvdW50KCk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMuc3Vic2NyaWJlcnMubGVuZ3RoO1xuICB9XG59XG5cbi8qKiBTaW5nbGV0b24gZW50aXR5IGV2ZW50IGVtaXR0ZXIg4oCUIHNoYXJlZCBhY3Jvc3MgdGhlIHByb2Nlc3MgKi9cbmV4cG9ydCBjb25zdCBlbnRpdHlFdmVudHMgPSBuZXcgRW50aXR5RXZlbnRFbWl0dGVyKCk7XG4iXX0=
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Parse `REDIS_SENTINELS` (comma-separated `host:port` list) into the ioredis
3
+ * `{ host, port }[]` sentinel array. Blank/garbage entries are skipped; a
4
+ * missing port defaults to the Sentinel default 26379. Returns an empty array
5
+ * when the env is unset/empty (→ non-sentinel mode).
6
+ */
7
+ export declare function parseSentinels(raw: string | undefined): Array<{
8
+ host: string;
9
+ port: number;
10
+ }>;
11
+ /**
12
+ * Construct an ioredis client from the standard `REDIS_URL` or
13
+ * `REDIS_HOST`/`REDIS_PORT` env — OR, for HA, from Redis Sentinel via
14
+ * `REDIS_SENTINELS` + `REDIS_SENTINEL_MASTER`. The shared construction was
15
+ * previously duplicated across the leader-lock, audit-spool, and
16
+ * token-revocation env factories.
17
+ *
18
+ * Connection modes (first that matches wins):
19
+ * 1. SENTINEL — `REDIS_SENTINELS` set (comma-separated `host:port`): ioredis
20
+ * resolves the current master from the sentinels (`name` = `REDIS_SENTINEL_MASTER`,
21
+ * default `mymaster`) and transparently reconnects to the new master after a
22
+ * failover. This is how the app gets HA against a single-node Redis loss —
23
+ * also the shape a managed ElastiCache (cluster-mode-disabled) speaks.
24
+ * 2. URL — `REDIS_URL`.
25
+ * 3. HOST/PORT — `REDIS_HOST`(+`REDIS_PORT`).
26
+ *
27
+ * Semantics every caller relies on (kept identical across the consolidation):
28
+ * - Returns `null` when NONE of sentinels/url/host is set, so a caller degrades
29
+ * to its no-Redis fallback (run-on-every-pod / best-effort / fail-open).
30
+ * - FAIL-SAFE: any ioredis load or construction error is swallowed and returns
31
+ * `null` — Redis being unavailable must never crash the importing service.
32
+ * - `ioredis` is a runtime-only optional dep loaded via `createRequire`, so this
33
+ * module stays importable in CJS bundles and where Redis isn't installed.
34
+ * - `maxRetriesPerRequest: 1` + `enableOfflineQueue: false`: commands fail fast
35
+ * rather than queueing during an outage (callers are fail-open/best-effort).
36
+ * - An `error` listener is attached (ioredis auto-reconnects) so a dropped
37
+ * connection can't surface as an unhandled `'error'` event that crashes Node.
38
+ *
39
+ * Optional auth: `REDIS_PASSWORD` (data-node AUTH) and `REDIS_SENTINEL_PASSWORD`
40
+ * (sentinel AUTH, sentinel mode only) are threaded through when set.
41
+ *
42
+ * @param label short human name for this client, used in the two warn logs.
43
+ */
44
+ export declare function createEnvRedisClient<T = unknown>(label: string): T | null;
@@ -0,0 +1,94 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { createRequire } from 'module';
4
+ import { createLogger } from '../utils/logger.js';
5
+ const logger = createLogger('env-redis');
6
+ /**
7
+ * Parse `REDIS_SENTINELS` (comma-separated `host:port` list) into the ioredis
8
+ * `{ host, port }[]` sentinel array. Blank/garbage entries are skipped; a
9
+ * missing port defaults to the Sentinel default 26379. Returns an empty array
10
+ * when the env is unset/empty (→ non-sentinel mode).
11
+ */
12
+ export function parseSentinels(raw) {
13
+ if (!raw)
14
+ return [];
15
+ return raw.split(',').map((s) => s.trim()).filter(Boolean).map((entry) => {
16
+ const [host, port] = entry.split(':');
17
+ return { host, port: parseInt(port ?? '26379', 10) || 26379 };
18
+ }).filter((s) => !!s.host);
19
+ }
20
+ /**
21
+ * Construct an ioredis client from the standard `REDIS_URL` or
22
+ * `REDIS_HOST`/`REDIS_PORT` env — OR, for HA, from Redis Sentinel via
23
+ * `REDIS_SENTINELS` + `REDIS_SENTINEL_MASTER`. The shared construction was
24
+ * previously duplicated across the leader-lock, audit-spool, and
25
+ * token-revocation env factories.
26
+ *
27
+ * Connection modes (first that matches wins):
28
+ * 1. SENTINEL — `REDIS_SENTINELS` set (comma-separated `host:port`): ioredis
29
+ * resolves the current master from the sentinels (`name` = `REDIS_SENTINEL_MASTER`,
30
+ * default `mymaster`) and transparently reconnects to the new master after a
31
+ * failover. This is how the app gets HA against a single-node Redis loss —
32
+ * also the shape a managed ElastiCache (cluster-mode-disabled) speaks.
33
+ * 2. URL — `REDIS_URL`.
34
+ * 3. HOST/PORT — `REDIS_HOST`(+`REDIS_PORT`).
35
+ *
36
+ * Semantics every caller relies on (kept identical across the consolidation):
37
+ * - Returns `null` when NONE of sentinels/url/host is set, so a caller degrades
38
+ * to its no-Redis fallback (run-on-every-pod / best-effort / fail-open).
39
+ * - FAIL-SAFE: any ioredis load or construction error is swallowed and returns
40
+ * `null` — Redis being unavailable must never crash the importing service.
41
+ * - `ioredis` is a runtime-only optional dep loaded via `createRequire`, so this
42
+ * module stays importable in CJS bundles and where Redis isn't installed.
43
+ * - `maxRetriesPerRequest: 1` + `enableOfflineQueue: false`: commands fail fast
44
+ * rather than queueing during an outage (callers are fail-open/best-effort).
45
+ * - An `error` listener is attached (ioredis auto-reconnects) so a dropped
46
+ * connection can't surface as an unhandled `'error'` event that crashes Node.
47
+ *
48
+ * Optional auth: `REDIS_PASSWORD` (data-node AUTH) and `REDIS_SENTINEL_PASSWORD`
49
+ * (sentinel AUTH, sentinel mode only) are threaded through when set.
50
+ *
51
+ * @param label short human name for this client, used in the two warn logs.
52
+ */
53
+ export function createEnvRedisClient(label) {
54
+ try {
55
+ const sentinels = parseSentinels(process.env.REDIS_SENTINELS);
56
+ const url = process.env.REDIS_URL;
57
+ const host = process.env.REDIS_HOST;
58
+ if (sentinels.length === 0 && !url && !host)
59
+ return null;
60
+ const req = createRequire(import.meta.url);
61
+ const mod = req('ioredis');
62
+ const RedisCtor = (mod.Redis ?? mod.default ?? mod);
63
+ const redisOpts = { maxRetriesPerRequest: 1, enableOfflineQueue: false };
64
+ if (process.env.REDIS_PASSWORD)
65
+ redisOpts.password = process.env.REDIS_PASSWORD;
66
+ let inst;
67
+ if (sentinels.length > 0) {
68
+ // Sentinel mode: ioredis tracks the master via the sentinels + auto-fails-over.
69
+ const sentinelOpts = {
70
+ sentinels,
71
+ name: process.env.REDIS_SENTINEL_MASTER || 'mymaster',
72
+ ...redisOpts,
73
+ };
74
+ if (process.env.REDIS_SENTINEL_PASSWORD)
75
+ sentinelOpts.sentinelPassword = process.env.REDIS_SENTINEL_PASSWORD;
76
+ inst = new RedisCtor(sentinelOpts);
77
+ logger.info(`Redis ${label}: Sentinel mode`, { master: sentinelOpts.name, sentinels: sentinels.length });
78
+ }
79
+ else if (url) {
80
+ inst = new RedisCtor(url, redisOpts);
81
+ }
82
+ else {
83
+ inst = new RedisCtor({ host, port: parseInt(process.env.REDIS_PORT ?? '6379', 10), ...redisOpts });
84
+ }
85
+ inst
86
+ .on('error', (e) => logger.warn(`Redis ${label} client error`, { error: e instanceof Error ? e.message : String(e) }));
87
+ return inst;
88
+ }
89
+ catch (err) {
90
+ logger.warn(`Redis unavailable for ${label}`, { error: err instanceof Error ? err.message : String(err) });
91
+ return null;
92
+ }
93
+ }
94
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW52LXJlZGlzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcnZpY2VzL2Vudi1yZWRpcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFFdkMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRWxELE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUV6Qzs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxjQUFjLENBQUMsR0FBdUI7SUFDcEQsSUFBSSxDQUFDLEdBQUc7UUFBRSxPQUFPLEVBQUUsQ0FBQztJQUNwQixPQUFPLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUU7UUFDdkUsTUFBTSxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3RDLE9BQU8sRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLFFBQVEsQ0FBQyxJQUFJLElBQUksT0FBTyxFQUFFLEVBQUUsQ0FBQyxJQUFJLEtBQUssRUFBRSxDQUFDO0lBQ2hFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUM3QixDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBZ0NHO0FBQ0gsTUFBTSxVQUFVLG9CQUFvQixDQUFjLEtBQWE7SUFDN0QsSUFBSSxDQUFDO1FBQ0gsTUFB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InNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBjcmVhdGVSZXF1aXJlIH0gZnJvbSAnbW9kdWxlJztcblxuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdlbnYtcmVkaXMnKTtcblxuLyoqXG4gKiBQYXJzZSBgUkVESVNfU0VOVElORUxTYCAoY29tbWEtc2VwYXJhdGVkIGBob3N0OnBvcnRgIGxpc3QpIGludG8gdGhlIGlvcmVkaXNcbiAqIGB7IGhvc3QsIHBvcnQgfVtdYCBzZW50aW5lbCBhcnJheS4gQmxhbmsvZ2FyYmFnZSBlbnRyaWVzIGFyZSBza2lwcGVkOyBhXG4gKiBtaXNzaW5nIHBvcnQgZGVmYXVsdHMgdG8gdGhlIFNlbnRpbmVsIGRlZmF1bHQgMjYzNzkuIFJldHVybnMgYW4gZW1wdHkgYXJyYXlcbiAqIHdoZW4gdGhlIGVudiBpcyB1bnNldC9lbXB0eSAo4oaSIG5vbi1zZW50aW5lbCBtb2RlKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlU2VudGluZWxzKHJhdzogc3RyaW5nIHwgdW5kZWZpbmVkKTogQXJyYXk8eyBob3N0OiBzdHJpbmc7IHBvcnQ6IG51bWJlciB9PiB7XG4gIGlmICghcmF3KSByZXR1cm4gW107XG4gIHJldHVybiByYXcuc3BsaXQoJywnKS5tYXAoKHMpID0+IHMudHJpbSgpKS5maWx0ZXIoQm9vbGVhbikubWFwKChlbnRyeSkgPT4ge1xuICAgIGNvbnN0IFtob3N0LCBwb3J0XSA9IGVudHJ5LnNwbGl0KCc6Jyk7XG4gICAgcmV0dXJuIHsgaG9zdCwgcG9ydDogcGFyc2VJbnQocG9ydCA/PyAnMjYzNzknLCAxMCkgfHwgMjYzNzkgfTtcbiAgfSkuZmlsdGVyKChzKSA9PiAhIXMuaG9zdCk7XG59XG5cbi8qKlxuICogQ29uc3RydWN0IGFuIGlvcmVkaXMgY2xpZW50IGZyb20gdGhlIHN0YW5kYXJkIGBSRURJU19VUkxgIG9yXG4gKiBgUkVESVNfSE9TVGAvYFJFRElTX1BPUlRgIGVudiDigJQgT1IsIGZvciBIQSwgZnJvbSBSZWRpcyBTZW50aW5lbCB2aWFcbiAqIGBSRURJU19TRU5USU5FTFNgICsgYFJFRElTX1NFTlRJTkVMX01BU1RFUmAuIFRoZSBzaGFyZWQgY29uc3RydWN0aW9uIHdhc1xuICogcHJldmlvdXNseSBkdXBsaWNhdGVkIGFjcm9zcyB0aGUgbGVhZGVyLWxvY2ssIGF1ZGl0LXNwb29sLCBhbmRcbiAqIHRva2VuLXJldm9jYXRpb24gZW52IGZhY3Rvcmllcy5cbiAqXG4gKiBDb25uZWN0aW9uIG1vZGVzIChmaXJzdCB0aGF0IG1hdGNoZXMgd2lucyk6XG4gKiAxLiBTRU5USU5FTCDigJQgYFJFRElTX1NFTlRJTkVMU2Agc2V0IChjb21tYS1zZXBhcmF0ZWQgYGhvc3Q6cG9ydGApOiBpb3JlZGlzXG4gKiAgICByZXNvbHZlcyB0aGUgY3VycmVudCBtYXN0ZXIgZnJvbSB0aGUgc2VudGluZWxzIChgbmFtZWAgPSBgUkVESVNfU0VOVElORUxfTUFTVEVSYCxcbiAqICAgIGRlZmF1bHQgYG15bWFzdGVyYCkgYW5kIHRyYW5zcGFyZW50bHkgcmVjb25uZWN0cyB0byB0aGUgbmV3IG1hc3RlciBhZnRlciBhXG4gKiAgICBmYWlsb3Zlci4gVGhpcyBpcyBob3cgdGhlIGFwcCBnZXRzIEhBIGFnYWluc3QgYSBzaW5nbGUtbm9kZSBSZWRpcyBsb3NzIOKAlFxuICogICAgYWxzbyB0aGUgc2hhcGUgYSBtYW5hZ2VkIEVsYXN0aUNhY2hlIChjbHVzdGVyLW1vZGUtZGlzYWJsZWQpIHNwZWFrcy5cbiAqIDIuIFVSTCDigJQgYFJFRElTX1VSTGAuXG4gKiAzLiBIT1NUL1BPUlQg4oCUIGBSRURJU19IT1NUYCgrYFJFRElTX1BPUlRgKS5cbiAqXG4gKiBTZW1hbnRpY3MgZXZlcnkgY2FsbGVyIHJlbGllcyBvbiAoa2VwdCBpZGVudGljYWwgYWNyb3NzIHRoZSBjb25zb2xpZGF0aW9uKTpcbiAqIC0gUmV0dXJucyBgbnVsbGAgd2hlbiBOT05FIG9mIHNlbnRpbmVscy91cmwvaG9zdCBpcyBzZXQsIHNvIGEgY2FsbGVyIGRlZ3JhZGVzXG4gKiAgIHRvIGl0cyBuby1SZWRpcyBmYWxsYmFjayAocnVuLW9uLWV2ZXJ5LXBvZCAvIGJlc3QtZWZmb3J0IC8gZmFpbC1vcGVuKS5cbiAqIC0gRkFJTC1TQUZFOiBhbnkgaW9yZWRpcyBsb2FkIG9yIGNvbnN0cnVjdGlvbiBlcnJvciBpcyBzd2FsbG93ZWQgYW5kIHJldHVybnNcbiAqICAgYG51bGxgIOKAlCBSZWRpcyBiZWluZyB1bmF2YWlsYWJsZSBtdXN0IG5ldmVyIGNyYXNoIHRoZSBpbXBvcnRpbmcgc2VydmljZS5cbiAqIC0gYGlvcmVkaXNgIGlzIGEgcnVudGltZS1vbmx5IG9wdGlvbmFsIGRlcCBsb2FkZWQgdmlhIGBjcmVhdGVSZXF1aXJlYCwgc28gdGhpc1xuICogICBtb2R1bGUgc3RheXMgaW1wb3J0YWJsZSBpbiBDSlMgYnVuZGxlcyBhbmQgd2hlcmUgUmVkaXMgaXNuJ3QgaW5zdGFsbGVkLlxuICogLSBgbWF4UmV0cmllc1BlclJlcXVlc3Q6IDFgICsgYGVuYWJsZU9mZmxpbmVRdWV1ZTogZmFsc2VgOiBjb21tYW5kcyBmYWlsIGZhc3RcbiAqICAgcmF0aGVyIHRoYW4gcXVldWVpbmcgZHVyaW5nIGFuIG91dGFnZSAoY2FsbGVycyBhcmUgZmFpbC1vcGVuL2Jlc3QtZWZmb3J0KS5cbiAqIC0gQW4gYGVycm9yYCBsaXN0ZW5lciBpcyBhdHRhY2hlZCAoaW9yZWRpcyBhdXRvLXJlY29ubmVjdHMpIHNvIGEgZHJvcHBlZFxuICogICBjb25uZWN0aW9uIGNhbid0IHN1cmZhY2UgYXMgYW4gdW5oYW5kbGVkIGAnZXJyb3InYCBldmVudCB0aGF0IGNyYXNoZXMgTm9kZS5cbiAqXG4gKiBPcHRpb25hbCBhdXRoOiBgUkVESVNfUEFTU1dPUkRgIChkYXRhLW5vZGUgQVVUSCkgYW5kIGBSRURJU19TRU5USU5FTF9QQVNTV09SRGBcbiAqIChzZW50aW5lbCBBVVRILCBzZW50aW5lbCBtb2RlIG9ubHkpIGFyZSB0aHJlYWRlZCB0aHJvdWdoIHdoZW4gc2V0LlxuICpcbiAqIEBwYXJhbSBsYWJlbCBzaG9ydCBodW1hbiBuYW1lIGZvciB0aGlzIGNsaWVudCwgdXNlZCBpbiB0aGUgdHdvIHdhcm4gbG9ncy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUVudlJlZGlzQ2xpZW50PFQgPSB1bmtub3duPihsYWJlbDogc3RyaW5nKTogVCB8IG51bGwge1xuICB0cnkge1xuICAgIGNvbnN0IHNlbnRpbmVscyA9IHBhcnNlU2VudGluZWxzKHByb2Nlc3MuZW52LlJFRElTX1NFTlRJTkVMUyk7XG4gICAgY29uc3QgdXJsID0gcHJvY2Vzcy5lbnYuUkVESVNfVVJMO1xuICAgIGNvbnN0IGhvc3QgPSBwcm9jZXNzLmVudi5SRURJU19IT1NUO1xuICAgIGlmIChzZW50aW5lbHMubGVuZ3RoID09PSAwICYmICF1cmwgJiYgIWhvc3QpIHJldHVybiBudWxsO1xuICAgIGNvbnN0IHJlcSA9IGNyZWF0ZVJlcXVpcmUoaW1wb3J0Lm1ldGEudXJsKTtcbiAgICBjb25zdCBtb2QgPSByZXEoJ2lvcmVkaXMnKSBhcyB7XG4gICAgICBSZWRpcz86IG5ldyAoLi4uYXJnczogdW5rbm93bltdKSA9PiB1bmtub3duO1xuICAgICAgZGVmYXVsdD86IG5ldyAoLi4uYXJnczogdW5rbm93bltdKSA9PiB1bmtub3duO1xuICAgIH07XG4gICAgY29uc3QgUmVkaXNDdG9yID0gKG1vZC5SZWRpcyA/PyBtb2QuZGVmYXVsdCA/PyBtb2QpIGFzIG5ldyAoLi4uYXJnczogdW5rbm93bltdKSA9PiBUO1xuICAgIGNvbnN0IHJlZGlzT3B0czogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSB7IG1heFJldHJpZXNQZXJSZXF1ZXN0OiAxLCBlbmFibGVPZmZsaW5lUXVldWU6IGZhbHNlIH07XG4gICAgaWYgKHByb2Nlc3MuZW52LlJFRElTX1BBU1NXT1JEKSByZWRpc09wdHMucGFzc3dvcmQgPSBwcm9jZXNzLmVudi5SRURJU19QQVNTV09SRDtcblxuICAgIGxldCBpbnN0OiBUO1xuICAgIGlmIChzZW50aW5lbHMubGVuZ3RoID4gMCkge1xuICAgICAgLy8gU2VudGluZWwgbW9kZTogaW9yZWRpcyB0cmFja3MgdGhlIG1hc3RlciB2aWEgdGhlIHNlbnRpbmVscyArIGF1dG8tZmFpbHMtb3Zlci5cbiAgICAgIGNvbnN0IHNlbnRpbmVsT3B0czogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gPSB7XG4gICAgICAgIHNlbnRpbmVscyxcbiAgICAgICAgbmFtZTogcHJvY2Vzcy5lbnYuUkVESVNfU0VOVElORUxfTUFTVEVSIHx8ICdteW1hc3RlcicsXG4gICAgICAgIC4uLnJlZGlzT3B0cyxcbiAgICAgIH07XG4gICAgICBpZiAocHJvY2Vzcy5lbnYuUkVESVNfU0VOVElORUxfUEFTU1dPUkQpIHNlbnRpbmVsT3B0cy5zZW50aW5lbFBhc3N3b3JkID0gcHJvY2Vzcy5lbnYuUkVESVNfU0VOVElORUxfUEFTU1dPUkQ7XG4gICAgICBpbnN0ID0gbmV3IFJlZGlzQ3RvcihzZW50aW5lbE9wdHMpO1xuICAgICAgbG9nZ2VyLmluZm8oYFJlZGlzICR7bGFiZWx9OiBTZW50aW5lbCBtb2RlYCwgeyBtYXN0ZXI6IHNlbnRpbmVsT3B0cy5uYW1lLCBzZW50aW5lbHM6IHNlbnRpbmVscy5sZW5ndGggfSk7XG4gICAgfSBlbHNlIGlmICh1cmwpIHtcbiAgICAgIGluc3QgPSBuZXcgUmVkaXNDdG9yKHVybCwgcmVkaXNPcHRzKTtcbiAgICB9IGVsc2Uge1xuICAgICAgaW5zdCA9IG5ldyBSZWRpc0N0b3IoeyBob3N0LCBwb3J0OiBwYXJzZUludChwcm9jZXNzLmVudi5SRURJU19QT1JUID8/ICc2Mzc5JywgMTApLCAuLi5yZWRpc09wdHMgfSk7XG4gICAgfVxuICAgIChpbnN0IGFzIHVua25vd24gYXMgeyBvbjogKGV2dDogc3RyaW5nLCBjYjogKGU6IHVua25vd24pID0+IHZvaWQpID0+IHZvaWQgfSlcbiAgICAgIC5vbignZXJyb3InLCAoZSkgPT4gbG9nZ2VyLndhcm4oYFJlZGlzICR7bGFiZWx9IGNsaWVudCBlcnJvcmAsIHsgZXJyb3I6IGUgaW5zdGFuY2VvZiBFcnJvciA/IGUubWVzc2FnZSA6IFN0cmluZyhlKSB9KSk7XG4gICAgcmV0dXJuIGluc3Q7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGxvZ2dlci53YXJuKGBSZWRpcyB1bmF2YWlsYWJsZSBmb3IgJHtsYWJlbH1gLCB7IGVycm9yOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycikgfSk7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbn1cbiJdfQ==