@devorash/node 0.1.0

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.
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Impersonation scope enforcement middleware
3
+ *
4
+ * Provides defense-in-depth by validating impersonation scope on the backend,
5
+ * blocking write operations in read-only mode even if the frontend SDK is bypassed.
6
+ *
7
+ * @module @devorash/node
8
+ */
9
+ import { isWriteMethod, matchEndpointPattern, isAmbiguousRequestPath, getRawRequestPath, getPolicyMethods, getDenyMethods, joinMountedTarget, createErrorResponse, getErrorStatusCode, } from "@devorash/core";
10
+ const AUTHORIZATION_SOURCES = new Set([
11
+ "standard",
12
+ "self_approved",
13
+ "self_approved_read",
14
+ "break_glass",
15
+ ]);
16
+ /** Resolve the canonical actor/subject ids from either field-name convention. */
17
+ export function resolveImpersonationIdentities(context) {
18
+ return {
19
+ actorId: context.actor?.id ?? context.impersonator?.id,
20
+ subjectId: context.subject?.id ?? context.targetUser?.id,
21
+ };
22
+ }
23
+ /**
24
+ * Check if methods match (considering wildcard)
25
+ */
26
+ function methodsMatch(patternMethod, requestMethod) {
27
+ if (patternMethod === "*")
28
+ return true;
29
+ return patternMethod.toUpperCase() === requestMethod.toUpperCase();
30
+ }
31
+ function targetForms(target) {
32
+ const raw = getRawRequestPath(target);
33
+ let decoded = raw;
34
+ try {
35
+ decoded = decodeURIComponent(raw);
36
+ }
37
+ catch {
38
+ // Undecodable targets are rejected as ambiguous before any matching.
39
+ }
40
+ return { raw, decoded };
41
+ }
42
+ /** First deny rule matching any method against any form of any target. */
43
+ function findBlockedEndpoint(targets, methods, blockedEndpoints) {
44
+ if (!blockedEndpoints || blockedEndpoints.length === 0)
45
+ return null;
46
+ const denyMethods = getDenyMethods(methods);
47
+ for (const endpoint of blockedEndpoints) {
48
+ if (!denyMethods.some((method) => methodsMatch(endpoint.method, method)))
49
+ continue;
50
+ for (const target of targets)
51
+ for (const candidate of [target.raw, target.decoded])
52
+ if (matchEndpointPattern(endpoint.pattern, candidate, {
53
+ caseSensitive: false,
54
+ ignoreTrailingSlash: true,
55
+ }))
56
+ return endpoint;
57
+ }
58
+ return null;
59
+ }
60
+ /** A safe-read allow rule must match both forms of every routed target. */
61
+ function isAllowlisted(targets, method, safeReadEndpoints) {
62
+ if (!safeReadEndpoints || safeReadEndpoints.length === 0)
63
+ return false;
64
+ return safeReadEndpoints.some((endpoint) => endpoint.method !== "*" &&
65
+ methodsMatch(endpoint.method, method) &&
66
+ targets.every((target) => matchEndpointPattern(endpoint.pattern, target.raw) &&
67
+ matchEndpointPattern(endpoint.pattern, target.decoded)));
68
+ }
69
+ /** The targets a request is judged on. */
70
+ function requestTargets(req) {
71
+ const original = req.originalUrl ?? req.url ?? req.path;
72
+ const effective = req.routedUrls ?? [joinMountedTarget(req.baseUrl, req.url ?? req.path)];
73
+ return {
74
+ routed: [...new Set([original, ...effective])],
75
+ // Express strips the current router mount from req.url. Deny rules
76
+ // must also see that view, just as Python's mount-relative aliases do.
77
+ aliases: [...(req.aliasUrls ?? []), ...(req.baseUrl ? [req.url ?? req.path] : [])],
78
+ };
79
+ }
80
+ /**
81
+ * Create an impersonation guard middleware
82
+ *
83
+ * This middleware validates impersonation scope and blocks write operations
84
+ * in read-only mode. It provides defense-in-depth protection on the backend.
85
+ *
86
+ * Every request target the framework may dispatch on is judged: the original
87
+ * target, the effective target after rewrites/mounts, and any deny-only aliases.
88
+ * Policy patterns match the full client-visible path, including mount prefixes.
89
+ *
90
+ * Enforcement Priority:
91
+ * 1. Check blacklist first - if match, BLOCK (regardless of scope)
92
+ * 2. Run the application's isImpersonationAllowed hook
93
+ * 3. If scope is "write", ALLOW (blacklist already checked)
94
+ * 4. If scope is "read" and write method, check whitelist
95
+ * 5. If not in whitelist, BLOCK
96
+ */
97
+ export function createImpersonationGuard(options) {
98
+ const { sdk, getImpersonationContext, onBlocked, blockedResponse = {}, showWarnings = process.env.NODE_ENV !== "production", enforceLiveness = true, livenessCacheTtlMs = 5_000, onLivenessUnavailable = "deny", isImpersonationAllowed, bridgePath, } = options;
99
+ if (onLivenessUnavailable !== "allow" && onLivenessUnavailable !== "deny")
100
+ throw new Error("Invalid liveness unavailable policy");
101
+ if (!Number.isFinite(livenessCacheTtlMs) || livenessCacheTtlMs < 0)
102
+ throw new Error("Invalid liveness cache TTL");
103
+ if (bridgePath !== undefined &&
104
+ (typeof bridgePath !== "string" ||
105
+ isAmbiguousRequestPath(bridgePath) ||
106
+ getRawRequestPath(bridgePath) !== bridgePath))
107
+ throw new Error("Invalid bridge path");
108
+ const responseMessage = blockedResponse.message ?? "This action is blocked during impersonation";
109
+ const responseErrorCode = blockedResponse.errorCode ?? "IMPERSONATION_SCOPE_VIOLATION";
110
+ // Per-guard cache of recent liveness results. `live: null` records an
111
+ // "unavailable" verdict for a short window so an outage costs one lookup per
112
+ // session per second instead of one blocked 5-second fetch per request.
113
+ const livenessCache = new Map();
114
+ const livenessInFlight = new Map();
115
+ const unavailableCacheMs = Math.min(1_000, livenessCacheTtlMs);
116
+ const maxCachedSessions = 1000;
117
+ function remember(sessionId, live) {
118
+ livenessCache.delete(sessionId);
119
+ livenessCache.set(sessionId, { live, checkedAt: performance.now() });
120
+ if (livenessCache.size > maxCachedSessions)
121
+ livenessCache.delete(livenessCache.keys().next().value);
122
+ }
123
+ /** Bounded single-flight lookups, including custom transports that stall. */
124
+ async function isSessionLive(sessionId) {
125
+ const now = performance.now();
126
+ const cached = livenessCache.get(sessionId);
127
+ if (cached) {
128
+ const ttl = cached.live === null ? unavailableCacheMs : livenessCacheTtlMs;
129
+ if (now - cached.checkedAt < ttl) {
130
+ livenessCache.delete(sessionId);
131
+ livenessCache.set(sessionId, cached);
132
+ return cached.live;
133
+ }
134
+ livenessCache.delete(sessionId);
135
+ }
136
+ const pending = livenessInFlight.get(sessionId);
137
+ if (pending)
138
+ return pending;
139
+ if (livenessInFlight.size >= 64)
140
+ return null;
141
+ const actual = Promise.resolve()
142
+ .then(() => sdk.getSessionStatus(sessionId))
143
+ .then((status) => (status == null ? null : status.valid === true))
144
+ .catch(() => null);
145
+ const lookup = new Promise((resolve) => {
146
+ let finished = false;
147
+ const timer = setTimeout(() => {
148
+ finished = true;
149
+ remember(sessionId, null);
150
+ resolve(null);
151
+ }, 5_000);
152
+ void actual.then((live) => {
153
+ clearTimeout(timer);
154
+ livenessInFlight.delete(sessionId);
155
+ if (!finished) {
156
+ finished = true;
157
+ remember(sessionId, live);
158
+ resolve(live);
159
+ }
160
+ });
161
+ });
162
+ // A timed-out transport retains its slot until it actually settles. That
163
+ // prevents repeated requests from creating unbounded background work.
164
+ livenessInFlight.set(sessionId, lookup);
165
+ return lookup;
166
+ }
167
+ async function decide(req) {
168
+ const context = await getImpersonationContext(req);
169
+ // Only an explicit absence of impersonation passes through unchecked. A
170
+ // value of any other shape is an integration error and fails closed.
171
+ if (context === null || context === undefined)
172
+ return { allowed: true };
173
+ if (typeof context !== "object" || typeof context.isImpersonation !== "boolean")
174
+ return {
175
+ allowed: false,
176
+ status: 500,
177
+ error: "Invalid impersonation context",
178
+ errorCode: "INVALID_IMPERSONATION_CONTEXT",
179
+ };
180
+ if (!context.isImpersonation)
181
+ return { allowed: true };
182
+ const contextValidation = validateImpersonationContext(context);
183
+ if (!contextValidation.valid) {
184
+ const errorCode = contextValidation.expired
185
+ ? "IMPERSONATION_EXPIRED"
186
+ : "INVALID_IMPERSONATION_CONTEXT";
187
+ return {
188
+ allowed: false,
189
+ status: getErrorStatusCode(errorCode),
190
+ error: contextValidation.expired
191
+ ? "Impersonation session expired"
192
+ : "Invalid impersonation context",
193
+ errorCode,
194
+ };
195
+ }
196
+ const { routed, aliases } = requestTargets(req);
197
+ if ([...routed, ...aliases].some((target) => isAmbiguousRequestPath(target)))
198
+ return {
199
+ allowed: false,
200
+ status: getErrorStatusCode("IMPERSONATION_ENDPOINT_BLOCKED"),
201
+ error: "Ambiguous request path",
202
+ errorCode: "IMPERSONATION_ENDPOINT_BLOCKED",
203
+ blockedContext: context,
204
+ };
205
+ const routedForms = routed.map(targetForms);
206
+ const allForms = [...routedForms, ...aliases.map(targetForms)];
207
+ const displayPath = routedForms[routedForms.length - 1].decoded;
208
+ const methods = getPolicyMethods(req.method, req.headers, req.originalUrl ?? req.url);
209
+ // Optional server-side liveness: block sessions terminated/revoked before their expiry.
210
+ if (enforceLiveness && context.sessionId) {
211
+ const live = await isSessionLive(context.sessionId);
212
+ if (live === null && onLivenessUnavailable === "deny") {
213
+ // Distinct from "ended": the session may well be live, but it cannot
214
+ // be verified. Fail closed for impersonated traffic only.
215
+ return {
216
+ allowed: false,
217
+ status: 503,
218
+ error: "Impersonation session liveness could not be verified",
219
+ errorCode: "IMPERSONATION_LIVENESS_UNAVAILABLE",
220
+ };
221
+ }
222
+ if (live === false)
223
+ return {
224
+ allowed: false,
225
+ status: getErrorStatusCode("IMPERSONATION_SESSION_ENDED"),
226
+ error: "Impersonation session is no longer active",
227
+ errorCode: "IMPERSONATION_SESSION_ENDED",
228
+ blockedContext: context,
229
+ warning: `${req.method} ${displayPath} - session no longer live`,
230
+ };
231
+ }
232
+ const policy = await sdk.getScopeConfig();
233
+ if (!policy)
234
+ return {
235
+ allowed: false,
236
+ status: getErrorStatusCode("IMPERSONATION_POLICY_UNAVAILABLE"),
237
+ error: "Impersonation policy is temporarily unavailable",
238
+ errorCode: "IMPERSONATION_POLICY_UNAVAILABLE",
239
+ };
240
+ // 1. Deny rules FIRST - blocked regardless of scope, on any target/alias.
241
+ const blockedBy = findBlockedEndpoint(allForms, methods, policy.blockedEndpoints);
242
+ if (blockedBy)
243
+ return {
244
+ allowed: false,
245
+ status: blockedResponse.status ?? getErrorStatusCode("IMPERSONATION_ENDPOINT_BLOCKED"),
246
+ error: "This endpoint is blocked during impersonation",
247
+ errorCode: "IMPERSONATION_ENDPOINT_BLOCKED",
248
+ blockedContext: context,
249
+ warning: `${methods.join("/")} ${displayPath} - blacklisted (${blockedBy.method} ${blockedBy.pattern})`,
250
+ };
251
+ // 2. Application-specific semantic authorization.
252
+ if (isImpersonationAllowed && (await isImpersonationAllowed(req, context)) !== true)
253
+ return {
254
+ allowed: false,
255
+ status: blockedResponse.status ?? getErrorStatusCode("IMPERSONATION_ENDPOINT_BLOCKED"),
256
+ error: "This action is not permitted during impersonation",
257
+ errorCode: "IMPERSONATION_ENDPOINT_BLOCKED",
258
+ blockedContext: context,
259
+ };
260
+ // 3. Write scope allows everything that is not denied.
261
+ if (context.scope === "write")
262
+ return { allowed: true };
263
+ const writeMethods = methods.filter((method) => isWriteMethod(method));
264
+ if (writeMethods.length === 0)
265
+ return { allowed: true };
266
+ // 4. The browser-session bridge mints a Devora resume code for this
267
+ // already-authenticated, live session. It is not a customer write.
268
+ if (bridgePath &&
269
+ methods.length === 1 &&
270
+ methods[0] === "POST" &&
271
+ routedForms.every((target) => target.raw === bridgePath && target.decoded === bridgePath))
272
+ return { allowed: true };
273
+ // 5. Read scope: every write method must be allowlisted on every routed target.
274
+ if (writeMethods.every((method) => isAllowlisted(routedForms, method, policy.safeReadEndpoints)))
275
+ return { allowed: true };
276
+ return {
277
+ allowed: false,
278
+ status: blockedResponse.status ?? getErrorStatusCode(responseErrorCode),
279
+ error: responseMessage,
280
+ errorCode: responseErrorCode,
281
+ blockedContext: context,
282
+ warning: `${methods.join("/")} ${displayPath} - read-only impersonation`,
283
+ };
284
+ }
285
+ return async (req, res, next) => {
286
+ let verdict;
287
+ try {
288
+ verdict = await decide(req);
289
+ }
290
+ catch {
291
+ // Extractor, policy and hook failures must never hang the request or
292
+ // surface as an unhandled rejection (Express 4); they fail closed.
293
+ verdict = {
294
+ allowed: false,
295
+ status: 500,
296
+ error: "Impersonation guard failed",
297
+ errorCode: "INTERNAL_ERROR",
298
+ };
299
+ }
300
+ if (verdict.allowed) {
301
+ await next();
302
+ return;
303
+ }
304
+ if (verdict.blockedContext) {
305
+ if (showWarnings && verdict.warning)
306
+ console.warn(`[Devora] Blocked ${verdict.warning} (session: ${verdict.blockedContext.sessionId})`);
307
+ try {
308
+ onBlocked?.(req, verdict.blockedContext);
309
+ }
310
+ catch {
311
+ // Audit callbacks cannot change the decision.
312
+ }
313
+ }
314
+ res.status(verdict.status).json(createErrorResponse(verdict.error, verdict.errorCode));
315
+ };
316
+ }
317
+ /**
318
+ * Validate impersonation context
319
+ *
320
+ * Utility function to check if impersonation context is valid and not expired.
321
+ * Types are checked strictly; nothing is coerced.
322
+ */
323
+ export function validateImpersonationContext(context) {
324
+ if (!context || typeof context !== "object") {
325
+ return { valid: false, error: "No impersonation context" };
326
+ }
327
+ if (context.isImpersonation !== true) {
328
+ return { valid: false, error: "Not an impersonation session" };
329
+ }
330
+ if (context.scope !== "read" && context.scope !== "write") {
331
+ return { valid: false, error: "Invalid scope" };
332
+ }
333
+ const identities = resolveImpersonationIdentities(context);
334
+ if (typeof identities.actorId !== "string" ||
335
+ !identities.actorId ||
336
+ typeof identities.subjectId !== "string" ||
337
+ !identities.subjectId) {
338
+ return { valid: false, error: "Missing impersonation actor or subject" };
339
+ }
340
+ if (context.authMethod !== "devora_impersonation") {
341
+ return { valid: false, error: "Invalid impersonation authentication method" };
342
+ }
343
+ if (!AUTHORIZATION_SOURCES.has(context.authorizationSource)) {
344
+ return { valid: false, error: "Invalid authorization source" };
345
+ }
346
+ if (typeof context.recordingAllowed !== "boolean") {
347
+ return { valid: false, error: "Missing recording authorization" };
348
+ }
349
+ if (typeof context.sessionId !== "string" || !context.sessionId) {
350
+ return { valid: false, error: "Missing session ID" };
351
+ }
352
+ // expiresAt must be an integer Unix timestamp in milliseconds (Date.now()).
353
+ // If your JWT uses seconds, convert it when extracting the context.
354
+ if (typeof context.expiresAt !== "number" || !Number.isSafeInteger(context.expiresAt)) {
355
+ return { valid: false, error: "Missing expiration" };
356
+ }
357
+ const YEAR_2000_MS = 946684800000;
358
+ if (context.expiresAt < YEAR_2000_MS) {
359
+ return { valid: false, error: "Expiration must be a Unix timestamp in milliseconds" };
360
+ }
361
+ if (Date.now() > context.expiresAt) {
362
+ return { valid: false, expired: true, error: "Impersonation session expired" };
363
+ }
364
+ return { valid: true };
365
+ }
366
+ /**
367
+ * Check if a request method is a write operation
368
+ *
369
+ * @deprecated Use isWriteMethod from @devorash/core instead
370
+ */
371
+ export function isWriteOperation(method) {
372
+ return isWriteMethod(method.toUpperCase());
373
+ }
374
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Atomic replay protection shared by every application instance.
3
+ *
4
+ * `consume` must be an atomic insert-if-absent (for example Redis
5
+ * `SET key 1 NX PXAT expiresAt`, or a unique-key database insert) and must
6
+ * never evict an entry before `expiresAt`. A store that cannot guarantee this
7
+ * must throw; the SDK then fails closed with a 503.
8
+ */
9
+ export interface ReplayStore {
10
+ /**
11
+ * Return true only for the first consumption of `requestId` within
12
+ * `namespace` before `expiresAt` (Unix milliseconds).
13
+ */
14
+ consume(namespace: string, requestId: string, expiresAt: number): Promise<boolean>;
15
+ }
16
+ /** Development-only implementation; production must supply a persistent store. */
17
+ export declare class InMemoryReplayStore implements ReplayStore {
18
+ private readonly entries;
19
+ consume(namespace: string, requestId: string, expiresAt: number): Promise<boolean>;
20
+ }
21
+ //# sourceMappingURL=replay-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-store.d.ts","sourceRoot":"","sources":["../src/replay-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC3B;;;OAGG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAClF;AAED,kFAAkF;AAClF,qBAAa,mBAAoB,YAAW,WAAW;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IAE9C,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CASxF"}
@@ -0,0 +1,18 @@
1
+ /** Development-only implementation; production must supply a persistent store. */
2
+ export class InMemoryReplayStore {
3
+ entries = new Map();
4
+ async consume(namespace, requestId, expiresAt) {
5
+ if (!Number.isFinite(expiresAt))
6
+ throw new Error("Invalid replay expiry");
7
+ const now = Date.now();
8
+ for (const [id, expiry] of this.entries)
9
+ if (expiry <= now)
10
+ this.entries.delete(id);
11
+ const key = `${namespace}\n${requestId}`;
12
+ if (this.entries.has(key))
13
+ return false;
14
+ this.entries.set(key, expiresAt);
15
+ return true;
16
+ }
17
+ }
18
+ //# sourceMappingURL=replay-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-store.js","sourceRoot":"","sources":["../src/replay-store.ts"],"names":[],"mappings":"AAgBA,kFAAkF;AAClF,MAAM,OAAO,mBAAmB;IACd,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA;IAEpD,KAAK,CAAC,OAAO,CAAC,SAAiB,EAAE,SAAiB,EAAE,SAAiB;QACpE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAA;QACzE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,MAAM,IAAI,GAAG;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QACnF,MAAM,GAAG,GAAG,GAAG,SAAS,KAAK,SAAS,EAAE,CAAA;QACxC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAA;QACvC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;QAChC,OAAO,IAAI,CAAA;IACZ,CAAC;CACD"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Scope configuration fetching and caching for backend SDK
3
+ *
4
+ * Fetches centrally managed whitelist and blacklist configuration from the Devora platform
5
+ * and caches it with automatic refresh.
6
+ *
7
+ * @module @devorash/node
8
+ */
9
+ /**
10
+ * Scope endpoint configuration
11
+ */
12
+ export interface ScopeEndpoint {
13
+ method: string;
14
+ pattern: string;
15
+ }
16
+ /**
17
+ * Scope configuration
18
+ */
19
+ export interface ScopeConfig {
20
+ /** Whitelist: Safe endpoints allowed in read-only mode */
21
+ safeReadEndpoints: ScopeEndpoint[];
22
+ /** Blacklist: Endpoints blocked during ANY impersonation */
23
+ blockedEndpoints: ScopeEndpoint[];
24
+ /** Configuration version */
25
+ version: number;
26
+ /** When the cache expires (Unix ms) */
27
+ cachedUntil: number;
28
+ }
29
+ /**
30
+ * Scope config fetcher options
31
+ */
32
+ export interface ScopeConfigFetcherOptions {
33
+ /** Public server API key identifier */
34
+ apiKey: string;
35
+ /**
36
+ * Returns fresh signature headers for `GET /api/sdk/scope-config` (request
37
+ * signing v3, customer-to-devora). The policy is not public; `devoraSDK`
38
+ * supplies this from its server secret.
39
+ */
40
+ signRequest: () => Record<string, string>;
41
+ /** Devora API origin (default: the origin baked into the SDK at build time) */
42
+ apiUrl?: string;
43
+ /** Enable debug logging */
44
+ debug?: boolean;
45
+ /**
46
+ * Cache TTL in milliseconds applied when a 304 revalidates an existing
47
+ * policy (default: 5 minutes, matching the server). A cached policy is served
48
+ * for at most `STALE_GRACE_MS` past its window when Devora is unreachable.
49
+ */
50
+ cacheTtlMs?: number;
51
+ }
52
+ /** How long a cached policy may be served past its window during an outage. */
53
+ export declare const STALE_GRACE_MS: number;
54
+ /**
55
+ * Create a scope config fetcher
56
+ *
57
+ * Handles fetching scope configuration from Devora API with caching
58
+ * and automatic background refresh.
59
+ */
60
+ export declare function createScopeConfigFetcher(options: ScopeConfigFetcherOptions): {
61
+ getConfig: () => Promise<ScopeConfig | null>;
62
+ getCachedConfig: () => ScopeConfig | null;
63
+ refresh: () => Promise<ScopeConfig | null>;
64
+ stop: () => void;
65
+ };
66
+ //# sourceMappingURL=scope-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-config.d.ts","sourceRoot":"","sources":["../src/scope-config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA2DH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,0DAA0D;IAC1D,iBAAiB,EAAE,aAAa,EAAE,CAAA;IAClC,4DAA4D;IAC5D,gBAAgB,EAAE,aAAa,EAAE,CAAA;IACjC,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACzC,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,WAAW,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,+EAA+E;AAC/E,eAAO,MAAM,cAAc,QAAgB,CAAA;AAG3C;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG;IAC7E,SAAS,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC5C,eAAe,EAAE,MAAM,WAAW,GAAG,IAAI,CAAA;IACzC,OAAO,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC1C,IAAI,EAAE,MAAM,IAAI,CAAA;CAChB,CAqJA"}