@avsbhq/node 0.3.0 → 1.2.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.
- package/README.md +345 -145
- package/dist/index.cjs +720 -343
- package/dist/index.d.cts +140 -121
- package/dist/index.d.ts +140 -121
- package/dist/index.js +727 -340
- package/package.json +11 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { FlagDatafile } from '@avsbhq/
|
|
2
|
-
export { ExposureEvent, FlagDatafile, FlagEvaluationDetails, MetricEvent } from '@avsbhq/
|
|
1
|
+
import { FlagDatafile, StickyBucketService, Logger, EvaluationSource, StickyAssignment, DecideOption, Flag, TrackPayload, DecisionLog, InitResult, EvalContext } from '@avsbhq/core';
|
|
2
|
+
export { DecideOption, DecisionLog, DecisionLogEntry, EvalContext, EvaluationSource, ExposureEvent, Flag, FlagDatafile, FlagEvaluationDetails, InitResult, Logger, MetricEvent, MultiContext, RuleType, SingleContext, StickyAssignment, StickyBucketService, TrackPayload, consoleLogger, consoleTransport, createLogger, noopLogger } from '@avsbhq/core';
|
|
3
|
+
import { AvsbUtils } from '@avsbhq/utils';
|
|
4
|
+
export { AvsbUtils } from '@avsbhq/utils';
|
|
5
|
+
export { StreamingClient, StreamingOptions, createStreamingClient } from '@avsbhq/utils/streaming';
|
|
3
6
|
|
|
4
|
-
interface
|
|
7
|
+
interface AvsbServerOptions {
|
|
5
8
|
/** SDK key for the flag environment (e.g., "env_abc123") */
|
|
6
9
|
sdkKey: string;
|
|
7
10
|
/** Datafile polling interval in ms. Default: 60_000 (60s). Min: 5_000. */
|
|
@@ -33,67 +36,6 @@ interface EventOptions {
|
|
|
33
36
|
/** Retry attempts on flush failure. Default: 3. */
|
|
34
37
|
retryAttempts?: number;
|
|
35
38
|
}
|
|
36
|
-
interface UserContext {
|
|
37
|
-
/** User identifier for bucketing. Required. */
|
|
38
|
-
userId: string;
|
|
39
|
-
/** Targeting attributes (plan, country, device, etc.). */
|
|
40
|
-
attributes?: Record<string, unknown>;
|
|
41
|
-
/** When true, evaluate without firing an exposure event. Default: false. */
|
|
42
|
-
suppressExposure?: boolean;
|
|
43
|
-
/** Collect evaluation details into this log. */
|
|
44
|
-
decisionLog?: DecisionLog;
|
|
45
|
-
/** Attribute keys to strip from event payloads (used for targeting, not sent to analytics). */
|
|
46
|
-
privateAttributes?: string[];
|
|
47
|
-
}
|
|
48
|
-
interface TrackContext {
|
|
49
|
-
/** User identifier. Required. */
|
|
50
|
-
userId: string;
|
|
51
|
-
/** Revenue value for conversion tracking. */
|
|
52
|
-
revenue?: number;
|
|
53
|
-
/** Custom event properties. */
|
|
54
|
-
properties?: Record<string, unknown>;
|
|
55
|
-
}
|
|
56
|
-
interface DecisionLog {
|
|
57
|
-
readonly entries: DecisionLogEntry[];
|
|
58
|
-
}
|
|
59
|
-
interface DecisionLogEntry {
|
|
60
|
-
flagKey: string;
|
|
61
|
-
value: unknown;
|
|
62
|
-
variationKey: string | null;
|
|
63
|
-
source: EvaluationSource;
|
|
64
|
-
ruleId: string | null;
|
|
65
|
-
ruleType: 'targeted_delivery' | 'ab_test' | null;
|
|
66
|
-
reasons: string[];
|
|
67
|
-
evaluatedAt: number;
|
|
68
|
-
durationMs: number;
|
|
69
|
-
}
|
|
70
|
-
type EvaluationSource = 'override' | 'sticky' | 'rule' | 'default' | 'not_found' | 'disabled' | 'runtime_override';
|
|
71
|
-
interface ServerFlagEvaluationDetails {
|
|
72
|
-
value: unknown;
|
|
73
|
-
variationKey: string | null;
|
|
74
|
-
source: EvaluationSource;
|
|
75
|
-
ruleId: string | null;
|
|
76
|
-
ruleType: 'targeted_delivery' | 'ab_test' | null;
|
|
77
|
-
flagKey: string;
|
|
78
|
-
reasons: string[];
|
|
79
|
-
}
|
|
80
|
-
interface InitResult {
|
|
81
|
-
success: boolean;
|
|
82
|
-
source: 'network' | 'bootstrap' | 'cache' | 'timeout' | 'error';
|
|
83
|
-
error?: Error;
|
|
84
|
-
}
|
|
85
|
-
interface StickyBucketService {
|
|
86
|
-
/** Look up stored variation for a user+flag. Return variationId or null. Synchronous. */
|
|
87
|
-
lookup(userId: string, flagKey: string): string | null;
|
|
88
|
-
/** Persist a variation assignment. Fire-and-forget (may be async internally). */
|
|
89
|
-
save(userId: string, flagKey: string, variationId: string): void;
|
|
90
|
-
}
|
|
91
|
-
interface Logger {
|
|
92
|
-
debug(message: string, data?: Record<string, unknown>): void;
|
|
93
|
-
info(message: string, data?: Record<string, unknown>): void;
|
|
94
|
-
warn(message: string, data?: Record<string, unknown>): void;
|
|
95
|
-
error(message: string, data?: Record<string, unknown>): void;
|
|
96
|
-
}
|
|
97
39
|
type ServerEventMap = {
|
|
98
40
|
ready: () => void;
|
|
99
41
|
configUpdate: (data: {
|
|
@@ -106,6 +48,12 @@ type ServerEventMap = {
|
|
|
106
48
|
source: EvaluationSource;
|
|
107
49
|
}) => void;
|
|
108
50
|
error: (error: Error) => void;
|
|
51
|
+
/** Fired when a sticky assignment references a rule that no longer matches the datafile. */
|
|
52
|
+
stickyOrphan: (data: {
|
|
53
|
+
flagKey: string;
|
|
54
|
+
userId: string;
|
|
55
|
+
assignment: StickyAssignment;
|
|
56
|
+
}) => void;
|
|
109
57
|
};
|
|
110
58
|
type ServerEventName = keyof ServerEventMap;
|
|
111
59
|
interface MiddlewareConfig {
|
|
@@ -114,26 +62,83 @@ interface MiddlewareConfig {
|
|
|
114
62
|
/** Extract targeting attributes from the request. */
|
|
115
63
|
attributesFrom?: (req: unknown) => Record<string, unknown>;
|
|
116
64
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Purchase order shape for server-side purchase tracking.
|
|
68
|
+
* Mirrors the client-side snippet PurchaseOrder interface.
|
|
69
|
+
* Decimal values are forwarded as-is — no server-side currency conversion.
|
|
70
|
+
*/
|
|
71
|
+
interface PurchaseOrder {
|
|
72
|
+
/** Required. Unique order/transaction identifier. */
|
|
73
|
+
orderId: string;
|
|
74
|
+
/** Required. Order total as a decimal (e.g. 99.95). */
|
|
75
|
+
total: number;
|
|
76
|
+
/** ISO 4217 currency code. */
|
|
77
|
+
currency?: string;
|
|
78
|
+
subtotal?: number;
|
|
79
|
+
shipping?: number;
|
|
80
|
+
tax?: number;
|
|
81
|
+
discount?: number;
|
|
82
|
+
coupon?: string;
|
|
83
|
+
items?: Array<{
|
|
84
|
+
sku: string;
|
|
85
|
+
name?: string;
|
|
86
|
+
price?: number;
|
|
87
|
+
quantity?: number;
|
|
88
|
+
category?: string;
|
|
89
|
+
}>;
|
|
129
90
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A per-request scoped client with a bound EvalContext.
|
|
94
|
+
*
|
|
95
|
+
* Created via `server.forUser(ctx)` or the `server.createUserContext(ctx)`
|
|
96
|
+
* alias. All flag evaluations and track calls use the bound context.
|
|
97
|
+
*
|
|
98
|
+
* Forced decisions set on a UserBoundClient survive only for the lifetime
|
|
99
|
+
* of the instance — they do not affect the shared AvsbServer override state.
|
|
100
|
+
* This makes them safe for test middleware and per-request debugging.
|
|
101
|
+
*
|
|
102
|
+
* Spec §5.8.
|
|
103
|
+
*/
|
|
104
|
+
interface UserBoundClient {
|
|
105
|
+
getFlag<T>(flagKey: string, defaultValue: T, options?: {
|
|
106
|
+
decideOptions?: DecideOption[];
|
|
107
|
+
}): Flag<T>;
|
|
108
|
+
getBoolFlag(flagKey: string, defaultValue: boolean, options?: {
|
|
109
|
+
decideOptions?: DecideOption[];
|
|
110
|
+
}): Flag<boolean>;
|
|
111
|
+
getStringFlag(flagKey: string, defaultValue: string, options?: {
|
|
112
|
+
decideOptions?: DecideOption[];
|
|
113
|
+
}): Flag<string>;
|
|
114
|
+
getNumberFlag(flagKey: string, defaultValue: number, options?: {
|
|
115
|
+
decideOptions?: DecideOption[];
|
|
116
|
+
}): Flag<number>;
|
|
117
|
+
getJsonFlag<T>(flagKey: string, defaultValue: T, options?: {
|
|
118
|
+
decideOptions?: DecideOption[];
|
|
119
|
+
}): Flag<T>;
|
|
120
|
+
getAllFlags(options?: GetAllFlagsOptions): Record<string, Flag>;
|
|
121
|
+
track(eventKey: string, payload?: Omit<TrackPayload, 'context'>): void;
|
|
122
|
+
setForcedDecision(flagKey: string, opts: {
|
|
123
|
+
ruleId?: string;
|
|
124
|
+
variationKey: string;
|
|
125
|
+
}): void;
|
|
126
|
+
clearForcedDecision(flagKey: string): void;
|
|
127
|
+
getDecisionLog(): DecisionLog;
|
|
134
128
|
}
|
|
135
129
|
|
|
136
|
-
|
|
130
|
+
interface GetFlagOptions {
|
|
131
|
+
context?: EvalContext;
|
|
132
|
+
decideOptions?: DecideOption[];
|
|
133
|
+
decisionLog?: DecisionLog;
|
|
134
|
+
/** Internal: UserBoundClient forced decision override. */
|
|
135
|
+
forcedVariationKey?: string;
|
|
136
|
+
}
|
|
137
|
+
interface GetAllFlagsOptions {
|
|
138
|
+
fireExposures?: boolean;
|
|
139
|
+
decideOptions?: DecideOption[];
|
|
140
|
+
}
|
|
141
|
+
declare class AvsbServer {
|
|
137
142
|
private sdkKey;
|
|
138
143
|
private pollingInterval;
|
|
139
144
|
private cdnHost;
|
|
@@ -141,57 +146,88 @@ declare class AvsBServer {
|
|
|
141
146
|
private logger;
|
|
142
147
|
private stickyBucketService;
|
|
143
148
|
private eventOptions;
|
|
149
|
+
private streamingEnabled;
|
|
150
|
+
private streamingEndpoint;
|
|
144
151
|
private datafile;
|
|
145
152
|
private ready;
|
|
146
153
|
private destroyed;
|
|
147
154
|
private tracker;
|
|
155
|
+
private streamingClient;
|
|
148
156
|
private pollingId;
|
|
157
|
+
private abortController;
|
|
149
158
|
private overrides;
|
|
150
159
|
private listeners;
|
|
151
160
|
private readyPromise;
|
|
152
|
-
|
|
161
|
+
private pendingEvents;
|
|
162
|
+
/**
|
|
163
|
+
* §6 — `client.utils.*` surface. Constructed lazily on first access so
|
|
164
|
+
* the utils package is only imported when actually consumed.
|
|
165
|
+
*
|
|
166
|
+
* The server's event surface (`ServerEventMap`) is a subset of the
|
|
167
|
+
* shared `ClientEventMap`; the utils-adapter `on` here ignores events
|
|
168
|
+
* the server doesn't emit (e.g. `flagChange`). Workstream 5 (CoW 6)
|
|
169
|
+
* harmonises the two maps when the streaming evaluator lands.
|
|
170
|
+
*/
|
|
171
|
+
private _utils;
|
|
172
|
+
get utils(): AvsbUtils;
|
|
173
|
+
constructor(options: AvsbServerOptions);
|
|
153
174
|
onReady(): Promise<InitResult>;
|
|
154
175
|
private initializeAndReady;
|
|
155
176
|
close(): Promise<void>;
|
|
156
177
|
flush(): Promise<void>;
|
|
157
178
|
refreshDatafile(): Promise<void>;
|
|
158
|
-
getFlag<T
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
179
|
+
getFlag<T>(flagKey: string, defaultValue: NoInfer<T> | T, options?: GetFlagOptions): Flag<T>;
|
|
180
|
+
getBoolFlag(flagKey: string, defaultValue: NoInfer<boolean> | boolean, options?: GetFlagOptions): Flag<boolean>;
|
|
181
|
+
getStringFlag(flagKey: string, defaultValue: NoInfer<string> | string, options?: GetFlagOptions): Flag<string>;
|
|
182
|
+
getNumberFlag(flagKey: string, defaultValue: NoInfer<number> | number, options?: GetFlagOptions): Flag<number>;
|
|
183
|
+
getJsonFlag<T>(flagKey: string, defaultValue: NoInfer<T> | T, options?: GetFlagOptions): Flag<T>;
|
|
184
|
+
getAllFlags(context: EvalContext, options?: GetAllFlagsOptions): Record<string, Flag>;
|
|
185
|
+
trackPurchase(visitorId: string, order: PurchaseOrder): Promise<void>;
|
|
186
|
+
track(eventKey: string, payload: TrackPayload & {
|
|
187
|
+
context: EvalContext;
|
|
164
188
|
}): void;
|
|
165
|
-
|
|
166
|
-
|
|
189
|
+
manualExposure(flagKey: string, context: EvalContext): void;
|
|
190
|
+
forUser(context: EvalContext): UserBoundClient;
|
|
191
|
+
createUserContext(context: EvalContext): UserBoundClient;
|
|
192
|
+
setOverrideForUser(flagKey: string, contextKey: string, variationKey: string, contextKind?: string): void;
|
|
193
|
+
setGlobalOverride(flagKey: string, variationKey: string): void;
|
|
194
|
+
clearOverrideForUser(flagKey: string, contextKey: string, contextKind?: string): void;
|
|
195
|
+
clearGlobalOverride(flagKey: string): void;
|
|
167
196
|
clearAllOverrides(): void;
|
|
168
197
|
createDecisionLog(): DecisionLog;
|
|
169
|
-
on<E extends ServerEventName>(event: E, cb: ServerEventMap[E]): void;
|
|
198
|
+
on<E extends ServerEventName>(event: E, cb: ServerEventMap[E]): () => void;
|
|
170
199
|
off<E extends ServerEventName>(event: E, cb: ServerEventMap[E]): void;
|
|
171
200
|
private evaluate;
|
|
201
|
+
private getTypedFlag;
|
|
202
|
+
private toFlag;
|
|
203
|
+
/** toFlag variant that never writes a decision log (used by getAllFlags suppressed path). */
|
|
204
|
+
private toFlagNoLog;
|
|
205
|
+
/** Build a Flag<T> from a forced variation key (per-request override). */
|
|
206
|
+
private buildFlagFromVariationKey;
|
|
207
|
+
private buildLegacyContext;
|
|
208
|
+
private resolveRuntimeOverride;
|
|
209
|
+
private resolveStickyBucket;
|
|
210
|
+
private findVariation;
|
|
211
|
+
private fireExposureForStickyAssignment;
|
|
172
212
|
private emit;
|
|
213
|
+
private emitEvaluation;
|
|
173
214
|
private initTracker;
|
|
215
|
+
private startStreamingIfEnabled;
|
|
174
216
|
private fetchDatafile;
|
|
175
217
|
private startPolling;
|
|
176
218
|
private sendHeartbeat;
|
|
177
|
-
private resolveRuntimeOverride;
|
|
178
|
-
private resolveStickyBucket;
|
|
179
|
-
private findVariation;
|
|
180
|
-
private fireExposureForVariation;
|
|
181
|
-
private recordDecisionLog;
|
|
182
|
-
private emitEvaluation;
|
|
183
219
|
}
|
|
184
220
|
|
|
185
|
-
declare function expressMiddleware(client:
|
|
221
|
+
declare function expressMiddleware(client: AvsbServer, config: MiddlewareConfig): (req: unknown, res: unknown, next: () => void) => void;
|
|
186
222
|
declare function fastifyPlugin(instance: unknown, opts: {
|
|
187
|
-
client:
|
|
223
|
+
client: AvsbServer;
|
|
188
224
|
} & MiddlewareConfig, done: () => void): void;
|
|
189
225
|
|
|
190
226
|
declare class InMemoryStickyBucketService implements StickyBucketService {
|
|
191
227
|
private cache;
|
|
192
228
|
private key;
|
|
193
|
-
lookup(userId: string, flagKey: string):
|
|
194
|
-
save(userId: string, flagKey: string,
|
|
229
|
+
lookup(userId: string, flagKey: string): StickyAssignment | null;
|
|
230
|
+
save(userId: string, flagKey: string, assignment: StickyAssignment): void;
|
|
195
231
|
}
|
|
196
232
|
interface RedisLike {
|
|
197
233
|
get(key: string): Promise<string | null>;
|
|
@@ -216,31 +252,14 @@ declare class RedisStickyBucketService implements StickyBucketService {
|
|
|
216
252
|
constructor(options: RedisStickyBucketOptions);
|
|
217
253
|
private redisKey;
|
|
218
254
|
private cacheKey;
|
|
219
|
-
lookup(userId: string, flagKey: string):
|
|
220
|
-
save(userId: string, flagKey: string,
|
|
255
|
+
lookup(userId: string, flagKey: string): StickyAssignment | null;
|
|
256
|
+
save(userId: string, flagKey: string, assignment: StickyAssignment): void;
|
|
221
257
|
private backgroundFetch;
|
|
222
258
|
private evictIfNeeded;
|
|
223
259
|
}
|
|
224
260
|
|
|
225
|
-
interface StreamingClient {
|
|
226
|
-
connect(): void;
|
|
227
|
-
disconnect(): void;
|
|
228
|
-
isConnected(): boolean;
|
|
229
|
-
}
|
|
230
|
-
interface StreamingOptions {
|
|
231
|
-
endpoint: string;
|
|
232
|
-
sdkKey: string;
|
|
233
|
-
onDatafileUpdated: () => void;
|
|
234
|
-
logger: Logger;
|
|
235
|
-
reconnectDelay?: number;
|
|
236
|
-
}
|
|
237
|
-
declare function createStreamingClient(options: StreamingOptions): StreamingClient;
|
|
238
|
-
|
|
239
261
|
declare function createDecisionLog(): DecisionLog;
|
|
240
262
|
|
|
241
|
-
/** No-op logger — used when no custom logger is provided. */
|
|
242
|
-
declare const noopLogger: Logger;
|
|
243
|
-
|
|
244
263
|
declare const SDK_VERSION = "0.3.0";
|
|
245
264
|
|
|
246
|
-
export {
|
|
265
|
+
export { AvsbServer, type AvsbServerOptions, type EventOptions, type GetAllFlagsOptions, type GetFlagOptions, InMemoryStickyBucketService, type MiddlewareConfig, type PurchaseOrder, type RedisLike, type RedisStickyBucketOptions, RedisStickyBucketService, SDK_VERSION, type UserBoundClient, createDecisionLog, expressMiddleware, fastifyPlugin };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { FlagDatafile } from '@avsbhq/
|
|
2
|
-
export { ExposureEvent, FlagDatafile, FlagEvaluationDetails, MetricEvent } from '@avsbhq/
|
|
1
|
+
import { FlagDatafile, StickyBucketService, Logger, EvaluationSource, StickyAssignment, DecideOption, Flag, TrackPayload, DecisionLog, InitResult, EvalContext } from '@avsbhq/core';
|
|
2
|
+
export { DecideOption, DecisionLog, DecisionLogEntry, EvalContext, EvaluationSource, ExposureEvent, Flag, FlagDatafile, FlagEvaluationDetails, InitResult, Logger, MetricEvent, MultiContext, RuleType, SingleContext, StickyAssignment, StickyBucketService, TrackPayload, consoleLogger, consoleTransport, createLogger, noopLogger } from '@avsbhq/core';
|
|
3
|
+
import { AvsbUtils } from '@avsbhq/utils';
|
|
4
|
+
export { AvsbUtils } from '@avsbhq/utils';
|
|
5
|
+
export { StreamingClient, StreamingOptions, createStreamingClient } from '@avsbhq/utils/streaming';
|
|
3
6
|
|
|
4
|
-
interface
|
|
7
|
+
interface AvsbServerOptions {
|
|
5
8
|
/** SDK key for the flag environment (e.g., "env_abc123") */
|
|
6
9
|
sdkKey: string;
|
|
7
10
|
/** Datafile polling interval in ms. Default: 60_000 (60s). Min: 5_000. */
|
|
@@ -33,67 +36,6 @@ interface EventOptions {
|
|
|
33
36
|
/** Retry attempts on flush failure. Default: 3. */
|
|
34
37
|
retryAttempts?: number;
|
|
35
38
|
}
|
|
36
|
-
interface UserContext {
|
|
37
|
-
/** User identifier for bucketing. Required. */
|
|
38
|
-
userId: string;
|
|
39
|
-
/** Targeting attributes (plan, country, device, etc.). */
|
|
40
|
-
attributes?: Record<string, unknown>;
|
|
41
|
-
/** When true, evaluate without firing an exposure event. Default: false. */
|
|
42
|
-
suppressExposure?: boolean;
|
|
43
|
-
/** Collect evaluation details into this log. */
|
|
44
|
-
decisionLog?: DecisionLog;
|
|
45
|
-
/** Attribute keys to strip from event payloads (used for targeting, not sent to analytics). */
|
|
46
|
-
privateAttributes?: string[];
|
|
47
|
-
}
|
|
48
|
-
interface TrackContext {
|
|
49
|
-
/** User identifier. Required. */
|
|
50
|
-
userId: string;
|
|
51
|
-
/** Revenue value for conversion tracking. */
|
|
52
|
-
revenue?: number;
|
|
53
|
-
/** Custom event properties. */
|
|
54
|
-
properties?: Record<string, unknown>;
|
|
55
|
-
}
|
|
56
|
-
interface DecisionLog {
|
|
57
|
-
readonly entries: DecisionLogEntry[];
|
|
58
|
-
}
|
|
59
|
-
interface DecisionLogEntry {
|
|
60
|
-
flagKey: string;
|
|
61
|
-
value: unknown;
|
|
62
|
-
variationKey: string | null;
|
|
63
|
-
source: EvaluationSource;
|
|
64
|
-
ruleId: string | null;
|
|
65
|
-
ruleType: 'targeted_delivery' | 'ab_test' | null;
|
|
66
|
-
reasons: string[];
|
|
67
|
-
evaluatedAt: number;
|
|
68
|
-
durationMs: number;
|
|
69
|
-
}
|
|
70
|
-
type EvaluationSource = 'override' | 'sticky' | 'rule' | 'default' | 'not_found' | 'disabled' | 'runtime_override';
|
|
71
|
-
interface ServerFlagEvaluationDetails {
|
|
72
|
-
value: unknown;
|
|
73
|
-
variationKey: string | null;
|
|
74
|
-
source: EvaluationSource;
|
|
75
|
-
ruleId: string | null;
|
|
76
|
-
ruleType: 'targeted_delivery' | 'ab_test' | null;
|
|
77
|
-
flagKey: string;
|
|
78
|
-
reasons: string[];
|
|
79
|
-
}
|
|
80
|
-
interface InitResult {
|
|
81
|
-
success: boolean;
|
|
82
|
-
source: 'network' | 'bootstrap' | 'cache' | 'timeout' | 'error';
|
|
83
|
-
error?: Error;
|
|
84
|
-
}
|
|
85
|
-
interface StickyBucketService {
|
|
86
|
-
/** Look up stored variation for a user+flag. Return variationId or null. Synchronous. */
|
|
87
|
-
lookup(userId: string, flagKey: string): string | null;
|
|
88
|
-
/** Persist a variation assignment. Fire-and-forget (may be async internally). */
|
|
89
|
-
save(userId: string, flagKey: string, variationId: string): void;
|
|
90
|
-
}
|
|
91
|
-
interface Logger {
|
|
92
|
-
debug(message: string, data?: Record<string, unknown>): void;
|
|
93
|
-
info(message: string, data?: Record<string, unknown>): void;
|
|
94
|
-
warn(message: string, data?: Record<string, unknown>): void;
|
|
95
|
-
error(message: string, data?: Record<string, unknown>): void;
|
|
96
|
-
}
|
|
97
39
|
type ServerEventMap = {
|
|
98
40
|
ready: () => void;
|
|
99
41
|
configUpdate: (data: {
|
|
@@ -106,6 +48,12 @@ type ServerEventMap = {
|
|
|
106
48
|
source: EvaluationSource;
|
|
107
49
|
}) => void;
|
|
108
50
|
error: (error: Error) => void;
|
|
51
|
+
/** Fired when a sticky assignment references a rule that no longer matches the datafile. */
|
|
52
|
+
stickyOrphan: (data: {
|
|
53
|
+
flagKey: string;
|
|
54
|
+
userId: string;
|
|
55
|
+
assignment: StickyAssignment;
|
|
56
|
+
}) => void;
|
|
109
57
|
};
|
|
110
58
|
type ServerEventName = keyof ServerEventMap;
|
|
111
59
|
interface MiddlewareConfig {
|
|
@@ -114,26 +62,83 @@ interface MiddlewareConfig {
|
|
|
114
62
|
/** Extract targeting attributes from the request. */
|
|
115
63
|
attributesFrom?: (req: unknown) => Record<string, unknown>;
|
|
116
64
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Purchase order shape for server-side purchase tracking.
|
|
68
|
+
* Mirrors the client-side snippet PurchaseOrder interface.
|
|
69
|
+
* Decimal values are forwarded as-is — no server-side currency conversion.
|
|
70
|
+
*/
|
|
71
|
+
interface PurchaseOrder {
|
|
72
|
+
/** Required. Unique order/transaction identifier. */
|
|
73
|
+
orderId: string;
|
|
74
|
+
/** Required. Order total as a decimal (e.g. 99.95). */
|
|
75
|
+
total: number;
|
|
76
|
+
/** ISO 4217 currency code. */
|
|
77
|
+
currency?: string;
|
|
78
|
+
subtotal?: number;
|
|
79
|
+
shipping?: number;
|
|
80
|
+
tax?: number;
|
|
81
|
+
discount?: number;
|
|
82
|
+
coupon?: string;
|
|
83
|
+
items?: Array<{
|
|
84
|
+
sku: string;
|
|
85
|
+
name?: string;
|
|
86
|
+
price?: number;
|
|
87
|
+
quantity?: number;
|
|
88
|
+
category?: string;
|
|
89
|
+
}>;
|
|
129
90
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A per-request scoped client with a bound EvalContext.
|
|
94
|
+
*
|
|
95
|
+
* Created via `server.forUser(ctx)` or the `server.createUserContext(ctx)`
|
|
96
|
+
* alias. All flag evaluations and track calls use the bound context.
|
|
97
|
+
*
|
|
98
|
+
* Forced decisions set on a UserBoundClient survive only for the lifetime
|
|
99
|
+
* of the instance — they do not affect the shared AvsbServer override state.
|
|
100
|
+
* This makes them safe for test middleware and per-request debugging.
|
|
101
|
+
*
|
|
102
|
+
* Spec §5.8.
|
|
103
|
+
*/
|
|
104
|
+
interface UserBoundClient {
|
|
105
|
+
getFlag<T>(flagKey: string, defaultValue: T, options?: {
|
|
106
|
+
decideOptions?: DecideOption[];
|
|
107
|
+
}): Flag<T>;
|
|
108
|
+
getBoolFlag(flagKey: string, defaultValue: boolean, options?: {
|
|
109
|
+
decideOptions?: DecideOption[];
|
|
110
|
+
}): Flag<boolean>;
|
|
111
|
+
getStringFlag(flagKey: string, defaultValue: string, options?: {
|
|
112
|
+
decideOptions?: DecideOption[];
|
|
113
|
+
}): Flag<string>;
|
|
114
|
+
getNumberFlag(flagKey: string, defaultValue: number, options?: {
|
|
115
|
+
decideOptions?: DecideOption[];
|
|
116
|
+
}): Flag<number>;
|
|
117
|
+
getJsonFlag<T>(flagKey: string, defaultValue: T, options?: {
|
|
118
|
+
decideOptions?: DecideOption[];
|
|
119
|
+
}): Flag<T>;
|
|
120
|
+
getAllFlags(options?: GetAllFlagsOptions): Record<string, Flag>;
|
|
121
|
+
track(eventKey: string, payload?: Omit<TrackPayload, 'context'>): void;
|
|
122
|
+
setForcedDecision(flagKey: string, opts: {
|
|
123
|
+
ruleId?: string;
|
|
124
|
+
variationKey: string;
|
|
125
|
+
}): void;
|
|
126
|
+
clearForcedDecision(flagKey: string): void;
|
|
127
|
+
getDecisionLog(): DecisionLog;
|
|
134
128
|
}
|
|
135
129
|
|
|
136
|
-
|
|
130
|
+
interface GetFlagOptions {
|
|
131
|
+
context?: EvalContext;
|
|
132
|
+
decideOptions?: DecideOption[];
|
|
133
|
+
decisionLog?: DecisionLog;
|
|
134
|
+
/** Internal: UserBoundClient forced decision override. */
|
|
135
|
+
forcedVariationKey?: string;
|
|
136
|
+
}
|
|
137
|
+
interface GetAllFlagsOptions {
|
|
138
|
+
fireExposures?: boolean;
|
|
139
|
+
decideOptions?: DecideOption[];
|
|
140
|
+
}
|
|
141
|
+
declare class AvsbServer {
|
|
137
142
|
private sdkKey;
|
|
138
143
|
private pollingInterval;
|
|
139
144
|
private cdnHost;
|
|
@@ -141,57 +146,88 @@ declare class AvsBServer {
|
|
|
141
146
|
private logger;
|
|
142
147
|
private stickyBucketService;
|
|
143
148
|
private eventOptions;
|
|
149
|
+
private streamingEnabled;
|
|
150
|
+
private streamingEndpoint;
|
|
144
151
|
private datafile;
|
|
145
152
|
private ready;
|
|
146
153
|
private destroyed;
|
|
147
154
|
private tracker;
|
|
155
|
+
private streamingClient;
|
|
148
156
|
private pollingId;
|
|
157
|
+
private abortController;
|
|
149
158
|
private overrides;
|
|
150
159
|
private listeners;
|
|
151
160
|
private readyPromise;
|
|
152
|
-
|
|
161
|
+
private pendingEvents;
|
|
162
|
+
/**
|
|
163
|
+
* §6 — `client.utils.*` surface. Constructed lazily on first access so
|
|
164
|
+
* the utils package is only imported when actually consumed.
|
|
165
|
+
*
|
|
166
|
+
* The server's event surface (`ServerEventMap`) is a subset of the
|
|
167
|
+
* shared `ClientEventMap`; the utils-adapter `on` here ignores events
|
|
168
|
+
* the server doesn't emit (e.g. `flagChange`). Workstream 5 (CoW 6)
|
|
169
|
+
* harmonises the two maps when the streaming evaluator lands.
|
|
170
|
+
*/
|
|
171
|
+
private _utils;
|
|
172
|
+
get utils(): AvsbUtils;
|
|
173
|
+
constructor(options: AvsbServerOptions);
|
|
153
174
|
onReady(): Promise<InitResult>;
|
|
154
175
|
private initializeAndReady;
|
|
155
176
|
close(): Promise<void>;
|
|
156
177
|
flush(): Promise<void>;
|
|
157
178
|
refreshDatafile(): Promise<void>;
|
|
158
|
-
getFlag<T
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
179
|
+
getFlag<T>(flagKey: string, defaultValue: NoInfer<T> | T, options?: GetFlagOptions): Flag<T>;
|
|
180
|
+
getBoolFlag(flagKey: string, defaultValue: NoInfer<boolean> | boolean, options?: GetFlagOptions): Flag<boolean>;
|
|
181
|
+
getStringFlag(flagKey: string, defaultValue: NoInfer<string> | string, options?: GetFlagOptions): Flag<string>;
|
|
182
|
+
getNumberFlag(flagKey: string, defaultValue: NoInfer<number> | number, options?: GetFlagOptions): Flag<number>;
|
|
183
|
+
getJsonFlag<T>(flagKey: string, defaultValue: NoInfer<T> | T, options?: GetFlagOptions): Flag<T>;
|
|
184
|
+
getAllFlags(context: EvalContext, options?: GetAllFlagsOptions): Record<string, Flag>;
|
|
185
|
+
trackPurchase(visitorId: string, order: PurchaseOrder): Promise<void>;
|
|
186
|
+
track(eventKey: string, payload: TrackPayload & {
|
|
187
|
+
context: EvalContext;
|
|
164
188
|
}): void;
|
|
165
|
-
|
|
166
|
-
|
|
189
|
+
manualExposure(flagKey: string, context: EvalContext): void;
|
|
190
|
+
forUser(context: EvalContext): UserBoundClient;
|
|
191
|
+
createUserContext(context: EvalContext): UserBoundClient;
|
|
192
|
+
setOverrideForUser(flagKey: string, contextKey: string, variationKey: string, contextKind?: string): void;
|
|
193
|
+
setGlobalOverride(flagKey: string, variationKey: string): void;
|
|
194
|
+
clearOverrideForUser(flagKey: string, contextKey: string, contextKind?: string): void;
|
|
195
|
+
clearGlobalOverride(flagKey: string): void;
|
|
167
196
|
clearAllOverrides(): void;
|
|
168
197
|
createDecisionLog(): DecisionLog;
|
|
169
|
-
on<E extends ServerEventName>(event: E, cb: ServerEventMap[E]): void;
|
|
198
|
+
on<E extends ServerEventName>(event: E, cb: ServerEventMap[E]): () => void;
|
|
170
199
|
off<E extends ServerEventName>(event: E, cb: ServerEventMap[E]): void;
|
|
171
200
|
private evaluate;
|
|
201
|
+
private getTypedFlag;
|
|
202
|
+
private toFlag;
|
|
203
|
+
/** toFlag variant that never writes a decision log (used by getAllFlags suppressed path). */
|
|
204
|
+
private toFlagNoLog;
|
|
205
|
+
/** Build a Flag<T> from a forced variation key (per-request override). */
|
|
206
|
+
private buildFlagFromVariationKey;
|
|
207
|
+
private buildLegacyContext;
|
|
208
|
+
private resolveRuntimeOverride;
|
|
209
|
+
private resolveStickyBucket;
|
|
210
|
+
private findVariation;
|
|
211
|
+
private fireExposureForStickyAssignment;
|
|
172
212
|
private emit;
|
|
213
|
+
private emitEvaluation;
|
|
173
214
|
private initTracker;
|
|
215
|
+
private startStreamingIfEnabled;
|
|
174
216
|
private fetchDatafile;
|
|
175
217
|
private startPolling;
|
|
176
218
|
private sendHeartbeat;
|
|
177
|
-
private resolveRuntimeOverride;
|
|
178
|
-
private resolveStickyBucket;
|
|
179
|
-
private findVariation;
|
|
180
|
-
private fireExposureForVariation;
|
|
181
|
-
private recordDecisionLog;
|
|
182
|
-
private emitEvaluation;
|
|
183
219
|
}
|
|
184
220
|
|
|
185
|
-
declare function expressMiddleware(client:
|
|
221
|
+
declare function expressMiddleware(client: AvsbServer, config: MiddlewareConfig): (req: unknown, res: unknown, next: () => void) => void;
|
|
186
222
|
declare function fastifyPlugin(instance: unknown, opts: {
|
|
187
|
-
client:
|
|
223
|
+
client: AvsbServer;
|
|
188
224
|
} & MiddlewareConfig, done: () => void): void;
|
|
189
225
|
|
|
190
226
|
declare class InMemoryStickyBucketService implements StickyBucketService {
|
|
191
227
|
private cache;
|
|
192
228
|
private key;
|
|
193
|
-
lookup(userId: string, flagKey: string):
|
|
194
|
-
save(userId: string, flagKey: string,
|
|
229
|
+
lookup(userId: string, flagKey: string): StickyAssignment | null;
|
|
230
|
+
save(userId: string, flagKey: string, assignment: StickyAssignment): void;
|
|
195
231
|
}
|
|
196
232
|
interface RedisLike {
|
|
197
233
|
get(key: string): Promise<string | null>;
|
|
@@ -216,31 +252,14 @@ declare class RedisStickyBucketService implements StickyBucketService {
|
|
|
216
252
|
constructor(options: RedisStickyBucketOptions);
|
|
217
253
|
private redisKey;
|
|
218
254
|
private cacheKey;
|
|
219
|
-
lookup(userId: string, flagKey: string):
|
|
220
|
-
save(userId: string, flagKey: string,
|
|
255
|
+
lookup(userId: string, flagKey: string): StickyAssignment | null;
|
|
256
|
+
save(userId: string, flagKey: string, assignment: StickyAssignment): void;
|
|
221
257
|
private backgroundFetch;
|
|
222
258
|
private evictIfNeeded;
|
|
223
259
|
}
|
|
224
260
|
|
|
225
|
-
interface StreamingClient {
|
|
226
|
-
connect(): void;
|
|
227
|
-
disconnect(): void;
|
|
228
|
-
isConnected(): boolean;
|
|
229
|
-
}
|
|
230
|
-
interface StreamingOptions {
|
|
231
|
-
endpoint: string;
|
|
232
|
-
sdkKey: string;
|
|
233
|
-
onDatafileUpdated: () => void;
|
|
234
|
-
logger: Logger;
|
|
235
|
-
reconnectDelay?: number;
|
|
236
|
-
}
|
|
237
|
-
declare function createStreamingClient(options: StreamingOptions): StreamingClient;
|
|
238
|
-
|
|
239
261
|
declare function createDecisionLog(): DecisionLog;
|
|
240
262
|
|
|
241
|
-
/** No-op logger — used when no custom logger is provided. */
|
|
242
|
-
declare const noopLogger: Logger;
|
|
243
|
-
|
|
244
263
|
declare const SDK_VERSION = "0.3.0";
|
|
245
264
|
|
|
246
|
-
export {
|
|
265
|
+
export { AvsbServer, type AvsbServerOptions, type EventOptions, type GetAllFlagsOptions, type GetFlagOptions, InMemoryStickyBucketService, type MiddlewareConfig, type PurchaseOrder, type RedisLike, type RedisStickyBucketOptions, RedisStickyBucketService, SDK_VERSION, type UserBoundClient, createDecisionLog, expressMiddleware, fastifyPlugin };
|