@redpennon/node-sdk 1.0.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/README.md +76 -0
- package/dist/index.d.ts +152 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +161 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @redpennon/node-sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@redpennon/node-sdk)
|
|
4
|
+
[](https://github.com/redpennon/sdks/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
Thin HTTP client for the [RedPennon](https://redpennon.dev) feature flag evaluation API.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @redpennon/node-sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Requires Node 20+. No runtime dependencies — uses the global `fetch`.
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { RedPennonClient } from "@redpennon/node-sdk";
|
|
20
|
+
|
|
21
|
+
const client = new RedPennonClient({ apiKey: "YOUR_ENV_API_KEY" });
|
|
22
|
+
|
|
23
|
+
// Simple: get the resolved value or your default on any failure.
|
|
24
|
+
const enabled = await client.variableValue("dark-mode-enabled", {
|
|
25
|
+
default: false,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// With user context (for targeting rules).
|
|
29
|
+
const user = { id: "user_123", email: "alice@example.com" };
|
|
30
|
+
const enabled = await client.variableValue("dark-mode-enabled", {
|
|
31
|
+
default: false,
|
|
32
|
+
user,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Full result: value + variation + reason.
|
|
36
|
+
const result = await client.variable("dark-mode-enabled", { user });
|
|
37
|
+
console.log(result.value, result.variation, result.reason);
|
|
38
|
+
|
|
39
|
+
// Batch: resolve many flags in one round-trip.
|
|
40
|
+
const results = await client.variables(["flag-a", "flag-b"], { user });
|
|
41
|
+
const flagA = results["flag-a"].value;
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
### `new RedPennonClient({ apiKey, baseUrl?, fetchImpl? })`
|
|
47
|
+
|
|
48
|
+
| Option | Type | Description |
|
|
49
|
+
|--------|------|-------------|
|
|
50
|
+
| `apiKey` | `string` | Environment API key from the RedPennon dashboard. |
|
|
51
|
+
| `baseUrl` | `string?` | Override the API base URL (default: `https://api.redpennon.dev`). |
|
|
52
|
+
| `fetchImpl` | `typeof fetch?` | Custom fetch — useful for testing. |
|
|
53
|
+
|
|
54
|
+
### `client.variableValue(key, { default, user? }) → Promise<T>`
|
|
55
|
+
|
|
56
|
+
Returns the resolved value or `default` on any failure (network error, non-2xx, `value=null`). Use this in production code — your default is the load-bearing contract.
|
|
57
|
+
|
|
58
|
+
### `client.variable(key, { user? }) → Promise<VariableResult>`
|
|
59
|
+
|
|
60
|
+
Returns the full evaluation result (`value`, `variation`, `reason`, `feature`). Throws `APIError` on network or non-2xx errors.
|
|
61
|
+
|
|
62
|
+
### `client.variables(keys, { user? }) → Promise<Record<string, VariableResult>>`
|
|
63
|
+
|
|
64
|
+
Batch evaluation — one HTTP round-trip for multiple flags. Unknown keys return `reason: "variable_not_found"` inline rather than throwing.
|
|
65
|
+
|
|
66
|
+
## Local development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm ci
|
|
70
|
+
npm test
|
|
71
|
+
npm run build
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
[MIT](../LICENSE) © RedPennon
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedPennon Node SDK — variable-key evaluation client.
|
|
3
|
+
*
|
|
4
|
+
* Two evaluation methods cover the two SDK ergonomics teams reach
|
|
5
|
+
* for:
|
|
6
|
+
*
|
|
7
|
+
* * {@link RedPennonClient.variableValue} — "give me the value, or
|
|
8
|
+
* this fallback". Returns the resolved value or the
|
|
9
|
+
* developer-supplied default. Network failures swallow into the
|
|
10
|
+
* default so the calling app never crashes because the API is
|
|
11
|
+
* unreachable; the developer's code default is the contract.
|
|
12
|
+
*
|
|
13
|
+
* * {@link RedPennonClient.variable} — "give me the full result
|
|
14
|
+
* object". Returns the typed result (`value`, `variation`,
|
|
15
|
+
* `reason`, `feature`) so callers can branch on `reason` for
|
|
16
|
+
* telemetry, dashboards, or per-state UI. Network failures
|
|
17
|
+
* surface as {@link APIError}; callers opting into the result
|
|
18
|
+
* shape opt into error handling.
|
|
19
|
+
*
|
|
20
|
+
* Batch evaluation ({@link RedPennonClient.variables}) lets one HTTP
|
|
21
|
+
* round-trip resolve many flags for the same user context.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEFAULT_API_BASE_URL = "https://api.redpennon.dev";
|
|
24
|
+
export type UserContext = {
|
|
25
|
+
id?: string;
|
|
26
|
+
email?: string;
|
|
27
|
+
organisation_id?: string;
|
|
28
|
+
ip?: string;
|
|
29
|
+
audiences?: string[];
|
|
30
|
+
/**
|
|
31
|
+
* Application version string supplied by the calling app. The
|
|
32
|
+
* evaluator treats it as opaque text — operators like `is`,
|
|
33
|
+
* `contains`, and `starts_with` work; numeric/semver comparisons do
|
|
34
|
+
* not. Server-side SDKs can't auto-detect this, so populate it
|
|
35
|
+
* manually if you need to target on it.
|
|
36
|
+
*/
|
|
37
|
+
app_version?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Free-form platform identifier (e.g. `"ios"`, `"android"`,
|
|
40
|
+
* `"web"`). Server-side SDKs don't auto-populate this either.
|
|
41
|
+
*/
|
|
42
|
+
platform?: string;
|
|
43
|
+
/**
|
|
44
|
+
* ISO-3166 alpha-2 country code (e.g. `"AU"`, `"US"`).
|
|
45
|
+
*/
|
|
46
|
+
country?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Arbitrary user attributes for `custom_property` targeting
|
|
49
|
+
* conditions. Keys are looked up by the rule's `custom_key`; values
|
|
50
|
+
* may be strings, numbers, booleans, or arrays of strings.
|
|
51
|
+
*/
|
|
52
|
+
custom_data?: Record<string, unknown>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Reasons surfaced by the evaluation engine. Mirrors the server-side
|
|
56
|
+
* constants 1:1 so SDK consumers can switch on the string value.
|
|
57
|
+
*/
|
|
58
|
+
export type EvaluationReason = "targeting_rule_matched" | "default_variation" | "no_rule_matched" | "targeting_disabled" | "feature_complete" | "feature_deleted" | "feature_archived" | "self_targeting_override" | "variable_not_found";
|
|
59
|
+
export type VariableResult<T> = {
|
|
60
|
+
key: string;
|
|
61
|
+
/** Resolved value, or `null` when the platform served no value. */
|
|
62
|
+
value: T | null;
|
|
63
|
+
/** Variation slug served, or `null` when no value was served. */
|
|
64
|
+
variation: string | null;
|
|
65
|
+
reason: EvaluationReason;
|
|
66
|
+
/** Parent feature slug, or `null` when the key didn't resolve. */
|
|
67
|
+
feature: string | null;
|
|
68
|
+
/** Opaque signed trace token returned by the server when the user has stable identity (`id` or `email`). `null` or absent when not available. Pass to `trackEvents` to correlate metric events with evaluations. */
|
|
69
|
+
evaluation_trace?: string | null;
|
|
70
|
+
};
|
|
71
|
+
export type BatchResults = Record<string, VariableResult<unknown>>;
|
|
72
|
+
export type EventPayload = {
|
|
73
|
+
event: string;
|
|
74
|
+
variable: string;
|
|
75
|
+
variation: string;
|
|
76
|
+
user?: UserContext;
|
|
77
|
+
value?: number;
|
|
78
|
+
/** ISO-8601 timestamp; defaults to server receive time when omitted. */
|
|
79
|
+
occurred_at?: string;
|
|
80
|
+
/** Opaque trace token from a prior {@link VariableResult.evaluation_trace}. */
|
|
81
|
+
evaluation_trace?: string;
|
|
82
|
+
};
|
|
83
|
+
export type TrackEventsResult = {
|
|
84
|
+
accepted: number;
|
|
85
|
+
};
|
|
86
|
+
export declare class APIError extends Error {
|
|
87
|
+
readonly statusCode: number;
|
|
88
|
+
readonly body: string;
|
|
89
|
+
/**
|
|
90
|
+
* Platform governance error code parsed from the response body's
|
|
91
|
+
* structured `{"error", "code"}` payload (e.g. `rate_limit_exceeded`,
|
|
92
|
+
* `organisation_suspended`, `monthly_active_users_exceeded`).
|
|
93
|
+
* `null` for transport errors or unstructured responses. Callers
|
|
94
|
+
* branching on governance state should match on `code`, not `message`.
|
|
95
|
+
*/
|
|
96
|
+
readonly code: string | null;
|
|
97
|
+
constructor(statusCode: number, message: string, body: string, code?: string | null);
|
|
98
|
+
}
|
|
99
|
+
export type ClientOptions = {
|
|
100
|
+
apiKey: string;
|
|
101
|
+
/** Override the API origin. Defaults to {@link DEFAULT_API_BASE_URL}. */
|
|
102
|
+
baseUrl?: string;
|
|
103
|
+
/** Override the fetch implementation (handy for tests). */
|
|
104
|
+
fetchImpl?: typeof fetch;
|
|
105
|
+
};
|
|
106
|
+
export type EvalOptions = {
|
|
107
|
+
user?: UserContext;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Thin client around the variable-key evaluation endpoints.
|
|
111
|
+
* Stateless — one instance can serve any number of concurrent
|
|
112
|
+
* evaluations and is safe to keep as a module-level singleton.
|
|
113
|
+
*/
|
|
114
|
+
export declare class RedPennonClient {
|
|
115
|
+
readonly origin: string;
|
|
116
|
+
readonly apiKey: string;
|
|
117
|
+
readonly fetchImpl: typeof fetch;
|
|
118
|
+
constructor(options: ClientOptions);
|
|
119
|
+
/**
|
|
120
|
+
* Resolve a single variable to its full result object.
|
|
121
|
+
*
|
|
122
|
+
* Network errors and non-2xx responses surface as {@link APIError};
|
|
123
|
+
* callers that want fail-open semantics should use
|
|
124
|
+
* {@link variableValue} instead.
|
|
125
|
+
*/
|
|
126
|
+
variable<T = unknown>(key: string, options?: EvalOptions): Promise<VariableResult<T>>;
|
|
127
|
+
/**
|
|
128
|
+
* Resolve a single variable to its value, falling back to
|
|
129
|
+
* `defaultValue` whenever the platform served no value (network
|
|
130
|
+
* error, unknown key, deleted/archived feature, targeting
|
|
131
|
+
* disabled). This is the method that should be reached for in
|
|
132
|
+
* application code: the developer-supplied default is the
|
|
133
|
+
* load-bearing contract.
|
|
134
|
+
*/
|
|
135
|
+
variableValue<T>(key: string, defaultValue: T, options?: EvalOptions): Promise<T>;
|
|
136
|
+
/**
|
|
137
|
+
* Record one or more metric events. Pass the `evaluation_trace` from a
|
|
138
|
+
* prior {@link variable} call to correlate events with the evaluation
|
|
139
|
+
* that triggered them.
|
|
140
|
+
*
|
|
141
|
+
* Throws {@link APIError} on any non-2xx response, including governance
|
|
142
|
+
* errors (`events_batch_too_large`, `rate_limit_exceeded`, etc.).
|
|
143
|
+
*/
|
|
144
|
+
trackEvents(events: EventPayload[]): Promise<TrackEventsResult>;
|
|
145
|
+
/**
|
|
146
|
+
* Resolve multiple variables in one HTTP round-trip. Each result
|
|
147
|
+
* has the same shape as {@link variable}; unknown keys surface as
|
|
148
|
+
* `variable_not_found` inline.
|
|
149
|
+
*/
|
|
150
|
+
variables(keys: string[], options?: EvalOptions): Promise<BatchResults>;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedPennon Node SDK — variable-key evaluation client.
|
|
3
|
+
*
|
|
4
|
+
* Two evaluation methods cover the two SDK ergonomics teams reach
|
|
5
|
+
* for:
|
|
6
|
+
*
|
|
7
|
+
* * {@link RedPennonClient.variableValue} — "give me the value, or
|
|
8
|
+
* this fallback". Returns the resolved value or the
|
|
9
|
+
* developer-supplied default. Network failures swallow into the
|
|
10
|
+
* default so the calling app never crashes because the API is
|
|
11
|
+
* unreachable; the developer's code default is the contract.
|
|
12
|
+
*
|
|
13
|
+
* * {@link RedPennonClient.variable} — "give me the full result
|
|
14
|
+
* object". Returns the typed result (`value`, `variation`,
|
|
15
|
+
* `reason`, `feature`) so callers can branch on `reason` for
|
|
16
|
+
* telemetry, dashboards, or per-state UI. Network failures
|
|
17
|
+
* surface as {@link APIError}; callers opting into the result
|
|
18
|
+
* shape opt into error handling.
|
|
19
|
+
*
|
|
20
|
+
* Batch evaluation ({@link RedPennonClient.variables}) lets one HTTP
|
|
21
|
+
* round-trip resolve many flags for the same user context.
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_API_BASE_URL = "https://api.redpennon.dev";
|
|
24
|
+
export class APIError extends Error {
|
|
25
|
+
statusCode;
|
|
26
|
+
body;
|
|
27
|
+
/**
|
|
28
|
+
* Platform governance error code parsed from the response body's
|
|
29
|
+
* structured `{"error", "code"}` payload (e.g. `rate_limit_exceeded`,
|
|
30
|
+
* `organisation_suspended`, `monthly_active_users_exceeded`).
|
|
31
|
+
* `null` for transport errors or unstructured responses. Callers
|
|
32
|
+
* branching on governance state should match on `code`, not `message`.
|
|
33
|
+
*/
|
|
34
|
+
code;
|
|
35
|
+
constructor(statusCode, message, body, code = null) {
|
|
36
|
+
super(`redpennon: api error ${statusCode}: ${message}`);
|
|
37
|
+
this.name = "APIError";
|
|
38
|
+
this.statusCode = statusCode;
|
|
39
|
+
this.body = body;
|
|
40
|
+
this.code = code;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function parseErrorCode(body) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(body);
|
|
46
|
+
if (parsed && typeof parsed === "object" && typeof parsed.code === "string" && parsed.code) {
|
|
47
|
+
return parsed.code;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Body is not JSON; leave code unparsed.
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Thin client around the variable-key evaluation endpoints.
|
|
57
|
+
* Stateless — one instance can serve any number of concurrent
|
|
58
|
+
* evaluations and is safe to keep as a module-level singleton.
|
|
59
|
+
*/
|
|
60
|
+
export class RedPennonClient {
|
|
61
|
+
origin;
|
|
62
|
+
apiKey;
|
|
63
|
+
fetchImpl;
|
|
64
|
+
constructor(options) {
|
|
65
|
+
this.origin = (options.baseUrl ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
|
|
66
|
+
this.apiKey = options.apiKey;
|
|
67
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolve a single variable to its full result object.
|
|
71
|
+
*
|
|
72
|
+
* Network errors and non-2xx responses surface as {@link APIError};
|
|
73
|
+
* callers that want fail-open semantics should use
|
|
74
|
+
* {@link variableValue} instead.
|
|
75
|
+
*/
|
|
76
|
+
async variable(key, options = {}) {
|
|
77
|
+
const body = {};
|
|
78
|
+
if (options.user)
|
|
79
|
+
body.user = options.user;
|
|
80
|
+
const response = await this.fetchImpl(`${this.origin}/v1/variables/${encodeURIComponent(key)}`, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: {
|
|
83
|
+
"X-API-Key": this.apiKey,
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify(body),
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
const text = await response.text();
|
|
90
|
+
throw new APIError(response.status, response.statusText, text, parseErrorCode(text));
|
|
91
|
+
}
|
|
92
|
+
return (await response.json());
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve a single variable to its value, falling back to
|
|
96
|
+
* `defaultValue` whenever the platform served no value (network
|
|
97
|
+
* error, unknown key, deleted/archived feature, targeting
|
|
98
|
+
* disabled). This is the method that should be reached for in
|
|
99
|
+
* application code: the developer-supplied default is the
|
|
100
|
+
* load-bearing contract.
|
|
101
|
+
*/
|
|
102
|
+
async variableValue(key, defaultValue, options = {}) {
|
|
103
|
+
try {
|
|
104
|
+
const result = await this.variable(key, options);
|
|
105
|
+
return result.value === null ? defaultValue : result.value;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Swallow all errors — network, API, JSON parse — and serve the
|
|
109
|
+
// developer-supplied default. The SDK's job is to keep the
|
|
110
|
+
// calling app running, not to surface infra problems.
|
|
111
|
+
return defaultValue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Record one or more metric events. Pass the `evaluation_trace` from a
|
|
116
|
+
* prior {@link variable} call to correlate events with the evaluation
|
|
117
|
+
* that triggered them.
|
|
118
|
+
*
|
|
119
|
+
* Throws {@link APIError} on any non-2xx response, including governance
|
|
120
|
+
* errors (`events_batch_too_large`, `rate_limit_exceeded`, etc.).
|
|
121
|
+
*/
|
|
122
|
+
async trackEvents(events) {
|
|
123
|
+
const response = await this.fetchImpl(`${this.origin}/v1/events`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"X-API-Key": this.apiKey,
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify({ events }),
|
|
130
|
+
});
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
const text = await response.text();
|
|
133
|
+
throw new APIError(response.status, response.statusText, text, parseErrorCode(text));
|
|
134
|
+
}
|
|
135
|
+
return (await response.json());
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Resolve multiple variables in one HTTP round-trip. Each result
|
|
139
|
+
* has the same shape as {@link variable}; unknown keys surface as
|
|
140
|
+
* `variable_not_found` inline.
|
|
141
|
+
*/
|
|
142
|
+
async variables(keys, options = {}) {
|
|
143
|
+
const body = { keys };
|
|
144
|
+
if (options.user)
|
|
145
|
+
body.user = options.user;
|
|
146
|
+
const response = await this.fetchImpl(`${this.origin}/v1/variables`, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: {
|
|
149
|
+
"X-API-Key": this.apiKey,
|
|
150
|
+
"Content-Type": "application/json",
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify(body),
|
|
153
|
+
});
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
const text = await response.text();
|
|
156
|
+
throw new APIError(response.status, response.statusText, text, parseErrorCode(text));
|
|
157
|
+
}
|
|
158
|
+
const parsed = (await response.json());
|
|
159
|
+
return parsed.results;
|
|
160
|
+
}
|
|
161
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@redpennon/node-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "RedPennon feature flag evaluation API client for Node.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist", "README.md"],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/redpennon/sdks.git",
|
|
18
|
+
"directory": "node"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://docs.redpennon.dev",
|
|
21
|
+
"bugs": "https://github.com/redpennon/sdks/issues",
|
|
22
|
+
"keywords": ["feature-flags", "feature-toggles", "redpennon"],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc -p tsconfig.build.json",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"prepublishOnly": "npm test && npm run build"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.14.1",
|
|
34
|
+
"typescript": "^5.8.3",
|
|
35
|
+
"vitest": "^3.1.2"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT"
|
|
38
|
+
}
|