@avsbhq/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,246 @@
1
+ import { FlagDatafile } from '@avsbhq/js';
2
+ export { ExposureEvent, FlagDatafile, FlagEvaluationDetails, MetricEvent } from '@avsbhq/js';
3
+
4
+ interface AvsBServerOptions {
5
+ /** SDK key for the flag environment (e.g., "env_abc123") */
6
+ sdkKey: string;
7
+ /** Datafile polling interval in ms. Default: 60_000 (60s). Min: 5_000. */
8
+ pollingInterval?: number;
9
+ /** CDN host for datafile fetches. Default: "https://cdn.avsb.app" */
10
+ cdnHost?: string;
11
+ /** Max ms to wait for initial datafile fetch in onReady(). Default: 10_000. */
12
+ initTimeout?: number;
13
+ /** Pre-fetched datafile to skip initial network request. */
14
+ bootstrap?: FlagDatafile;
15
+ /** Enable SSE streaming for real-time datafile updates. Default: false. */
16
+ streaming?: boolean;
17
+ /** Custom SSE endpoint URL. Default: derived from app URL. */
18
+ streamingEndpoint?: string;
19
+ /** Event tracking configuration. */
20
+ events?: EventOptions;
21
+ /** Pluggable sticky bucketing service. */
22
+ stickyBucketService?: StickyBucketService;
23
+ /** Custom logger. Default: silent (no output). */
24
+ logger?: Logger;
25
+ }
26
+ interface EventOptions {
27
+ /** Ms between automatic event flushes. Default: 2000. */
28
+ flushInterval?: number;
29
+ /** Max events per HTTP request. Default: 10. */
30
+ batchSize?: number;
31
+ /** Max events in queue. Oldest dropped when exceeded. Default: 10_000. */
32
+ maxQueueSize?: number;
33
+ /** Retry attempts on flush failure. Default: 3. */
34
+ retryAttempts?: number;
35
+ }
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
+ type ServerEventMap = {
98
+ ready: () => void;
99
+ configUpdate: (data: {
100
+ publishedAt: string;
101
+ }) => void;
102
+ evaluation: (data: {
103
+ flagKey: string;
104
+ value: unknown;
105
+ userId: string;
106
+ source: EvaluationSource;
107
+ }) => void;
108
+ error: (error: Error) => void;
109
+ };
110
+ type ServerEventName = keyof ServerEventMap;
111
+ interface MiddlewareConfig {
112
+ /** Extract userId from the request. Return undefined to skip AvsB for this request. */
113
+ userIdFrom: (req: unknown) => string | undefined;
114
+ /** Extract targeting attributes from the request. */
115
+ attributesFrom?: (req: unknown) => Record<string, unknown>;
116
+ }
117
+ interface RequestAvsB {
118
+ /** Evaluate a flag for the request's user. */
119
+ getFlag<T = unknown>(flagKey: string, defaultValue?: T): T;
120
+ /** Get full evaluation details for the request's user. */
121
+ getFlagDetails(flagKey: string): ServerFlagEvaluationDetails;
122
+ /** Get all flag values for the request's user. */
123
+ getAllFlags(): Record<string, unknown>;
124
+ /** Track a conversion event for the request's user. */
125
+ track(eventKey: string, properties?: {
126
+ revenue?: number;
127
+ [key: string]: unknown;
128
+ }): void;
129
+ }
130
+ interface OverrideEntry {
131
+ flagKey: string;
132
+ variationKey: string;
133
+ userId?: string;
134
+ }
135
+
136
+ declare class AvsBServer {
137
+ private sdkKey;
138
+ private pollingInterval;
139
+ private cdnHost;
140
+ private initTimeout;
141
+ private logger;
142
+ private stickyBucketService;
143
+ private eventOptions;
144
+ private datafile;
145
+ private ready;
146
+ private destroyed;
147
+ private tracker;
148
+ private pollingId;
149
+ private overrides;
150
+ private listeners;
151
+ private readyPromise;
152
+ constructor(options: AvsBServerOptions);
153
+ onReady(): Promise<InitResult>;
154
+ private initializeAndReady;
155
+ close(): Promise<void>;
156
+ flush(): Promise<void>;
157
+ refreshDatafile(): Promise<void>;
158
+ getFlag<T = unknown>(flagKey: string, userContext: UserContext, defaultValue?: T): T;
159
+ getFlagDetails(flagKey: string, userContext: UserContext): ServerFlagEvaluationDetails;
160
+ getAllFlags(userContext: UserContext): Record<string, unknown>;
161
+ track(eventKey: string, context: TrackContext): void;
162
+ logExposure(flagKey: string, userContext: {
163
+ userId: string;
164
+ }): void;
165
+ setOverride(flagKey: string, variationKey: string, userId?: string): void;
166
+ clearOverride(flagKey: string, userId?: string): void;
167
+ clearAllOverrides(): void;
168
+ createDecisionLog(): DecisionLog;
169
+ on<E extends ServerEventName>(event: E, cb: ServerEventMap[E]): void;
170
+ off<E extends ServerEventName>(event: E, cb: ServerEventMap[E]): void;
171
+ private evaluate;
172
+ private emit;
173
+ private initTracker;
174
+ private fetchDatafile;
175
+ private startPolling;
176
+ private sendHeartbeat;
177
+ private resolveRuntimeOverride;
178
+ private resolveStickyBucket;
179
+ private findVariation;
180
+ private fireExposureForVariation;
181
+ private recordDecisionLog;
182
+ private emitEvaluation;
183
+ }
184
+
185
+ declare function expressMiddleware(client: AvsBServer, config: MiddlewareConfig): (req: unknown, res: unknown, next: () => void) => void;
186
+ declare function fastifyPlugin(instance: unknown, opts: {
187
+ client: AvsBServer;
188
+ } & MiddlewareConfig, done: () => void): void;
189
+
190
+ declare class InMemoryStickyBucketService implements StickyBucketService {
191
+ private cache;
192
+ private key;
193
+ lookup(userId: string, flagKey: string): string | null;
194
+ save(userId: string, flagKey: string, variationId: string): void;
195
+ }
196
+ interface RedisLike {
197
+ get(key: string): Promise<string | null>;
198
+ set(key: string, value: string, options?: {
199
+ EX?: number;
200
+ }): Promise<unknown>;
201
+ }
202
+ interface RedisStickyBucketOptions {
203
+ redis: RedisLike;
204
+ prefix?: string;
205
+ ttl?: number;
206
+ cacheSize?: number;
207
+ logger?: Logger;
208
+ }
209
+ declare class RedisStickyBucketService implements StickyBucketService {
210
+ private redis;
211
+ private prefix;
212
+ private ttl;
213
+ private maxCacheSize;
214
+ private logger;
215
+ private cache;
216
+ constructor(options: RedisStickyBucketOptions);
217
+ private redisKey;
218
+ private cacheKey;
219
+ lookup(userId: string, flagKey: string): string | null;
220
+ save(userId: string, flagKey: string, variationId: string): void;
221
+ private backgroundFetch;
222
+ private evictIfNeeded;
223
+ }
224
+
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
+ declare function createDecisionLog(): DecisionLog;
240
+
241
+ /** No-op logger — used when no custom logger is provided. */
242
+ declare const noopLogger: Logger;
243
+
244
+ declare const SDK_VERSION = "0.1.0";
245
+
246
+ export { AvsBServer, type AvsBServerOptions, type DecisionLog, type DecisionLogEntry, type EvaluationSource, type EventOptions, InMemoryStickyBucketService, type InitResult, type Logger, type MiddlewareConfig, type OverrideEntry, type RedisLike, type RedisStickyBucketOptions, RedisStickyBucketService, type RequestAvsB, SDK_VERSION, type ServerEventMap, type ServerEventName, type ServerFlagEvaluationDetails, type StickyBucketService, type StreamingClient, type StreamingOptions, type TrackContext, type UserContext, createDecisionLog, createStreamingClient, expressMiddleware, fastifyPlugin, noopLogger };
@@ -0,0 +1,246 @@
1
+ import { FlagDatafile } from '@avsbhq/js';
2
+ export { ExposureEvent, FlagDatafile, FlagEvaluationDetails, MetricEvent } from '@avsbhq/js';
3
+
4
+ interface AvsBServerOptions {
5
+ /** SDK key for the flag environment (e.g., "env_abc123") */
6
+ sdkKey: string;
7
+ /** Datafile polling interval in ms. Default: 60_000 (60s). Min: 5_000. */
8
+ pollingInterval?: number;
9
+ /** CDN host for datafile fetches. Default: "https://cdn.avsb.app" */
10
+ cdnHost?: string;
11
+ /** Max ms to wait for initial datafile fetch in onReady(). Default: 10_000. */
12
+ initTimeout?: number;
13
+ /** Pre-fetched datafile to skip initial network request. */
14
+ bootstrap?: FlagDatafile;
15
+ /** Enable SSE streaming for real-time datafile updates. Default: false. */
16
+ streaming?: boolean;
17
+ /** Custom SSE endpoint URL. Default: derived from app URL. */
18
+ streamingEndpoint?: string;
19
+ /** Event tracking configuration. */
20
+ events?: EventOptions;
21
+ /** Pluggable sticky bucketing service. */
22
+ stickyBucketService?: StickyBucketService;
23
+ /** Custom logger. Default: silent (no output). */
24
+ logger?: Logger;
25
+ }
26
+ interface EventOptions {
27
+ /** Ms between automatic event flushes. Default: 2000. */
28
+ flushInterval?: number;
29
+ /** Max events per HTTP request. Default: 10. */
30
+ batchSize?: number;
31
+ /** Max events in queue. Oldest dropped when exceeded. Default: 10_000. */
32
+ maxQueueSize?: number;
33
+ /** Retry attempts on flush failure. Default: 3. */
34
+ retryAttempts?: number;
35
+ }
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
+ type ServerEventMap = {
98
+ ready: () => void;
99
+ configUpdate: (data: {
100
+ publishedAt: string;
101
+ }) => void;
102
+ evaluation: (data: {
103
+ flagKey: string;
104
+ value: unknown;
105
+ userId: string;
106
+ source: EvaluationSource;
107
+ }) => void;
108
+ error: (error: Error) => void;
109
+ };
110
+ type ServerEventName = keyof ServerEventMap;
111
+ interface MiddlewareConfig {
112
+ /** Extract userId from the request. Return undefined to skip AvsB for this request. */
113
+ userIdFrom: (req: unknown) => string | undefined;
114
+ /** Extract targeting attributes from the request. */
115
+ attributesFrom?: (req: unknown) => Record<string, unknown>;
116
+ }
117
+ interface RequestAvsB {
118
+ /** Evaluate a flag for the request's user. */
119
+ getFlag<T = unknown>(flagKey: string, defaultValue?: T): T;
120
+ /** Get full evaluation details for the request's user. */
121
+ getFlagDetails(flagKey: string): ServerFlagEvaluationDetails;
122
+ /** Get all flag values for the request's user. */
123
+ getAllFlags(): Record<string, unknown>;
124
+ /** Track a conversion event for the request's user. */
125
+ track(eventKey: string, properties?: {
126
+ revenue?: number;
127
+ [key: string]: unknown;
128
+ }): void;
129
+ }
130
+ interface OverrideEntry {
131
+ flagKey: string;
132
+ variationKey: string;
133
+ userId?: string;
134
+ }
135
+
136
+ declare class AvsBServer {
137
+ private sdkKey;
138
+ private pollingInterval;
139
+ private cdnHost;
140
+ private initTimeout;
141
+ private logger;
142
+ private stickyBucketService;
143
+ private eventOptions;
144
+ private datafile;
145
+ private ready;
146
+ private destroyed;
147
+ private tracker;
148
+ private pollingId;
149
+ private overrides;
150
+ private listeners;
151
+ private readyPromise;
152
+ constructor(options: AvsBServerOptions);
153
+ onReady(): Promise<InitResult>;
154
+ private initializeAndReady;
155
+ close(): Promise<void>;
156
+ flush(): Promise<void>;
157
+ refreshDatafile(): Promise<void>;
158
+ getFlag<T = unknown>(flagKey: string, userContext: UserContext, defaultValue?: T): T;
159
+ getFlagDetails(flagKey: string, userContext: UserContext): ServerFlagEvaluationDetails;
160
+ getAllFlags(userContext: UserContext): Record<string, unknown>;
161
+ track(eventKey: string, context: TrackContext): void;
162
+ logExposure(flagKey: string, userContext: {
163
+ userId: string;
164
+ }): void;
165
+ setOverride(flagKey: string, variationKey: string, userId?: string): void;
166
+ clearOverride(flagKey: string, userId?: string): void;
167
+ clearAllOverrides(): void;
168
+ createDecisionLog(): DecisionLog;
169
+ on<E extends ServerEventName>(event: E, cb: ServerEventMap[E]): void;
170
+ off<E extends ServerEventName>(event: E, cb: ServerEventMap[E]): void;
171
+ private evaluate;
172
+ private emit;
173
+ private initTracker;
174
+ private fetchDatafile;
175
+ private startPolling;
176
+ private sendHeartbeat;
177
+ private resolveRuntimeOverride;
178
+ private resolveStickyBucket;
179
+ private findVariation;
180
+ private fireExposureForVariation;
181
+ private recordDecisionLog;
182
+ private emitEvaluation;
183
+ }
184
+
185
+ declare function expressMiddleware(client: AvsBServer, config: MiddlewareConfig): (req: unknown, res: unknown, next: () => void) => void;
186
+ declare function fastifyPlugin(instance: unknown, opts: {
187
+ client: AvsBServer;
188
+ } & MiddlewareConfig, done: () => void): void;
189
+
190
+ declare class InMemoryStickyBucketService implements StickyBucketService {
191
+ private cache;
192
+ private key;
193
+ lookup(userId: string, flagKey: string): string | null;
194
+ save(userId: string, flagKey: string, variationId: string): void;
195
+ }
196
+ interface RedisLike {
197
+ get(key: string): Promise<string | null>;
198
+ set(key: string, value: string, options?: {
199
+ EX?: number;
200
+ }): Promise<unknown>;
201
+ }
202
+ interface RedisStickyBucketOptions {
203
+ redis: RedisLike;
204
+ prefix?: string;
205
+ ttl?: number;
206
+ cacheSize?: number;
207
+ logger?: Logger;
208
+ }
209
+ declare class RedisStickyBucketService implements StickyBucketService {
210
+ private redis;
211
+ private prefix;
212
+ private ttl;
213
+ private maxCacheSize;
214
+ private logger;
215
+ private cache;
216
+ constructor(options: RedisStickyBucketOptions);
217
+ private redisKey;
218
+ private cacheKey;
219
+ lookup(userId: string, flagKey: string): string | null;
220
+ save(userId: string, flagKey: string, variationId: string): void;
221
+ private backgroundFetch;
222
+ private evictIfNeeded;
223
+ }
224
+
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
+ declare function createDecisionLog(): DecisionLog;
240
+
241
+ /** No-op logger — used when no custom logger is provided. */
242
+ declare const noopLogger: Logger;
243
+
244
+ declare const SDK_VERSION = "0.1.0";
245
+
246
+ export { AvsBServer, type AvsBServerOptions, type DecisionLog, type DecisionLogEntry, type EvaluationSource, type EventOptions, InMemoryStickyBucketService, type InitResult, type Logger, type MiddlewareConfig, type OverrideEntry, type RedisLike, type RedisStickyBucketOptions, RedisStickyBucketService, type RequestAvsB, SDK_VERSION, type ServerEventMap, type ServerEventName, type ServerFlagEvaluationDetails, type StickyBucketService, type StreamingClient, type StreamingOptions, type TrackContext, type UserContext, createDecisionLog, createStreamingClient, expressMiddleware, fastifyPlugin, noopLogger };