@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/dist/index.d.cts CHANGED
@@ -1,7 +1,10 @@
1
- import { FlagDatafile } from '@avsbhq/js';
2
- export { ExposureEvent, FlagDatafile, FlagEvaluationDetails, MetricEvent } from '@avsbhq/js';
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 AvsBServerOptions {
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
- 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;
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
- interface OverrideEntry {
131
- flagKey: string;
132
- variationKey: string;
133
- userId?: string;
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
- declare class AvsBServer {
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
- constructor(options: AvsBServerOptions);
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 = 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;
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
- setOverride(flagKey: string, variationKey: string, userId?: string): void;
166
- clearOverride(flagKey: string, userId?: string): void;
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: AvsBServer, config: MiddlewareConfig): (req: unknown, res: unknown, next: () => void) => void;
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: AvsBServer;
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): string | null;
194
- save(userId: string, flagKey: string, variationId: string): void;
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): string | null;
220
- save(userId: string, flagKey: string, variationId: string): void;
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 { 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 };
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/js';
2
- export { ExposureEvent, FlagDatafile, FlagEvaluationDetails, MetricEvent } from '@avsbhq/js';
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 AvsBServerOptions {
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
- 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;
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
- interface OverrideEntry {
131
- flagKey: string;
132
- variationKey: string;
133
- userId?: string;
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
- declare class AvsBServer {
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
- constructor(options: AvsBServerOptions);
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 = 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;
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
- setOverride(flagKey: string, variationKey: string, userId?: string): void;
166
- clearOverride(flagKey: string, userId?: string): void;
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: AvsBServer, config: MiddlewareConfig): (req: unknown, res: unknown, next: () => void) => void;
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: AvsBServer;
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): string | null;
194
- save(userId: string, flagKey: string, variationId: string): void;
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): string | null;
220
- save(userId: string, flagKey: string, variationId: string): void;
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 { 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 };
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 };