@smplkit/sdk 1.1.9 → 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.ts CHANGED
@@ -1,3 +1,50 @@
1
+ /**
2
+ * Shared WebSocket connection to the app service event gateway.
3
+ *
4
+ * A single {@link SharedWebSocket} instance is shared across all product
5
+ * modules (config, flags) within one {@link SmplClient}. Product modules
6
+ * register listeners for specific event types; the shared connection
7
+ * dispatches incoming events to the appropriate listeners.
8
+ *
9
+ * Protocol:
10
+ * - Connect to `wss://app.smplkit.com/api/ws/v1/events?api_key={key}`
11
+ * - Receive `{"type": "connected"}` on success
12
+ * - Receive events: `{"event": "config_changed", ...}`, `{"event": "flag_changed", ...}`
13
+ * - No subscribe message — the API key determines the account
14
+ * - Heartbeat: server sends `ping`, client responds with `pong`
15
+ * - Reconnect with exponential backoff
16
+ */
17
+ type EventCallback = (data: Record<string, any>) => void;
18
+ /**
19
+ * Manages a single WebSocket connection to the app service event gateway.
20
+ *
21
+ * Shared across config and flags modules for efficient multiplexing.
22
+ */
23
+ declare class SharedWebSocket {
24
+ private readonly _appBaseUrl;
25
+ private readonly _apiKey;
26
+ private _listeners;
27
+ private _connectionStatus;
28
+ private _closed;
29
+ private _ws;
30
+ private _reconnectTimer;
31
+ private _backoffIndex;
32
+ constructor(appBaseUrl: string, apiKey: string);
33
+ /** Register a listener for a specific event type. */
34
+ on(eventName: string, callback: EventCallback): void;
35
+ /** Unregister a listener for a specific event type. */
36
+ off(eventName: string, callback: EventCallback): void;
37
+ private _dispatch;
38
+ get connectionStatus(): string;
39
+ /** Start the WebSocket connection. */
40
+ start(): void;
41
+ /** Stop the WebSocket connection. */
42
+ stop(): void;
43
+ private _buildWsUrl;
44
+ private _connect;
45
+ private _scheduleReconnect;
46
+ }
47
+
1
48
  /**
2
49
  * Deep-merge resolution algorithm for config inheritance chains.
3
50
  *
@@ -74,6 +121,7 @@ interface ConfigRuntimeOptions {
74
121
  apiKey: string;
75
122
  baseUrl: string;
76
123
  fetchChain: (() => Promise<ChainConfig[]>) | null;
124
+ sharedWs?: SharedWebSocket | null;
77
125
  }
78
126
  /**
79
127
  * Runtime configuration handle for a specific environment.
@@ -89,16 +137,10 @@ declare class ConfigRuntime {
89
137
  private _fetchCount;
90
138
  private _lastFetchAt;
91
139
  private _closed;
92
- private _wsStatus;
93
- private _ws;
94
- private _reconnectTimer;
95
- private _backoffIndex;
96
140
  private _listeners;
97
- private readonly _configId;
98
141
  private readonly _environment;
99
- private readonly _apiKey;
100
- private readonly _baseUrl;
101
142
  private readonly _fetchChain;
143
+ private _sharedWs;
102
144
  /** @internal */
103
145
  constructor(options: ConfigRuntimeOptions);
104
146
  /**
@@ -158,31 +200,20 @@ declare class ConfigRuntime {
158
200
  /**
159
201
  * Close the runtime connection.
160
202
  *
161
- * Shuts down the WebSocket and cancels any pending reconnect timer.
162
- * Safe to call multiple times.
203
+ * Unregisters from the shared WebSocket. Safe to call multiple times.
163
204
  */
164
205
  close(): Promise<void>;
165
206
  /**
166
207
  * Async dispose support for `await using` (TypeScript 5.2+).
167
208
  */
168
209
  [Symbol.asyncDispose](): Promise<void>;
169
- private _buildWsUrl;
170
- private _connectWebSocket;
171
- private _scheduleReconnect;
172
- private _handleMessage;
210
+ private _handleConfigChanged;
211
+ private _handleConfigDeleted;
173
212
  private _applyChanges;
174
213
  private _diffAndFire;
175
214
  private _fireListeners;
176
215
  }
177
216
 
178
- /**
179
- * Config resource — management-plane model with runtime connect support.
180
- *
181
- * Instances are returned by {@link ConfigClient} methods and provide
182
- * management-plane operations (`update`, `setValues`, `setValue`) as well
183
- * as the {@link connect} entry point for runtime value resolution.
184
- */
185
-
186
217
  /**
187
218
  * Internal type used by {@link ConfigClient}. Not part of the public API.
188
219
  * @internal
@@ -239,6 +270,7 @@ declare class Config {
239
270
  }): Promise<Config>;
240
271
  readonly _apiKey: string;
241
272
  readonly _baseUrl: string;
273
+ _getSharedWs?: () => SharedWebSocket;
242
274
  }, fields: {
243
275
  id: string;
244
276
  key: string;
@@ -342,14 +374,6 @@ interface GetConfigOptions {
342
374
  id?: string;
343
375
  }
344
376
 
345
- /**
346
- * ConfigClient — management-plane operations for configs.
347
- *
348
- * Uses the generated OpenAPI types (`src/generated/config.d.ts`) via
349
- * `openapi-fetch` for all HTTP calls, keeping the client layer fully
350
- * type-safe without hand-coded request/response shapes.
351
- */
352
-
353
377
  /**
354
378
  * Client for the smplkit Config API (management plane).
355
379
  *
@@ -365,6 +389,8 @@ declare class ConfigClient {
365
389
  readonly _baseUrl: string;
366
390
  /** @internal */
367
391
  private readonly _http;
392
+ /** @internal — returns the shared WebSocket for real-time updates. */
393
+ _getSharedWs?: () => SharedWebSocket;
368
394
  /** @internal */
369
395
  constructor(apiKey: string, timeout?: number);
370
396
  /**
@@ -403,6 +429,384 @@ declare class ConfigClient {
403
429
  private _getByKey;
404
430
  }
405
431
 
432
+ /**
433
+ * Flag and ContextType resource models returned by the management API.
434
+ */
435
+
436
+ /**
437
+ * A flag resource returned by {@link FlagsClient} management methods.
438
+ *
439
+ * Provides `update()` for partial updates and `addRule()` for
440
+ * conveniently appending a rule to an environment.
441
+ */
442
+ declare class Flag {
443
+ /** UUID of the flag. */
444
+ id: string;
445
+ /** Unique key within the account. */
446
+ key: string;
447
+ /** Human-readable display name. */
448
+ name: string;
449
+ /** Value type: BOOLEAN, STRING, NUMERIC, or JSON. */
450
+ type: string;
451
+ /** Flag-level default value. */
452
+ default: unknown;
453
+ /** Closed set of possible values. */
454
+ values: Array<{
455
+ name: string;
456
+ value: unknown;
457
+ }>;
458
+ /** Optional description. */
459
+ description: string | null;
460
+ /** Per-environment configuration. */
461
+ environments: Record<string, any>;
462
+ /** When the flag was created. */
463
+ createdAt: string | null;
464
+ /** When the flag was last updated. */
465
+ updatedAt: string | null;
466
+ /** @internal */
467
+ private readonly _client;
468
+ /** @internal */
469
+ constructor(client: FlagsClient, fields: {
470
+ id: string;
471
+ key: string;
472
+ name: string;
473
+ type: string;
474
+ default: unknown;
475
+ values: Array<{
476
+ name: string;
477
+ value: unknown;
478
+ }>;
479
+ description: string | null;
480
+ environments: Record<string, any>;
481
+ createdAt: string | null;
482
+ updatedAt: string | null;
483
+ });
484
+ /**
485
+ * Update this flag's attributes on the server.
486
+ *
487
+ * Only provided fields are changed; others retain their current values.
488
+ */
489
+ update(options: {
490
+ environments?: Record<string, any>;
491
+ values?: Array<{
492
+ name: string;
493
+ value: unknown;
494
+ }>;
495
+ default?: unknown;
496
+ description?: string;
497
+ name?: string;
498
+ }): Promise<void>;
499
+ /**
500
+ * Add a rule to a specific environment.
501
+ *
502
+ * The built rule must include an `environment` key (set via
503
+ * `Rule(...).environment("env_key")`). Re-fetches current state
504
+ * first to avoid stale data.
505
+ */
506
+ addRule(builtRule: Record<string, any>): Promise<void>;
507
+ /** @internal */
508
+ _apply(other: Flag): void;
509
+ toString(): string;
510
+ }
511
+ /** A context type resource returned by management API methods. */
512
+ declare class ContextType {
513
+ /** UUID. */
514
+ id: string;
515
+ /** Unique key within the account. */
516
+ key: string;
517
+ /** Human-readable display name. */
518
+ name: string;
519
+ /** Known attributes. */
520
+ attributes: Record<string, any>;
521
+ constructor(fields: {
522
+ id: string;
523
+ key: string;
524
+ name: string;
525
+ attributes: Record<string, any>;
526
+ });
527
+ toString(): string;
528
+ }
529
+
530
+ /**
531
+ * Public types for the Flags SDK: FlagType, Context, Rule.
532
+ */
533
+ /** The value type of a flag. */
534
+ type FlagType = "BOOLEAN" | "STRING" | "NUMERIC" | "JSON";
535
+ /**
536
+ * A typed evaluation context entity.
537
+ *
538
+ * Represents a single entity (user, account, device, etc.) in the
539
+ * evaluation context. The *type* and *key* identify the entity;
540
+ * *attributes* carry the data that JSON Logic rules target.
541
+ *
542
+ * @example
543
+ * ```typescript
544
+ * new Context("user", "user-123", { plan: "enterprise", firstName: "Alice" })
545
+ * new Context("user", "user-123", { plan: "enterprise" }, { name: "Alice Smith" })
546
+ * ```
547
+ */
548
+ declare class Context {
549
+ readonly type: string;
550
+ readonly key: string;
551
+ readonly name: string | null;
552
+ readonly attributes: Record<string, unknown>;
553
+ constructor(type: string, key: string, attributes?: Record<string, unknown>, options?: {
554
+ name?: string;
555
+ });
556
+ toString(): string;
557
+ }
558
+ /**
559
+ * Fluent builder for JSON Logic rule dicts.
560
+ *
561
+ * @example
562
+ * ```typescript
563
+ * new Rule("Enable for enterprise users")
564
+ * .when("user.plan", "==", "enterprise")
565
+ * .when("account.region", "==", "us")
566
+ * .serve(true)
567
+ * .build()
568
+ * ```
569
+ *
570
+ * Multiple `.when()` calls are AND'd. `.environment()` tags the
571
+ * built dict with an environment key for use with `Flag.addRule()`.
572
+ */
573
+ declare class Rule {
574
+ private _description;
575
+ private _conditions;
576
+ private _value;
577
+ private _environment;
578
+ constructor(description: string);
579
+ /** Tag this rule with an environment key (used by `addRule`). */
580
+ environment(envKey: string): Rule;
581
+ /** Add a condition. Multiple calls are AND'd. */
582
+ when(variable: string, op: string, value: any): Rule;
583
+ /** Set the value returned when this rule matches. */
584
+ serve(value: any): Rule;
585
+ /** Finalize and return the rule as a plain object. */
586
+ build(): Record<string, any>;
587
+ }
588
+
589
+ /**
590
+ * FlagsClient — management + prescriptive runtime for Smpl Flags.
591
+ *
592
+ * Uses the generated OpenAPI types (`src/generated/flags.d.ts`) via
593
+ * `openapi-fetch` for all HTTP calls. Context type management and
594
+ * context registration use direct HTTP via the Transport class since
595
+ * these endpoints are on the flags service but not in the generated spec.
596
+ */
597
+
598
+ /** Describes a flag definition change. */
599
+ declare class FlagChangeEvent {
600
+ readonly key: string;
601
+ readonly source: string;
602
+ constructor(key: string, source: string);
603
+ }
604
+ /** Cache statistics for the flags runtime. */
605
+ declare class FlagStats {
606
+ readonly cacheHits: number;
607
+ readonly cacheMisses: number;
608
+ constructor(cacheHits: number, cacheMisses: number);
609
+ }
610
+ /** @internal */
611
+ declare class FlagHandleBase {
612
+ /** @internal */ readonly _namespace: FlagsClient;
613
+ /** @internal */ readonly _key: string;
614
+ /** @internal */ readonly _default: any;
615
+ /** @internal */ _listeners: Array<(event: FlagChangeEvent) => void>;
616
+ constructor(namespace: FlagsClient, key: string, defaultValue: any);
617
+ get key(): string;
618
+ get default(): any;
619
+ get(options?: {
620
+ context?: Context[];
621
+ }): any;
622
+ /** Register a flag-specific change listener. Works as a decorator. */
623
+ onChange(callback: (event: FlagChangeEvent) => void): (event: FlagChangeEvent) => void;
624
+ }
625
+ /** Typed handle for a boolean flag. */
626
+ declare class BoolFlagHandle extends FlagHandleBase {
627
+ get(options?: {
628
+ context?: Context[];
629
+ }): boolean;
630
+ }
631
+ /** Typed handle for a string flag. */
632
+ declare class StringFlagHandle extends FlagHandleBase {
633
+ get(options?: {
634
+ context?: Context[];
635
+ }): string;
636
+ }
637
+ /** Typed handle for a numeric flag. */
638
+ declare class NumberFlagHandle extends FlagHandleBase {
639
+ get(options?: {
640
+ context?: Context[];
641
+ }): number;
642
+ }
643
+ /** Typed handle for a JSON flag. */
644
+ declare class JsonFlagHandle extends FlagHandleBase {
645
+ get(options?: {
646
+ context?: Context[];
647
+ }): Record<string, any>;
648
+ }
649
+ /**
650
+ * Client for the smplkit Flags API — management plane + prescriptive runtime.
651
+ *
652
+ * Obtained via `SmplClient.flags`.
653
+ */
654
+ declare class FlagsClient {
655
+ /** @internal */
656
+ readonly _apiKey: string;
657
+ /** @internal */
658
+ readonly _baseUrl: string;
659
+ /** @internal */
660
+ private readonly _http;
661
+ /** @internal */
662
+ private readonly _transport;
663
+ private _environment;
664
+ private _flagStore;
665
+ private _connected;
666
+ private _cache;
667
+ private _contextProvider;
668
+ private _contextBuffer;
669
+ private _handles;
670
+ private _globalListeners;
671
+ private _wsManager;
672
+ private readonly _ensureWs;
673
+ /** @internal */
674
+ constructor(apiKey: string, ensureWs: () => SharedWebSocket, timeout?: number);
675
+ /** Create a flag. */
676
+ create(key: string, options: {
677
+ name: string;
678
+ type: FlagType;
679
+ default: unknown;
680
+ description?: string;
681
+ values?: Array<{
682
+ name: string;
683
+ value: unknown;
684
+ }>;
685
+ }): Promise<Flag>;
686
+ /** Fetch a flag by UUID. */
687
+ get(flagId: string): Promise<Flag>;
688
+ /** List all flags. */
689
+ list(): Promise<Flag[]>;
690
+ /** Delete a flag by UUID. */
691
+ delete(flagId: string): Promise<void>;
692
+ /**
693
+ * Internal: PUT a full flag update.
694
+ * Called by {@link Flag} instance methods.
695
+ * @internal
696
+ */
697
+ _updateFlag(options: {
698
+ flag: Flag;
699
+ environments?: Record<string, any>;
700
+ values?: Array<{
701
+ name: string;
702
+ value: unknown;
703
+ }>;
704
+ default?: unknown;
705
+ description?: string;
706
+ name?: string;
707
+ }): Promise<Flag>;
708
+ /** Create a context type. */
709
+ createContextType(key: string, options: {
710
+ name: string;
711
+ }): Promise<ContextType>;
712
+ /** Update a context type (merge attributes). */
713
+ updateContextType(ctId: string, options: {
714
+ attributes: Record<string, any>;
715
+ }): Promise<ContextType>;
716
+ /** List all context types. */
717
+ listContextTypes(): Promise<ContextType[]>;
718
+ /** Delete a context type. */
719
+ deleteContextType(ctId: string): Promise<void>;
720
+ /** List context instances filtered by context type key. */
721
+ listContexts(options: {
722
+ contextTypeKey: string;
723
+ }): Promise<any[]>;
724
+ /** Declare a boolean flag handle. */
725
+ boolFlag(key: string, defaultValue: boolean): BoolFlagHandle;
726
+ /** Declare a string flag handle. */
727
+ stringFlag(key: string, defaultValue: string): StringFlagHandle;
728
+ /** Declare a numeric flag handle. */
729
+ numberFlag(key: string, defaultValue: number): NumberFlagHandle;
730
+ /** Declare a JSON flag handle. */
731
+ jsonFlag(key: string, defaultValue: Record<string, any>): JsonFlagHandle;
732
+ /**
733
+ * Register a context provider function.
734
+ *
735
+ * Called on every `handle.get()` to supply the current evaluation
736
+ * context. Can also be used as a decorator:
737
+ *
738
+ * ```typescript
739
+ * client.flags.setContextProvider(() => [
740
+ * new Context("user", userId, { plan: userPlan }),
741
+ * ]);
742
+ * ```
743
+ */
744
+ setContextProvider(fn: () => Context[]): void;
745
+ /**
746
+ * Register a context provider — decorator-style alias.
747
+ *
748
+ * ```typescript
749
+ * const provider = client.flags.contextProvider(() => [...]);
750
+ * ```
751
+ */
752
+ contextProvider(fn: () => Context[]): () => Context[];
753
+ /**
754
+ * Connect to an environment: fetch flag definitions, register on
755
+ * shared WebSocket, enable local evaluation.
756
+ */
757
+ connect(environment: string, _options?: {
758
+ timeout?: number;
759
+ }): Promise<void>;
760
+ /** Disconnect: unregister from WebSocket, flush contexts, clear state. */
761
+ disconnect(): Promise<void>;
762
+ /** Re-fetch all flag definitions and clear cache. */
763
+ refresh(): Promise<void>;
764
+ /** Return the current WebSocket connection status. */
765
+ connectionStatus(): string;
766
+ /** Return cache statistics. */
767
+ stats(): FlagStats;
768
+ /** Register a global change listener that fires for any flag change. */
769
+ onChangeAny(callback: (event: FlagChangeEvent) => void): (event: FlagChangeEvent) => void;
770
+ /**
771
+ * Register a global change listener — decorator-style alias.
772
+ *
773
+ * ```typescript
774
+ * const listener = client.flags.onChange((event) => { ... });
775
+ * ```
776
+ */
777
+ onChange(callback: (event: FlagChangeEvent) => void): (event: FlagChangeEvent) => void;
778
+ /**
779
+ * Explicitly register context(s) for background batch registration.
780
+ *
781
+ * Accepts a single Context or an array. Fire-and-forget — never
782
+ * blocks. Works before `connect()` is called.
783
+ */
784
+ register(context: Context | Context[]): void;
785
+ /** Flush pending context registrations to the server. */
786
+ flushContexts(): Promise<void>;
787
+ /**
788
+ * Tier 1 explicit evaluation — stateless, no provider or cache.
789
+ *
790
+ * Useful for scripts, one-off jobs, and infrastructure code.
791
+ */
792
+ evaluate(key: string, options: {
793
+ environment: string;
794
+ context: Context[];
795
+ }): Promise<any>;
796
+ /** @internal */
797
+ _evaluateHandle(key: string, defaultValue: any, context: Context[] | null): any;
798
+ private _handleFlagChanged;
799
+ private _handleFlagDeleted;
800
+ private _fetchAllFlags;
801
+ private _fetchFlagsList;
802
+ private _fireChangeListeners;
803
+ private _fireChangeListenersAll;
804
+ private _flushContexts;
805
+ private _resourceToModel;
806
+ private _resourceToPlainDict;
807
+ private _parseContextType;
808
+ }
809
+
406
810
  /**
407
811
  * Top-level SDK client — SmplClient.
408
812
  *
@@ -438,7 +842,15 @@ interface SmplClientOptions {
438
842
  declare class SmplClient {
439
843
  /** Client for config management-plane operations. */
440
844
  readonly config: ConfigClient;
845
+ /** Client for flags management and runtime operations. */
846
+ readonly flags: FlagsClient;
847
+ private _wsManager;
848
+ private readonly _apiKey;
441
849
  constructor(options?: SmplClientOptions);
850
+ /** Lazily create and start the shared WebSocket. @internal */
851
+ private _ensureWs;
852
+ /** Close the shared WebSocket and release resources. */
853
+ close(): void;
442
854
  }
443
855
 
444
856
  /**
@@ -477,4 +889,4 @@ declare class SmplValidationError extends SmplError {
477
889
  constructor(message: string, statusCode?: number, responseBody?: string);
478
890
  }
479
891
 
480
- export { Config, type ConfigChangeEvent, ConfigClient, ConfigRuntime, type ConfigStats, type ConnectOptions, type ConnectionStatus, type CreateConfigOptions, type GetConfigOptions, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotFoundError, SmplTimeoutError, SmplValidationError };
892
+ export { BoolFlagHandle, Config, type ConfigChangeEvent, ConfigClient, ConfigRuntime, type ConfigStats, type ConnectOptions, type ConnectionStatus, Context, ContextType, type CreateConfigOptions, Flag, FlagChangeEvent, FlagStats, type FlagType, FlagsClient, type GetConfigOptions, JsonFlagHandle, NumberFlagHandle, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotFoundError, SmplTimeoutError, SmplValidationError, StringFlagHandle };