@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.
- package/LICENSE +202 -0
- package/README.md +98 -0
- package/lib/constants/ai-providers.d.ts +52 -0
- package/lib/constants/ai-providers.js +129 -0
- package/lib/constants/http-status.d.ts +20 -0
- package/lib/constants/http-status.js +26 -0
- package/lib/constants/index.d.ts +3 -0
- package/lib/constants/index.js +6 -0
- package/lib/constants/time.d.ts +10 -0
- package/lib/constants/time.js +13 -0
- package/lib/errors/app-errors.d.ts +30 -0
- package/lib/errors/app-errors.js +53 -0
- package/lib/errors/index.d.ts +1 -0
- package/lib/errors/index.js +4 -0
- package/lib/helpers/access-helpers.d.ts +63 -0
- package/lib/helpers/access-helpers.js +82 -0
- package/lib/helpers/crud-helpers.d.ts +9 -0
- package/lib/helpers/crud-helpers.js +23 -0
- package/lib/helpers/index.d.ts +6 -0
- package/lib/helpers/index.js +9 -0
- package/lib/helpers/org-hierarchy-http.d.ts +74 -0
- package/lib/helpers/org-hierarchy-http.js +144 -0
- package/lib/helpers/org-hierarchy.d.ts +57 -0
- package/lib/helpers/org-hierarchy.js +110 -0
- package/lib/helpers/restore-helpers.d.ts +49 -0
- package/lib/helpers/restore-helpers.js +78 -0
- package/lib/helpers/sse-helpers.d.ts +20 -0
- package/lib/helpers/sse-helpers.js +45 -0
- package/lib/index.d.ts +58 -0
- package/lib/index.js +71 -0
- package/lib/middleware/auth.d.ts +208 -0
- package/lib/middleware/auth.js +588 -0
- package/lib/middleware/index.d.ts +3 -0
- package/lib/middleware/index.js +6 -0
- package/lib/middleware/mongo-sanitize.d.ts +32 -0
- package/lib/middleware/mongo-sanitize.js +100 -0
- package/lib/middleware/step-up.d.ts +45 -0
- package/lib/middleware/step-up.js +138 -0
- package/lib/openapi/extend-zod.d.ts +1 -0
- package/lib/openapi/extend-zod.js +6 -0
- package/lib/openapi/index.d.ts +2 -0
- package/lib/openapi/index.js +5 -0
- package/lib/openapi/registry.d.ts +17 -0
- package/lib/openapi/registry.js +38 -0
- package/lib/openapi/routes/ask-routes.d.ts +7 -0
- package/lib/openapi/routes/ask-routes.js +62 -0
- package/lib/openapi/routes/billing-routes.d.ts +1 -0
- package/lib/openapi/routes/billing-routes.js +66 -0
- package/lib/openapi/routes/image-registry-routes.d.ts +7 -0
- package/lib/openapi/routes/image-registry-routes.js +98 -0
- package/lib/openapi/routes/index.d.ts +5 -0
- package/lib/openapi/routes/index.js +28 -0
- package/lib/openapi/routes/message-routes.d.ts +1 -0
- package/lib/openapi/routes/message-routes.js +105 -0
- package/lib/openapi/routes/pipeline-routes.d.ts +1 -0
- package/lib/openapi/routes/pipeline-routes.js +105 -0
- package/lib/openapi/routes/pipeline-template-routes.d.ts +1 -0
- package/lib/openapi/routes/pipeline-template-routes.js +61 -0
- package/lib/openapi/routes/plugin-routes.d.ts +1 -0
- package/lib/openapi/routes/plugin-routes.js +96 -0
- package/lib/openapi/routes/quota-routes.d.ts +1 -0
- package/lib/openapi/routes/quota-routes.js +62 -0
- package/lib/openapi/schema-registry.d.ts +25 -0
- package/lib/openapi/schema-registry.js +91 -0
- package/lib/routes/health.d.ts +41 -0
- package/lib/routes/health.js +82 -0
- package/lib/routes/index.d.ts +1 -0
- package/lib/routes/index.js +4 -0
- package/lib/services/audit-spool.d.ts +80 -0
- package/lib/services/audit-spool.js +172 -0
- package/lib/services/cache-service.d.ts +124 -0
- package/lib/services/cache-service.js +276 -0
- package/lib/services/circuit-breaker.d.ts +45 -0
- package/lib/services/circuit-breaker.js +138 -0
- package/lib/services/compliance-client.d.ts +46 -0
- package/lib/services/compliance-client.js +128 -0
- package/lib/services/compliance-event-subscriber.d.ts +16 -0
- package/lib/services/compliance-event-subscriber.js +72 -0
- package/lib/services/durable-event-bus.d.ts +96 -0
- package/lib/services/durable-event-bus.js +204 -0
- package/lib/services/entity-events.d.ts +48 -0
- package/lib/services/entity-events.js +79 -0
- package/lib/services/env-redis.d.ts +44 -0
- package/lib/services/env-redis.js +94 -0
- package/lib/services/http-client.d.ts +143 -0
- package/lib/services/http-client.js +335 -0
- package/lib/services/index.d.ts +18 -0
- package/lib/services/index.js +20 -0
- package/lib/services/leader-lock.d.ts +45 -0
- package/lib/services/leader-lock.js +85 -0
- package/lib/services/quota.d.ts +130 -0
- package/lib/services/quota.js +264 -0
- package/lib/services/remote-audit-client.d.ts +160 -0
- package/lib/services/remote-audit-client.js +390 -0
- package/lib/services/retry-strategy.d.ts +74 -0
- package/lib/services/retry-strategy.js +118 -0
- package/lib/services/scheduler.d.ts +41 -0
- package/lib/services/scheduler.js +86 -0
- package/lib/services/service-boot.d.ts +20 -0
- package/lib/services/service-boot.js +28 -0
- package/lib/services/sse-ticket-store.d.ts +51 -0
- package/lib/services/sse-ticket-store.js +130 -0
- package/lib/services/token-revocation.d.ts +52 -0
- package/lib/services/token-revocation.js +120 -0
- package/lib/testing/tier-mock.d.ts +44 -0
- package/lib/testing/tier-mock.js +65 -0
- package/lib/types/audit-events.d.ts +69 -0
- package/lib/types/audit-events.js +4 -0
- package/lib/types/catalog-metadata.d.ts +36 -0
- package/lib/types/catalog-metadata.js +21 -0
- package/lib/types/common.d.ts +255 -0
- package/lib/types/common.js +41 -0
- package/lib/types/error-codes.d.ts +54 -0
- package/lib/types/error-codes.js +107 -0
- package/lib/types/feature-flags.d.ts +36 -0
- package/lib/types/feature-flags.js +128 -0
- package/lib/types/http.d.ts +27 -0
- package/lib/types/http.js +4 -0
- package/lib/types/index.d.ts +11 -0
- package/lib/types/index.js +14 -0
- package/lib/types/permissions.d.ts +65 -0
- package/lib/types/permissions.js +131 -0
- package/lib/types/pipeline-template.d.ts +30 -0
- package/lib/types/pipeline-template.js +13 -0
- package/lib/types/pipeline.d.ts +59 -0
- package/lib/types/pipeline.js +31 -0
- package/lib/types/quota-tiers.d.ts +79 -0
- package/lib/types/quota-tiers.js +221 -0
- package/lib/types/visibility.d.ts +20 -0
- package/lib/types/visibility.js +22 -0
- package/lib/utils/alias-resolver.d.ts +37 -0
- package/lib/utils/alias-resolver.js +68 -0
- package/lib/utils/audit.d.ts +15 -0
- package/lib/utils/audit.js +39 -0
- package/lib/utils/aws-scrub.d.ts +11 -0
- package/lib/utils/aws-scrub.js +67 -0
- package/lib/utils/compliance-attributes.d.ts +6 -0
- package/lib/utils/compliance-attributes.js +69 -0
- package/lib/utils/concurrency.d.ts +9 -0
- package/lib/utils/concurrency.js +29 -0
- package/lib/utils/crash-handlers.d.ts +18 -0
- package/lib/utils/crash-handlers.js +36 -0
- package/lib/utils/env.d.ts +28 -0
- package/lib/utils/env.js +54 -0
- package/lib/utils/headers.d.ts +18 -0
- package/lib/utils/headers.js +21 -0
- package/lib/utils/identity.d.ts +40 -0
- package/lib/utils/identity.js +51 -0
- package/lib/utils/index.d.ts +17 -0
- package/lib/utils/index.js +20 -0
- package/lib/utils/logger.d.ts +38 -0
- package/lib/utils/logger.js +174 -0
- package/lib/utils/metric-emitter.d.ts +36 -0
- package/lib/utils/metric-emitter.js +32 -0
- package/lib/utils/object.d.ts +13 -0
- package/lib/utils/object.js +18 -0
- package/lib/utils/params.d.ts +150 -0
- package/lib/utils/params.js +206 -0
- package/lib/utils/response.d.ts +117 -0
- package/lib/utils/response.js +223 -0
- package/lib/utils/safe-require.d.ts +13 -0
- package/lib/utils/safe-require.js +19 -0
- package/lib/utils/secret-encryption.d.ts +176 -0
- package/lib/utils/secret-encryption.js +374 -0
- package/lib/utils/ssrf.d.ts +30 -0
- package/lib/utils/ssrf.js +106 -0
- package/lib/validation/ai-schemas.d.ts +56 -0
- package/lib/validation/ai-schemas.js +78 -0
- package/lib/validation/common-schemas.d.ts +133 -0
- package/lib/validation/common-schemas.js +94 -0
- package/lib/validation/index.d.ts +7 -0
- package/lib/validation/index.js +10 -0
- package/lib/validation/message-schemas.d.ts +160 -0
- package/lib/validation/message-schemas.js +156 -0
- package/lib/validation/middleware.d.ts +72 -0
- package/lib/validation/middleware.js +65 -0
- package/lib/validation/pipeline-schemas.d.ts +181 -0
- package/lib/validation/pipeline-schemas.js +101 -0
- package/lib/validation/pipeline-template-schemas.d.ts +155 -0
- package/lib/validation/pipeline-template-schemas.js +66 -0
- package/lib/validation/plugin-schemas.d.ts +171 -0
- package/lib/validation/plugin-schemas.js +81 -0
- package/openapi.yaml +292 -0
- package/package.json +167 -0
|
@@ -0,0 +1,172 @@
|
|
|
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('audit-spool');
|
|
7
|
+
const DEFAULT_SPOOL_KEY = 'audit:spool';
|
|
8
|
+
const DEFAULT_MAX_DEPTH = 10_000;
|
|
9
|
+
/** Guard so a pathological in-progress list can't spin `recover`/`take` forever. */
|
|
10
|
+
const MAX_MOVE_ITERATIONS = 100_000;
|
|
11
|
+
function errMsg(err) {
|
|
12
|
+
return err instanceof Error ? err.message : String(err);
|
|
13
|
+
}
|
|
14
|
+
function safeParse(raw) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
return parsed && parsed.event && parsed.serviceName ? parsed : null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Redis-LIST-backed spool. Durable across process restarts (unlike an in-memory
|
|
25
|
+
* or per-pod-disk buffer), shared by all replicas of a service, and bounded via
|
|
26
|
+
* a trim-to-tail on overflow (drops the OLDEST, emitting `audit_spool_dropped_total`).
|
|
27
|
+
*/
|
|
28
|
+
export function createRedisAuditSpool(redis, opts = {}) {
|
|
29
|
+
const key = opts.key ?? DEFAULT_SPOOL_KEY;
|
|
30
|
+
// The in-progress "processing" list — a taken batch lives here until it is
|
|
31
|
+
// acked (delivered) or requeued (still failing). It is a security log's
|
|
32
|
+
// durability net: a crash between take and ack/requeue leaves the batch here,
|
|
33
|
+
// and `recover` reclaims it on restart instead of silently losing it.
|
|
34
|
+
const inProgressKey = `${key}:inflight`;
|
|
35
|
+
const maxDepth = Math.max(1, opts.maxDepth ?? DEFAULT_MAX_DEPTH);
|
|
36
|
+
// The EXACT serialized string each returned entry was parsed from, so `ack` /
|
|
37
|
+
// `requeue` can LREM the precise value off the in-progress list. Keyed by the
|
|
38
|
+
// returned object identity (the drain consumer passes the SAME objects back),
|
|
39
|
+
// so it needs no stable re-serialization. WeakMap → entries GC naturally.
|
|
40
|
+
const rawByEntry = new WeakMap();
|
|
41
|
+
const rawOf = (entry) => rawByEntry.get(entry) ?? JSON.stringify(entry);
|
|
42
|
+
return {
|
|
43
|
+
async enqueue(entry) {
|
|
44
|
+
try {
|
|
45
|
+
const len = await redis.rpush(key, JSON.stringify(entry));
|
|
46
|
+
emitCounter('audit_spool_enqueued_total', { service: entry.serviceName });
|
|
47
|
+
if (len > maxDepth) {
|
|
48
|
+
// Keep only the newest `maxDepth`; drop the overflow at the head.
|
|
49
|
+
const dropped = len - maxDepth;
|
|
50
|
+
await redis.ltrim(key, dropped, -1);
|
|
51
|
+
emitCounter('audit_spool_dropped_total', { service: entry.serviceName }, dropped);
|
|
52
|
+
logger.warn('Audit spool overflow — dropped oldest buffered events', { dropped, maxDepth });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
logger.warn('Audit spool enqueue failed (event lost)', { error: errMsg(err) });
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
async take(max) {
|
|
60
|
+
try {
|
|
61
|
+
const out = [];
|
|
62
|
+
const limit = Math.max(1, max);
|
|
63
|
+
for (let i = 0; i < limit; i++) {
|
|
64
|
+
// Atomically MOVE head→in-progress-tail so the entry is never in limbo:
|
|
65
|
+
// it is on exactly one list at all times, recoverable after a crash.
|
|
66
|
+
const raw = await redis.lmove(key, inProgressKey, 'LEFT', 'RIGHT');
|
|
67
|
+
if (raw === null || raw === undefined)
|
|
68
|
+
break;
|
|
69
|
+
const parsed = safeParse(raw);
|
|
70
|
+
if (parsed) {
|
|
71
|
+
rawByEntry.set(parsed, raw);
|
|
72
|
+
out.push(parsed);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// Corrupt payload — drop it off the in-progress list so it can't wedge
|
|
76
|
+
// the drain, and account for it.
|
|
77
|
+
await redis.lrem(inProgressKey, 1, raw);
|
|
78
|
+
emitCounter('audit_spool_dropped_total', { service: 'unknown' });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
logger.warn('Audit spool take failed', { error: errMsg(err) });
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
async ack(entries) {
|
|
89
|
+
if (entries.length === 0)
|
|
90
|
+
return;
|
|
91
|
+
try {
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
await redis.lrem(inProgressKey, 1, rawOf(entry));
|
|
94
|
+
rawByEntry.delete(entry);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
logger.warn('Audit spool ack failed (may re-deliver on recover)', { error: errMsg(err) });
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
async requeue(entries) {
|
|
102
|
+
if (entries.length === 0)
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
// Put failed re-deliveries back at the HEAD so they're retried first and
|
|
106
|
+
// rough emission order is preserved across the outage — and clear them
|
|
107
|
+
// from the in-progress list so `recover` doesn't double them. Iterate in
|
|
108
|
+
// reverse so lpush restores the batch's original order at the head.
|
|
109
|
+
//
|
|
110
|
+
// ORDERING INVARIANT (at-least-once): lpush to the main list FIRST, THEN
|
|
111
|
+
// lrem from in-progress. A crash between the two leaves the entry on BOTH
|
|
112
|
+
// lists → harmless double-delivery (deduped downstream by idempotency
|
|
113
|
+
// reuse), never a LOSS. The reverse order (lrem then lpush) would drop the
|
|
114
|
+
// event outright on a crash in the gap, which a security log must not do.
|
|
115
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
116
|
+
const raw = rawOf(entries[i]);
|
|
117
|
+
await redis.lpush(key, raw);
|
|
118
|
+
await redis.lrem(inProgressKey, 1, raw);
|
|
119
|
+
rawByEntry.delete(entries[i]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
logger.warn('Audit spool requeue failed (events lost)', { error: errMsg(err) });
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
async recover() {
|
|
127
|
+
try {
|
|
128
|
+
let reclaimed = 0;
|
|
129
|
+
for (let i = 0; i < MAX_MOVE_ITERATIONS; i++) {
|
|
130
|
+
// Move in-progress-tail → main-head: an older stranded entry (nearer the
|
|
131
|
+
// in-progress head) ends up ahead of a newer one, preserving order.
|
|
132
|
+
const raw = await redis.lmove(inProgressKey, key, 'RIGHT', 'LEFT');
|
|
133
|
+
if (raw === null || raw === undefined)
|
|
134
|
+
break;
|
|
135
|
+
reclaimed++;
|
|
136
|
+
}
|
|
137
|
+
if (reclaimed > 0) {
|
|
138
|
+
emitCounter('audit_spool_recovered_total', {}, reclaimed);
|
|
139
|
+
logger.warn('Audit spool recovered stranded in-progress events', { reclaimed });
|
|
140
|
+
}
|
|
141
|
+
return reclaimed;
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
logger.warn('Audit spool recover failed', { error: errMsg(err) });
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
async depth() {
|
|
149
|
+
try {
|
|
150
|
+
return await redis.llen(key);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build a Redis-backed audit spool from the ambient `REDIS_URL` / `REDIS_HOST`
|
|
160
|
+
* env via the shared `createEnvRedisClient`. Returns `null` when Redis is not
|
|
161
|
+
* configured or the client can't be constructed — the caller then runs without a
|
|
162
|
+
* spool (pre-spool best-effort behavior), never crashing. The shared helper loads
|
|
163
|
+
* ioredis via `createRequire`, so this stays importable where Redis isn't present.
|
|
164
|
+
*/
|
|
165
|
+
export function createEnvRedisAuditSpool(opts = {}) {
|
|
166
|
+
const inst = createEnvRedisClient('audit-spool');
|
|
167
|
+
if (!inst)
|
|
168
|
+
return null;
|
|
169
|
+
logger.info('Redis audit spool initialized');
|
|
170
|
+
return createRedisAuditSpool(inst, opts);
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXVkaXQtc3Bvb2wuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZXMvYXVkaXQtc3Bvb2wudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV0RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDbEQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRXpELE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxhQUFhLENBQUMsQ0FBQztBQStEM0MsTUFBTSxpQkFBaUIsR0FBRyxhQUFhLENBQUM7QUFDeEMsTUFBTSxpQkFBaUIsR0FBRyxNQUFNLENBQUM7QUFDakMsb0ZBQW9GO0FBQ3BGLE1BQU0sbUJBQW1CLEdBQUcsT0FBTyxDQUFDO0FBRXBDLFNBQVMsTUFBTSxDQUFDLEdBQVk7SUFDMUIsT0FBTyxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDMUQsQ0FBQztBQUVELFNBQVMsU0FBUyxDQUFDLEdBQVc7SUFDNUIsSUFBSSxDQUFDO1FBQ0gsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQW9CLENBQUM7UUFDbEQsT0FBTyxNQUFNLElBQUksTUFBTSxDQUFDLEtBQUssSUFBSSxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUN0RSxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUscUJBQXFCLENBQ25DLEtBQXNCLEVBQ3RCLElBQUksR0FBd0Ms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LENBQUM7Z0JBQ0QsSUFBSSxTQUFTLEdBQUcsQ0FBQyxFQUFFLENBQUM7b0JBQ2xCLFdBQVcsQ0FBQyw2QkFBNkIsRUFBRSxFQUFFLEVBQUUsU0FBUyxDQUFDLENBQUM7b0JBQzFELE1BQU0sQ0FBQyxJQUFJLENBQUMsbURBQW1ELEVBQUUsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO2dCQUNsRixDQUFDO2dCQUNELE9BQU8sU0FBUyxDQUFDO1lBQ25CLENBQUM7WUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO2dCQUNiLE1BQU0sQ0FBQyxJQUFJLENBQUMsNEJBQTRCLEVBQUUsRUFBRSxLQUFLLEVBQUUsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztnQkFDbEUsT0FBTyxDQUFDLENBQUM7WUFDWCxDQUFDO1FBQ0gsQ0FBQztRQUVELEtBQUssQ0FBQyxLQUFLO1lBQ1QsSUFBSSxDQUFDO2dCQUNILE9BQU8sTUFBTSxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQy9CLENBQUM7WUFBQyxNQUFNLENBQUM7Z0JBQ1AsT0FBTyxDQUFDLENBQUM7WUFDWCxDQUFDO1FBQ0gsQ0FBQztLQUNGLENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLHdCQUF3QixDQUFDLElBQUksR0FBd0MsRUFBRTtJQUNyRixNQUFNLElBQUksR0FBRyxvQkFBb0IsQ0FBa0IsYUFBYSxDQUFDLENBQUM7SUFDbEUsSUFBSSxDQUFDLElBQUk7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN2QixNQUFNLENBQUMsSUFBSSxDQUFDLCtCQUErQixDQUFDLENBQUM7SUFDN0MsT0FBTyxxQkFBcUIsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDM0MsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBjcmVhdGVFbnZSZWRpc0NsaWVudCB9IGZyb20gJy4vZW52LXJlZGlzLmpzJztcbmltcG9ydCB0eXBlIHsgUmVtb3RlQXVkaXRFdmVudCB9IGZyb20gJy4vcmVtb3RlLWF1ZGl0LWNsaWVudC5qcyc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIgfSBmcm9tICcuLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHsgZW1pdENvdW50ZXIgfSBmcm9tICcuLi91dGlscy9tZXRyaWMtZW1pdHRlci5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignYXVkaXQtc3Bvb2wnKTtcblxuLyoqIEEgYnVmZmVyZWQgZW1pc3Npb246IHRoZSBldmVudCBwbHVzIHdoaWNoIHNlcnZpY2UgbXVzdCByZS1kZWxpdmVyIGl0LiAqL1xuZXhwb3J0IGludGVyZmFjZSBBdWRpdFNwb29sRW50cnkge1xuICBldmVudDogUmVtb3RlQXVkaXRFdmVudDtcbiAgc2VydmljZU5hbWU6IHN0cmluZztcbn1cblxuLyoqXG4gKiBEdXJhYmxlIGJ1ZmZlciBmb3IgYXVkaXQgZXZlbnRzIHRoYXQgZmFpbGVkIExJVkUgZGVsaXZlcnkgdG8gdGhlIHBsYXRmb3JtXG4gKiAocmV0cmllcyBleGhhdXN0ZWQgZHVyaW5nIGEgc3VzdGFpbmVkIG91dGFnZSkuIFdpdGhvdXQgdGhpcywgdGhlIGF1ZGl0IHRyYWlsIOKAlFxuICogYSBzZWN1cml0eSBsb2cg4oCUIGlzIHNpbGVudGx5IGxvc3QgdGhlIG1vbWVudCB0aGUgcGxhdGZvcm0gaXMgZG93biBmb3IgbG9uZ2VyXG4gKiB0aGFuIHRoZSBjbGllbnQncyBzbWFsbCByZXRyeSBidWRnZXQuXG4gKlxuICogQ29udHJhY3Q6XG4gKiAtIEJPVU5ERUQuIEEgc2VjdXJpdHkgbG9nIG11c3QgZGVncmFkZSB0byBcImRyb3BwZWQgdGhlIG9sZGVzdCwgbG91ZGx5XCIgKGFcbiAqICAgbWV0cmljKSByYXRoZXIgdGhhbiBncm93IHdpdGhvdXQgbGltaXQgYW5kIE9PTSB0aGUgZW1pdHRlci5cbiAqIC0gRkFJTC1TQUZFLiBFdmVyeSBtZXRob2Qgc3dhbGxvd3MgaXRzIG93biBlcnJvcnMg4oCUIGEgYnJva2VuL3VucmVhY2hhYmxlIHNwb29sXG4gKiAgIG11c3QgbmV2ZXIgYnJlYWsgb3IgYmxvY2sgdGhlIGF1ZGl0IGVtaXNzaW9uIHBhdGggKHdoaWNoIGlzIGl0c2VsZlxuICogICBmaXJlLWFuZC1mb3JnZXQpLiBBIHNwb29sIHByb2JsZW0gZGVncmFkZXMgdG8gdGhlIHByZS1zcG9vbCBiZWhhdmlvci5cbiAqIC0gT3JkZXJpbmcgaXMgYmVzdC1lZmZvcnQuIFJlLWRlbGl2ZXJlZCBldmVudHMgY2hhaW4gYXQgcmUtZGVsaXZlcnkgdGltZVxuICogICAodGhhdCdzIHdoZW4gdGhlIHBsYXRmb3JtIGluc2VydHMgdGhlbSksIHNvIGVhY2ggZXZlbnQgYWxzbyBjYXJyaWVzIGFuXG4gKiAgIGBvY2N1cnJlZEF0YCBzdGFtcGVkIGF0IGVtaXNzaW9uIGZvciByZXZpZXdlcnMg4oCUIHNlZSBSZW1vdGVBdWRpdEV2ZW50LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEF1ZGl0U3Bvb2wge1xuICAvKiogQnVmZmVyIGFuIGV2ZW50IHRoYXQgZmFpbGVkIGxpdmUgZGVsaXZlcnkuICovXG4gIGVucXVldWUoZW50cnk6IEF1ZGl0U3Bvb2xFbnRyeSk6IFByb21pc2U8dm9pZD47XG4gIC8qKlxuICAgKiBBdG9taWNhbGx5IE1PVkUgdXAgdG8gYG1heGAgYnVmZmVyZWQgZW50cmllcyBvbnRvIGFuIGluLXByb2dyZXNzIGxpc3QgYW5kXG4gICAqIHJldHVybiB0aGVtLiBCZWNhdXNlIHRoZSBlbnRyaWVzIGFyZSBtb3ZlZCAobm90IHBvcHBlZC1hbmQtZHJvcHBlZCksIGEgY3Jhc2hcbiAgICogbWlkLWRyYWluIGxlYXZlcyB0aGVtIHJlY292ZXJhYmxlIG9uIHRoZSBpbi1wcm9ncmVzcyBsaXN0IOKAlCBzZWUgYHJlY292ZXJgLlxuICAgKiBFYWNoIHRha2VuIGVudHJ5IE1VU1QgYmUgcmVzb2x2ZWQgd2l0aCBleGFjdGx5IG9uZSBvZiBgYWNrYCAoZGVsaXZlcmVkKSBvclxuICAgKiBgcmVxdWV1ZWAgKHN0aWxsIGZhaWxpbmcpLCBvciBpdCBsaW5nZXJzIG9uIHRoZSBpbi1wcm9ncmVzcyBsaXN0IHVudGlsIHRoZVxuICAgKiBuZXh0IGByZWNvdmVyYC5cbiAgICovXG4gIHRha2UobWF4OiBudW1iZXIpOiBQcm9taXNlPEF1ZGl0U3Bvb2xFbnRyeVtdPjtcbiAgLyoqIEFja25vd2xlZGdlIHN1Y2Nlc3NmdWxseSByZS1kZWxpdmVyZWQgZW50cmllcyDigJQgcmVtb3ZlcyB0aGVtIGZyb20gdGhlXG4gICAqICBpbi1wcm9ncmVzcyBsaXN0IHNvIHRoZXkgYXJlIG5vdCByZWNvdmVyZWQgYWdhaW4uICovXG4gIGFjayhlbnRyaWVzOiBBdWRpdFNwb29sRW50cnlbXSk6IFByb21pc2U8dm9pZD47XG4gIC8qKiBSZXR1cm4gZW50cmllcyB3aG9zZSByZS1kZWxpdmVyeSBmYWlsZWQgdG8gdGhlIEhFQUQgb2YgdGhlIGJ1ZmZlciAocmV0cmllZFxuICAgKiAgZmlyc3QpIGFuZCBjbGVhciB0aGVtIGZyb20gdGhlIGluLXByb2dyZXNzIGxpc3QuIEJlc3QtZWZmb3J0LiAqL1xuICByZXF1ZXVlKGVudHJpZXM6IEF1ZGl0U3Bvb2xFbnRyeVtdKTogUHJvbWlzZTx2b2lkPjtcbiAgLyoqIFJlY2xhaW0gYW55IGVudHJpZXMgc3RyYW5kZWQgb24gdGhlIGluLXByb2dyZXNzIGxpc3QgYnkgYSBwcmlvciBjcmFzaCxcbiAgICogIG1vdmluZyB0aGVtIGJhY2sgdG8gdGhlIGhlYWQgb2YgdGhlIG1haW4gYnVmZmVyLiBDYWxsIG9uY2UgYXQgc3RhcnR1cC5cbiAgICogIFJldHVybnMgdGhlIG51bWJlciByZWNsYWltZWQuICovXG4gIHJlY292ZXIoKTogUHJvbWlzZTxudW1iZXI+O1xuICAvKiogQXBwcm94aW1hdGUgY3VycmVudCBkZXB0aCAoZm9yIG1ldHJpY3MgLyBkcmFpbiBkZWNpc2lvbnMpLiAqL1xuICBkZXB0aCgpOiBQcm9taXNlPG51bWJlcj47XG59XG5cbi8qKiBNaW5pbWFsIFJlZGlzIExJU1Qgc3VyZmFjZSB0aGUgc3Bvb2wgbmVlZHMgKGEgc3Vic2V0IG9mIGlvcmVkaXMpLiAqL1xuaW50ZXJmYWNlIFJlZGlzTGlzdENsaWVudCB7XG4gIHJwdXNoKGtleTogc3RyaW5nLCAuLi52YWx1ZXM6IHN0cmluZ1tdKTogUHJvbWlzZTxudW1iZXI+O1xuICBscHVzaChrZXk6IHN0cmluZywgLi4udmFsdWVzOiBzdHJpbmdbXSk6IFByb21pc2U8bnVtYmVyPjtcbiAgbHBvcChrZXk6IHN0cmluZywgY291bnQ6IG51bWJlcik6IFByb21pc2U8c3RyaW5nW10gfCBudWxsPjtcbiAgbHRyaW0oa2V5OiBzdHJpbmcsIHN0YXJ0OiBudW1iZXIsIHN0b3A6IG51bWJlcik6IFByb21pc2U8dW5rbm93bj47XG4gIGxsZW4oa2V5OiBzdHJpbmcpOiBQcm9taXNlPG51bWJlcj47XG4gIC8qKiBBdG9taWNhbGx5IHBvcCBmcm9tIG9uZSBsaXN0IGVuZCBhbmQgcHVzaCB0byBhbm90aGVyIChSZWRpcyDiiaUgNi4yKS4gKi9cbiAgbG1vdmUoc291cmNlOiBzdHJpbmcsIGRlc3RpbmF0aW9uOiBzdHJpbmcsIGZyb206ICdMRUZUJyB8ICdSSUdIVCcsIHRvOiAnTEVGVCcgfCAnUklHSFQnKTogUHJvbWlzZTxzdHJpbmcgfCBudWxsPjtcbiAgLyoqIFJlbW92ZSBgY291bnRgIG9jY3VycmVuY2VzIG9mIGB2YWx1ZWAgZnJvbSBhIGxpc3QuICovXG4gIGxyZW0oa2V5OiBzdHJpbmcsIGNvdW50OiBudW1iZXIsIHZhbHVlOiBzdHJpbmcpOiBQcm9taXNlPG51bWJlcj47XG59XG5cbmNvbnN0IERFRkFVTFRfU1BPT0xfS0VZID0gJ2F1ZGl0OnNwb29sJztcbmNvbnN0IERFRkFVTFRfTUFYX0RFUFRIID0gMTBfMDAwO1xuLyoqIEd1YXJkIHNvIGEgcGF0aG9sb2dpY2FsIGluLXByb2dyZXNzIGxpc3QgY2FuJ3Qgc3BpbiBgcmVjb3ZlcmAvYHRha2VgIGZvcmV2ZXIuICovXG5jb25zdCBNQVhfTU9WRV9JVEVSQVRJT05TID0gMTAwXzAwMDtcblxuZnVuY3Rpb24gZXJyTXNnKGVycjogdW5rbm93bik6IHN0cmluZyB7XG4gIHJldHVybiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycik7XG59XG5cbmZ1bmN0aW9uIHNhZmVQYXJzZShyYXc6IHN0cmluZyk6IEF1ZGl0U3Bvb2xFbnRyeSB8IG51bGwge1xuICB0cnkge1xuICAgIGNvbnN0IHBhcnNlZCA9IEpTT04ucGFyc2UocmF3KSBhcyBBdWRpdFNwb29sRW50cnk7XG4gICAgcmV0dXJuIHBhcnNlZCAmJiBwYXJzZWQuZXZlbnQgJiYgcGFyc2VkLnNlcnZpY2VOYW1lID8gcGFyc2VkIDogbnVsbDtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbn1cblxuLyoqXG4gKiBSZWRpcy1MSVNULWJhY2tlZCBzcG9vbC4gRHVyYWJsZSBhY3Jvc3MgcHJvY2VzcyByZXN0YXJ0cyAodW5saWtlIGFuIGluLW1lbW9yeVxuICogb3IgcGVyLXBvZC1kaXNrIGJ1ZmZlciksIHNoYXJlZCBieSBhbGwgcmVwbGljYXMgb2YgYSBzZXJ2aWNlLCBhbmQgYm91bmRlZCB2aWFcbiAqIGEgdHJpbS10by10YWlsIG9uIG92ZXJmbG93IChkcm9wcyB0aGUgT0xERVNULCBlbWl0dGluZyBgYXVkaXRfc3Bvb2xfZHJvcHBlZF90b3RhbGApLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlUmVkaXNBdWRpdFNwb29sKFxuICByZWRpczogUmVkaXNMaXN0Q2xpZW50LFxuICBvcHRzOiB7IG1heERlcHRoPzogbnVtYmVyOyBrZXk/OiBzdHJpbmcgfSA9IHt9LFxuKTogQXVkaXRTcG9vbCB7XG4gIGNvbnN0IGtleSA9IG9wdHMua2V5ID8/IERFRkFVTFRfU1BPT0xfS0VZO1xuICAvLyBUaGUgaW4tcHJvZ3Jlc3MgXCJwcm9jZXNzaW5nXCIgbGlzdCDigJQgYSB0YWtlbiBiYXRjaCBsaXZlcyBoZXJlIHVudGlsIGl0IGlzXG4gIC8vIGFja2VkIChkZWxpdmVyZWQpIG9yIHJlcXVldWVkIChzdGlsbCBmYWlsaW5nKS4gSXQgaXMgYSBzZWN1cml0eSBsb2cnc1xuICAvLyBkdXJhYmlsaXR5IG5ldDogYSBjcmFzaCBiZXR3ZWVuIHRha2UgYW5kIGFjay9yZXF1ZXVlIGxlYXZlcyB0aGUgYmF0Y2ggaGVyZSxcbiAgLy8gYW5kIGByZWNvdmVyYCByZWNsYWltcyBpdCBvbiByZXN0YXJ0IGluc3RlYWQgb2Ygc2lsZW50bHkgbG9zaW5nIGl0LlxuICBjb25zdCBpblByb2dyZXNzS2V5ID0gYCR7a2V5fTppbmZsaWdodGA7XG4gIGNvbnN0IG1heERlcHRoID0gTWF0aC5tYXgoMSwgb3B0cy5tYXhEZXB0aCA/PyBERUZBVUxUX01BWF9ERVBUSCk7XG5cbiAgLy8gVGhlIEVYQUNUIHNlcmlhbGl6ZWQgc3RyaW5nIGVhY2ggcmV0dXJuZWQgZW50cnkgd2FzIHBhcnNlZCBmcm9tLCBzbyBgYWNrYCAvXG4gIC8vIGByZXF1ZXVlYCBjYW4gTFJFTSB0aGUgcHJlY2lzZSB2YWx1ZSBvZmYgdGhlIGluLXByb2dyZXNzIGxpc3QuIEtleWVkIGJ5IHRoZVxuICAvLyByZXR1cm5lZCBvYmplY3QgaWRlbnRpdHkgKHRoZSBkcmFpbiBjb25zdW1lciBwYXNzZXMgdGhlIFNBTUUgb2JqZWN0cyBiYWNrKSxcbiAgLy8gc28gaXQgbmVlZHMgbm8gc3RhYmxlIHJlLXNlcmlhbGl6YXRpb24uIFdlYWtNYXAg4oaSIGVudHJpZXMgR0MgbmF0dXJhbGx5LlxuICBjb25zdCByYXdCeUVudHJ5ID0gbmV3IFdlYWtNYXA8QXVkaXRTcG9vbEVudHJ5LCBzdHJpbmc+KCk7XG4gIGNvbnN0IHJhd09mID0gKGVudHJ5OiBBdWRpdFNwb29sRW50cnkpOiBzdHJpbmcgPT4gcmF3QnlFbnRyeS5nZXQoZW50cnkpID8/IEpTT04uc3RyaW5naWZ5KGVudHJ5KTtcblxuICByZXR1cm4ge1xuICAgIGFzeW5jIGVucXVldWUoZW50cnkpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGxlbiA9IGF3YWl0IHJlZGlzLnJwdXNoKGtleSwgSlNPTi5zdHJpbmdpZnkoZW50cnkpKTtcbiAgICAgICAgZW1pdENvdW50ZXIoJ2F1ZGl0X3Nwb29sX2VucXVldWVkX3RvdGFsJywgeyBzZXJ2aWNlOiBlbnRyeS5zZXJ2aWNlTmFtZSB9KTtcbiAgICAgICAgaWYgKGxlbiA+IG1heERlcHRoKSB7XG4gICAgICAgICAgLy8gS2VlcCBvbmx5IHRoZSBuZXdlc3QgYG1heERlcHRoYDsgZHJvcCB0aGUgb3ZlcmZsb3cgYXQgdGhlIGhlYWQuXG4gICAgICAgICAgY29uc3QgZHJvcHBlZCA9IGxlbiAtIG1heERlcHRoO1xuICAgICAgICAgIGF3YWl0IHJlZGlzLmx0cmltKGtleSwgZHJvcHBlZCwgLTEpO1xuICAgICAgICAgIGVtaXRDb3VudGVyKCdhdWRpdF9zcG9vbF9kcm9wcGVkX3RvdGFsJywgeyBzZXJ2aWNlOiBlbnRyeS5zZXJ2aWNlTmFtZSB9LCBkcm9wcGVkKTtcbiAgICAgICAgICBsb2dnZXIud2FybignQXVkaXQgc3Bvb2wgb3ZlcmZsb3cg4oCUIGRyb3BwZWQgb2xkZXN0IGJ1ZmZlcmVkIGV2ZW50cycsIHsgZHJvcHBlZCwgbWF4RGVwdGggfSk7XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBsb2dnZXIud2FybignQXVkaXQgc3Bvb2wgZW5xdWV1ZSBmYWlsZWQgKGV2ZW50IGxvc3QpJywgeyBlcnJvcjogZXJyTXNnKGVycikgfSk7XG4gICAgICB9XG4gICAgfSxcblxuICAgIGFzeW5jIHRha2UobWF4KSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBvdXQ6IEF1ZGl0U3Bvb2xFbnRyeVtdID0gW107XG4gICAgICAgIGNvbnN0IGxpbWl0ID0gTWF0aC5tYXgoMSwgbWF4KTtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBsaW1pdDsgaSsrKSB7XG4gICAgICAgICAgLy8gQXRvbWljYWxseSBNT1ZFIGhlYWTihpJpbi1wcm9ncmVzcy10YWlsIHNvIHRoZSBlbnRyeSBpcyBuZXZlciBpbiBsaW1ibzpcbiAgICAgICAgICAvLyBpdCBpcyBvbiBleGFjdGx5IG9uZSBsaXN0IGF0IGFsbCB0aW1lcywgcmVjb3ZlcmFibGUgYWZ0ZXIgYSBjcmFzaC5cbiAgICAgICAgICBjb25zdCByYXcgPSBhd2FpdCByZWRpcy5sbW92ZShrZXksIGluUHJvZ3Jlc3NLZXksICdMRUZUJywgJ1JJR0hUJyk7XG4gICAgICAgICAgaWYgKHJhdyA9PT0gbnVsbCB8fCByYXcgPT09IHVuZGVmaW5lZCkgYnJlYWs7XG4gICAgICAgICAgY29uc3QgcGFyc2VkID0gc2FmZVBhcnNlKHJhdyk7XG4gICAgICAgICAgaWYgKHBhcnNlZCkge1xuICAgICAgICAgICAgcmF3QnlFbnRyeS5zZXQocGFyc2VkLCByYXcpO1xuICAgICAgICAgICAgb3V0LnB1c2gocGFyc2VkKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gQ29ycnVwdCBwYXlsb2FkIOKAlCBkcm9wIGl0IG9mZiB0aGUgaW4tcHJvZ3Jlc3MgbGlzdCBzbyBpdCBjYW4ndCB3ZWRnZVxuICAgICAgICAgICAgLy8gdGhlIGRyYWluLCBhbmQgYWNjb3VudCBmb3IgaXQuXG4gICAgICAgICAgICBhd2FpdCByZWRpcy5scmVtKGluUHJvZ3Jlc3NLZXksIDEsIHJhdyk7XG4gICAgICAgICAgICBlbWl0Q291bnRlcignYXVkaXRfc3Bvb2xfZHJvcHBlZF90b3RhbCcsIHsgc2VydmljZTogJ3Vua25vd24nIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gb3V0O1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKCdBdWRpdCBzcG9vbCB0YWtlIGZhaWxlZCcsIHsgZXJyb3I6IGVyck1zZyhlcnIpIH0pO1xuICAgICAgICByZXR1cm4gW107XG4gICAgICB9XG4gICAgfSxcblxuICAgIGFzeW5jIGFjayhlbnRyaWVzKSB7XG4gICAgICBpZiAoZW50cmllcy5sZW5ndGggPT09IDApIHJldHVybjtcbiAgICAgIHRyeSB7XG4gICAgICAgIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgICAgICAgIGF3YWl0IHJlZGlzLmxyZW0oaW5Qcm9ncmVzc0tleSwgMSwgcmF3T2YoZW50cnkpKTtcbiAgICAgICAgICByYXdCeUVudHJ5LmRlbGV0ZShlbnRyeSk7XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBsb2dnZXIud2FybignQXVkaXQgc3Bvb2wgYWNrIGZhaWxlZCAobWF5IHJlLWRlbGl2ZXIgb24gcmVjb3ZlciknLCB7IGVycm9yOiBlcnJNc2coZXJyKSB9KTtcbiAgICAgIH1cbiAgICB9LFxuXG4gICAgYXN5bmMgcmVxdWV1ZShlbnRyaWVzKSB7XG4gICAgICBpZiAoZW50cmllcy5sZW5ndGggPT09IDApIHJldHVybjtcbiAgICAgIHRyeSB7XG4gICAgICAgIC8vIFB1dCBmYWlsZWQgcmUtZGVsaXZlcmllcyBiYWNrIGF0IHRoZSBIRUFEIHNvIHRoZXkncmUgcmV0cmllZCBmaXJzdCBhbmRcbiAgICAgICAgLy8gcm91Z2ggZW1pc3Npb24gb3JkZXIgaXMgcHJlc2VydmVkIGFjcm9zcyB0aGUgb3V0YWdlIOKAlCBhbmQgY2xlYXIgdGhlbVxuICAgICAgICAvLyBmcm9tIHRoZSBpbi1wcm9ncmVzcyBsaXN0IHNvIGByZWNvdmVyYCBkb2Vzbid0IGRvdWJsZSB0aGVtLiBJdGVyYXRlIGluXG4gICAgICAgIC8vIHJldmVyc2Ugc28gbHB1c2ggcmVzdG9yZXMgdGhlIGJhdGNoJ3Mgb3JpZ2luYWwgb3JkZXIgYXQgdGhlIGhlYWQuXG4gICAgICAgIC8vXG4gICAgICAgIC8vIE9SREVSSU5HIElOVkFSSUFOVCAoYXQtbGVhc3Qtb25jZSk6IGxwdXNoIHRvIHRoZSBtYWluIGxpc3QgRklSU1QsIFRIRU5cbiAgICAgICAgLy8gbHJlbSBmcm9tIGluLXByb2dyZXNzLiBBIGNyYXNoIGJldHdlZW4gdGhlIHR3byBsZWF2ZXMgdGhlIGVudHJ5IG9uIEJPVEhcbiAgICAgICAgLy8gbGlzdHMg4oaSIGhhcm1sZXNzIGRvdWJsZS1kZWxpdmVyeSAoZGVkdXBlZCBkb3duc3RyZWFtIGJ5IGlkZW1wb3RlbmN5XG4gICAgICAgIC8vIHJldXNlKSwgbmV2ZXIgYSBMT1NTLiBUaGUgcmV2ZXJzZSBvcmRlciAobHJlbSB0aGVuIGxwdXNoKSB3b3VsZCBkcm9wIHRoZVxuICAgICAgICAvLyBldmVudCBvdXRyaWdodCBvbiBhIGNyYXNoIGluIHRoZSBnYXAsIHdoaWNoIGEgc2VjdXJpdHkgbG9nIG11c3Qgbm90IGRvLlxuICAgICAgICBmb3IgKGxldCBpID0gZW50cmllcy5sZW5ndGggLSAxOyBpID49IDA7IGktLSkge1xuICAgICAgICAgIGNvbnN0IHJhdyA9IHJhd09mKGVudHJpZXNbaV0pO1xuICAgICAgICAgIGF3YWl0IHJlZGlzLmxwdXNoKGtleSwgcmF3KTtcbiAgICAgICAgICBhd2FpdCByZWRpcy5scmVtKGluUHJvZ3Jlc3NLZXksIDEsIHJhdyk7XG4gICAgICAgICAgcmF3QnlFbnRyeS5kZWxldGUoZW50cmllc1tpXSk7XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBsb2dnZXIud2FybignQXVkaXQgc3Bvb2wgcmVxdWV1ZSBmYWlsZWQgKGV2ZW50cyBsb3N0KScsIHsgZXJyb3I6IGVyck1zZyhlcnIpIH0pO1xuICAgICAgfVxuICAgIH0sXG5cbiAgICBhc3luYyByZWNvdmVyKCkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgbGV0IHJlY2xhaW1lZCA9IDA7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgTUFYX01PVkVfSVRFUkFUSU9OUzsgaSsrKSB7XG4gICAgICAgICAgLy8gTW92ZSBpbi1wcm9ncmVzcy10YWlsIOKGkiBtYWluLWhlYWQ6IGFuIG9sZGVyIHN0cmFuZGVkIGVudHJ5IChuZWFyZXIgdGhlXG4gICAgICAgICAgLy8gaW4tcHJvZ3Jlc3MgaGVhZCkgZW5kcyB1cCBhaGVhZCBvZiBhIG5ld2VyIG9uZSwgcHJlc2VydmluZyBvcmRlci5cbiAgICAgICAgICBjb25zdCByYXcgPSBhd2FpdCByZWRpcy5sbW92ZShpblByb2dyZXNzS2V5LCBrZXksICdSSUdIVCcsICdMRUZUJyk7XG4gICAgICAgICAgaWYgKHJhdyA9PT0gbnVsbCB8fCByYXcgPT09IHVuZGVmaW5lZCkgYnJlYWs7XG4gICAgICAgICAgcmVjbGFpbWVkKys7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHJlY2xhaW1lZCA+IDApIHtcbiAgICAgICAgICBlbWl0Q291bnRlcignYXVkaXRfc3Bvb2xfcmVjb3ZlcmVkX3RvdGFsJywge30sIHJlY2xhaW1lZCk7XG4gICAgICAgICAgbG9nZ2VyLndhcm4oJ0F1ZGl0IHNwb29sIHJlY292ZXJlZCBzdHJhbmRlZCBpbi1wcm9ncmVzcyBldmVudHMnLCB7IHJlY2xhaW1lZCB9KTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gcmVjbGFpbWVkO1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKCdBdWRpdCBzcG9vbCByZWNvdmVyIGZhaWxlZCcsIHsgZXJyb3I6IGVyck1zZyhlcnIpIH0pO1xuICAgICAgICByZXR1cm4gMDtcbiAgICAgIH1cbiAgICB9LFxuXG4gICAgYXN5bmMgZGVwdGgoKSB7XG4gICAgICB0cnkge1xuICAgICAgICByZXR1cm4gYXdhaXQgcmVkaXMubGxlbihrZXkpO1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIHJldHVybiAwO1xuICAgICAgfVxuICAgIH0sXG4gIH07XG59XG5cbi8qKlxuICogQnVpbGQgYSBSZWRpcy1iYWNrZWQgYXVkaXQgc3Bvb2wgZnJvbSB0aGUgYW1iaWVudCBgUkVESVNfVVJMYCAvIGBSRURJU19IT1NUYFxuICogZW52IHZpYSB0aGUgc2hhcmVkIGBjcmVhdGVFbnZSZWRpc0NsaWVudGAuIFJldHVybnMgYG51bGxgIHdoZW4gUmVkaXMgaXMgbm90XG4gKiBjb25maWd1cmVkIG9yIHRoZSBjbGllbnQgY2FuJ3QgYmUgY29uc3RydWN0ZWQg4oCUIHRoZSBjYWxsZXIgdGhlbiBydW5zIHdpdGhvdXQgYVxuICogc3Bvb2wgKHByZS1zcG9vbCBiZXN0LWVmZm9ydCBiZWhhdmlvciksIG5ldmVyIGNyYXNoaW5nLiBUaGUgc2hhcmVkIGhlbHBlciBsb2Fkc1xuICogaW9yZWRpcyB2aWEgYGNyZWF0ZVJlcXVpcmVgLCBzbyB0aGlzIHN0YXlzIGltcG9ydGFibGUgd2hlcmUgUmVkaXMgaXNuJ3QgcHJlc2VudC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUVudlJlZGlzQXVkaXRTcG9vbChvcHRzOiB7IG1heERlcHRoPzogbnVtYmVyOyBrZXk/OiBzdHJpbmcgfSA9IHt9KTogQXVkaXRTcG9vbCB8IG51bGwge1xuICBjb25zdCBpbnN0ID0gY3JlYXRlRW52UmVkaXNDbGllbnQ8UmVkaXNMaXN0Q2xpZW50PignYXVkaXQtc3Bvb2wnKTtcbiAgaWYgKCFpbnN0KSByZXR1cm4gbnVsbDtcbiAgbG9nZ2VyLmluZm8oJ1JlZGlzIGF1ZGl0IHNwb29sIGluaXRpYWxpemVkJyk7XG4gIHJldHVybiBjcmVhdGVSZWRpc0F1ZGl0U3Bvb2woaW5zdCwgb3B0cyk7XG59XG4iXX0=
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Redis-like client interface (subset of ioredis).
|
|
3
|
+
* Services pass their own Redis client instance.
|
|
4
|
+
*/
|
|
5
|
+
export interface RedisCacheClient {
|
|
6
|
+
get(key: string): Promise<string | null>;
|
|
7
|
+
set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
|
|
8
|
+
del(...keys: string[]): Promise<number>;
|
|
9
|
+
keys(pattern: string): Promise<string[]>;
|
|
10
|
+
/** SCAN-based iteration (preferred over KEYS for production). */
|
|
11
|
+
scanStream?(options: {
|
|
12
|
+
match: string;
|
|
13
|
+
count?: number;
|
|
14
|
+
}): NodeJS.ReadableStream;
|
|
15
|
+
}
|
|
16
|
+
export interface CacheConfig {
|
|
17
|
+
/** Key prefix for namespace isolation (e.g., 'plugin:', 'compliance:') */
|
|
18
|
+
prefix: string;
|
|
19
|
+
/** Default TTL in seconds */
|
|
20
|
+
defaultTtlSeconds: number;
|
|
21
|
+
/** Max entries for in-memory cache (default 1000) */
|
|
22
|
+
maxEntries?: number;
|
|
23
|
+
/** Optional Redis client — uses in-memory cache if not provided */
|
|
24
|
+
redis?: RedisCacheClient;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Cache service with get/set/del operations and automatic TTL expiry.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const cache = new CacheService({ prefix: 'plugin:', defaultTtlSeconds: 300 });
|
|
32
|
+
*
|
|
33
|
+
* // Set with default TTL
|
|
34
|
+
* await cache.set('org123:list', plugins);
|
|
35
|
+
*
|
|
36
|
+
* // Get (returns null on miss)
|
|
37
|
+
* const cached = await cache.get<Plugin[]>('org123:list');
|
|
38
|
+
*
|
|
39
|
+
* // Invalidate
|
|
40
|
+
* await cache.del('org123:list');
|
|
41
|
+
*
|
|
42
|
+
* // Invalidate by pattern
|
|
43
|
+
* await cache.invalidatePattern('org123:*');
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare class CacheService {
|
|
47
|
+
private memory;
|
|
48
|
+
/**
|
|
49
|
+
* In-flight `getOrSet` factory promises, keyed by full cache key. Coalesces
|
|
50
|
+
* concurrent cold callers onto a single `factory()` invocation (single-flight
|
|
51
|
+
* / stampede protection); entries are cleared once the promise settles.
|
|
52
|
+
*/
|
|
53
|
+
private inflight;
|
|
54
|
+
private readonly prefix;
|
|
55
|
+
private readonly defaultTtlMs;
|
|
56
|
+
private readonly maxEntries;
|
|
57
|
+
private readonly redis?;
|
|
58
|
+
/** Cache metrics — tracks hits, misses, and invalidations. */
|
|
59
|
+
readonly metrics: {
|
|
60
|
+
hits: number;
|
|
61
|
+
misses: number;
|
|
62
|
+
sets: number;
|
|
63
|
+
invalidations: number;
|
|
64
|
+
};
|
|
65
|
+
constructor(config: CacheConfig);
|
|
66
|
+
private fullKey;
|
|
67
|
+
/**
|
|
68
|
+
* Get a cached value. Returns null on miss or error.
|
|
69
|
+
*
|
|
70
|
+
* The returned value is an INDEPENDENT deep copy — mutating it never affects
|
|
71
|
+
* the cached entry (the in-memory backend clones on read for parity with
|
|
72
|
+
* Redis, which returns a fresh JSON clone). Callers may still treat results as
|
|
73
|
+
* immutable, but doing so is no longer load-bearing for cache integrity.
|
|
74
|
+
*/
|
|
75
|
+
get<T>(key: string): Promise<T | null>;
|
|
76
|
+
/**
|
|
77
|
+
* Set a cached value with optional TTL override.
|
|
78
|
+
*
|
|
79
|
+
* @param key - Cache key (prefix is added automatically)
|
|
80
|
+
* @param value - Value to cache (must be JSON-serializable for Redis)
|
|
81
|
+
* @param ttlSeconds - TTL override (uses default if not provided)
|
|
82
|
+
*/
|
|
83
|
+
set<T>(key: string, value: T, ttlSeconds?: number): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Delete a cached value.
|
|
86
|
+
*/
|
|
87
|
+
del(key: string): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Invalidate all keys matching a pattern (e.g., 'org123:*').
|
|
90
|
+
* Uses SCAN for Redis (non-blocking) with KEYS fallback, or regex for in-memory.
|
|
91
|
+
*/
|
|
92
|
+
invalidatePattern(pattern: string): Promise<number>;
|
|
93
|
+
/** Collect keys via Redis SCAN (non-blocking alternative to KEYS). */
|
|
94
|
+
private scanKeys;
|
|
95
|
+
/**
|
|
96
|
+
* Get or compute: returns cached value if available, otherwise calls the
|
|
97
|
+
* factory function, caches the result, and returns it.
|
|
98
|
+
*
|
|
99
|
+
* Single-flight / stampede protection: when N callers miss concurrently for
|
|
100
|
+
* the same key, only ONE `factory()` runs — the rest await its shared promise.
|
|
101
|
+
* The in-flight entry is cleared once it settles (success or failure), so a
|
|
102
|
+
* failed factory doesn't poison later calls.
|
|
103
|
+
*
|
|
104
|
+
* @param key - Cache key
|
|
105
|
+
* @param factory - Async function to compute the value on cache miss
|
|
106
|
+
* @param ttlSeconds - Optional TTL override
|
|
107
|
+
* @returns The cached or computed value
|
|
108
|
+
*/
|
|
109
|
+
getOrSet<T>(key: string, factory: () => Promise<T>, ttlSeconds?: number): Promise<T>;
|
|
110
|
+
/**
|
|
111
|
+
* Clear all entries (useful for testing).
|
|
112
|
+
*/
|
|
113
|
+
clear(): Promise<void>;
|
|
114
|
+
/** Current in-memory cache size (for diagnostics). */
|
|
115
|
+
get size(): number;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Create a cache service instance.
|
|
119
|
+
*
|
|
120
|
+
* @param prefix - Namespace prefix (e.g., 'compliance:', 'plugin:')
|
|
121
|
+
* @param defaultTtlSeconds - Default TTL in seconds (default 300 = 5 min)
|
|
122
|
+
* @param redis - Optional Redis client for cross-process caching
|
|
123
|
+
*/
|
|
124
|
+
export declare function createCacheService(prefix: string, defaultTtlSeconds?: number, redis?: RedisCacheClient): CacheService;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Deep-clone a value so the in-memory backend hands back an INDEPENDENT copy on
|
|
5
|
+
* every read — matching the Redis backend, which returns a fresh `JSON.parse`
|
|
6
|
+
* clone each time. Without this the in-memory cache returns the same stored
|
|
7
|
+
* object reference, so a consumer mutating a "cached" object silently corrupts
|
|
8
|
+
* the shared cache (a footgun that only bites under the memory backend).
|
|
9
|
+
*
|
|
10
|
+
* Primitives are returned as-is. Prefers the structured-clone algorithm; falls
|
|
11
|
+
* back to a JSON round-trip on older runtimes or exotic values.
|
|
12
|
+
*/
|
|
13
|
+
function cloneValue(value) {
|
|
14
|
+
if (value === null || typeof value !== 'object')
|
|
15
|
+
return value;
|
|
16
|
+
try {
|
|
17
|
+
return structuredClone(value);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return JSON.parse(JSON.stringify(value));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Cache service with get/set/del operations and automatic TTL expiry.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const cache = new CacheService({ prefix: 'plugin:', defaultTtlSeconds: 300 });
|
|
29
|
+
*
|
|
30
|
+
* // Set with default TTL
|
|
31
|
+
* await cache.set('org123:list', plugins);
|
|
32
|
+
*
|
|
33
|
+
* // Get (returns null on miss)
|
|
34
|
+
* const cached = await cache.get<Plugin[]>('org123:list');
|
|
35
|
+
*
|
|
36
|
+
* // Invalidate
|
|
37
|
+
* await cache.del('org123:list');
|
|
38
|
+
*
|
|
39
|
+
* // Invalidate by pattern
|
|
40
|
+
* await cache.invalidatePattern('org123:*');
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export class CacheService {
|
|
44
|
+
memory = new Map();
|
|
45
|
+
/**
|
|
46
|
+
* In-flight `getOrSet` factory promises, keyed by full cache key. Coalesces
|
|
47
|
+
* concurrent cold callers onto a single `factory()` invocation (single-flight
|
|
48
|
+
* / stampede protection); entries are cleared once the promise settles.
|
|
49
|
+
*/
|
|
50
|
+
inflight = new Map();
|
|
51
|
+
prefix;
|
|
52
|
+
defaultTtlMs;
|
|
53
|
+
maxEntries;
|
|
54
|
+
redis;
|
|
55
|
+
/** Cache metrics — tracks hits, misses, and invalidations. */
|
|
56
|
+
metrics = { hits: 0, misses: 0, sets: 0, invalidations: 0 };
|
|
57
|
+
constructor(config) {
|
|
58
|
+
this.prefix = config.prefix;
|
|
59
|
+
this.defaultTtlMs = config.defaultTtlSeconds * 1000;
|
|
60
|
+
this.maxEntries = config.maxEntries ?? 1000;
|
|
61
|
+
this.redis = config.redis;
|
|
62
|
+
}
|
|
63
|
+
fullKey(key) {
|
|
64
|
+
return `${this.prefix}${key}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get a cached value. Returns null on miss or error.
|
|
68
|
+
*
|
|
69
|
+
* The returned value is an INDEPENDENT deep copy — mutating it never affects
|
|
70
|
+
* the cached entry (the in-memory backend clones on read for parity with
|
|
71
|
+
* Redis, which returns a fresh JSON clone). Callers may still treat results as
|
|
72
|
+
* immutable, but doing so is no longer load-bearing for cache integrity.
|
|
73
|
+
*/
|
|
74
|
+
async get(key) {
|
|
75
|
+
const fk = this.fullKey(key);
|
|
76
|
+
try {
|
|
77
|
+
if (this.redis) {
|
|
78
|
+
const raw = await this.redis.get(fk);
|
|
79
|
+
if (!raw) {
|
|
80
|
+
this.metrics.misses++;
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
this.metrics.hits++;
|
|
84
|
+
return JSON.parse(raw);
|
|
85
|
+
}
|
|
86
|
+
// In-memory
|
|
87
|
+
const entry = this.memory.get(fk);
|
|
88
|
+
if (!entry) {
|
|
89
|
+
this.metrics.misses++;
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (Date.now() > entry.expiresAt) {
|
|
93
|
+
this.memory.delete(fk);
|
|
94
|
+
this.metrics.misses++;
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
// LRU touch: delete + re-insert moves this key to the newest position
|
|
98
|
+
// (Map preserves insertion order), so `set`'s oldest-first eviction is a
|
|
99
|
+
// true least-recently-USED eviction rather than first-inserted (FIFO).
|
|
100
|
+
this.memory.delete(fk);
|
|
101
|
+
this.memory.set(fk, entry);
|
|
102
|
+
this.metrics.hits++;
|
|
103
|
+
// Clone so a caller mutating the result can't corrupt the shared entry.
|
|
104
|
+
return cloneValue(entry.value);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
this.metrics.misses++;
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Set a cached value with optional TTL override.
|
|
113
|
+
*
|
|
114
|
+
* @param key - Cache key (prefix is added automatically)
|
|
115
|
+
* @param value - Value to cache (must be JSON-serializable for Redis)
|
|
116
|
+
* @param ttlSeconds - TTL override (uses default if not provided)
|
|
117
|
+
*/
|
|
118
|
+
async set(key, value, ttlSeconds) {
|
|
119
|
+
const fk = this.fullKey(key);
|
|
120
|
+
const ttl = ttlSeconds ?? this.defaultTtlMs / 1000;
|
|
121
|
+
try {
|
|
122
|
+
this.metrics.sets++;
|
|
123
|
+
if (this.redis) {
|
|
124
|
+
await this.redis.set(fk, JSON.stringify(value), 'EX', ttl);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// In-memory — a write counts as a use, so drop any existing entry first
|
|
128
|
+
// and re-insert below as the newest (Map preserves insertion order). This
|
|
129
|
+
// keeps the Map ordered oldest-USED → newest-USED so the eviction below is
|
|
130
|
+
// true LRU, and avoids evicting a victim when merely updating a key.
|
|
131
|
+
this.memory.delete(fk);
|
|
132
|
+
if (this.memory.size >= this.maxEntries) {
|
|
133
|
+
const firstKey = this.memory.keys().next().value;
|
|
134
|
+
if (firstKey)
|
|
135
|
+
this.memory.delete(firstKey);
|
|
136
|
+
}
|
|
137
|
+
this.memory.set(fk, {
|
|
138
|
+
value,
|
|
139
|
+
expiresAt: Date.now() + ttl * 1000,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Cache set failure is non-fatal
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Delete a cached value.
|
|
148
|
+
*/
|
|
149
|
+
async del(key) {
|
|
150
|
+
const fk = this.fullKey(key);
|
|
151
|
+
try {
|
|
152
|
+
if (this.redis) {
|
|
153
|
+
await this.redis.del(fk);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.memory.delete(fk);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Cache delete failure is non-fatal
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Invalidate all keys matching a pattern (e.g., 'org123:*').
|
|
164
|
+
* Uses SCAN for Redis (non-blocking) with KEYS fallback, or regex for in-memory.
|
|
165
|
+
*/
|
|
166
|
+
async invalidatePattern(pattern) {
|
|
167
|
+
const fp = this.fullKey(pattern);
|
|
168
|
+
try {
|
|
169
|
+
if (this.redis) {
|
|
170
|
+
const keys = this.redis.scanStream
|
|
171
|
+
? await this.scanKeys(fp)
|
|
172
|
+
: await this.redis.keys(fp);
|
|
173
|
+
if (keys.length > 0) {
|
|
174
|
+
await this.redis.del(...keys);
|
|
175
|
+
this.metrics.invalidations += keys.length;
|
|
176
|
+
}
|
|
177
|
+
return keys.length;
|
|
178
|
+
}
|
|
179
|
+
// In-memory — match glob-style pattern. Escape regex metacharacters first
|
|
180
|
+
// so a literal `.`/`(`/`[` in a key prefix (e.g. `org:v1.2:*`) can't
|
|
181
|
+
// over-match or throw (a throw here is swallowed → nothing invalidated →
|
|
182
|
+
// stale reads); only `*` is treated as a wildcard.
|
|
183
|
+
const regex = new RegExp('^' + fp.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*') + '$');
|
|
184
|
+
let deleted = 0;
|
|
185
|
+
for (const key of this.memory.keys()) {
|
|
186
|
+
if (regex.test(key)) {
|
|
187
|
+
this.memory.delete(key);
|
|
188
|
+
deleted++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
this.metrics.invalidations += deleted;
|
|
192
|
+
return deleted;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Collect keys via Redis SCAN (non-blocking alternative to KEYS). */
|
|
199
|
+
scanKeys(pattern) {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
const stream = this.redis.scanStream({ match: pattern, count: 100 });
|
|
202
|
+
const keys = [];
|
|
203
|
+
stream.on('data', (batch) => keys.push(...batch));
|
|
204
|
+
stream.once('end', () => resolve(keys));
|
|
205
|
+
stream.once('error', reject);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get or compute: returns cached value if available, otherwise calls the
|
|
210
|
+
* factory function, caches the result, and returns it.
|
|
211
|
+
*
|
|
212
|
+
* Single-flight / stampede protection: when N callers miss concurrently for
|
|
213
|
+
* the same key, only ONE `factory()` runs — the rest await its shared promise.
|
|
214
|
+
* The in-flight entry is cleared once it settles (success or failure), so a
|
|
215
|
+
* failed factory doesn't poison later calls.
|
|
216
|
+
*
|
|
217
|
+
* @param key - Cache key
|
|
218
|
+
* @param factory - Async function to compute the value on cache miss
|
|
219
|
+
* @param ttlSeconds - Optional TTL override
|
|
220
|
+
* @returns The cached or computed value
|
|
221
|
+
*/
|
|
222
|
+
async getOrSet(key, factory, ttlSeconds) {
|
|
223
|
+
const cached = await this.get(key);
|
|
224
|
+
if (cached !== null)
|
|
225
|
+
return cached;
|
|
226
|
+
const fk = this.fullKey(key);
|
|
227
|
+
// Coalesce concurrent cold callers onto a single factory() invocation. The
|
|
228
|
+
// in-flight promise resolves to the RAW value; each awaiter (the primary
|
|
229
|
+
// caller below AND every coalesced caller here) clones it INDEPENDENTLY.
|
|
230
|
+
// Cloning inside the flight instead would hand one shared clone to all N
|
|
231
|
+
// coalesced callers, so one caller mutating its "independent" result would
|
|
232
|
+
// corrupt what the others read — the exact footgun get()'s clone-on-read
|
|
233
|
+
// prevents. (Redis backend is unaffected: set serializes, get JSON.parses.)
|
|
234
|
+
const existing = this.inflight.get(fk);
|
|
235
|
+
if (existing)
|
|
236
|
+
return cloneValue(await existing);
|
|
237
|
+
const flight = (async () => {
|
|
238
|
+
const value = await factory();
|
|
239
|
+
await this.set(key, value, ttlSeconds);
|
|
240
|
+
return value;
|
|
241
|
+
})();
|
|
242
|
+
this.inflight.set(fk, flight);
|
|
243
|
+
try {
|
|
244
|
+
return cloneValue(await flight);
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
this.inflight.delete(fk);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Clear all entries (useful for testing).
|
|
252
|
+
*/
|
|
253
|
+
async clear() {
|
|
254
|
+
if (this.redis) {
|
|
255
|
+
await this.invalidatePattern('*');
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
this.memory.clear();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** Current in-memory cache size (for diagnostics). */
|
|
262
|
+
get size() {
|
|
263
|
+
return this.memory.size;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Create a cache service instance.
|
|
268
|
+
*
|
|
269
|
+
* @param prefix - Namespace prefix (e.g., 'compliance:', 'plugin:')
|
|
270
|
+
* @param defaultTtlSeconds - Default TTL in seconds (default 300 = 5 min)
|
|
271
|
+
* @param redis - Optional Redis client for cross-process caching
|
|
272
|
+
*/
|
|
273
|
+
export function createCacheService(prefix, defaultTtlSeconds = 300, redis) {
|
|
274
|
+
return new CacheService({ prefix, defaultTtlSeconds, redis });
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FjaGUtc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2aWNlcy9jYWNoZS1zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUEwQnRDOzs7Ozs7Ozs7R0FTRztBQUNILFNBQVMsVUFBVSxDQUFJLEtBQVE7SUFDN0IsSUFBSSxLQUFLLEtBQUssSUFBSSxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVE7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUM5RCxJQUFJLENBQUM7UUFDSCxPQUFPLGVBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQU0sQ0FBQztJQUNoRCxDQUFDO0FBQ0gsQ0FBQztBQTBCRDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUNILE1BQU0sT0FBTyxZQUFZO0lBQ2YsTUFBTSxHQUFHLElBQUksR0FBRyxFQUErQixDQUFDO0lBQ3hEOzs7O09BSUc7SUFDSyxRQUFRLEdBQUcsSUFBSSxHQUFHLEVBQTRCLENBQUM7SUFDdEMsTUFBTSxDQUFTO0lBQ2YsWUFBWSxDQUFTO0lBQ3JCLFVBQVUsQ0FBUztJQUNuQixLQUFLLENBQW9CO0lBRTFDLDhEQUE4RDtJQUNyRCxPQUFPLEdBQUcsRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxhQUFhLEVBQUUsQ0FBQyxFQUFFLENBQUM7SUFFckUsWUFBWSxNQUFtQjtRQUM3QixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7UUFDNUIsSUFBSSxDQUFDLFlBQVksR0FBRyxNQUFNLENBQUMsaUJBQWlCLEdBQUcsSUFBSSxDQUFDO1FBQ3BELElBQUksQ0FBQyxVQUFVLEdB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RXhCLE9BQU8sSUFBSSxZQUFZLENBQUMsRUFBRSxNQUFNLEVBQUUsaUJBQWlCLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztBQUNoRSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogTGlnaHR3ZWlnaHQgY2FjaGluZyBzZXJ2aWNlIHdpdGggVFRMIHN1cHBvcnQuXG4gKlxuICogVHdvIGltcGxlbWVudGF0aW9uczpcbiAqIC0gSW4tbWVtb3J5IChkZWZhdWx0KTogdHJ1ZS1MUlUgTWFwIGNhY2hlLCBubyBleHRlcm5hbCBkZXBlbmRlbmNpZXNcbiAqIC0gUmVkaXM6IFdoZW4gYSBSZWRpcyBjbGllbnQgaXMgcHJvdmlkZWQsIHVzZXMgUmVkaXMgZm9yIGNyb3NzLXByb2Nlc3MgY2FjaGluZ1xuICpcbiAqIERlc2lnbjpcbiAqIC0gQWxsIG9wZXJhdGlvbnMgYXJlIGZhaWwtc2FmZTogY2FjaGUgbWlzc2VzL2Vycm9ycyByZXR1cm4gbnVsbCwgbmV2ZXIgdGhyb3dcbiAqIC0gSlNPTiBzZXJpYWxpemF0aW9uIGZvciBSZWRpczsgdGhlIGluLW1lbW9yeSBiYWNrZW5kIGRlZXAtY2xvbmVzIG9uIHJlYWQgc29cbiAqICAgYm90aCBiYWNrZW5kcyByZXR1cm4gYW4gaW5kZXBlbmRlbnQgY29weSAobXV0YXRpbmcgYSByZXN1bHQgaXMgYWx3YXlzIHNhZmUpXG4gKiAtIGBnZXRPclNldGAgaXMgc2luZ2xlLWZsaWdodDogY29uY3VycmVudCBtaXNzZXMgc2hhcmUgb25lIGZhY3RvcnkoKSBjYWxsXG4gKiAtIEtleSBuYW1lc3BhY2UgcHJlZml4aW5nIHRvIGF2b2lkIGNvbGxpc2lvbnMgYmV0d2VlbiBzZXJ2aWNlc1xuICovXG5cblxuLyoqXG4gKiBDYWNoZSBlbnRyeSB3aXRoIHZhbHVlIGFuZCBleHBpcmF0aW9uIHRpbWUuXG4gKi9cbmludGVyZmFjZSBDYWNoZUVudHJ5PFQ+IHtcbiAgdmFsdWU6IFQ7XG4gIGV4cGlyZXNBdDogbnVtYmVyOyAvLyBVbml4IHRpbWVzdGFtcCBpbiBtc1xufVxuXG4vKipcbiAqIERlZXAtY2xvbmUgYSB2YWx1ZSBzbyB0aGUgaW4tbWVtb3J5IGJhY2tlbmQgaGFuZHMgYmFjayBhbiBJTkRFUEVOREVOVCBjb3B5IG9uXG4gKiBldmVyeSByZWFkIOKAlCBtYXRjaGluZyB0aGUgUmVkaXMgYmFja2VuZCwgd2hpY2ggcmV0dXJucyBhIGZyZXNoIGBKU09OLnBhcnNlYFxuICogY2xvbmUgZWFjaCB0aW1lLiBXaXRob3V0IHRoaXMgdGhlIGluLW1lbW9yeSBjYWNoZSByZXR1cm5zIHRoZSBzYW1lIHN0b3JlZFxuICogb2JqZWN0IHJlZmVyZW5jZSwgc28gYSBjb25zdW1lciBtdXRhdGluZyBhIFwiY2FjaGVkXCIgb2JqZWN0IHNpbGVudGx5IGNvcnJ1cHRzXG4gKiB0aGUgc2hhcmVkIGNhY2hlIChhIGZvb3RndW4gdGhhdCBvbmx5IGJpdGVzIHVuZGVyIHRoZSBtZW1vcnkgYmFja2VuZCkuXG4gKlxuICogUHJpbWl0aXZlcyBhcmUgcmV0dXJuZWQgYXMtaXMuIFByZWZlcnMgdGhlIHN0cnVjdHVyZWQtY2xvbmUgYWxnb3JpdGhtOyBmYWxsc1xuICogYmFjayB0byBhIEpTT04gcm91bmQtdHJpcCBvbiBvbGRlciBydW50aW1lcyBvciBleG90aWMgdmFsdWVzLlxuICovXG5mdW5jdGlvbiBjbG9uZVZhbHVlPFQ+KHZhbHVlOiBUKTogVCB7XG4gIGlmICh2YWx1ZSA9PT0gbnVsbCB8fCB0eXBlb2YgdmFsdWUgIT09ICdvYmplY3QnKSByZXR1cm4gdmFsdWU7XG4gIHRyeSB7XG4gICAgcmV0dXJuIHN0cnVjdHVyZWRDbG9uZSh2YWx1ZSk7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiBKU09OLnBhcnNlKEpTT04uc3RyaW5naWZ5KHZhbHVlKSkgYXMgVDtcbiAgfVxufVxuXG4vKipcbiAqIE1pbmltYWwgUmVkaXMtbGlrZSBjbGllbnQgaW50ZXJmYWNlIChzdWJzZXQgb2YgaW9yZWRpcykuXG4gKiBTZXJ2aWNlcyBwYXNzIHRoZWlyIG93biBSZWRpcyBjbGllbnQgaW5zdGFuY2UuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVkaXNDYWNoZUNsaWVudCB7XG4gIGdldChrZXk6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nIHwgbnVsbD47XG4gIHNldChrZXk6IHN0cmluZywgdmFsdWU6IHN0cmluZywgLi4uYXJnczogdW5rbm93bltdKTogUHJvbWlzZTx1bmtub3duPjtcbiAgZGVsKC4uLmtleXM6IHN0cmluZ1tdKTogUHJvbWlzZTxudW1iZXI+O1xuICBrZXlzKHBhdHRlcm46IHN0cmluZyk6IFByb21pc2U8c3RyaW5nW10+O1xuICAvKiogU0NBTi1iYXNlZCBpdGVyYXRpb24gKHByZWZlcnJlZCBvdmVyIEtFWVMgZm9yIHByb2R1Y3Rpb24pLiAqL1xuICBzY2FuU3RyZWFtPyhvcHRpb25zOiB7IG1hdGNoOiBzdHJpbmc7IGNvdW50PzogbnVtYmVyIH0pOiBOb2RlSlMuUmVhZGFibGVTdHJlYW07XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgQ2FjaGVDb25maWcge1xuICAvKiogS2V5IHByZWZpeCBmb3IgbmFtZXNwYWNlIGlzb2xhdGlvbiAoZS5nLiwgJ3BsdWdpbjonLCAnY29tcGxpYW5jZTonKSAqL1xuICBwcmVmaXg6IHN0cmluZztcbiAgLyoqIERlZmF1bHQgVFRMIGluIHNlY29uZHMgKi9cbiAgZGVmYXVsdFR0bFNlY29uZHM6IG51bWJlcjtcbiAgLyoqIE1heCBlbnRyaWVzIGZvciBpbi1tZW1vcnkgY2FjaGUgKGRlZmF1bHQgMTAwMCkgKi9cbiAgbWF4RW50cmllcz86IG51bWJlcjtcbiAgLyoqIE9wdGlvbmFsIFJlZGlzIGNsaWVudCDigJQgdXNlcyBpbi1tZW1vcnkgY2FjaGUgaWYgbm90IHByb3ZpZGVkICovXG4gIHJlZGlzPzogUmVkaXNDYWNoZUNsaWVudDtcbn1cblxuLyoqXG4gKiBDYWNoZSBzZXJ2aWNlIHdpdGggZ2V0L3NldC9kZWwgb3BlcmF0aW9ucyBhbmQgYXV0b21hdGljIFRUTCBleHBpcnkuXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGNvbnN0IGNhY2hlID0gbmV3IENhY2hlU2VydmljZSh7IHByZWZpeDogJ3BsdWdpbjonLCBkZWZhdWx0VHRsU2Vjb25kczogMzAwIH0pO1xuICpcbiAqIC8vIFNldCB3aXRoIGRlZmF1bHQgVFRMXG4gKiBhd2FpdCBjYWNoZS5zZXQoJ29yZzEyMzpsaXN0JywgcGx1Z2lucyk7XG4gKlxuICogLy8gR2V0IChyZXR1cm5zIG51bGwgb24gbWlzcylcbiAqIGNvbnN0IGNhY2hlZCA9IGF3YWl0IGNhY2hlLmdldDxQbHVnaW5bXT4oJ29yZzEyMzpsaXN0Jyk7XG4gKlxuICogLy8gSW52YWxpZGF0ZVxuICogYXdhaXQgY2FjaGUuZGVsKCdvcmcxMjM6bGlzdCcpO1xuICpcbiAqIC8vIEludmFsaWRhdGUgYnkgcGF0dGVyblxuICogYXdhaXQgY2FjaGUuaW52YWxpZGF0ZVBhdHRlcm4oJ29yZzEyMzoqJyk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGNsYXNzIENhY2hlU2VydmljZSB7XG4gIHByaXZhdGUgbWVtb3J5ID0gbmV3IE1hcDxzdHJpbmcsIENhY2hlRW50cnk8dW5rbm93bj4+KCk7XG4gIC8qKlxuICAgKiBJbi1mbGlnaHQgYGdldE9yU2V0YCBmYWN0b3J5IHByb21pc2VzLCBrZXllZCBieSBmdWxsIGNhY2hlIGtleS4gQ29hbGVzY2VzXG4gICAqIGNvbmN1cnJlbnQgY29sZCBjYWxsZXJzIG9udG8gYSBzaW5nbGUgYGZhY3RvcnkoKWAgaW52b2NhdGlvbiAoc2luZ2xlLWZsaWdodFxuICAgKiAvIHN0YW1wZWRlIHByb3RlY3Rpb24pOyBlbnRyaWVzIGFyZSBjbGVhcmVkIG9uY2UgdGhlIHByb21pc2Ugc2V0dGxlcy5cbiAgICovXG4gIHByaXZhdGUgaW5mbGlnaHQgPSBuZXcgTWFwPHN0cmluZywgUHJvbWlzZTx1bmtub3duPj4oKTtcbiAgcHJpdmF0ZSByZWFkb25seSBwcmVmaXg6IHN0cmluZztcbiAgcHJpdmF0ZSByZWFkb25seSBkZWZhdWx0VHRsTXM6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBtYXhFbnRyaWVzOiBudW1iZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgcmVkaXM/OiBSZWRpc0NhY2hlQ2xpZW50O1xuXG4gIC8qKiBDYWNoZSBtZXRyaWNzIOKAlCB0cmFja3MgaGl0cywgbWlzc2VzLCBhbmQgaW52YWxpZGF0aW9ucy4gKi9cbiAgcmVhZG9ubHkgbWV0cmljcyA9IHsgaGl0czogMCwgbWlzc2VzOiAwLCBzZXRzOiAwLCBpbnZhbGlkYXRpb25zOiAwIH07XG5cbiAgY29uc3RydWN0b3IoY29uZmlnOiBDYWNoZUNvbmZpZykge1xuICAgIHRoaXMucHJlZml4ID0gY29uZmlnLnByZWZpeDtcbiAgICB0aGlzLmRlZmF1bHRUdGxNcyA9IGNvbmZpZy5kZWZhdWx0VHRsU2Vjb25kcyAqIDEwMDA7XG4gICAgdGhpcy5tYXhFbnRyaWVzID0gY29uZmlnLm1heEVudHJpZXMgPz8gMTAwMDtcbiAgICB0aGlzLnJlZGlzID0gY29uZmlnLnJlZGlzO1xuICB9XG5cbiAgcHJpdmF0ZSBmdWxsS2V5KGtleTogc3RyaW5nKTogc3RyaW5nIHtcbiAgICByZXR1cm4gYCR7dGhpcy5wcmVmaXh9JHtrZXl9YDtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgYSBjYWNoZWQgdmFsdWUuIFJldHVybnMgbnVsbCBvbiBtaXNzIG9yIGVycm9yLlxuICAgKlxuICAgKiBUaGUgcmV0dXJuZWQgdmFsdWUgaXMgYW4gSU5ERVBFTkRFTlQgZGVlcCBjb3B5IOKAlCBtdXRhdGluZyBpdCBuZXZlciBhZmZlY3RzXG4gICAqIHRoZSBjYWNoZWQgZW50cnkgKHRoZSBpbi1tZW1vcnkgYmFja2VuZCBjbG9uZXMgb24gcmVhZCBmb3IgcGFyaXR5IHdpdGhcbiAgICogUmVkaXMsIHdoaWNoIHJldHVybnMgYSBmcmVzaCBKU09OIGNsb25lKS4gQ2FsbGVycyBtYXkgc3RpbGwgdHJlYXQgcmVzdWx0cyBhc1xuICAgKiBpbW11dGFibGUsIGJ1dCBkb2luZyBzbyBpcyBubyBsb25nZXIgbG9hZC1iZWFyaW5nIGZvciBjYWNoZSBpbnRlZ3JpdHkuXG4gICAqL1xuICBhc3luYyBnZXQ8VD4oa2V5OiBzdHJpbmcpOiBQcm9taXNlPFQgfCBudWxsPiB7XG4gICAgY29uc3QgZmsgPSB0aGlzLmZ1bGxLZXkoa2V5KTtcblxuICAgIHRyeSB7XG4gICAgICBpZiAodGhpcy5yZWRpcykge1xuICAgICAgICBjb25zdCByYXcgPSBhd2FpdCB0aGlzLnJlZGlzLmdldChmayk7XG4gICAgICAgIGlmICghcmF3KSB7IHRoaXMubWV0cmljcy5taXNzZXMrKzsgcmV0dXJuIG51bGw7IH1cbiAgICAgICAgdGhpcy5tZXRyaWNzLmhpdHMrKztcbiAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UocmF3KSBhcyBUO1xuICAgICAgfVxuXG4gICAgICAvLyBJbi1tZW1vcnlcbiAgICAgIGNvbnN0IGVudHJ5ID0gdGhpcy5tZW1vcnkuZ2V0KGZrKTtcbiAgICAgIGlmICghZW50cnkpIHsgdGhpcy5tZXRyaWNzLm1pc3NlcysrOyByZXR1cm4gbnVsbDsgfVxuICAgICAgaWYgKERhdGUubm93KCkgPiBlbnRyeS5leHBpcmVzQXQpIHtcbiAgICAgICAgdGhpcy5tZW1vcnkuZGVsZXRlKGZrKTtcbiAgICAgICAgdGhpcy5tZXRyaWNzLm1pc3NlcysrO1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIC8vIExSVSB0b3VjaDogZGVsZXRlICsgcmUtaW5zZXJ0IG1vdmVzIHRoaXMga2V5IHRvIHRoZSBuZXdlc3QgcG9zaXRpb25cbiAgICAgIC8vIChNYXAgcHJlc2VydmVzIGluc2VydGlvbiBvcmRlciksIHNvIGBzZXRgJ3Mgb2xkZXN0LWZpcnN0IGV2aWN0aW9uIGlzIGFcbiAgICAgIC8vIHRydWUgbGVhc3QtcmVjZW50bHktVVNFRCBldmljdGlvbiByYXRoZXIgdGhhbiBmaXJzdC1pbnNlcnRlZCAoRklGTykuXG4gICAgICB0aGlzLm1lbW9yeS5kZWxldGUoZmspO1xuICAgICAgdGhpcy5tZW1vcnkuc2V0KGZrLCBlbnRyeSk7XG4gICAgICB0aGlzLm1ldHJpY3MuaGl0cysrO1xuICAgICAgLy8gQ2xvbmUgc28gYSBjYWxsZXIgbXV0YXRpbmcgdGhlIHJlc3VsdCBjYW4ndCBjb3JydXB0IHRoZSBzaGFyZWQgZW50cnkuXG4gICAgICByZXR1cm4gY2xvbmVWYWx1ZShlbnRyeS52YWx1ZSkgYXMgVDtcbiAgICB9IGNhdGNoIHtcbiAgICAgIHRoaXMubWV0cmljcy5taXNzZXMrKztcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBTZXQgYSBjYWNoZWQgdmFsdWUgd2l0aCBvcHRpb25hbCBUVEwgb3ZlcnJpZGUuXG4gICAqXG4gICAqIEBwYXJhbSBrZXkgLSBDYWNoZSBrZXkgKHByZWZpeCBpcyBhZGRlZCBhdXRvbWF0aWNhbGx5KVxuICAgKiBAcGFyYW0gdmFsdWUgLSBWYWx1ZSB0byBjYWNoZSAobXVzdCBiZSBKU09OLXNlcmlhbGl6YWJsZSBmb3IgUmVkaXMpXG4gICAqIEBwYXJhbSB0dGxTZWNvbmRzIC0gVFRMIG92ZXJyaWRlICh1c2VzIGRlZmF1bHQgaWYgbm90IHByb3ZpZGVkKVxuICAgKi9cbiAgYXN5bmMgc2V0PFQ+KGtleTogc3RyaW5nLCB2YWx1ZTogVCwgdHRsU2Vjb25kcz86IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGZrID0gdGhpcy5mdWxsS2V5KGtleSk7XG4gICAgY29uc3QgdHRsID0gdHRsU2Vjb25kcyA/PyB0aGlzLmRlZmF1bHRUdGxNcyAvIDEwMDA7XG5cbiAgICB0cnkge1xuICAgICAgdGhpcy5tZXRyaWNzLnNldHMrKztcbiAgICAgIGlmICh0aGlzLnJlZGlzKSB7XG4gICAgICAgIGF3YWl0IHRoaXMucmVkaXMuc2V0KGZrLCBKU09OLnN0cmluZ2lmeSh2YWx1ZSksICdFWCcsIHR0bCk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cblxuICAgICAgLy8gSW4tbWVtb3J5IOKAlCBhIHdyaXRlIGNvdW50cyBhcyBhIHVzZSwgc28gZHJvcCBhbnkgZXhpc3RpbmcgZW50cnkgZmlyc3RcbiAgICAgIC8vIGFuZCByZS1pbnNlcnQgYmVsb3cgYXMgdGhlIG5ld2VzdCAoTWFwIHByZXNlcnZlcyBpbnNlcnRpb24gb3JkZXIpLiBUaGlzXG4gICAgICAvLyBrZWVwcyB0aGUgTWFwIG9yZGVyZWQgb2xkZXN0LVVTRUQg4oaSIG5ld2VzdC1VU0VEIHNvIHRoZSBldmljdGlvbiBiZWxvdyBpc1xuICAgICAgLy8gdHJ1ZSBMUlUsIGFuZCBhdm9pZHMgZXZpY3RpbmcgYSB2aWN0aW0gd2hlbiBtZXJlbHkgdXBkYXRpbmcgYSBrZXkuXG4gICAgICB0aGlzLm1lbW9yeS5kZWxldGUoZmspO1xuICAgICAgaWYgKHRoaXMubWVtb3J5LnNpemUgPj0gdGhpcy5tYXhFbnRyaWVzKSB7XG4gICAgICAgIGNvbnN0IGZpcnN0S2V5ID0gdGhpcy5tZW1vcnkua2V5cygpLm5leHQoKS52YWx1ZTtcbiAgICAgICAgaWYgKGZpcnN0S2V5KSB0aGlzLm1lbW9yeS5kZWxldGUoZmlyc3RLZXkpO1xuICAgICAgfVxuXG4gICAgICB0aGlzLm1lbW9yeS5zZXQoZmssIHtcbiAgICAgICAgdmFsdWUsXG4gICAgICAgIGV4cGlyZXNBdDogRGF0ZS5ub3coKSArIHR0bCAqIDEwMDAsXG4gICAgICB9KTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIENhY2hlIHNldCBmYWlsdXJlIGlzIG5vbi1mYXRhbFxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBEZWxldGUgYSBjYWNoZWQgdmFsdWUuXG4gICAqL1xuICBhc3luYyBkZWwoa2V5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBmayA9IHRoaXMuZnVsbEtleShrZXkpO1xuXG4gICAgdHJ5IHtcbiAgICAgIGlmICh0aGlzLnJlZGlzKSB7XG4gICAgICAgIGF3YWl0IHRoaXMucmVkaXMuZGVsKGZrKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgdGhpcy5tZW1vcnkuZGVsZXRlKGZrKTtcbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIENhY2hlIGRlbGV0ZSBmYWlsdXJlIGlzIG5vbi1mYXRhbFxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZhbGlkYXRlIGFsbCBrZXlzIG1hdGNoaW5nIGEgcGF0dGVybiAoZS5nLiwgJ29yZzEyMzoqJykuXG4gICAqIFVzZXMgU0NBTiBmb3IgUmVkaXMgKG5vbi1ibG9ja2luZykgd2l0aCBLRVlTIGZhbGxiYWNrLCBvciByZWdleCBmb3IgaW4tbWVtb3J5LlxuICAgKi9cbiAgYXN5bmMgaW52YWxpZGF0ZVBhdHRlcm4ocGF0dGVybjogc3RyaW5nKTogUHJvbWlzZTxudW1iZXI+IHtcbiAgICBjb25zdCBmcCA9IHRoaXMuZnVsbEtleShwYXR0ZXJuKTtcblxuICAgIHRyeSB7XG4gICAgICBpZiAodGhpcy5yZWRpcykge1xuICAgICAgICBjb25zdCBrZXlzID0gdGhpcy5yZWRpcy5zY2FuU3RyZWFtXG4gICAgICAgICAgPyBhd2FpdCB0aGlzLnNjYW5LZXlzKGZwKVxuICAgICAgICAgIDogYXdhaXQgdGhpcy5yZWRpcy5rZXlzKGZwKTtcbiAgICAgICAgaWYgKGtleXMubGVuZ3RoID4gMCkge1xuICAgICAgICAgIGF3YWl0IHRoaXMucmVkaXMuZGVsKC4uLmtleXMpO1xuICAgICAgICAgIHRoaXMubWV0cmljcy5pbnZhbGlkYXRpb25zICs9IGtleXMubGVuZ3RoO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBrZXlzLmxlbmd0aDtcbiAgICAgIH1cblxuICAgICAgLy8gSW4tbWVtb3J5IOKAlCBtYXRjaCBnbG9iLXN0eWxlIHBhdHRlcm4uIEVzY2FwZSByZWdleCBtZXRhY2hhcmFjdGVycyBmaXJzdFxuICAgICAgLy8gc28gYSBsaXRlcmFsIGAuYC9gKGAvYFtgIGluIGEga2V5IHByZWZpeCAoZS5nLiBgb3JnOnYxLjI6KmApIGNhbid0XG4gICAgICAvLyBvdmVyLW1hdGNoIG9yIHRocm93IChhIHRocm93IGhlcmUgaXMgc3dhbGxvd2VkIOKGkiBub3RoaW5nIGludmFsaWRhdGVkIOKGklxuICAgICAgLy8gc3RhbGUgcmVhZHMpOyBvbmx5IGAqYCBpcyB0cmVhdGVkIGFzIGEgd2lsZGNhcmQuXG4gICAgICBjb25zdCByZWdleCA9IG5ldyBSZWdFeHAoJ14nICsgZnAucmVwbGFjZSgvWy4qKz9eJHt9KCl8W1xcXVxcXFxdL2csICdcXFxcJCYnKS5yZXBsYWNlKC9cXFxcXFwqL2csICcuKicpICsgJyQnKTtcbiAgICAgIGxldCBkZWxldGVkID0gMDtcbiAgICAgIGZvciAoY29uc3Qga2V5IG9mIHRoaXMubWVtb3J5LmtleXMoKSkge1xuICAgICAgICBpZiAocmVnZXgudGVzdChrZXkpKSB7XG4gICAgICAgICAgdGhpcy5tZW1vcnkuZGVsZXRlKGtleSk7XG4gICAgICAgICAgZGVsZXRlZCsrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICB0aGlzLm1ldHJpY3MuaW52YWxpZGF0aW9ucyArPSBkZWxldGVkO1xuICAgICAgcmV0dXJuIGRlbGV0ZWQ7XG4gICAgfSBjYXRjaCB7XG4gICAgICByZXR1cm4gMDtcbiAgICB9XG4gIH1cblxuICAvKiogQ29sbGVjdCBrZXlzIHZpYSBSZWRpcyBTQ0FOIChub24tYmxvY2tpbmcgYWx0ZXJuYXRpdmUgdG8gS0VZUykuICovXG4gIHByaXZhdGUgc2NhbktleXMocGF0dGVybjogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmdbXT4ge1xuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICBjb25zdCBzdHJlYW0gPSB0aGlzLnJlZGlzIS5zY2FuU3RyZWFtISh7IG1hdGNoOiBwYXR0ZXJuLCBjb3VudDogMTAwIH0pO1xuICAgICAgY29uc3Qga2V5czogc3RyaW5nW10gPSBbXTtcbiAgICAgIHN0cmVhbS5vbignZGF0YScsIChiYXRjaDogc3RyaW5nW10pID0+IGtleXMucHVzaCguLi5iYXRjaCkpO1xuICAgICAgc3RyZWFtLm9uY2UoJ2VuZCcsICgpID0+IHJlc29sdmUoa2V5cykpO1xuICAgICAgc3RyZWFtLm9uY2UoJ2Vycm9yJywgcmVqZWN0KTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgb3IgY29tcHV0ZTogcmV0dXJucyBjYWNoZWQgdmFsdWUgaWYgYXZhaWxhYmxlLCBvdGhlcndpc2UgY2FsbHMgdGhlXG4gICAqIGZhY3RvcnkgZnVuY3Rpb24sIGNhY2hlcyB0aGUgcmVzdWx0LCBhbmQgcmV0dXJucyBpdC5cbiAgICpcbiAgICogU2luZ2xlLWZsaWdodCAvIHN0YW1wZWRlIHByb3RlY3Rpb246IHdoZW4gTiBjYWxsZXJzIG1pc3MgY29uY3VycmVudGx5IGZvclxuICAgKiB0aGUgc2FtZSBrZXksIG9ubHkgT05FIGBmYWN0b3J5KClgIHJ1bnMg4oCUIHRoZSByZXN0IGF3YWl0IGl0cyBzaGFyZWQgcHJvbWlzZS5cbiAgICogVGhlIGluLWZsaWdodCBlbnRyeSBpcyBjbGVhcmVkIG9uY2UgaXQgc2V0dGxlcyAoc3VjY2VzcyBvciBmYWlsdXJlKSwgc28gYVxuICAgKiBmYWlsZWQgZmFjdG9yeSBkb2Vzbid0IHBvaXNvbiBsYXRlciBjYWxscy5cbiAgICpcbiAgICogQHBhcmFtIGtleSAtIENhY2hlIGtleVxuICAgKiBAcGFyYW0gZmFjdG9yeSAtIEFzeW5jIGZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIHZhbHVlIG9uIGNhY2hlIG1pc3NcbiAgICogQHBhcmFtIHR0bFNlY29uZHMgLSBPcHRpb25hbCBUVEwgb3ZlcnJpZGVcbiAgICogQHJldHVybnMgVGhlIGNhY2hlZCBvciBjb21wdXRlZCB2YWx1ZVxuICAgKi9cbiAgYXN5bmMgZ2V0T3JTZXQ8VD4oa2V5OiBzdHJpbmcsIGZhY3Rvcnk6ICgpID0+IFByb21pc2U8VD4sIHR0bFNlY29uZHM/OiBudW1iZXIpOiBQcm9taXNlPFQ+IHtcbiAgICBjb25zdCBjYWNoZWQgPSBhd2FpdCB0aGlzLmdldDxUPihrZXkpO1xuICAgIGlmIChjYWNoZWQgIT09IG51bGwpIHJldHVybiBjYWNoZWQ7XG5cbiAgICBjb25zdCBmayA9IHRoaXMuZnVsbEtleShrZXkpO1xuICAgIC8vIENvYWxlc2NlIGNvbmN1cnJlbnQgY29sZCBjYWxsZXJzIG9udG8gYSBzaW5nbGUgZmFjdG9yeSgpIGludm9jYXRpb24uIFRoZVxuICAgIC8vIGluLWZsaWdodCBwcm9taXNlIHJlc29sdmVzIHRvIHRoZSBSQVcgdmFsdWU7IGVhY2ggYXdhaXRlciAodGhlIHByaW1hcnlcbiAgICAvLyBjYWxsZXIgYmVsb3cgQU5EIGV2ZXJ5IGNvYWxlc2NlZCBjYWxsZXIgaGVyZSkgY2xvbmVzIGl0IElOREVQRU5ERU5UTFkuXG4gICAgLy8gQ2xvbmluZyBpbnNpZGUgdGhlIGZsaWdodCBpbnN0ZWFkIHdvdWxkIGhhbmQgb25lIHNoYXJlZCBjbG9uZSB0byBhbGwgTlxuICAgIC8vIGNvYWxlc2NlZCBjYWxsZXJzLCBzbyBvbmUgY2FsbGVyIG11dGF0aW5nIGl0cyBcImluZGVwZW5kZW50XCIgcmVzdWx0IHdvdWxkXG4gICAgLy8gY29ycnVwdCB3aGF0IHRoZSBvdGhlcnMgcmVhZCDigJQgdGhlIGV4YWN0IGZvb3RndW4gZ2V0KCkncyBjbG9uZS1vbi1yZWFkXG4gICAgLy8gcHJldmVudHMuIChSZWRpcyBiYWNrZW5kIGlzIHVuYWZmZWN0ZWQ6IHNldCBzZXJpYWxpemVzLCBnZXQgSlNPTi5wYXJzZXMuKVxuICAgIGNvbnN0IGV4aXN0aW5nID0gdGhpcy5pbmZsaWdodC5nZXQoZmspIGFzIFByb21pc2U8VD4gfCB1bmRlZmluZWQ7XG4gICAgaWYgKGV4aXN0aW5nKSByZXR1cm4gY2xvbmVWYWx1ZShhd2FpdCBleGlzdGluZyk7XG5cbiAgICBjb25zdCBmbGlnaHQgPSAoYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgdmFsdWUgPSBhd2FpdCBmYWN0b3J5KCk7XG4gICAgICBhd2FpdCB0aGlzLnNldChrZXksIHZhbHVlLCB0dGxTZWNvbmRzKTtcbiAgICAgIHJldHVybiB2YWx1ZTtcbiAgICB9KSgpO1xuICAgIHRoaXMuaW5mbGlnaHQuc2V0KGZrLCBmbGlnaHQpO1xuICAgIHRyeSB7XG4gICAgICByZXR1cm4gY2xvbmVWYWx1ZShhd2FpdCBmbGlnaHQpO1xuICAgIH0gZmluYWxseSB7XG4gICAgICB0aGlzLmluZmxpZ2h0LmRlbGV0ZShmayk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIENsZWFyIGFsbCBlbnRyaWVzICh1c2VmdWwgZm9yIHRlc3RpbmcpLlxuICAgKi9cbiAgYXN5bmMgY2xlYXIoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKHRoaXMucmVkaXMpIHtcbiAgICAgIGF3YWl0IHRoaXMuaW52YWxpZGF0ZVBhdHRlcm4oJyonKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5tZW1vcnkuY2xlYXIoKTtcbiAgICB9XG4gIH1cblxuICAvKiogQ3VycmVudCBpbi1tZW1vcnkgY2FjaGUgc2l6ZSAoZm9yIGRpYWdub3N0aWNzKS4gKi9cbiAgZ2V0IHNpemUoKTogbnVtYmVyIHtcbiAgICByZXR1cm4gdGhpcy5tZW1vcnkuc2l6ZTtcbiAgfVxufVxuXG4vKipcbiAqIENyZWF0ZSBhIGNhY2hlIHNlcnZpY2UgaW5zdGFuY2UuXG4gKlxuICogQHBhcmFtIHByZWZpeCAtIE5hbWVzcGFjZSBwcmVmaXggKGUuZy4sICdjb21wbGlhbmNlOicsICdwbHVnaW46JylcbiAqIEBwYXJhbSBkZWZhdWx0VHRsU2Vjb25kcyAtIERlZmF1bHQgVFRMIGluIHNlY29uZHMgKGRlZmF1bHQgMzAwID0gNSBtaW4pXG4gKiBAcGFyYW0gcmVkaXMgLSBPcHRpb25hbCBSZWRpcyBjbGllbnQgZm9yIGNyb3NzLXByb2Nlc3MgY2FjaGluZ1xuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlQ2FjaGVTZXJ2aWNlKFxuICBwcmVmaXg6IHN0cmluZyxcbiAgZGVmYXVsdFR0bFNlY29uZHMgPSAzMDAsXG4gIHJlZGlzPzogUmVkaXNDYWNoZUNsaWVudCxcbik6IENhY2hlU2VydmljZSB7XG4gIHJldHVybiBuZXcgQ2FjaGVTZXJ2aWNlKHsgcHJlZml4LCBkZWZhdWx0VHRsU2Vjb25kcywgcmVkaXMgfSk7XG59XG4iXX0=
|