@setgo/sdk 1.0.1
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 +184 -0
- package/dist/core/client.d.ts +81 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +319 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +14 -0
- package/dist/core/metrics-reporter.d.ts +40 -0
- package/dist/core/metrics-reporter.d.ts.map +1 -0
- package/dist/core/metrics-reporter.js +152 -0
- package/dist/core/repository.d.ts +37 -0
- package/dist/core/repository.d.ts.map +1 -0
- package/dist/core/repository.js +58 -0
- package/dist/core/strategy-evaluator.d.ts +43 -0
- package/dist/core/strategy-evaluator.d.ts.map +1 -0
- package/dist/core/strategy-evaluator.js +209 -0
- package/dist/errors/index.d.ts +22 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +52 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/nestjs/index.d.ts +7 -0
- package/dist/nestjs/index.d.ts.map +1 -0
- package/dist/nestjs/index.js +13 -0
- package/dist/nestjs/setgo.constants.d.ts +6 -0
- package/dist/nestjs/setgo.constants.d.ts.map +1 -0
- package/dist/nestjs/setgo.constants.js +8 -0
- package/dist/nestjs/setgo.module.d.ts +20 -0
- package/dist/nestjs/setgo.module.d.ts.map +1 -0
- package/dist/nestjs/setgo.module.js +64 -0
- package/dist/nestjs/setgo.service.d.ts +34 -0
- package/dist/nestjs/setgo.service.d.ts.map +1 -0
- package/dist/nestjs/setgo.service.js +84 -0
- package/dist/types/config.d.ts +41 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +5 -0
- package/dist/types/events.d.ts +21 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +5 -0
- package/dist/types/features.d.ts +41 -0
- package/dist/types/features.d.ts.map +1 -0
- package/dist/types/features.js +5 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/metrics.d.ts +16 -0
- package/dist/types/metrics.d.ts.map +1 -0
- package/dist/types/metrics.js +5 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +12 -0
- package/dist/utils/ip-matcher.d.ts +14 -0
- package/dist/utils/ip-matcher.d.ts.map +1 -0
- package/dist/utils/ip-matcher.js +165 -0
- package/dist/utils/murmurhash.d.ts +10 -0
- package/dist/utils/murmurhash.d.ts.map +1 -0
- package/dist/utils/murmurhash.js +59 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# @setgo/sdk
|
|
2
|
+
|
|
3
|
+
SDK oficial do SetGo Feature Flags para Node.js e NestJS.
|
|
4
|
+
|
|
5
|
+
## Instalacao
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn add @setgo/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Uso Basico (Node.js)
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { SetGoClient } from '@setgo/sdk';
|
|
15
|
+
|
|
16
|
+
const client = new SetGoClient({
|
|
17
|
+
baseUrl: process.env.SETGO_URL,
|
|
18
|
+
token: process.env.SETGO_TOKEN,
|
|
19
|
+
appName: 'minha-app',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Iniciar o cliente (registra e busca features)
|
|
23
|
+
await client.start();
|
|
24
|
+
|
|
25
|
+
// Verificar se uma feature esta habilitada
|
|
26
|
+
if (client.isEnabled('dark-mode', { userId: '123' })) {
|
|
27
|
+
// Feature habilitada para este usuario
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Obter uma feature especifica
|
|
31
|
+
const feature = client.getFeature('dark-mode');
|
|
32
|
+
|
|
33
|
+
// Obter todas as features
|
|
34
|
+
const features = client.getAllFeatures();
|
|
35
|
+
|
|
36
|
+
// Escutar eventos
|
|
37
|
+
client.on('update', (event) => {
|
|
38
|
+
console.log('Features atualizadas:', event.features);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
client.on('error', (event) => {
|
|
42
|
+
console.error('Erro:', event.error);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Parar o cliente (envia metricas pendentes)
|
|
46
|
+
await client.stop();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Uso com NestJS
|
|
50
|
+
|
|
51
|
+
### Configuracao do Modulo
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { Module } from '@nestjs/common';
|
|
55
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
56
|
+
import { SetGoModule } from '@setgo/sdk/nestjs';
|
|
57
|
+
|
|
58
|
+
@Module({
|
|
59
|
+
imports: [
|
|
60
|
+
SetGoModule.forRootAsync({
|
|
61
|
+
imports: [ConfigModule],
|
|
62
|
+
inject: [ConfigService],
|
|
63
|
+
useFactory: (config: ConfigService) => ({
|
|
64
|
+
baseUrl: config.get('SETGO_URL'),
|
|
65
|
+
token: config.get('SETGO_TOKEN'),
|
|
66
|
+
appName: config.get('APP_NAME'),
|
|
67
|
+
}),
|
|
68
|
+
}),
|
|
69
|
+
],
|
|
70
|
+
})
|
|
71
|
+
export class AppModule {}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Usando o Service
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { Injectable } from '@nestjs/common';
|
|
78
|
+
import { SetGoService } from '@setgo/sdk/nestjs';
|
|
79
|
+
|
|
80
|
+
@Injectable()
|
|
81
|
+
export class MyService {
|
|
82
|
+
constructor(private readonly setgo: SetGoService) {}
|
|
83
|
+
|
|
84
|
+
async doSomething(userId: string) {
|
|
85
|
+
if (this.setgo.isEnabled('new-feature', { userId })) {
|
|
86
|
+
// Feature habilitada
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Configuracao
|
|
93
|
+
|
|
94
|
+
| Opcao | Tipo | Padrao | Descricao |
|
|
95
|
+
|-------|------|--------|-----------|
|
|
96
|
+
| `baseUrl` | string | **obrigatorio** | URL da API do SetGo |
|
|
97
|
+
| `token` | string | **obrigatorio** | Token de API do cliente |
|
|
98
|
+
| `appName` | string | **obrigatorio** | Nome da aplicacao |
|
|
99
|
+
| `environment` | string | - | Ambiente (geralmente definido no token) |
|
|
100
|
+
| `instanceId` | string | auto-gerado | Identificador unico da instancia |
|
|
101
|
+
| `refreshInterval` | number | 15000 | Intervalo de polling em ms |
|
|
102
|
+
| `metricsInterval` | number | 60000 | Intervalo de envio de metricas em ms |
|
|
103
|
+
| `timeout` | number | 10000 | Timeout das requisicoes em ms |
|
|
104
|
+
| `disableMetrics` | boolean | false | Desabilitar coleta de metricas |
|
|
105
|
+
|
|
106
|
+
## Contexto de Avaliacao
|
|
107
|
+
|
|
108
|
+
Ao avaliar uma feature, voce pode passar um contexto:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
client.isEnabled('feature-name', {
|
|
112
|
+
userId: '123', // ID do usuario
|
|
113
|
+
sessionId: 'abc', // ID da sessao
|
|
114
|
+
remoteAddress: '192.168.1.1', // IP do cliente
|
|
115
|
+
properties: { // Propriedades customizadas
|
|
116
|
+
plan: 'premium',
|
|
117
|
+
country: 'BR',
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Estrategias Suportadas
|
|
123
|
+
|
|
124
|
+
O SDK avalia as seguintes estrategias localmente:
|
|
125
|
+
|
|
126
|
+
- **DEFAULT**: Feature habilitada/desabilitada globalmente
|
|
127
|
+
- **GRADUAL_ROLLOUT**: Rollout gradual baseado em porcentagem
|
|
128
|
+
- **USER_WITH_ID**: Habilita para usuarios especificos
|
|
129
|
+
- **FLEXIBLE_ROLLOUT**: Rollout com stickiness customizado
|
|
130
|
+
- **REMOTE_ADDRESS**: Habilita para IPs/CIDRs especificos
|
|
131
|
+
|
|
132
|
+
## Eventos
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Quando o cliente esta pronto
|
|
136
|
+
client.on('ready', (event) => {
|
|
137
|
+
console.log('Pronto com', event.features.length, 'features');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Quando features sao atualizadas
|
|
141
|
+
client.on('update', (event) => {
|
|
142
|
+
console.log('Versao', event.version, 'com', event.features.length, 'features');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Quando ocorre um erro
|
|
146
|
+
client.on('error', (event) => {
|
|
147
|
+
console.error('Erro:', event.error);
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Tratamento de Erros
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { SetGoError, NetworkError, AuthenticationError, TimeoutError } from '@setgo/sdk';
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
await client.start();
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error instanceof AuthenticationError) {
|
|
160
|
+
// Token invalido
|
|
161
|
+
} else if (error instanceof NetworkError) {
|
|
162
|
+
// Erro de rede
|
|
163
|
+
} else if (error instanceof TimeoutError) {
|
|
164
|
+
// Timeout
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Metricas
|
|
170
|
+
|
|
171
|
+
O SDK coleta automaticamente metricas de uso (quantas vezes cada feature foi avaliada como true/false) e envia para a API em intervalos regulares. As metricas sao agregadas por hora.
|
|
172
|
+
|
|
173
|
+
Para desabilitar:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const client = new SetGoClient({
|
|
177
|
+
// ...
|
|
178
|
+
disableMetrics: true,
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Licenca
|
|
183
|
+
|
|
184
|
+
UNLICENSED - Uso interno apenas.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SetGo Client - main SDK entry point
|
|
3
|
+
*/
|
|
4
|
+
import type { SetGoConfig, Feature, FeatureContext, SetGoEventType, SetGoEventHandler } from '../types/index.js';
|
|
5
|
+
export declare class SetGoClient {
|
|
6
|
+
private config;
|
|
7
|
+
private repository;
|
|
8
|
+
private metricsReporter;
|
|
9
|
+
private evaluator;
|
|
10
|
+
private pollInterval?;
|
|
11
|
+
private etag?;
|
|
12
|
+
private ready;
|
|
13
|
+
private started;
|
|
14
|
+
private events;
|
|
15
|
+
constructor(config: SetGoConfig);
|
|
16
|
+
/**
|
|
17
|
+
* Validate configuration
|
|
18
|
+
*/
|
|
19
|
+
private validateConfig;
|
|
20
|
+
/**
|
|
21
|
+
* Generate a unique instance ID
|
|
22
|
+
*/
|
|
23
|
+
private generateInstanceId;
|
|
24
|
+
/**
|
|
25
|
+
* Start the client - registers with API and starts polling
|
|
26
|
+
*/
|
|
27
|
+
start(): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Stop the client - stops polling and flushes metrics
|
|
30
|
+
*/
|
|
31
|
+
stop(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Register client with the server
|
|
34
|
+
*/
|
|
35
|
+
private register;
|
|
36
|
+
/**
|
|
37
|
+
* Start polling for feature updates
|
|
38
|
+
*/
|
|
39
|
+
private startPolling;
|
|
40
|
+
/**
|
|
41
|
+
* Fetch features from the API
|
|
42
|
+
*/
|
|
43
|
+
private fetchFeatures;
|
|
44
|
+
/**
|
|
45
|
+
* Make an HTTP request
|
|
46
|
+
*/
|
|
47
|
+
private makeRequest;
|
|
48
|
+
/**
|
|
49
|
+
* Check if a feature is enabled
|
|
50
|
+
*/
|
|
51
|
+
isEnabled(featureName: string, context?: FeatureContext): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Evaluate a feature's strategies
|
|
54
|
+
*/
|
|
55
|
+
private evaluateFeature;
|
|
56
|
+
/**
|
|
57
|
+
* Get a feature by name
|
|
58
|
+
*/
|
|
59
|
+
getFeature(featureName: string): Feature | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Get all features
|
|
62
|
+
*/
|
|
63
|
+
getAllFeatures(): Feature[];
|
|
64
|
+
/**
|
|
65
|
+
* Check if client is ready
|
|
66
|
+
*/
|
|
67
|
+
isReady(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Subscribe to an event
|
|
70
|
+
*/
|
|
71
|
+
on<T extends SetGoEventType>(event: T, handler: SetGoEventHandler<T>): void;
|
|
72
|
+
/**
|
|
73
|
+
* Unsubscribe from an event
|
|
74
|
+
*/
|
|
75
|
+
off<T extends SetGoEventType>(event: T, handler: SetGoEventHandler<T>): void;
|
|
76
|
+
/**
|
|
77
|
+
* Emit an event
|
|
78
|
+
*/
|
|
79
|
+
private emit;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,WAAW,EAEX,OAAO,EACP,cAAc,EAEd,cAAc,EACd,iBAAiB,EAKlB,MAAM,mBAAmB,CAAC;AAa3B,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAiC;IACtD,OAAO,CAAC,IAAI,CAAC,CAAS;IACtB,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,MAAM,CAA0E;gBAE5E,MAAM,EAAE,WAAW;IAyB/B;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAI1B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsC5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B;;OAEG;YACW,QAAQ;IAoBtB;;OAEG;IACH,OAAO,CAAC,YAAY;IAgBpB;;OAEG;YACW,aAAa;IAwC3B;;OAEG;YACW,WAAW;IA2DzB;;OAEG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO;IAqBjE;;OAEG;IACH,OAAO,CAAC,eAAe;IAmBvB;;OAEG;IACH,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAIpD;;OAEG;IACH,cAAc,IAAI,OAAO,EAAE;IAI3B;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI;IAO3E;;OAEG;IACH,GAAG,CAAC,CAAC,SAAS,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI;IAO5E;;OAEG;IACH,OAAO,CAAC,IAAI;CAkBb"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SetGo Client - main SDK entry point
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SetGoClient = void 0;
|
|
7
|
+
const repository_js_1 = require("./repository.js");
|
|
8
|
+
const metrics_reporter_js_1 = require("./metrics-reporter.js");
|
|
9
|
+
const strategy_evaluator_js_1 = require("./strategy-evaluator.js");
|
|
10
|
+
const index_js_1 = require("../errors/index.js");
|
|
11
|
+
const SDK_VERSION = '1.0.0';
|
|
12
|
+
class SetGoClient {
|
|
13
|
+
config;
|
|
14
|
+
repository;
|
|
15
|
+
metricsReporter;
|
|
16
|
+
evaluator;
|
|
17
|
+
pollInterval;
|
|
18
|
+
etag;
|
|
19
|
+
ready = false;
|
|
20
|
+
started = false;
|
|
21
|
+
events = new Map();
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.validateConfig(config);
|
|
24
|
+
this.config = {
|
|
25
|
+
baseUrl: config.baseUrl.replace(/\/$/, ''), // Remove trailing slash
|
|
26
|
+
token: config.token,
|
|
27
|
+
appName: config.appName,
|
|
28
|
+
environment: config.environment,
|
|
29
|
+
instanceId: config.instanceId ?? this.generateInstanceId(),
|
|
30
|
+
refreshInterval: config.refreshInterval ?? 15000,
|
|
31
|
+
metricsInterval: config.metricsInterval ?? 60000,
|
|
32
|
+
timeout: config.timeout ?? 10000,
|
|
33
|
+
disableMetrics: config.disableMetrics ?? false,
|
|
34
|
+
};
|
|
35
|
+
this.repository = new repository_js_1.FeatureRepository();
|
|
36
|
+
this.metricsReporter = new metrics_reporter_js_1.MetricsReporter(this.config);
|
|
37
|
+
this.evaluator = new strategy_evaluator_js_1.StrategyEvaluator();
|
|
38
|
+
// Initialize event maps
|
|
39
|
+
this.events.set('ready', new Set());
|
|
40
|
+
this.events.set('update', new Set());
|
|
41
|
+
this.events.set('error', new Set());
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate configuration
|
|
45
|
+
*/
|
|
46
|
+
validateConfig(config) {
|
|
47
|
+
if (!config.baseUrl) {
|
|
48
|
+
throw new index_js_1.ConfigurationError('baseUrl is required');
|
|
49
|
+
}
|
|
50
|
+
if (!config.token) {
|
|
51
|
+
throw new index_js_1.ConfigurationError('token is required');
|
|
52
|
+
}
|
|
53
|
+
if (!config.appName) {
|
|
54
|
+
throw new index_js_1.ConfigurationError('appName is required');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Generate a unique instance ID
|
|
59
|
+
*/
|
|
60
|
+
generateInstanceId() {
|
|
61
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Start the client - registers with API and starts polling
|
|
65
|
+
*/
|
|
66
|
+
async start() {
|
|
67
|
+
if (this.started) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.started = true;
|
|
71
|
+
try {
|
|
72
|
+
// Register client with the server
|
|
73
|
+
await this.register();
|
|
74
|
+
// Initial feature fetch
|
|
75
|
+
await this.fetchFeatures();
|
|
76
|
+
// Mark as ready
|
|
77
|
+
this.ready = true;
|
|
78
|
+
// Emit ready event
|
|
79
|
+
this.emit('ready', {
|
|
80
|
+
type: 'ready',
|
|
81
|
+
features: this.repository.getAll(),
|
|
82
|
+
});
|
|
83
|
+
// Start polling
|
|
84
|
+
this.startPolling();
|
|
85
|
+
// Start metrics reporting
|
|
86
|
+
this.metricsReporter.start();
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
this.started = false;
|
|
90
|
+
this.emit('error', {
|
|
91
|
+
type: 'error',
|
|
92
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
93
|
+
});
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Stop the client - stops polling and flushes metrics
|
|
99
|
+
*/
|
|
100
|
+
async stop() {
|
|
101
|
+
if (!this.started) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Stop polling
|
|
105
|
+
if (this.pollInterval) {
|
|
106
|
+
clearInterval(this.pollInterval);
|
|
107
|
+
this.pollInterval = undefined;
|
|
108
|
+
}
|
|
109
|
+
// Flush remaining metrics
|
|
110
|
+
await this.metricsReporter.flush();
|
|
111
|
+
this.metricsReporter.stop();
|
|
112
|
+
this.started = false;
|
|
113
|
+
this.ready = false;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Register client with the server
|
|
117
|
+
*/
|
|
118
|
+
async register() {
|
|
119
|
+
const url = `${this.config.baseUrl}/client/register`;
|
|
120
|
+
const payload = {
|
|
121
|
+
appName: this.config.appName,
|
|
122
|
+
instanceId: this.config.instanceId,
|
|
123
|
+
sdkVersion: SDK_VERSION,
|
|
124
|
+
platformInfo: {
|
|
125
|
+
nodeVersion: typeof process !== 'undefined' ? process.version : 'unknown',
|
|
126
|
+
platform: typeof process !== 'undefined' ? process.platform : 'unknown',
|
|
127
|
+
},
|
|
128
|
+
interval: this.config.refreshInterval,
|
|
129
|
+
};
|
|
130
|
+
await this.makeRequest(url, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
body: JSON.stringify(payload),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Start polling for feature updates
|
|
137
|
+
*/
|
|
138
|
+
startPolling() {
|
|
139
|
+
this.pollInterval = setInterval(() => {
|
|
140
|
+
void this.fetchFeatures().catch((error) => {
|
|
141
|
+
this.emit('error', {
|
|
142
|
+
type: 'error',
|
|
143
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}, this.config.refreshInterval);
|
|
147
|
+
// Ensure the interval doesn't prevent Node.js from exiting
|
|
148
|
+
if (this.pollInterval.unref) {
|
|
149
|
+
this.pollInterval.unref();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Fetch features from the API
|
|
154
|
+
*/
|
|
155
|
+
async fetchFeatures() {
|
|
156
|
+
const url = `${this.config.baseUrl}/client/features`;
|
|
157
|
+
const headers = {};
|
|
158
|
+
if (this.etag) {
|
|
159
|
+
headers['If-None-Match'] = this.etag;
|
|
160
|
+
}
|
|
161
|
+
const response = await this.makeRequest(url, {
|
|
162
|
+
method: 'GET',
|
|
163
|
+
headers,
|
|
164
|
+
expectNotModified: true,
|
|
165
|
+
});
|
|
166
|
+
// 304 Not Modified - no changes
|
|
167
|
+
if (response.status === 304) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const data = (await response.json());
|
|
171
|
+
const previousVersion = this.repository.getVersion();
|
|
172
|
+
this.repository.update(data);
|
|
173
|
+
// Update etag
|
|
174
|
+
const newEtag = response.headers.get('etag');
|
|
175
|
+
if (newEtag) {
|
|
176
|
+
this.etag = newEtag;
|
|
177
|
+
}
|
|
178
|
+
// Emit update event if version changed and client was already ready
|
|
179
|
+
if (this.ready && data.version !== previousVersion) {
|
|
180
|
+
this.emit('update', {
|
|
181
|
+
type: 'update',
|
|
182
|
+
features: this.repository.getAll(),
|
|
183
|
+
version: data.version,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Make an HTTP request
|
|
189
|
+
*/
|
|
190
|
+
async makeRequest(url, options) {
|
|
191
|
+
const controller = new AbortController();
|
|
192
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
193
|
+
try {
|
|
194
|
+
const response = await fetch(url, {
|
|
195
|
+
method: options.method,
|
|
196
|
+
headers: {
|
|
197
|
+
'Content-Type': 'application/json',
|
|
198
|
+
Authorization: this.config.token,
|
|
199
|
+
...options.headers,
|
|
200
|
+
},
|
|
201
|
+
body: options.body,
|
|
202
|
+
signal: controller.signal,
|
|
203
|
+
});
|
|
204
|
+
// Handle 304 Not Modified
|
|
205
|
+
if (options.expectNotModified && response.status === 304) {
|
|
206
|
+
return response;
|
|
207
|
+
}
|
|
208
|
+
if (response.status === 401 || response.status === 403) {
|
|
209
|
+
throw new index_js_1.AuthenticationError();
|
|
210
|
+
}
|
|
211
|
+
if (!response.ok) {
|
|
212
|
+
throw new index_js_1.NetworkError(`Request failed: ${response.statusText}`, response.status);
|
|
213
|
+
}
|
|
214
|
+
return response;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
218
|
+
throw new index_js_1.TimeoutError();
|
|
219
|
+
}
|
|
220
|
+
if (error instanceof index_js_1.NetworkError ||
|
|
221
|
+
error instanceof index_js_1.AuthenticationError ||
|
|
222
|
+
error instanceof index_js_1.TimeoutError) {
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
throw new index_js_1.NetworkError(`Request failed: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, error instanceof Error ? error : undefined);
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
clearTimeout(timeoutId);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Check if a feature is enabled
|
|
233
|
+
*/
|
|
234
|
+
isEnabled(featureName, context) {
|
|
235
|
+
const feature = this.repository.get(featureName);
|
|
236
|
+
if (!feature) {
|
|
237
|
+
// Unknown feature - return false
|
|
238
|
+
this.metricsReporter.track(featureName, false);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
if (!feature.enabled) {
|
|
242
|
+
// Feature is globally disabled
|
|
243
|
+
this.metricsReporter.track(featureName, false);
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
// Evaluate strategies
|
|
247
|
+
const enabled = this.evaluateFeature(feature, context);
|
|
248
|
+
this.metricsReporter.track(featureName, enabled);
|
|
249
|
+
return enabled;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Evaluate a feature's strategies
|
|
253
|
+
*/
|
|
254
|
+
evaluateFeature(feature, context) {
|
|
255
|
+
if (!feature.strategies || feature.strategies.length === 0) {
|
|
256
|
+
// No strategies - return the feature's enabled status
|
|
257
|
+
return feature.enabled;
|
|
258
|
+
}
|
|
259
|
+
// Strategies are evaluated in order (by sortOrder)
|
|
260
|
+
// First matching enabled strategy wins
|
|
261
|
+
const sortedStrategies = [...feature.strategies].sort((a, b) => a.sortOrder - b.sortOrder);
|
|
262
|
+
for (const strategy of sortedStrategies) {
|
|
263
|
+
if (this.evaluator.evaluate(strategy, context)) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get a feature by name
|
|
271
|
+
*/
|
|
272
|
+
getFeature(featureName) {
|
|
273
|
+
return this.repository.get(featureName);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Get all features
|
|
277
|
+
*/
|
|
278
|
+
getAllFeatures() {
|
|
279
|
+
return this.repository.getAll();
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Check if client is ready
|
|
283
|
+
*/
|
|
284
|
+
isReady() {
|
|
285
|
+
return this.ready;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Subscribe to an event
|
|
289
|
+
*/
|
|
290
|
+
on(event, handler) {
|
|
291
|
+
const handlers = this.events.get(event);
|
|
292
|
+
if (handlers) {
|
|
293
|
+
handlers.add(handler);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Unsubscribe from an event
|
|
298
|
+
*/
|
|
299
|
+
off(event, handler) {
|
|
300
|
+
const handlers = this.events.get(event);
|
|
301
|
+
if (handlers) {
|
|
302
|
+
handlers.delete(handler);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
emit(event, data) {
|
|
306
|
+
const handlers = this.events.get(event);
|
|
307
|
+
if (handlers) {
|
|
308
|
+
for (const handler of handlers) {
|
|
309
|
+
try {
|
|
310
|
+
handler(data);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
// Ignore handler errors
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
exports.SetGoClient = SetGoClient;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SetGo SDK Core - Re-exports
|
|
3
|
+
*/
|
|
4
|
+
export { SetGoClient } from './client.js';
|
|
5
|
+
export { FeatureRepository } from './repository.js';
|
|
6
|
+
export { StrategyEvaluator } from './strategy-evaluator.js';
|
|
7
|
+
export { MetricsReporter } from './metrics-reporter.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SetGo SDK Core - Re-exports
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MetricsReporter = exports.StrategyEvaluator = exports.FeatureRepository = exports.SetGoClient = void 0;
|
|
7
|
+
var client_js_1 = require("./client.js");
|
|
8
|
+
Object.defineProperty(exports, "SetGoClient", { enumerable: true, get: function () { return client_js_1.SetGoClient; } });
|
|
9
|
+
var repository_js_1 = require("./repository.js");
|
|
10
|
+
Object.defineProperty(exports, "FeatureRepository", { enumerable: true, get: function () { return repository_js_1.FeatureRepository; } });
|
|
11
|
+
var strategy_evaluator_js_1 = require("./strategy-evaluator.js");
|
|
12
|
+
Object.defineProperty(exports, "StrategyEvaluator", { enumerable: true, get: function () { return strategy_evaluator_js_1.StrategyEvaluator; } });
|
|
13
|
+
var metrics_reporter_js_1 = require("./metrics-reporter.js");
|
|
14
|
+
Object.defineProperty(exports, "MetricsReporter", { enumerable: true, get: function () { return metrics_reporter_js_1.MetricsReporter; } });
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics Reporter - collects and sends usage metrics
|
|
3
|
+
*/
|
|
4
|
+
import type { ResolvedSetGoConfig } from '../types/index.js';
|
|
5
|
+
export declare class MetricsReporter {
|
|
6
|
+
private metrics;
|
|
7
|
+
private bucketStart;
|
|
8
|
+
private sendInterval?;
|
|
9
|
+
private config;
|
|
10
|
+
constructor(config: ResolvedSetGoConfig);
|
|
11
|
+
/**
|
|
12
|
+
* Get the current hour bucket timestamp
|
|
13
|
+
*/
|
|
14
|
+
private getCurrentHourBucket;
|
|
15
|
+
/**
|
|
16
|
+
* Start the metrics reporting interval
|
|
17
|
+
*/
|
|
18
|
+
start(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Stop the metrics reporting interval
|
|
21
|
+
*/
|
|
22
|
+
stop(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Track a feature evaluation
|
|
25
|
+
*/
|
|
26
|
+
track(featureName: string, enabled: boolean): void;
|
|
27
|
+
/**
|
|
28
|
+
* Flush metrics to the server
|
|
29
|
+
*/
|
|
30
|
+
flush(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Send metrics to the API
|
|
33
|
+
*/
|
|
34
|
+
private sendMetrics;
|
|
35
|
+
/**
|
|
36
|
+
* Get current metrics count (for testing/debugging)
|
|
37
|
+
*/
|
|
38
|
+
getMetricsCount(): number;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=metrics-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics-reporter.d.ts","sourceRoot":"","sources":["../../src/core/metrics-reporter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAiC,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAQ5F,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAA6C;IAC5D,OAAO,CAAC,WAAW,CAAO;IAC1B,OAAO,CAAC,YAAY,CAAC,CAAiC;IACtD,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,EAAE,mBAAmB;IAKvC;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAM5B;;OAEG;IACH,KAAK,IAAI,IAAI;IAab;;OAEG;IACH,IAAI,IAAI,IAAI;IAOZ;;OAEG;IACH,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAsBlD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyC5B;;OAEG;YACW,WAAW;IAqCzB;;OAEG;IACH,eAAe,IAAI,MAAM;CAG1B"}
|