@jewel998/config 0.1.6 → 0.1.7
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/dist/cache/browserStorage.d.ts +0 -1
- package/dist/cache/memoryStorage.d.ts +0 -1
- package/dist/cache/storage.d.ts +14 -0
- package/dist/cache/storage.js +43 -0
- package/dist/cache/types.d.ts +2 -0
- package/dist/cache/types.js +2 -0
- package/dist/client/ConfigClient.d.ts +0 -1
- package/dist/context/autoContext.d.ts +0 -1
- package/dist/createConfig.d.ts +0 -1
- package/dist/errors/index.d.ts +16 -0
- package/dist/errors/index.js +29 -0
- package/dist/events/EventEmitter.d.ts +0 -1
- package/dist/fetch/batchFetcher.d.ts +0 -1
- package/dist/fetch/projectedFetcher.d.ts +0 -1
- package/dist/index.d.ts +13 -0
- package/dist/initConfig.d.ts +0 -1
- package/dist/loading/deferred.d.ts +2 -0
- package/dist/loading/deferred.js +5 -0
- package/dist/loading/optimistic.d.ts +2 -0
- package/dist/loading/pessimistic.d.ts +2 -0
- package/dist/plugins/evaluatePipeline.d.ts +0 -1
- package/dist/plugins/index.d.ts +4 -0
- package/dist/plugins/lifecycle/archivedPlugin.d.ts +0 -1
- package/dist/plugins/lifecycle/index.d.ts +3 -0
- package/dist/plugins/lifecycle/stateTransitions.d.ts +0 -1
- package/dist/plugins/models.d.ts +50 -0
- package/dist/plugins/models.js +5 -0
- package/dist/plugins/overrides/index.d.ts +1 -0
- package/dist/plugins/overrides/overridesPlugin.d.ts +0 -1
- package/dist/plugins/prerequisites/index.d.ts +2 -0
- package/dist/plugins/prerequisites/prerequisitePlugin.d.ts +0 -1
- package/dist/plugins/rollout/computeBucket.d.ts +0 -1
- package/dist/plugins/rollout/index.d.ts +2 -0
- package/dist/plugins/rollout/murmurhash3.d.ts +5 -0
- package/dist/plugins/rollout/murmurhash3.js +56 -0
- package/dist/plugins/rollout/rolloutPlugin.d.ts +0 -1
- package/dist/plugins/schedule/index.d.ts +1 -0
- package/dist/plugins/schedule/schedulePlugin.d.ts +0 -1
- package/dist/plugins/targeting/index.d.ts +3 -0
- package/dist/plugins/targeting/predicates.d.ts +22 -0
- package/dist/plugins/targeting/predicates.js +110 -0
- package/dist/plugins/targeting/segmentResolver.d.ts +0 -1
- package/dist/plugins/targeting/targetingPlugin.d.ts +0 -1
- package/dist/plugins/types.d.ts +34 -0
- package/dist/plugins/types.js +13 -0
- package/dist/retry/RetryEngine.d.ts +0 -1
- package/dist/transport/HttpTransport.d.ts +0 -1
- package/dist/types.d.ts +203 -0
- package/dist/types.js +14 -0
- package/dist/webhook-formats.d.ts +91 -0
- package/dist/webhook-formats.js +141 -0
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CacheStorage {
|
|
2
|
+
get<T>(key: string): Promise<T | undefined> | T | undefined;
|
|
3
|
+
set<T>(key: string, value: T): Promise<void> | void;
|
|
4
|
+
delete(key: string): Promise<void> | void;
|
|
5
|
+
}
|
|
6
|
+
export interface CacheDriver {
|
|
7
|
+
name: string;
|
|
8
|
+
getItem<T>(key: string): Promise<T | undefined>;
|
|
9
|
+
setItem<T>(key: string, value: T): Promise<void>;
|
|
10
|
+
removeItem(key: string): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare const memoryStorage: () => CacheStorage;
|
|
13
|
+
export declare const createDriverStorage: (driver: CacheDriver, storageKey?: string) => CacheStorage;
|
|
14
|
+
export declare const browserStorage: (storageKey?: string) => CacheStorage;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
export const memoryStorage = () => {
|
|
3
|
+
const store = new Map();
|
|
4
|
+
return {
|
|
5
|
+
get: (key) => store.get(key),
|
|
6
|
+
set: (key, value) => {
|
|
7
|
+
store.set(key, value);
|
|
8
|
+
},
|
|
9
|
+
delete: (key) => {
|
|
10
|
+
store.delete(key);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export const createDriverStorage = (driver, storageKey = "@jewel998/config") => ({
|
|
15
|
+
get: async (key) => driver.getItem(`${storageKey}:${key}`),
|
|
16
|
+
set: async (key, value) => {
|
|
17
|
+
await driver.setItem(`${storageKey}:${key}`, value);
|
|
18
|
+
},
|
|
19
|
+
delete: async (key) => {
|
|
20
|
+
await driver.removeItem(`${storageKey}:${key}`);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
export const browserStorage = (storageKey = "@jewel998/config") => {
|
|
24
|
+
const isBrowser = typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
25
|
+
if (!isBrowser) {
|
|
26
|
+
return memoryStorage();
|
|
27
|
+
}
|
|
28
|
+
const driver = {
|
|
29
|
+
name: "localStorage",
|
|
30
|
+
getItem: async (key) => {
|
|
31
|
+
const serialized = window.localStorage.getItem(key);
|
|
32
|
+
return serialized ? JSON.parse(serialized) : undefined;
|
|
33
|
+
},
|
|
34
|
+
setItem: async (key, value) => {
|
|
35
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
36
|
+
},
|
|
37
|
+
removeItem: async (key) => {
|
|
38
|
+
window.localStorage.removeItem(key);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
return createDriverStorage(driver, storageKey);
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -25,4 +25,3 @@ export declare function autoContext(user?: {
|
|
|
25
25
|
* Deep-merge two evaluation contexts. User-provided values take precedence.
|
|
26
26
|
*/
|
|
27
27
|
export declare function mergeContext(auto: EvaluationContext, user: EvaluationContext): EvaluationContext;
|
|
28
|
-
//# sourceMappingURL=autoContext.d.ts.map
|
package/dist/createConfig.d.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ConfigErrorCode = "MISSING_CLIENT_ID" | "AUTHENTICATION_FAILED" | "INITIALIZATION_FAILED" | "TIMEOUT" | "RATE_LIMITED" | "REVOKED" | "NETWORK_ERROR";
|
|
2
|
+
export declare class ConfigError extends Error {
|
|
3
|
+
readonly code: ConfigErrorCode;
|
|
4
|
+
name: string;
|
|
5
|
+
constructor(message: string, code: ConfigErrorCode);
|
|
6
|
+
}
|
|
7
|
+
export declare class InitializationError extends ConfigError {
|
|
8
|
+
readonly cause?: Error | undefined;
|
|
9
|
+
constructor(message: string, cause?: Error | undefined);
|
|
10
|
+
}
|
|
11
|
+
export declare class TimeoutError extends ConfigError {
|
|
12
|
+
constructor(timeoutMs: number);
|
|
13
|
+
}
|
|
14
|
+
export declare class AuthenticationError extends ConfigError {
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class ConfigError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
name = "ConfigError";
|
|
4
|
+
constructor(message, code) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class InitializationError extends ConfigError {
|
|
10
|
+
cause;
|
|
11
|
+
constructor(message, cause) {
|
|
12
|
+
super(message, "INITIALIZATION_FAILED");
|
|
13
|
+
this.cause = cause;
|
|
14
|
+
this.name = "InitializationError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class TimeoutError extends ConfigError {
|
|
18
|
+
constructor(timeoutMs) {
|
|
19
|
+
super(`Config initialization timed out after ${timeoutMs}ms`, "TIMEOUT");
|
|
20
|
+
this.name = "TimeoutError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class AuthenticationError extends ConfigError {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message, "AUTHENTICATION_FAILED");
|
|
26
|
+
this.name = "AuthenticationError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { initConfig } from "./initConfig.js";
|
|
2
|
+
export type { InitConfigOptions, Flags } from "./initConfig.js";
|
|
3
|
+
export { createConfig } from "./createConfig.js";
|
|
4
|
+
export type { CacheStorage, ClientIdRecord, ConfigClient, ConfigEventCallback, ConfigEventPayloads, ConfigEventType, ConfigRecord, CreateConfigOptions, EnvironmentRecord, EvaluationWarning, FetchGranularity, LoadingStrategy, ProjectRecord, RetryConfig, } from "./types.js";
|
|
5
|
+
export type { EvaluationContext, EvaluationPlugin, PipelineStepId, PipelineStepResult, PipelineHelpers, } from "./plugins/types.js";
|
|
6
|
+
export { PIPELINE_ORDER } from "./plugins/types.js";
|
|
7
|
+
export { autoContext, mergeContext } from "./context/autoContext.js";
|
|
8
|
+
export { WEBHOOK_FORMATS, WEBHOOK_FORMAT_INFO, TEMPLATE_VARIABLES, WEBHOOK_EVENT_TYPES, WEBHOOK_RESOURCE_CATEGORIES, SAMPLE_WEBHOOK_EVENT, } from "./webhook-formats.js";
|
|
9
|
+
export type { WebhookFormat, WebhookEventType, WebhookResourceCategory, } from "./webhook-formats.js";
|
|
10
|
+
export { memoryStorage } from "./cache/memoryStorage.js";
|
|
11
|
+
export { browserStorage } from "./cache/browserStorage.js";
|
|
12
|
+
export { AuthenticationError, ConfigError, InitializationError, TimeoutError, } from "./errors/index.js";
|
|
13
|
+
export type { ConfigErrorCode } from "./errors/index.js";
|
package/dist/initConfig.d.ts
CHANGED
|
@@ -12,4 +12,3 @@ import type { EvaluationContext, EvaluationPlugin, PipelineHelpers } from "./typ
|
|
|
12
12
|
* Steps with no registered plugin are transparently skipped.
|
|
13
13
|
*/
|
|
14
14
|
export declare function evaluatePipeline(plugins: EvaluationPlugin[], flag: ConfigFlagData, context: EvaluationContext, helpers: PipelineHelpers): unknown;
|
|
15
|
-
//# sourceMappingURL=evaluatePipeline.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { PipelineStepId, EvaluationContext, PipelineStepResult, EvaluationPlugin, PipelineHelpers, } from "./types.js";
|
|
2
|
+
export { PIPELINE_ORDER } from "./types.js";
|
|
3
|
+
export type { ConfigFlagData, TargetingRule, PredicateGroup, Predicate, PredicateOperator, Segment, } from "./models.js";
|
|
4
|
+
export { evaluatePipeline } from "./evaluatePipeline.js";
|
|
@@ -24,4 +24,3 @@ export declare const VALID_TRANSITIONS: Record<LifecycleState, LifecycleState[]>
|
|
|
24
24
|
* @returns `true` if the transition is valid, `false` otherwise
|
|
25
25
|
*/
|
|
26
26
|
export declare function validateStateTransition(current: LifecycleState, target: LifecycleState): boolean;
|
|
27
|
-
//# sourceMappingURL=stateTransitions.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** The full config flag document as fetched from Firestore */
|
|
2
|
+
export interface ConfigFlagData {
|
|
3
|
+
key: string;
|
|
4
|
+
value: unknown;
|
|
5
|
+
valueType: "string" | "number" | "boolean" | "json";
|
|
6
|
+
version: string;
|
|
7
|
+
lifecycleState: "draft" | "active" | "stale" | "archived";
|
|
8
|
+
targetingRules?: TargetingRule[];
|
|
9
|
+
rolloutPercentage?: number;
|
|
10
|
+
rolloutValue?: unknown;
|
|
11
|
+
overrides?: Record<string, unknown>;
|
|
12
|
+
schedule?: {
|
|
13
|
+
targetValue: unknown;
|
|
14
|
+
activateAt: string;
|
|
15
|
+
};
|
|
16
|
+
prerequisites?: Array<{
|
|
17
|
+
flagKey: string;
|
|
18
|
+
operator?: "equals" | "not_equals" | "greater_than" | "less_than" | "contains";
|
|
19
|
+
requiredValue: unknown;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
/** A single targeting rule */
|
|
23
|
+
export interface TargetingRule {
|
|
24
|
+
id: string;
|
|
25
|
+
priority: number;
|
|
26
|
+
value: unknown;
|
|
27
|
+
conditions: PredicateGroup[];
|
|
28
|
+
}
|
|
29
|
+
/** A group of predicates combined with AND logic */
|
|
30
|
+
export interface PredicateGroup {
|
|
31
|
+
predicates: Predicate[];
|
|
32
|
+
}
|
|
33
|
+
/** A single predicate */
|
|
34
|
+
export interface Predicate {
|
|
35
|
+
attribute: string;
|
|
36
|
+
operator: PredicateOperator;
|
|
37
|
+
value: string | number | boolean | string[];
|
|
38
|
+
}
|
|
39
|
+
/** Supported predicate operators */
|
|
40
|
+
export type PredicateOperator = "equals" | "not_equals" | "contains" | "starts_with" | "ends_with" | "in_list" | "not_in_list" | "greater_than" | "less_than" | "regex_match" | "in_segment" | "not_in_segment";
|
|
41
|
+
/** A reusable audience segment */
|
|
42
|
+
export interface Segment {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
conditions: PredicateGroup[];
|
|
47
|
+
createdAt: string;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
createdBy: string;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { overridesPlugin } from "./overridesPlugin.js";
|
|
@@ -21,4 +21,3 @@ export interface PrerequisitePluginOptions {
|
|
|
21
21
|
* that tracks which flags are currently being evaluated for prerequisites.
|
|
22
22
|
*/
|
|
23
23
|
export declare function prerequisitePlugin(options?: PrerequisitePluginOptions): EvaluationPlugin;
|
|
24
|
-
//# sourceMappingURL=prerequisitePlugin.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// MurmurHash3_x86_32
|
|
3
|
+
// Public domain — Austin Appleby
|
|
4
|
+
// Adapted for TypeScript (operates on UTF-8 encoded string bytes)
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════
|
|
6
|
+
/**
|
|
7
|
+
* MurmurHash3_x86_32 — deterministic 32-bit hash.
|
|
8
|
+
* Used for percentage rollout bucketing (same algorithm as flagd/OpenFeature).
|
|
9
|
+
*/
|
|
10
|
+
export function murmurhash3_32(key, seed = 0) {
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
const bytes = encoder.encode(key);
|
|
13
|
+
const len = bytes.length;
|
|
14
|
+
let h1 = seed >>> 0;
|
|
15
|
+
const c1 = 0xcc9e2d51;
|
|
16
|
+
const c2 = 0x1b873593;
|
|
17
|
+
let i = 0;
|
|
18
|
+
while (i + 4 <= len) {
|
|
19
|
+
let k1 = (bytes[i] |
|
|
20
|
+
(bytes[i + 1] << 8) |
|
|
21
|
+
(bytes[i + 2] << 16) |
|
|
22
|
+
(bytes[i + 3] << 24)) >>>
|
|
23
|
+
0;
|
|
24
|
+
k1 = Math.imul(k1, c1) >>> 0;
|
|
25
|
+
k1 = ((k1 << 15) | (k1 >>> 17)) >>> 0;
|
|
26
|
+
k1 = Math.imul(k1, c2) >>> 0;
|
|
27
|
+
h1 ^= k1;
|
|
28
|
+
h1 = ((h1 << 13) | (h1 >>> 19)) >>> 0;
|
|
29
|
+
h1 = (Math.imul(h1, 5) + 0xe6546b64) >>> 0;
|
|
30
|
+
i += 4;
|
|
31
|
+
}
|
|
32
|
+
let k1 = 0;
|
|
33
|
+
const remainder = len & 3;
|
|
34
|
+
if (remainder >= 3) {
|
|
35
|
+
k1 ^= bytes[i + 2] << 16;
|
|
36
|
+
}
|
|
37
|
+
if (remainder >= 2) {
|
|
38
|
+
k1 ^= bytes[i + 1] << 8;
|
|
39
|
+
}
|
|
40
|
+
if (remainder >= 1) {
|
|
41
|
+
k1 ^= bytes[i];
|
|
42
|
+
k1 = Math.imul(k1, c1) >>> 0;
|
|
43
|
+
k1 = ((k1 << 15) | (k1 >>> 17)) >>> 0;
|
|
44
|
+
k1 = Math.imul(k1, c2) >>> 0;
|
|
45
|
+
h1 ^= k1;
|
|
46
|
+
}
|
|
47
|
+
h1 ^= len;
|
|
48
|
+
// fmix32
|
|
49
|
+
h1 ^= h1 >>> 16;
|
|
50
|
+
h1 = Math.imul(h1, 0x85ebca6b) >>> 0;
|
|
51
|
+
h1 ^= h1 >>> 13;
|
|
52
|
+
h1 = Math.imul(h1, 0xc2b2ae35) >>> 0;
|
|
53
|
+
h1 ^= h1 >>> 16;
|
|
54
|
+
return h1 >>> 0;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=murmurhash3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { schedulePlugin } from "./schedulePlugin.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Predicate, PredicateGroup } from "../models.js";
|
|
2
|
+
type AttributeValue = string | number | boolean | string[];
|
|
3
|
+
/**
|
|
4
|
+
* Evaluate a single predicate against the provided attributes.
|
|
5
|
+
*
|
|
6
|
+
* Returns false for:
|
|
7
|
+
* - Missing attributes in context
|
|
8
|
+
* - Type mismatches (e.g., greater_than on a non-numeric value)
|
|
9
|
+
* - Invalid regex patterns (also calls emitError)
|
|
10
|
+
* - in_segment / not_in_segment (handled externally by segment resolver)
|
|
11
|
+
*/
|
|
12
|
+
export declare function evaluatePredicate(predicate: Predicate, attributes: Record<string, AttributeValue>, emitError?: (message: string) => void): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Evaluate predicate groups in Disjunctive Normal Form (DNF).
|
|
15
|
+
*
|
|
16
|
+
* Groups are OR'd — any group matching means the whole expression matches.
|
|
17
|
+
* Within each group, predicates are AND'd — all must match.
|
|
18
|
+
*
|
|
19
|
+
* Returns false if groups array is empty.
|
|
20
|
+
*/
|
|
21
|
+
export declare function evaluatePredicateGroups(groups: PredicateGroup[], attributes: Record<string, AttributeValue>, emitError?: (message: string) => void): boolean;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// Predicate Evaluator — DNF logic for targeting rules
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
/**
|
|
5
|
+
* Evaluate a single predicate against the provided attributes.
|
|
6
|
+
*
|
|
7
|
+
* Returns false for:
|
|
8
|
+
* - Missing attributes in context
|
|
9
|
+
* - Type mismatches (e.g., greater_than on a non-numeric value)
|
|
10
|
+
* - Invalid regex patterns (also calls emitError)
|
|
11
|
+
* - in_segment / not_in_segment (handled externally by segment resolver)
|
|
12
|
+
*/
|
|
13
|
+
export function evaluatePredicate(predicate, attributes, emitError) {
|
|
14
|
+
const { attribute, operator, value } = predicate;
|
|
15
|
+
// in_segment / not_in_segment are resolved externally by the segment resolver
|
|
16
|
+
if (operator === "in_segment" || operator === "not_in_segment") {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
// Missing attribute → predicate is non-matching (Property 4)
|
|
20
|
+
if (!(attribute in attributes)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const attrValue = attributes[attribute];
|
|
24
|
+
// Attribute value is undefined/null → non-matching
|
|
25
|
+
if (attrValue === undefined || attrValue === null) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
switch (operator) {
|
|
29
|
+
case "equals":
|
|
30
|
+
return attrValue === value;
|
|
31
|
+
case "not_equals":
|
|
32
|
+
return attrValue !== value;
|
|
33
|
+
case "contains": {
|
|
34
|
+
if (typeof attrValue !== "string" || typeof value !== "string") {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return attrValue.includes(value);
|
|
38
|
+
}
|
|
39
|
+
case "starts_with": {
|
|
40
|
+
if (typeof attrValue !== "string" || typeof value !== "string") {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return attrValue.startsWith(value);
|
|
44
|
+
}
|
|
45
|
+
case "ends_with": {
|
|
46
|
+
if (typeof attrValue !== "string" || typeof value !== "string") {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return attrValue.endsWith(value);
|
|
50
|
+
}
|
|
51
|
+
case "in_list": {
|
|
52
|
+
if (!Array.isArray(value)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return value.includes(attrValue);
|
|
56
|
+
}
|
|
57
|
+
case "not_in_list": {
|
|
58
|
+
if (!Array.isArray(value)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return !value.includes(attrValue);
|
|
62
|
+
}
|
|
63
|
+
case "greater_than": {
|
|
64
|
+
if (typeof attrValue !== "number" || typeof value !== "number") {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return attrValue > value;
|
|
68
|
+
}
|
|
69
|
+
case "less_than": {
|
|
70
|
+
if (typeof attrValue !== "number" || typeof value !== "number") {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return attrValue < value;
|
|
74
|
+
}
|
|
75
|
+
case "regex_match": {
|
|
76
|
+
if (typeof attrValue !== "string" || typeof value !== "string") {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const regex = new RegExp(value);
|
|
81
|
+
return regex.test(attrValue);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
emitError?.(`Invalid regex pattern: ${value}`);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
default:
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Evaluate predicate groups in Disjunctive Normal Form (DNF).
|
|
94
|
+
*
|
|
95
|
+
* Groups are OR'd — any group matching means the whole expression matches.
|
|
96
|
+
* Within each group, predicates are AND'd — all must match.
|
|
97
|
+
*
|
|
98
|
+
* Returns false if groups array is empty.
|
|
99
|
+
*/
|
|
100
|
+
export function evaluatePredicateGroups(groups, attributes, emitError) {
|
|
101
|
+
if (groups.length === 0) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
// OR across groups: at least one group must fully match
|
|
105
|
+
return groups.some((group) => {
|
|
106
|
+
// AND within each group: all predicates must match
|
|
107
|
+
return group.predicates.every((predicate) => evaluatePredicate(predicate, attributes, emitError));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=predicates.js.map
|
|
@@ -13,4 +13,3 @@ type AttributeValue = string | number | boolean | string[];
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function resolveSegment(segmentId: string, segments: Record<string, Segment>, attributes: Record<string, AttributeValue>, emitError?: (message: string) => void): boolean;
|
|
15
15
|
export {};
|
|
16
|
-
//# sourceMappingURL=segmentResolver.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ConfigFlagData } from "./models.js";
|
|
2
|
+
/** Pipeline step identifier — determines execution order */
|
|
3
|
+
export type PipelineStepId = "archived" | "prerequisites" | "overrides" | "schedule" | "targeting" | "rollout";
|
|
4
|
+
/** The fixed execution order (per Requirement 11) */
|
|
5
|
+
export declare const PIPELINE_ORDER: PipelineStepId[];
|
|
6
|
+
/** Evaluation context provided by the consumer */
|
|
7
|
+
export interface EvaluationContext {
|
|
8
|
+
userId?: string;
|
|
9
|
+
attributes?: Record<string, string | number | boolean | string[]>;
|
|
10
|
+
consentGranted?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Result from a pipeline step */
|
|
13
|
+
export type PipelineStepResult = {
|
|
14
|
+
resolved: true;
|
|
15
|
+
value: unknown;
|
|
16
|
+
} | {
|
|
17
|
+
resolved: false;
|
|
18
|
+
};
|
|
19
|
+
/** A registered evaluation plugin */
|
|
20
|
+
export interface EvaluationPlugin {
|
|
21
|
+
/** Which pipeline step this plugin handles */
|
|
22
|
+
stepId: PipelineStepId;
|
|
23
|
+
/** Evaluate a config flag at this pipeline step */
|
|
24
|
+
evaluate(flag: ConfigFlagData, context: EvaluationContext, helpers: PipelineHelpers): PipelineStepResult;
|
|
25
|
+
}
|
|
26
|
+
/** Helpers provided to plugins for cross-cutting concerns */
|
|
27
|
+
export interface PipelineHelpers {
|
|
28
|
+
/** Evaluate another flag (for prerequisites) */
|
|
29
|
+
evaluateFlag(key: string, context: EvaluationContext): unknown;
|
|
30
|
+
/** Emit an error */
|
|
31
|
+
emitError(message: string): void;
|
|
32
|
+
/** Current timestamp (injectable for testing) */
|
|
33
|
+
now(): number;
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// Plugin System Types and Pipeline Interfaces
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
/** The fixed execution order (per Requirement 11) */
|
|
5
|
+
export const PIPELINE_ORDER = [
|
|
6
|
+
"archived",
|
|
7
|
+
"prerequisites",
|
|
8
|
+
"overrides",
|
|
9
|
+
"schedule",
|
|
10
|
+
"targeting",
|
|
11
|
+
"rollout",
|
|
12
|
+
];
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -2,4 +2,3 @@ import type { RetryConfig } from "../types.js";
|
|
|
2
2
|
export declare const sleep: (ms: number) => Promise<void>;
|
|
3
3
|
export declare const isNonRetryableError: (error: unknown) => boolean;
|
|
4
4
|
export declare const withRetry: <T>(fn: () => Promise<T>, options?: RetryConfig, onRetry?: (attempt: number, error: Error, nextDelay: number) => void) => Promise<T>;
|
|
5
|
-
//# sourceMappingURL=RetryEngine.d.ts.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { EvaluationContext, EvaluationPlugin } from "./plugins/types.js";
|
|
2
|
+
/** Loading strategy for SDK initialization */
|
|
3
|
+
export type LoadingStrategy = "optimistic" | "pessimistic" | "deferred";
|
|
4
|
+
/** Evaluation mode for the SDK */
|
|
5
|
+
export type EvaluationMode = "server" | "client";
|
|
6
|
+
/** Fetch granularity for remote config retrieval */
|
|
7
|
+
export type FetchGranularity = "batch" | "projected";
|
|
8
|
+
/** Default cache TTL: 7 days in milliseconds */
|
|
9
|
+
export declare const DEFAULT_CACHE_TTL: number;
|
|
10
|
+
/** Default pessimistic timeout in milliseconds */
|
|
11
|
+
export declare const DEFAULT_TIMEOUT = 10000;
|
|
12
|
+
export interface CreateConfigOptions {
|
|
13
|
+
/** Required. The clientId token generated in the Portal. */
|
|
14
|
+
clientId: string;
|
|
15
|
+
/**
|
|
16
|
+
* Loading strategy for initialization.
|
|
17
|
+
* - "optimistic" (default): Sync return, background fetch
|
|
18
|
+
* - "pessimistic": Async, blocks until fetch completes
|
|
19
|
+
* - "deferred": Sync return, lazy fetch on first access
|
|
20
|
+
*/
|
|
21
|
+
loadingStrategy?: LoadingStrategy;
|
|
22
|
+
/**
|
|
23
|
+
* Fetch granularity for remote config retrieval.
|
|
24
|
+
* - "batch" (default): Fetch all keys in one request
|
|
25
|
+
* - "projected": Fetch only requested keys on demand
|
|
26
|
+
*/
|
|
27
|
+
fetchGranularity?: FetchGranularity;
|
|
28
|
+
/** Cache storage adapter. Default: memoryStorage() */
|
|
29
|
+
storage?: CacheStorage;
|
|
30
|
+
/** Retry configuration for failed fetches */
|
|
31
|
+
retry?: RetryConfig;
|
|
32
|
+
/** Timeout for pessimistic loading (ms). Default: 10000 */
|
|
33
|
+
timeout?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Your self-hosted API URL. Points to your Firebase deployment.
|
|
36
|
+
* Default: https://jewel998-config.web.app/api (demo instance)
|
|
37
|
+
*
|
|
38
|
+
* @example "https://your-project.web.app/api"
|
|
39
|
+
*/
|
|
40
|
+
baseUrl?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Evaluation plugins to register (tree-shakeable pipeline steps).
|
|
43
|
+
* Only used with server keys (svr_). Client keys (cid_) receive pre-resolved values.
|
|
44
|
+
*/
|
|
45
|
+
plugins?: EvaluationPlugin[];
|
|
46
|
+
/** Evaluation context for targeting (can be updated post-init via setContext) */
|
|
47
|
+
context?: EvaluationContext;
|
|
48
|
+
/** Enable consent-aware mode (GDPR): if true and context.consentGranted !== true, returns defaults */
|
|
49
|
+
consentAware?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface ConfigClient {
|
|
52
|
+
/** Get a typed config value by key. */
|
|
53
|
+
getValue<T = unknown>(key: string): T | undefined;
|
|
54
|
+
getValue<T = unknown>(key: string, defaultValue: T): T;
|
|
55
|
+
/** Get a boolean flag by key. Returns false if missing. */
|
|
56
|
+
getFlag(key: string): boolean;
|
|
57
|
+
/** Get all config key-value pairs. */
|
|
58
|
+
getAll(): Record<string, unknown>;
|
|
59
|
+
/** Force refresh from remote. Updates cache and emits 'updated'. */
|
|
60
|
+
refresh(): Promise<void>;
|
|
61
|
+
/** Update the evaluation context (used by plugins for targeting, rollout, etc.) */
|
|
62
|
+
setContext(newContext: EvaluationContext): void;
|
|
63
|
+
/** Subscribe to a lifecycle event. */
|
|
64
|
+
on<E extends ConfigEventType>(event: E, callback: ConfigEventCallback<E>): void;
|
|
65
|
+
/** Unsubscribe from a lifecycle event. */
|
|
66
|
+
off<E extends ConfigEventType>(event: E, callback: ConfigEventCallback<E>): void;
|
|
67
|
+
}
|
|
68
|
+
export interface RetryConfig {
|
|
69
|
+
/** Maximum number of retry attempts. Default: 3 */
|
|
70
|
+
maxRetries?: number;
|
|
71
|
+
/** Base delay in milliseconds. Default: 1000 */
|
|
72
|
+
baseDelay?: number;
|
|
73
|
+
/** Backoff multiplier. Default: 2 (exponential) */
|
|
74
|
+
multiplier?: number;
|
|
75
|
+
/** Maximum delay cap in milliseconds. Default: 30000 */
|
|
76
|
+
maxDelay?: number;
|
|
77
|
+
}
|
|
78
|
+
export declare const DEFAULT_RETRY: Required<RetryConfig>;
|
|
79
|
+
export interface CacheStorage {
|
|
80
|
+
/** Get a value by key. Returns undefined if missing or TTL expired. */
|
|
81
|
+
get<T>(key: string): T | undefined;
|
|
82
|
+
/** Set a value with optional TTL override (ms). Default TTL: 7 days. */
|
|
83
|
+
set<T>(key: string, value: T, ttl?: number): void;
|
|
84
|
+
/** Delete a specific key from cache. */
|
|
85
|
+
delete(key: string): void;
|
|
86
|
+
/** Clear all cached values. */
|
|
87
|
+
clear(): void;
|
|
88
|
+
}
|
|
89
|
+
export interface CacheEntry<T = unknown> {
|
|
90
|
+
value: T;
|
|
91
|
+
expiresAt: number;
|
|
92
|
+
}
|
|
93
|
+
export interface BrowserStorageOptions {
|
|
94
|
+
/** localStorage key prefix. Default: "@jewel998/config" */
|
|
95
|
+
prefix?: string;
|
|
96
|
+
/** Default TTL in milliseconds. Default: 604800000 (7 days) */
|
|
97
|
+
defaultTtl?: number;
|
|
98
|
+
}
|
|
99
|
+
export type ConfigEventType = "ready" | "updated" | "fetchError" | "revoked";
|
|
100
|
+
export interface ConfigEventPayloads {
|
|
101
|
+
ready: {
|
|
102
|
+
loadingStrategy: LoadingStrategy;
|
|
103
|
+
cachedKeys: number;
|
|
104
|
+
};
|
|
105
|
+
updated: {
|
|
106
|
+
keys: string[];
|
|
107
|
+
source: "background" | "refresh";
|
|
108
|
+
};
|
|
109
|
+
fetchError: {
|
|
110
|
+
error: Error;
|
|
111
|
+
retryCount: number;
|
|
112
|
+
willRetry: boolean;
|
|
113
|
+
};
|
|
114
|
+
revoked: {
|
|
115
|
+
clientId: string;
|
|
116
|
+
message: string;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export type ConfigEventCallback<E extends ConfigEventType> = (payload: ConfigEventPayloads[E]) => void;
|
|
120
|
+
export interface LoadingContext {
|
|
121
|
+
clientId: string;
|
|
122
|
+
fetcher: ConfigFetcher;
|
|
123
|
+
cache: CacheStorage;
|
|
124
|
+
events: EventEmitterInterface;
|
|
125
|
+
retry: Required<RetryConfig>;
|
|
126
|
+
timeout: number;
|
|
127
|
+
granularity: FetchGranularity;
|
|
128
|
+
}
|
|
129
|
+
export interface LoadingResult {
|
|
130
|
+
initialData: Record<string, unknown>;
|
|
131
|
+
status: "ready" | "pending";
|
|
132
|
+
}
|
|
133
|
+
export interface LoadingStrategyHandler {
|
|
134
|
+
execute(context: LoadingContext): LoadingResult | Promise<LoadingResult>;
|
|
135
|
+
}
|
|
136
|
+
export interface ConfigFetcher {
|
|
137
|
+
/** Fetch all configs for the bound project+environment */
|
|
138
|
+
fetchAll(): Promise<Record<string, unknown>>;
|
|
139
|
+
/** Fetch specific keys only */
|
|
140
|
+
fetchKeys(keys: string[]): Promise<Record<string, unknown>>;
|
|
141
|
+
}
|
|
142
|
+
export interface HttpTransport {
|
|
143
|
+
request<T>(endpoint: string, body?: Record<string, unknown>): Promise<T>;
|
|
144
|
+
}
|
|
145
|
+
export interface GetConfigRequest {
|
|
146
|
+
clientId: string;
|
|
147
|
+
keys?: string[];
|
|
148
|
+
context?: {
|
|
149
|
+
userId?: string;
|
|
150
|
+
attributes?: Record<string, string | number | boolean | string[]>;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export interface EvaluationWarning {
|
|
154
|
+
key: string;
|
|
155
|
+
reason: "evaluation_error" | "segment_not_found" | "prerequisite_failed";
|
|
156
|
+
message: string;
|
|
157
|
+
}
|
|
158
|
+
export interface GetConfigResponse {
|
|
159
|
+
data: Record<string, unknown>;
|
|
160
|
+
segments?: Record<string, unknown>;
|
|
161
|
+
version: string;
|
|
162
|
+
timestamp: string;
|
|
163
|
+
warnings?: EvaluationWarning[];
|
|
164
|
+
}
|
|
165
|
+
export interface EventEmitterInterface {
|
|
166
|
+
on<E extends ConfigEventType>(event: E, callback: ConfigEventCallback<E>): void;
|
|
167
|
+
off<E extends ConfigEventType>(event: E, callback: ConfigEventCallback<E>): void;
|
|
168
|
+
emit<E extends ConfigEventType>(event: E, payload: ConfigEventPayloads[E]): void;
|
|
169
|
+
removeAllListeners(): void;
|
|
170
|
+
}
|
|
171
|
+
export interface ProjectRecord {
|
|
172
|
+
id: string;
|
|
173
|
+
name: string;
|
|
174
|
+
ownerId: string;
|
|
175
|
+
authorizedUsers: string[];
|
|
176
|
+
createdAt: string;
|
|
177
|
+
updatedAt: string;
|
|
178
|
+
}
|
|
179
|
+
export interface EnvironmentRecord {
|
|
180
|
+
id: string;
|
|
181
|
+
projectId: string;
|
|
182
|
+
name: string;
|
|
183
|
+
allowedDomains: string[];
|
|
184
|
+
createdAt: string;
|
|
185
|
+
updatedAt: string;
|
|
186
|
+
}
|
|
187
|
+
export interface ClientIdRecord {
|
|
188
|
+
token: string;
|
|
189
|
+
status: "active" | "revoked";
|
|
190
|
+
createdAt: string;
|
|
191
|
+
revokedAt: string | null;
|
|
192
|
+
createdBy: string;
|
|
193
|
+
label?: string;
|
|
194
|
+
}
|
|
195
|
+
export interface ConfigRecord {
|
|
196
|
+
key: string;
|
|
197
|
+
value: unknown;
|
|
198
|
+
valueType: "string" | "number" | "boolean" | "json";
|
|
199
|
+
version: string;
|
|
200
|
+
publishedAt: string;
|
|
201
|
+
updatedAt: string;
|
|
202
|
+
updatedBy: string;
|
|
203
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// Core SDK Types
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
/** Default cache TTL: 7 days in milliseconds */
|
|
5
|
+
export const DEFAULT_CACHE_TTL = 7 * 24 * 60 * 60 * 1000; // 604_800_000 ms
|
|
6
|
+
/** Default pessimistic timeout in milliseconds */
|
|
7
|
+
export const DEFAULT_TIMEOUT = 10_000;
|
|
8
|
+
export const DEFAULT_RETRY = {
|
|
9
|
+
maxRetries: 3,
|
|
10
|
+
baseDelay: 1000,
|
|
11
|
+
multiplier: 2,
|
|
12
|
+
maxDelay: 30_000,
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported webhook format identifiers.
|
|
3
|
+
*/
|
|
4
|
+
export declare const WEBHOOK_FORMATS: readonly ["standard", "slack", "discord", "google-chat", "ms-teams", "custom"];
|
|
5
|
+
export type WebhookFormat = (typeof WEBHOOK_FORMATS)[number];
|
|
6
|
+
/**
|
|
7
|
+
* Human-readable descriptions for each webhook format.
|
|
8
|
+
*/
|
|
9
|
+
export declare const WEBHOOK_FORMAT_INFO: Record<WebhookFormat, {
|
|
10
|
+
label: string;
|
|
11
|
+
description: string;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Available template variables for the custom format.
|
|
15
|
+
* Used by: portal UI (variable reference), docs page, and the function's template engine.
|
|
16
|
+
*/
|
|
17
|
+
export declare const TEMPLATE_VARIABLES: readonly [{
|
|
18
|
+
readonly variable: "{{action}}";
|
|
19
|
+
readonly description: "The action that occurred";
|
|
20
|
+
readonly example: "create, update, delete, state_change";
|
|
21
|
+
}, {
|
|
22
|
+
readonly variable: "{{resource.category}}";
|
|
23
|
+
readonly description: "Resource category";
|
|
24
|
+
readonly example: "config, segment, api_key, team";
|
|
25
|
+
}, {
|
|
26
|
+
readonly variable: "{{resource.path}}";
|
|
27
|
+
readonly description: "Full resource path";
|
|
28
|
+
readonly example: "environments/prod/configs/feature.dark_mode";
|
|
29
|
+
}, {
|
|
30
|
+
readonly variable: "{{resource.name}}";
|
|
31
|
+
readonly description: "Human-readable resource name";
|
|
32
|
+
readonly example: "feature.dark_mode";
|
|
33
|
+
}, {
|
|
34
|
+
readonly variable: "{{environment}}";
|
|
35
|
+
readonly description: "Environment name (empty if none)";
|
|
36
|
+
readonly example: "production";
|
|
37
|
+
}, {
|
|
38
|
+
readonly variable: "{{actor.id}}";
|
|
39
|
+
readonly description: "User ID of the person who made the change";
|
|
40
|
+
readonly example: "user_abc123";
|
|
41
|
+
}, {
|
|
42
|
+
readonly variable: "{{timestamp}}";
|
|
43
|
+
readonly description: "ISO 8601 timestamp of the event";
|
|
44
|
+
readonly example: "2025-01-15T09:30:00.000Z";
|
|
45
|
+
}, {
|
|
46
|
+
readonly variable: "{{project.id}}";
|
|
47
|
+
readonly description: "Project identifier";
|
|
48
|
+
readonly example: "proj_xyz";
|
|
49
|
+
}, {
|
|
50
|
+
readonly variable: "{{webhook.id}}";
|
|
51
|
+
readonly description: "Webhook identifier";
|
|
52
|
+
readonly example: "whk_123";
|
|
53
|
+
}, {
|
|
54
|
+
readonly variable: "{{changes.old}}";
|
|
55
|
+
readonly description: "Previous value as JSON string (empty if create)";
|
|
56
|
+
readonly example: "{\"enabled\": true}";
|
|
57
|
+
}, {
|
|
58
|
+
readonly variable: "{{changes.new}}";
|
|
59
|
+
readonly description: "New value as JSON string (empty if delete)";
|
|
60
|
+
readonly example: "{\"enabled\": false}";
|
|
61
|
+
}];
|
|
62
|
+
/**
|
|
63
|
+
* Event types that can trigger webhooks.
|
|
64
|
+
*/
|
|
65
|
+
export declare const WEBHOOK_EVENT_TYPES: readonly ["create", "update", "delete", "state_change"];
|
|
66
|
+
export type WebhookEventType = (typeof WEBHOOK_EVENT_TYPES)[number];
|
|
67
|
+
/**
|
|
68
|
+
* Resource categories for webhook filtering.
|
|
69
|
+
*/
|
|
70
|
+
export declare const WEBHOOK_RESOURCE_CATEGORIES: readonly ["config", "segment", "api_key", "project", "team", "environment"];
|
|
71
|
+
export type WebhookResourceCategory = (typeof WEBHOOK_RESOURCE_CATEGORIES)[number];
|
|
72
|
+
/**
|
|
73
|
+
* Sample payload used for preview/testing (shared between portal and docs).
|
|
74
|
+
*/
|
|
75
|
+
export declare const SAMPLE_WEBHOOK_EVENT: {
|
|
76
|
+
readonly action: "update";
|
|
77
|
+
readonly resourceCategory: "config";
|
|
78
|
+
readonly resourcePath: "environments/production/configs/feature.dark_mode";
|
|
79
|
+
readonly resourceName: "feature.dark_mode";
|
|
80
|
+
readonly environment: "production";
|
|
81
|
+
readonly actorId: "user_abc123";
|
|
82
|
+
readonly timestamp: "2025-01-15T09:30:00.000Z";
|
|
83
|
+
readonly oldValue: {
|
|
84
|
+
readonly value: false;
|
|
85
|
+
};
|
|
86
|
+
readonly newValue: {
|
|
87
|
+
readonly value: true;
|
|
88
|
+
};
|
|
89
|
+
readonly projectId: "proj_xyz";
|
|
90
|
+
readonly webhookId: "whk_sample";
|
|
91
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2
|
+
// Webhook Format Constants — shared between portal, docs, and functions
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
/**
|
|
5
|
+
* Supported webhook format identifiers.
|
|
6
|
+
*/
|
|
7
|
+
export const WEBHOOK_FORMATS = [
|
|
8
|
+
"standard",
|
|
9
|
+
"slack",
|
|
10
|
+
"discord",
|
|
11
|
+
"google-chat",
|
|
12
|
+
"ms-teams",
|
|
13
|
+
"custom",
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Human-readable descriptions for each webhook format.
|
|
17
|
+
*/
|
|
18
|
+
export const WEBHOOK_FORMAT_INFO = {
|
|
19
|
+
standard: {
|
|
20
|
+
label: "Standard JSON",
|
|
21
|
+
description: "Plain JSON payload with all event details",
|
|
22
|
+
},
|
|
23
|
+
slack: {
|
|
24
|
+
label: "Slack",
|
|
25
|
+
description: "Block Kit formatted message for Slack channels",
|
|
26
|
+
},
|
|
27
|
+
discord: {
|
|
28
|
+
label: "Discord",
|
|
29
|
+
description: "Rich embed message for Discord channels",
|
|
30
|
+
},
|
|
31
|
+
"google-chat": {
|
|
32
|
+
label: "Google Chat",
|
|
33
|
+
description: "Cards v2 formatted message for Google Chat",
|
|
34
|
+
},
|
|
35
|
+
"ms-teams": {
|
|
36
|
+
label: "Microsoft Teams",
|
|
37
|
+
description: "Adaptive Card for Microsoft Teams channels",
|
|
38
|
+
},
|
|
39
|
+
custom: {
|
|
40
|
+
label: "Custom Template",
|
|
41
|
+
description: "User-defined template with {{variable}} interpolation",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Available template variables for the custom format.
|
|
46
|
+
* Used by: portal UI (variable reference), docs page, and the function's template engine.
|
|
47
|
+
*/
|
|
48
|
+
export const TEMPLATE_VARIABLES = [
|
|
49
|
+
{
|
|
50
|
+
variable: "{{action}}",
|
|
51
|
+
description: "The action that occurred",
|
|
52
|
+
example: "create, update, delete, state_change",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
variable: "{{resource.category}}",
|
|
56
|
+
description: "Resource category",
|
|
57
|
+
example: "config, segment, api_key, team",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
variable: "{{resource.path}}",
|
|
61
|
+
description: "Full resource path",
|
|
62
|
+
example: "environments/prod/configs/feature.dark_mode",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
variable: "{{resource.name}}",
|
|
66
|
+
description: "Human-readable resource name",
|
|
67
|
+
example: "feature.dark_mode",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
variable: "{{environment}}",
|
|
71
|
+
description: "Environment name (empty if none)",
|
|
72
|
+
example: "production",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
variable: "{{actor.id}}",
|
|
76
|
+
description: "User ID of the person who made the change",
|
|
77
|
+
example: "user_abc123",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
variable: "{{timestamp}}",
|
|
81
|
+
description: "ISO 8601 timestamp of the event",
|
|
82
|
+
example: "2025-01-15T09:30:00.000Z",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
variable: "{{project.id}}",
|
|
86
|
+
description: "Project identifier",
|
|
87
|
+
example: "proj_xyz",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
variable: "{{webhook.id}}",
|
|
91
|
+
description: "Webhook identifier",
|
|
92
|
+
example: "whk_123",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
variable: "{{changes.old}}",
|
|
96
|
+
description: "Previous value as JSON string (empty if create)",
|
|
97
|
+
example: '{"enabled": true}',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
variable: "{{changes.new}}",
|
|
101
|
+
description: "New value as JSON string (empty if delete)",
|
|
102
|
+
example: '{"enabled": false}',
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
/**
|
|
106
|
+
* Event types that can trigger webhooks.
|
|
107
|
+
*/
|
|
108
|
+
export const WEBHOOK_EVENT_TYPES = [
|
|
109
|
+
"create",
|
|
110
|
+
"update",
|
|
111
|
+
"delete",
|
|
112
|
+
"state_change",
|
|
113
|
+
];
|
|
114
|
+
/**
|
|
115
|
+
* Resource categories for webhook filtering.
|
|
116
|
+
*/
|
|
117
|
+
export const WEBHOOK_RESOURCE_CATEGORIES = [
|
|
118
|
+
"config",
|
|
119
|
+
"segment",
|
|
120
|
+
"api_key",
|
|
121
|
+
"project",
|
|
122
|
+
"team",
|
|
123
|
+
"environment",
|
|
124
|
+
];
|
|
125
|
+
/**
|
|
126
|
+
* Sample payload used for preview/testing (shared between portal and docs).
|
|
127
|
+
*/
|
|
128
|
+
export const SAMPLE_WEBHOOK_EVENT = {
|
|
129
|
+
action: "update",
|
|
130
|
+
resourceCategory: "config",
|
|
131
|
+
resourcePath: "environments/production/configs/feature.dark_mode",
|
|
132
|
+
resourceName: "feature.dark_mode",
|
|
133
|
+
environment: "production",
|
|
134
|
+
actorId: "user_abc123",
|
|
135
|
+
timestamp: "2025-01-15T09:30:00.000Z",
|
|
136
|
+
oldValue: { value: false },
|
|
137
|
+
newValue: { value: true },
|
|
138
|
+
projectId: "proj_xyz",
|
|
139
|
+
webhookId: "whk_sample",
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=webhook-formats.js.map
|