@smplkit/sdk 3.0.3 → 3.0.4
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 +210 -115
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,34 +16,40 @@ npm install @smplkit/sdk
|
|
|
16
16
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
|
+
`SmplClient` requires `apiKey`, `environment`, and `service`. Each can come from the constructor, an environment variable, or `~/.smplkit`.
|
|
20
|
+
|
|
19
21
|
```typescript
|
|
20
22
|
import { SmplClient } from "@smplkit/sdk";
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const client = new SmplClient({
|
|
25
|
+
apiKey: "sk_api_...",
|
|
26
|
+
environment: "production",
|
|
27
|
+
service: "my-service",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Block until cache is warm and the live-updates WebSocket is connected.
|
|
31
|
+
// Optional but recommended at process start so the first reads hit cache.
|
|
32
|
+
await client.waitUntilReady();
|
|
24
33
|
|
|
25
|
-
//
|
|
26
|
-
// export SMPLKIT_API_KEY=sk_api_...
|
|
27
|
-
const client2 = new SmplClient();
|
|
34
|
+
// ... do work ...
|
|
28
35
|
|
|
29
|
-
//
|
|
30
|
-
// [default]
|
|
31
|
-
// api_key = sk_api_...
|
|
32
|
-
const client3 = new SmplClient();
|
|
36
|
+
client.close(); // releases the WebSocket and stops background timers
|
|
33
37
|
```
|
|
34
38
|
|
|
39
|
+
If `SMPLKIT_API_KEY` / `SMPLKIT_ENVIRONMENT` / `SMPLKIT_SERVICE` are set (or a `~/.smplkit` profile supplies them), `new SmplClient()` works with no arguments.
|
|
40
|
+
|
|
35
41
|
## Configuration
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
Settings are resolved in order of precedence:
|
|
38
44
|
|
|
39
|
-
1. **Constructor options** — highest priority
|
|
40
|
-
2. **Environment variables** —
|
|
45
|
+
1. **Constructor options** — highest priority.
|
|
46
|
+
2. **Environment variables** — `SMPLKIT_API_KEY`, `SMPLKIT_ENVIRONMENT`, `SMPLKIT_SERVICE`, `SMPLKIT_BASE_DOMAIN`, `SMPLKIT_SCHEME`, `SMPLKIT_DEBUG`, `SMPLKIT_DISABLE_TELEMETRY`, `SMPLKIT_PROFILE`.
|
|
41
47
|
3. **Configuration file** (`~/.smplkit`) — INI-format with profile support.
|
|
42
|
-
4. **
|
|
48
|
+
4. **Built-in defaults**.
|
|
43
49
|
|
|
44
50
|
### Configuration File
|
|
45
51
|
|
|
46
|
-
|
|
52
|
+
`~/.smplkit` supports a `[common]` section (applied to every profile) plus named profiles:
|
|
47
53
|
|
|
48
54
|
```ini
|
|
49
55
|
[common]
|
|
@@ -61,202 +67,291 @@ environment = development
|
|
|
61
67
|
debug = true
|
|
62
68
|
```
|
|
63
69
|
|
|
64
|
-
### Constructor Examples
|
|
65
|
-
|
|
66
70
|
```typescript
|
|
67
|
-
// Use a named profile
|
|
68
71
|
const client = new SmplClient({ profile: "local" });
|
|
69
|
-
|
|
70
|
-
// Or configure explicitly
|
|
71
|
-
const client = new SmplClient({
|
|
72
|
-
apiKey: "sk_api_...",
|
|
73
|
-
environment: "production",
|
|
74
|
-
service: "my-service",
|
|
75
|
-
});
|
|
76
72
|
```
|
|
77
73
|
|
|
78
|
-
For the complete
|
|
74
|
+
For the complete reference, see the [Configuration Guide](https://docs.smplkit.com/getting-started/configuration).
|
|
79
75
|
|
|
80
76
|
## Config
|
|
81
77
|
|
|
82
|
-
### Runtime
|
|
78
|
+
### Runtime — resolve config values
|
|
79
|
+
|
|
80
|
+
`client.config.get(key)` returns a `LiveConfigProxy`: a read-only, dict-like view that always reflects the latest server-pushed values.
|
|
83
81
|
|
|
84
82
|
```typescript
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
console.log(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
});
|
|
83
|
+
const cfg = await client.config.get("user-service");
|
|
84
|
+
|
|
85
|
+
// Dict-style access — both forms work
|
|
86
|
+
console.log(cfg.get("database.host"));
|
|
87
|
+
console.log(cfg["max_retries"]);
|
|
88
|
+
for (const key of Object.keys(cfg)) console.log(key, cfg[key]);
|
|
89
|
+
|
|
90
|
+
// Per-config and per-item change listeners
|
|
91
|
+
cfg.onChange((event) => console.log(`${event.itemKey}: ${event.oldValue} -> ${event.newValue}`));
|
|
92
|
+
cfg.onChange("max_retries", (event) => console.log("retries changed:", event.newValue));
|
|
93
|
+
|
|
94
|
+
// Or attach a global listener that fires for any config change
|
|
95
|
+
client.config.onChange((event) => console.log(`${event.configId}.${event.itemKey} changed`));
|
|
96
|
+
|
|
97
|
+
// Manual re-fetch (useful after suspecting drift)
|
|
98
|
+
await client.config.refresh();
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
You can also pass a model class as the second argument; the proxy reconstructs the model from the latest values on every read so attribute access type-checks against your model:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
class UserServiceConfig {
|
|
105
|
+
database!: { host: string; port: number };
|
|
106
|
+
max_retries!: number;
|
|
107
|
+
constructor(data: any) {
|
|
108
|
+
Object.assign(this, data);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const typed = await client.config.get("user-service", UserServiceConfig);
|
|
112
|
+
console.log(typed.database.host);
|
|
94
113
|
```
|
|
95
114
|
|
|
96
115
|
### Management (CRUD)
|
|
97
116
|
|
|
117
|
+
CRUD lives under `client.manage.config.*`. You can also construct a standalone `SmplManagementClient` for setup scripts or admin tooling.
|
|
118
|
+
|
|
98
119
|
```typescript
|
|
99
|
-
//
|
|
100
|
-
const cfg = client.config.
|
|
120
|
+
// Author a config — `set*` mutations are local until `.save()` is called.
|
|
121
|
+
const cfg = client.manage.config.new("my-service", {
|
|
101
122
|
name: "My Service",
|
|
102
123
|
description: "Configuration for my service",
|
|
103
124
|
});
|
|
125
|
+
cfg.setString("database.host", "localhost");
|
|
126
|
+
cfg.setNumber("max_retries", 3);
|
|
127
|
+
cfg.setBoolean("enable_signup", true);
|
|
128
|
+
cfg.setJson("feature_matrix", { v2: true });
|
|
129
|
+
|
|
130
|
+
// Per-environment override
|
|
131
|
+
cfg.setNumber("max_retries", 5, { environment: "production" });
|
|
104
132
|
await cfg.save();
|
|
105
133
|
|
|
106
|
-
//
|
|
107
|
-
const
|
|
134
|
+
// Read / list / delete
|
|
135
|
+
const fetched = await client.manage.config.get("my-service");
|
|
136
|
+
const all = await client.manage.config.list();
|
|
137
|
+
await client.manage.config.delete("my-service");
|
|
138
|
+
```
|
|
108
139
|
|
|
109
|
-
|
|
110
|
-
const fetched = await client.config.management.get("my-service");
|
|
140
|
+
A config can have a `parent`; resolved values from the parent are inherited and overridden by the child. The hierarchy is at most two levels deep — the server rejects a parent that itself has a parent.
|
|
111
141
|
|
|
112
|
-
|
|
113
|
-
|
|
142
|
+
```typescript
|
|
143
|
+
const child = client.manage.config.new("user-service", {
|
|
144
|
+
name: "User Service",
|
|
145
|
+
parent: "my-service", // or pass a Config instance
|
|
146
|
+
});
|
|
114
147
|
```
|
|
115
148
|
|
|
116
149
|
## Flags
|
|
117
150
|
|
|
118
|
-
### Runtime
|
|
151
|
+
### Runtime — evaluate flags
|
|
119
152
|
|
|
120
153
|
```typescript
|
|
121
|
-
import { SmplClient,
|
|
154
|
+
import { SmplClient, Context } from "@smplkit/sdk";
|
|
122
155
|
|
|
123
156
|
const client = new SmplClient({ environment: "production", service: "my-service" });
|
|
157
|
+
await client.waitUntilReady();
|
|
158
|
+
|
|
159
|
+
// Declare typed flag handles. The default is returned when smplkit is
|
|
160
|
+
// unreachable or the flag does not exist.
|
|
161
|
+
const checkoutV2 = client.flags.booleanFlag("checkout-v2", false);
|
|
162
|
+
const bannerColor = client.flags.stringFlag("banner-color", "red");
|
|
163
|
+
const maxRetries = client.flags.numberFlag("max-retries", 3);
|
|
164
|
+
|
|
165
|
+
// Evaluate with explicit per-call context
|
|
166
|
+
const enabled = checkoutV2.get({
|
|
167
|
+
context: [
|
|
168
|
+
new Context("user", "alice@acme.com", { plan: "enterprise" }),
|
|
169
|
+
new Context("account", "1234", { region: "us" }),
|
|
170
|
+
],
|
|
171
|
+
});
|
|
124
172
|
|
|
125
|
-
//
|
|
126
|
-
|
|
173
|
+
// Or register an ambient context provider that fires per evaluation
|
|
174
|
+
client.flags.setContextProvider(() => [
|
|
175
|
+
new Context("user", currentUser.email, { plan: currentUser.plan }),
|
|
176
|
+
]);
|
|
177
|
+
const colour = bannerColor.get(); // uses the provider
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`flag.get()` is synchronous — `initialize()` (or `waitUntilReady()`) populates the local store; subsequent reads hit cache and never block.
|
|
127
181
|
|
|
128
|
-
|
|
129
|
-
|
|
182
|
+
#### Listening for changes
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
client.flags.onChange((event) => console.log(`${event.id} changed`));
|
|
186
|
+
client.flags.onChange("banner-color", (event) => console.log("banner-color updated"));
|
|
130
187
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
console.log("checkout-v2:", enabled);
|
|
188
|
+
// Manual re-fetch
|
|
189
|
+
await client.flags.refresh();
|
|
134
190
|
|
|
135
|
-
|
|
191
|
+
// Cache stats (cacheHits / cacheMisses)
|
|
192
|
+
const stats = client.flags.stats();
|
|
136
193
|
```
|
|
137
194
|
|
|
138
195
|
### Management (CRUD)
|
|
139
196
|
|
|
140
197
|
```typescript
|
|
141
|
-
|
|
142
|
-
|
|
198
|
+
import { Rule, Op } from "@smplkit/sdk";
|
|
199
|
+
|
|
200
|
+
const flag = client.manage.flags.newBooleanFlag("checkout-v2", {
|
|
143
201
|
default: false,
|
|
144
202
|
description: "Controls rollout of the new checkout experience.",
|
|
145
203
|
});
|
|
146
204
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
new Rule("Enable for enterprise users")
|
|
150
|
-
.
|
|
151
|
-
.when("
|
|
152
|
-
.serve(true)
|
|
153
|
-
.build(),
|
|
205
|
+
// Targeting rule — `environment` is required on the Rule constructor
|
|
206
|
+
flag.addRule(
|
|
207
|
+
new Rule("Enable for enterprise users", { environment: "production" })
|
|
208
|
+
.when("user.plan", Op.EQ, "enterprise")
|
|
209
|
+
.when("account.region", Op.EQ, "us")
|
|
210
|
+
.serve(true),
|
|
154
211
|
);
|
|
155
212
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
213
|
+
// Per-environment defaults and kill-switch
|
|
214
|
+
flag.setDefault(false, { environment: "production" });
|
|
215
|
+
flag.disableRules({ environment: "staging" }); // kill switch
|
|
216
|
+
flag.enableRules({ environment: "production" });
|
|
159
217
|
|
|
160
|
-
await
|
|
218
|
+
await flag.save();
|
|
161
219
|
|
|
162
|
-
// Other
|
|
163
|
-
const
|
|
220
|
+
// Other typed factories
|
|
221
|
+
const banner = client.manage.flags.newStringFlag("banner-color", {
|
|
164
222
|
default: "red",
|
|
165
|
-
values: [
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
default: { mode: "light" },
|
|
223
|
+
values: [
|
|
224
|
+
{ name: "Red", value: "red" },
|
|
225
|
+
{ name: "Blue", value: "blue" },
|
|
226
|
+
],
|
|
170
227
|
});
|
|
228
|
+
const retries = client.manage.flags.newNumberFlag("max-retries", { default: 3 });
|
|
229
|
+
const theme = client.manage.flags.newJsonFlag("ui-theme", { default: { mode: "light" } });
|
|
230
|
+
|
|
231
|
+
// CRUD
|
|
232
|
+
const all = await client.manage.flags.list();
|
|
233
|
+
const fetched = await client.manage.flags.get("checkout-v2");
|
|
234
|
+
await client.manage.flags.delete("checkout-v2");
|
|
235
|
+
```
|
|
171
236
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
237
|
+
### Contexts
|
|
238
|
+
|
|
239
|
+
Bulk-register context entities so the platform knows about them (used in the targeting UI, dashboards, etc.):
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
await client.manage.contexts.register([
|
|
243
|
+
new Context("user", "alice@acme.com", { plan: "enterprise" }),
|
|
244
|
+
new Context("account", "1234", { region: "us" }),
|
|
245
|
+
]);
|
|
246
|
+
await client.manage.contexts.flush(); // or pass `{ flush: true }` to register
|
|
176
247
|
```
|
|
177
248
|
|
|
178
249
|
## Logging
|
|
179
250
|
|
|
180
|
-
### Runtime
|
|
251
|
+
### Runtime — live log level management
|
|
252
|
+
|
|
253
|
+
`install()` auto-discovers winston and pino loggers, hooks new-logger creation, applies server-managed levels, and subscribes to live updates over the shared WebSocket.
|
|
181
254
|
|
|
182
255
|
```typescript
|
|
256
|
+
import { SmplClient, LogLevel } from "@smplkit/sdk";
|
|
257
|
+
|
|
183
258
|
const client = new SmplClient({ environment: "production", service: "my-service" });
|
|
259
|
+
await client.logging.install();
|
|
184
260
|
|
|
185
|
-
|
|
186
|
-
|
|
261
|
+
client.logging.onChange((event) => {
|
|
262
|
+
console.log(`${event.id}: ${event.level} (source=${event.source})`);
|
|
263
|
+
});
|
|
187
264
|
|
|
188
|
-
//
|
|
189
|
-
await client.logging.
|
|
265
|
+
// Force a manual re-sync (e.g. after suspecting drift)
|
|
266
|
+
await client.logging.refresh();
|
|
267
|
+
```
|
|
190
268
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
269
|
+
**Adapter coverage.** Winston named loggers (`winston.loggers.*`) and the default winston logger are auto-discovered at install time. Pino has no global registry, so only loggers created through `pino()` / `logger.child()` after `install()` runs are tracked — pre-existing pino loggers must be recreated or explicitly registered via `client.manage.loggers.register([...])`. There is no console adapter; use a supported framework (winston or pino) to bring loggers under management.
|
|
270
|
+
|
|
271
|
+
You can also register a custom adapter:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
client.logging.registerAdapter(myAdapter); // must implement LoggingAdapter
|
|
275
|
+
await client.logging.install();
|
|
194
276
|
```
|
|
195
277
|
|
|
196
278
|
### Management (CRUD)
|
|
197
279
|
|
|
280
|
+
Loggers and log groups have separate namespaces:
|
|
281
|
+
|
|
198
282
|
```typescript
|
|
199
|
-
//
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
await
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
283
|
+
// Loggers
|
|
284
|
+
const sql = client.manage.loggers.new("sqlalchemy.engine", { managed: true });
|
|
285
|
+
sql.setLevel(LogLevel.WARN);
|
|
286
|
+
sql.setLevel(LogLevel.ERROR, { environment: "production" });
|
|
287
|
+
await sql.save();
|
|
288
|
+
|
|
289
|
+
const all = await client.manage.loggers.list();
|
|
290
|
+
const fetched = await client.manage.loggers.get("sqlalchemy.engine");
|
|
291
|
+
await client.manage.loggers.delete("sqlalchemy.engine");
|
|
292
|
+
|
|
293
|
+
// Log groups (a way to bulk-set levels across many loggers)
|
|
294
|
+
const group = client.manage.logGroups.new("sql", { name: "SQL Loggers" });
|
|
207
295
|
group.setLevel(LogLevel.WARN);
|
|
208
296
|
await group.save();
|
|
209
297
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
await
|
|
298
|
+
await client.manage.logGroups.list();
|
|
299
|
+
await client.manage.logGroups.get("sql");
|
|
300
|
+
await client.manage.logGroups.delete("sql");
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Standalone management client
|
|
213
304
|
|
|
214
|
-
|
|
215
|
-
const loggers = await client.logging.management.list();
|
|
216
|
-
const fetched = await client.logging.management.get("Sqlalchemy.Engine");
|
|
217
|
-
await client.logging.management.delete("Sqlalchemy.Engine");
|
|
305
|
+
For setup scripts, CI tooling, and admin utilities you don't need the runtime plane (no WebSocket, no metrics thread, no logger discovery). Construct `SmplManagementClient` directly:
|
|
218
306
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
307
|
+
```typescript
|
|
308
|
+
import { SmplManagementClient } from "@smplkit/sdk";
|
|
309
|
+
|
|
310
|
+
const manage = new SmplManagementClient(); // resolves apiKey from env / ~/.smplkit
|
|
311
|
+
await manage.environments.list();
|
|
312
|
+
await manage.config.new("my-service", { name: "My Service" }).save();
|
|
313
|
+
await manage.close(); // flushes any buffered context/flag/logger registrations
|
|
222
314
|
```
|
|
223
315
|
|
|
316
|
+
The runtime `client.manage` and a standalone `SmplManagementClient` expose the same surface: `config`, `flags`, `loggers`, `logGroups`, `contexts`, `contextTypes`, `environments`, `accountSettings`.
|
|
317
|
+
|
|
224
318
|
## Error Handling
|
|
225
319
|
|
|
226
|
-
All SDK errors extend `SmplError
|
|
320
|
+
All SDK errors extend `SmplError` (also re-exported as `SmplkitError` for callers that prefer the longer prefix).
|
|
227
321
|
|
|
228
322
|
```typescript
|
|
229
323
|
import { SmplError, SmplNotFoundError } from "@smplkit/sdk";
|
|
230
324
|
|
|
231
325
|
try {
|
|
232
|
-
|
|
326
|
+
await client.manage.flags.get("nonexistent");
|
|
233
327
|
} catch (err) {
|
|
234
328
|
if (err instanceof SmplNotFoundError) {
|
|
235
329
|
console.log("Not found:", err.message);
|
|
236
330
|
} else if (err instanceof SmplError) {
|
|
237
331
|
console.log("SDK error:", err.statusCode, err.responseBody);
|
|
332
|
+
console.log("Structured details:", err.errors);
|
|
238
333
|
}
|
|
239
334
|
}
|
|
240
335
|
```
|
|
241
336
|
|
|
242
|
-
| Error
|
|
243
|
-
|
|
244
|
-
| `SmplNotFoundError`
|
|
245
|
-
| `SmplConflictError`
|
|
246
|
-
| `SmplValidationError`
|
|
247
|
-
| `SmplTimeoutError`
|
|
248
|
-
| `SmplConnectionError`
|
|
249
|
-
| `SmplError`
|
|
337
|
+
| Error | Cause |
|
|
338
|
+
| --------------------- | ---------------------------------- |
|
|
339
|
+
| `SmplNotFoundError` | HTTP 404 — resource not found |
|
|
340
|
+
| `SmplConflictError` | HTTP 409 — conflict |
|
|
341
|
+
| `SmplValidationError` | HTTP 422 — validation error |
|
|
342
|
+
| `SmplTimeoutError` | Request timed out |
|
|
343
|
+
| `SmplConnectionError` | Network connectivity issue |
|
|
344
|
+
| `SmplError` | Base class for any other SDK error |
|
|
250
345
|
|
|
251
346
|
## Debug Logging
|
|
252
347
|
|
|
253
|
-
Set `SMPLKIT_DEBUG
|
|
348
|
+
Set `SMPLKIT_DEBUG` to enable verbose diagnostic output to stderr — useful when troubleshooting WebSocket connectivity, level resolution, or initialization.
|
|
254
349
|
|
|
255
350
|
```bash
|
|
256
351
|
SMPLKIT_DEBUG=1 node my-app.js
|
|
257
352
|
```
|
|
258
353
|
|
|
259
|
-
Accepted values: `1`, `true`, `yes` (case-insensitive). Any other value (or unset) disables debug output.
|
|
354
|
+
Accepted values: `1`, `true`, `yes` (case-insensitive). Any other value (or unset) disables debug output. You can also enable it programmatically via `new SmplClient({ debug: true })`.
|
|
260
355
|
|
|
261
356
|
## Documentation
|
|
262
357
|
|