@fireweaveai/server-sdk-cjs 0.0.0-reserve.0 → 0.0.0-stage
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 +2 -54
- package/package.json +4 -56
- package/LICENSE +0 -21
- package/dist/application/client.d.ts +0 -77
- package/dist/application/client.js +0 -144
- package/dist/application/mode.d.ts +0 -70
- package/dist/application/mode.js +0 -74
- package/dist/application/ports.d.ts +0 -94
- package/dist/application/ports.js +0 -2
- package/dist/application/runtime.d.ts +0 -101
- package/dist/application/runtime.js +0 -346
- package/dist/domain/context.d.ts +0 -67
- package/dist/domain/context.js +0 -90
- package/dist/domain/errors.d.ts +0 -45
- package/dist/domain/errors.js +0 -99
- package/dist/domain/target.d.ts +0 -2
- package/dist/domain/target.js +0 -2
- package/dist/domain/types.d.ts +0 -107
- package/dist/domain/types.js +0 -2
- package/dist/domain/validation.d.ts +0 -124
- package/dist/domain/validation.js +0 -355
- package/dist/index.d.ts +0 -34
- package/dist/index.js +0 -52
- package/dist/infrastructure/adapters/inmemory.d.ts +0 -77
- package/dist/infrastructure/adapters/inmemory.js +0 -151
- package/dist/infrastructure/adapters/local.d.ts +0 -108
- package/dist/infrastructure/adapters/local.js +0 -105
- package/dist/infrastructure/adapters/remote.d.ts +0 -74
- package/dist/infrastructure/adapters/remote.js +0 -373
- package/dist/infrastructure/hosts.d.ts +0 -18
- package/dist/infrastructure/hosts.js +0 -72
- package/dist/register-browser.d.ts +0 -1
- package/dist/register-browser.js +0 -7
- package/dist/register.d.ts +0 -1
- package/dist/register.js +0 -13
- package/dist/start/browser.d.ts +0 -12
- package/dist/start/browser.js +0 -36
- package/dist/start/build-info.d.ts +0 -2
- package/dist/start/build-info.js +0 -9
- package/dist/start/control-points.d.ts +0 -20
- package/dist/start/control-points.js +0 -59
- package/dist/start/env.d.ts +0 -11
- package/dist/start/env.js +0 -64
- package/dist/start/fw.d.ts +0 -29
- package/dist/start/fw.js +0 -76
- package/dist/start/index.d.ts +0 -23
- package/dist/start/index.js +0 -28
- package/dist/start/instance.d.ts +0 -17
- package/dist/start/instance.js +0 -56
- package/dist/start/names.d.ts +0 -22
- package/dist/start/names.js +0 -25
- package/dist/start/resolve.d.ts +0 -40
- package/dist/start/resolve.js +0 -139
- package/dist/start/state.d.ts +0 -96
- package/dist/start/state.js +0 -303
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateControlPointKey = validateControlPointKey;
|
|
4
|
-
exports.matchesExpectedType = matchesExpectedType;
|
|
5
|
-
exports.validateDefaultValue = validateDefaultValue;
|
|
6
|
-
exports.validateTargetingKey = validateTargetingKey;
|
|
7
|
-
exports.validateContext = validateContext;
|
|
8
|
-
exports.canonicalizeContext = canonicalizeContext;
|
|
9
|
-
exports.validateInitOptions = validateInitOptions;
|
|
10
|
-
/**
|
|
11
|
-
* Fireweave SDK validation — pure, total functions per spec/control-points.md
|
|
12
|
-
* "Validation, before any I/O" and spec/modes.md "Initialisation validation".
|
|
13
|
-
*
|
|
14
|
-
* Every read-path validator here (`validateControlPointKey`,
|
|
15
|
-
* `validateDefaultValue`, `validateContext`, `validateTargetingKey`) returns
|
|
16
|
-
* `Validated<T>` instead of throwing. `FireweaveRuntime.evaluate` runs them,
|
|
17
|
-
* in the fixed order the spec names — key, default-vs-type, context,
|
|
18
|
-
* lifecycle — and degrades to the caller's default on the first failure; it
|
|
19
|
-
* NEVER throws (spec/control-points.md "Return discipline — never throw into
|
|
20
|
-
* a read path").
|
|
21
|
-
*
|
|
22
|
-
* `validateInitOptions` is the one named exception (spec/modes.md
|
|
23
|
-
* "Initialisation validation"): its failures are converted to a THROW by
|
|
24
|
-
* `initFireweave` (src/init.ts). The validator itself still returns
|
|
25
|
-
* `Validated<T>` — the entry point does the throwing, not this module.
|
|
26
|
-
*
|
|
27
|
-
* Collected into one module so it can move wholesale into `domain/` (plan
|
|
28
|
-
* Phase 1.4) without touching its exports or call sites again. Everything
|
|
29
|
-
* below is pure (no I/O, no ambient state) and total (every branch returns a
|
|
30
|
-
* `Validated<T>`) — `conformance/` can exercise all four read-path rules
|
|
31
|
-
* offline, with no backend.
|
|
32
|
-
*
|
|
33
|
-
* `validateInitOptions` is deliberately generic rather than importing
|
|
34
|
-
* `InitFireweaveOptions` from `init.ts`: init.ts is the "mode" entry point
|
|
35
|
-
* (Phase 1.4 assigns it to `application/`), and this module is destined for
|
|
36
|
-
* `domain/`, which must not depend on outer layers. It also does not
|
|
37
|
-
* validate "apiUrl fails the host allowlist" (spec/modes.md row 3) — that
|
|
38
|
-
* check (`assertHostAllowed`) lives in `hosts.ts`, which Phase 1.4 assigns to
|
|
39
|
-
* `infrastructure/`; it stays reachable today via `FireweaveRuntime.initialize`
|
|
40
|
-
* (runtime.ts) / `FireweaveRemoteAdapter.initialize` (adapters/remote.ts),
|
|
41
|
-
* unchanged by this module.
|
|
42
|
-
*/
|
|
43
|
-
const errors_js_1 = require("./errors.js");
|
|
44
|
-
const context_js_1 = require("./context.js");
|
|
45
|
-
const ok = (value) => ({ ok: true, value });
|
|
46
|
-
const fail = (error) => ({ ok: false, error });
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Rule 1 — validateControlPointKey (spec/control-points.md "Validation,
|
|
49
|
-
// before any I/O": "key — non-empty, ≤256 characters, no control characters")
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
/** C0 + C1 control characters (U+0000-U+001F, U+007F-U+009F). */
|
|
52
|
-
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/;
|
|
53
|
-
const MAX_CONTROL_POINT_KEY_LENGTH = 256;
|
|
54
|
-
/**
|
|
55
|
-
* key — non-empty, ≤256 characters, no control characters
|
|
56
|
-
* (spec/control-points.md rule 1, the first check in the fixed order).
|
|
57
|
-
*
|
|
58
|
-
* No taxonomy kind names "malformed key" explicitly (the return-discipline
|
|
59
|
-
* table's closest row is "key unknown to the backend" → ControlPointNotFound): a key
|
|
60
|
-
* that can never identify a flag is treated the same as one the backend
|
|
61
|
-
* doesn't recognise, so this maps to ControlPointNotFound too.
|
|
62
|
-
*
|
|
63
|
-
* Controller-ruled interim mapping (Task 3 review): the 15-kind taxonomy in
|
|
64
|
-
* errors.schema.json is frozen at exactly 15 entries, `InvalidContext` is
|
|
65
|
-
* textually scoped to the evaluation *context* (not the key), and the
|
|
66
|
-
* schema already maps another non-literal case — quota-limited responses —
|
|
67
|
-
* onto `ControlPointNotFound` rather than adding a kind for it. `ControlPointNotFound` is
|
|
68
|
-
* therefore the least-wrong existing kind, not a literal fit. Other-language
|
|
69
|
-
* implementations copying node as the reference SHOULD use the same mapping
|
|
70
|
-
* rather than each independently guessing a different one.
|
|
71
|
-
*/
|
|
72
|
-
function validateControlPointKey(key) {
|
|
73
|
-
if (typeof key !== 'string' || key.length === 0) {
|
|
74
|
-
return fail(new errors_js_1.FireweaveError('ControlPointNotFound', { message: 'control point key must be a non-empty string' }));
|
|
75
|
-
}
|
|
76
|
-
if (key.length > MAX_CONTROL_POINT_KEY_LENGTH) {
|
|
77
|
-
return fail(new errors_js_1.FireweaveError('ControlPointNotFound', { message: 'control point key exceeds maximum length' }));
|
|
78
|
-
}
|
|
79
|
-
if (CONTROL_CHARACTERS.test(key)) {
|
|
80
|
-
return fail(new errors_js_1.FireweaveError('ControlPointNotFound', { message: 'control point key contains control characters' }));
|
|
81
|
-
}
|
|
82
|
-
return ok(key);
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Whether `value` matches the shape `expected` names. Shared by
|
|
86
|
-
* {@link validateDefaultValue} (the caller's default, before any I/O) and
|
|
87
|
-
* `FireweaveRuntime.evaluate`'s post-resolve check (the backend's resolved
|
|
88
|
-
* value, after I/O) — same predicate, two different inputs.
|
|
89
|
-
*/
|
|
90
|
-
function matchesExpectedType(value, expected) {
|
|
91
|
-
switch (expected) {
|
|
92
|
-
case 'boolean':
|
|
93
|
-
return typeof value === 'boolean';
|
|
94
|
-
case 'string':
|
|
95
|
-
return typeof value === 'string';
|
|
96
|
-
case 'number':
|
|
97
|
-
return typeof value === 'number';
|
|
98
|
-
case 'object':
|
|
99
|
-
return value !== null && typeof value === 'object';
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* default vs type — e.g. `getBooleanValue` with a non-boolean default is
|
|
104
|
-
* TypeMismatch (spec/control-points.md rule 2, checked before any I/O).
|
|
105
|
-
*/
|
|
106
|
-
function validateDefaultValue(expectedType, defaultValue) {
|
|
107
|
-
if (!matchesExpectedType(defaultValue, expectedType)) {
|
|
108
|
-
return fail(new errors_js_1.FireweaveError('TypeMismatch'));
|
|
109
|
-
}
|
|
110
|
-
return ok(defaultValue);
|
|
111
|
-
}
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
// validateTargetingKey (spec/control-points.md "Context": targetingKey)
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
/**
|
|
116
|
-
* targetingKey: "An SDK MUST NOT invent one: a missing targeting key is
|
|
117
|
-
* InvalidContext where the evaluation needs it, never a generated anonymous
|
|
118
|
-
* id" (spec/control-points.md "Context"). `required` is call-site policy —
|
|
119
|
-
* the remote adapter always requires one for `resolve`/`registerTarget`
|
|
120
|
-
* (spec/remote-protocol.md "Two identity paths"); the generic context
|
|
121
|
-
* pipeline ({@link validateContext}) only does when
|
|
122
|
-
* `ContextPolicy.requireTargetingKey` opts in.
|
|
123
|
-
*/
|
|
124
|
-
function validateTargetingKey(targetingKey, required) {
|
|
125
|
-
if (required && (targetingKey === undefined || targetingKey === '')) {
|
|
126
|
-
return fail(new errors_js_1.FireweaveError('InvalidContext', {
|
|
127
|
-
message: 'targeting key missing',
|
|
128
|
-
openFeatureErrorCode: 'TARGETING_KEY_MISSING',
|
|
129
|
-
}));
|
|
130
|
-
}
|
|
131
|
-
return ok(targetingKey);
|
|
132
|
-
}
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
// Rule 3 — validateContext (spec/control-points.md rule 3: "context — depth,
|
|
135
|
-
// key count, value size, reserved keys (evaluation-context.schema.json)")
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
137
|
-
/**
|
|
138
|
-
* UTF-8 byte length. `TextEncoder` rather than `Buffer.byteLength`: `Buffer`
|
|
139
|
-
* is a Node global that Deno only exposes under npm compatibility, and this
|
|
140
|
-
* bound is enforced on every evaluation (ADR-0008 runtime portability).
|
|
141
|
-
*/
|
|
142
|
-
const utf8Encoder = new TextEncoder();
|
|
143
|
-
const byteLength = (s) => utf8Encoder.encode(s).length;
|
|
144
|
-
/**
|
|
145
|
-
* Sentinel returned (never thrown) when `deepCopyJson` walks back into an
|
|
146
|
-
* object already on the current recursion path — an actual reference cycle,
|
|
147
|
-
* not merely the same object shared by two sibling branches (which is legal
|
|
148
|
-
* and handled by backtracking `seen.delete(...)` below). Untyped caller
|
|
149
|
-
* input is `unknown`, so a circular attribute value is reachable from a
|
|
150
|
-
* caller today; without this, recursion never terminates and crashes the
|
|
151
|
-
* whole process with `RangeError: Maximum call stack size exceeded` —
|
|
152
|
-
* exactly the kind of throw a "before any I/O" validator must not allow
|
|
153
|
-
* (spec/control-points.md "Return discipline — never throw into a read
|
|
154
|
-
* path"). Module-local: never returned across this file's public surface,
|
|
155
|
-
* only used to fail `validateContext` closed as `InvalidContext`.
|
|
156
|
-
*/
|
|
157
|
-
const CYCLIC = Symbol('fireweave.cyclicJson');
|
|
158
|
-
function deepCopyJson(value, seen = new WeakSet()) {
|
|
159
|
-
// Structured clone of JSON-compatible data; drops functions/undefined like JSON round-trip.
|
|
160
|
-
if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number') {
|
|
161
|
-
return value;
|
|
162
|
-
}
|
|
163
|
-
if (Array.isArray(value)) {
|
|
164
|
-
if (seen.has(value))
|
|
165
|
-
return CYCLIC;
|
|
166
|
-
seen.add(value);
|
|
167
|
-
const out = [];
|
|
168
|
-
for (const v of value) {
|
|
169
|
-
const copied = deepCopyJson(v, seen);
|
|
170
|
-
if (copied === CYCLIC)
|
|
171
|
-
return CYCLIC;
|
|
172
|
-
out.push(copied);
|
|
173
|
-
}
|
|
174
|
-
seen.delete(value);
|
|
175
|
-
return out;
|
|
176
|
-
}
|
|
177
|
-
if (typeof value === 'object') {
|
|
178
|
-
if (seen.has(value))
|
|
179
|
-
return CYCLIC;
|
|
180
|
-
seen.add(value);
|
|
181
|
-
const out = {};
|
|
182
|
-
for (const [k, v] of Object.entries(value)) {
|
|
183
|
-
if (v !== undefined) {
|
|
184
|
-
const copied = deepCopyJson(v, seen);
|
|
185
|
-
if (copied === CYCLIC)
|
|
186
|
-
return CYCLIC;
|
|
187
|
-
out[k] = copied;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
seen.delete(value);
|
|
191
|
-
return out;
|
|
192
|
-
}
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Cycle-safe defensively: `nestingDepth` only ever runs on `deepCopyJson`'s
|
|
197
|
-
* own output today, which is guaranteed acyclic by construction (it builds
|
|
198
|
-
* fresh containers, never back-references). Guarded anyway per the same
|
|
199
|
-
* "must be genuinely total" reasoning as `deepCopyJson`, so a future caller
|
|
200
|
-
* (e.g. a Task 4 reuse) can't reintroduce the same crash by feeding this
|
|
201
|
-
* function un-copied input. A detected cycle returns `Infinity`, which
|
|
202
|
-
* always exceeds `limits.maxNestingDepth` and fails closed through the
|
|
203
|
-
* existing bound check in {@link validateContext} — no new failure path.
|
|
204
|
-
*/
|
|
205
|
-
function nestingDepth(value, seen = new WeakSet()) {
|
|
206
|
-
if (value === null || typeof value !== 'object')
|
|
207
|
-
return 0;
|
|
208
|
-
if (seen.has(value))
|
|
209
|
-
return Infinity;
|
|
210
|
-
seen.add(value);
|
|
211
|
-
let max = 0;
|
|
212
|
-
const children = Array.isArray(value) ? value : Object.values(value);
|
|
213
|
-
for (const child of children) {
|
|
214
|
-
const d = nestingDepth(child, seen);
|
|
215
|
-
if (d > max)
|
|
216
|
-
max = d;
|
|
217
|
-
}
|
|
218
|
-
seen.delete(value);
|
|
219
|
-
return max + 1;
|
|
220
|
-
}
|
|
221
|
-
const invalidContext = (message) => new errors_js_1.FireweaveError('InvalidContext', { message });
|
|
222
|
-
/**
|
|
223
|
-
* context — depth, key count, value size, reserved keys
|
|
224
|
-
* (evaluation-context.schema.json) (spec/control-points.md rule 3). Also
|
|
225
|
-
* enforces `policy.requireTargetingKey` via {@link validateTargetingKey}, and
|
|
226
|
-
* — on success — extracts `groups`/`groupProperties` the same way the former
|
|
227
|
-
* `canonicalizeContext` did.
|
|
228
|
-
*
|
|
229
|
-
* `merged` is the already-merged (global → client → invocation) raw input;
|
|
230
|
-
* merging itself (`mergeContexts`) lives in context.ts and carries no
|
|
231
|
-
* validation concerns of its own.
|
|
232
|
-
*/
|
|
233
|
-
function validateContext(merged, policy) {
|
|
234
|
-
const { limits } = policy;
|
|
235
|
-
const copied = deepCopyJson(merged.attributes);
|
|
236
|
-
if (copied === CYCLIC) {
|
|
237
|
-
return fail(invalidContext('context contains a circular reference'));
|
|
238
|
-
}
|
|
239
|
-
const attributes = copied;
|
|
240
|
-
const keys = Object.keys(attributes);
|
|
241
|
-
if (keys.length > limits.maxAttributeCount) {
|
|
242
|
-
return fail(invalidContext('context exceeds maximum attribute count'));
|
|
243
|
-
}
|
|
244
|
-
for (const key of keys) {
|
|
245
|
-
if (byteLength(key) > limits.maxKeyBytes) {
|
|
246
|
-
return fail(invalidContext('context key exceeds maximum size'));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
for (const key of keys) {
|
|
250
|
-
const value = attributes[key];
|
|
251
|
-
if (byteLength(JSON.stringify(value ?? null)) > limits.maxValueBytes) {
|
|
252
|
-
return fail(invalidContext('context value exceeds maximum size'));
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
for (const key of keys) {
|
|
256
|
-
const value = attributes[key];
|
|
257
|
-
if (value !== undefined && nestingDepth(value) > limits.maxNestingDepth) {
|
|
258
|
-
return fail(invalidContext('context exceeds maximum nesting depth'));
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
const serialized = JSON.stringify({ targetingKey: merged.targetingKey, attributes });
|
|
262
|
-
if (byteLength(serialized) > limits.maxSerializedContextBytes) {
|
|
263
|
-
return fail(invalidContext('serialized context exceeds maximum size'));
|
|
264
|
-
}
|
|
265
|
-
for (const key of keys) {
|
|
266
|
-
if (policy.reservedAttributeKeys.includes(key)) {
|
|
267
|
-
return fail(invalidContext('invalid evaluation context'));
|
|
268
|
-
}
|
|
269
|
-
// Ruling 13: exactly fireweave.groups + fireweave.groupProperties are
|
|
270
|
-
// permitted in the reserved namespace; every other fireweave.* key is rejected.
|
|
271
|
-
if (key.startsWith('fireweave.') && !context_js_1.ALLOWED_FIREWEAVE_CONTEXT_KEYS.includes(key)) {
|
|
272
|
-
return fail(invalidContext('invalid evaluation context'));
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
const targetingKeyResult = validateTargetingKey(merged.targetingKey, policy.requireTargetingKey);
|
|
276
|
-
if (!targetingKeyResult.ok)
|
|
277
|
-
return targetingKeyResult;
|
|
278
|
-
const canonical = { attributes };
|
|
279
|
-
if (merged.targetingKey !== undefined)
|
|
280
|
-
canonical.targetingKey = merged.targetingKey;
|
|
281
|
-
// Group memberships: canonical carrier `fireweave.groups` (rulings 13–14),
|
|
282
|
-
// with plain `groups` retained as a documented alias.
|
|
283
|
-
const groups = attributes['fireweave.groups'] ?? attributes['groups'];
|
|
284
|
-
if (groups !== null && groups !== undefined && typeof groups === 'object' && !Array.isArray(groups)) {
|
|
285
|
-
const g = {};
|
|
286
|
-
let allStrings = true;
|
|
287
|
-
for (const [k, v] of Object.entries(groups)) {
|
|
288
|
-
if (typeof v === 'string')
|
|
289
|
-
g[k] = v;
|
|
290
|
-
else
|
|
291
|
-
allStrings = false;
|
|
292
|
-
}
|
|
293
|
-
if (allStrings)
|
|
294
|
-
canonical.groups = g;
|
|
295
|
-
}
|
|
296
|
-
// Group properties: canonical carrier `fireweave.groupProperties`,
|
|
297
|
-
// plain `groupProperties` retained as alias.
|
|
298
|
-
const groupProps = attributes['fireweave.groupProperties'] ?? attributes['groupProperties'];
|
|
299
|
-
if (groupProps !== null &&
|
|
300
|
-
groupProps !== undefined &&
|
|
301
|
-
typeof groupProps === 'object' &&
|
|
302
|
-
!Array.isArray(groupProps)) {
|
|
303
|
-
canonical.groupProperties = groupProps;
|
|
304
|
-
}
|
|
305
|
-
return ok(canonical);
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Throwing wrapper around {@link validateContext} kept for the pre-existing
|
|
309
|
-
* public surface (`FireweaveRuntime.resolveContext`, direct callers of the
|
|
310
|
-
* package export). Read paths do not use this — they call
|
|
311
|
-
* {@link validateContext} directly so a failure degrades instead of throwing.
|
|
312
|
-
*/
|
|
313
|
-
function canonicalizeContext(merged, policy) {
|
|
314
|
-
const result = validateContext(merged, policy);
|
|
315
|
-
if (!result.ok)
|
|
316
|
-
throw result.error;
|
|
317
|
-
return result.value;
|
|
318
|
-
}
|
|
319
|
-
// ---------------------------------------------------------------------------
|
|
320
|
-
// validateInitOptions (spec/modes.md "Initialisation validation")
|
|
321
|
-
// ---------------------------------------------------------------------------
|
|
322
|
-
/** "missing" and "blank" collapse to one check: not a non-empty string. */
|
|
323
|
-
const isBlank = (value) => typeof value !== 'string' || value.trim().length === 0;
|
|
324
|
-
const configError = (message) => new errors_js_1.FireweaveError('Configuration', { message });
|
|
325
|
-
/**
|
|
326
|
-
* Initialisation-validation table (spec/modes.md), rows 1, 2 and 4:
|
|
327
|
-
* - `mode` absent or unrecognised
|
|
328
|
-
* - `mode: 'remote'` with `apiKey` or `apiUrl` missing/blank
|
|
329
|
-
* - `mode: 'local'` with credentials supplied (a config half-migrated from
|
|
330
|
-
* remote to local reads as neither, silently — reject it instead)
|
|
331
|
-
*
|
|
332
|
-
* Row 3 ("apiUrl fails the host allowlist") is intentionally NOT checked
|
|
333
|
-
* here — see the module doc comment.
|
|
334
|
-
*/
|
|
335
|
-
function validateInitOptions(options) {
|
|
336
|
-
const mode = options?.mode;
|
|
337
|
-
if (mode !== 'local' && mode !== 'remote') {
|
|
338
|
-
return fail(configError('mode is required and must be "local" or "remote"'));
|
|
339
|
-
}
|
|
340
|
-
// Runtime-only guard: a config object half-migrated from remote to local
|
|
341
|
-
// can carry apiKey/apiUrl even though the local-mode type declares
|
|
342
|
-
// neither — TypeScript's excess-property check only fires on a fresh
|
|
343
|
-
// object literal, not on a variable assembled elsewhere and passed in.
|
|
344
|
-
const credentials = options;
|
|
345
|
-
if (mode === 'remote') {
|
|
346
|
-
if (isBlank(credentials.apiKey) || isBlank(credentials.apiUrl)) {
|
|
347
|
-
return fail(configError('mode "remote" requires apiKey and apiUrl'));
|
|
348
|
-
}
|
|
349
|
-
return ok(options);
|
|
350
|
-
}
|
|
351
|
-
if (!isBlank(credentials.apiKey) || !isBlank(credentials.apiUrl)) {
|
|
352
|
-
return fail(configError('mode "local" must not be combined with apiKey/apiUrl — the caller means one or the other'));
|
|
353
|
-
}
|
|
354
|
-
return ok(options);
|
|
355
|
-
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fireweaveai/server-sdk — Fireweave release-engineering SDK for server runtimes
|
|
3
|
-
* (Node, Bun, Deno).
|
|
4
|
-
*
|
|
5
|
-
* Exactly two v1 capabilities (spec/control-points.md): control points and
|
|
6
|
-
* target registration.
|
|
7
|
-
*
|
|
8
|
-
* Production default: {@link FireweaveRemoteAdapter} (Fireweave project key →
|
|
9
|
-
* fw-server). {@link InMemoryAdapter} for offline work and tests. The backend
|
|
10
|
-
* fw-server forwards to is fw-server's concern — no vendor SDK, key, or host
|
|
11
|
-
* appears in the application process (ADR-0005, ADR-0006).
|
|
12
|
-
*/
|
|
13
|
-
export { FireweaveError, ERROR_TAXONOMY, redactSecrets, isFireweaveError } from './domain/errors.js';
|
|
14
|
-
export type { FireweaveErrorKind, OpenFeatureErrorCode, ErrorKindSpec } from './domain/errors.js';
|
|
15
|
-
export type { JsonValue, Decision, DecisionReason, CanonicalContext, LifecycleState, Signal, SignalKind, ReleaseContext, ReleaseState, ReleaseStatus, Exposure, Capabilities, FlagValueType, } from './domain/types.js';
|
|
16
|
-
export { DEFAULT_CONTEXT_LIMITS, DEFAULT_RESERVED_ATTRIBUTE_KEYS, ALLOWED_FIREWEAVE_CONTEXT_KEYS, mergeContexts, normalizeContextInput, resolvedContextView, } from './domain/context.js';
|
|
17
|
-
export type { ContextInput, ContextLimits, ContextPolicy } from './domain/context.js';
|
|
18
|
-
export { canonicalizeContext, validateControlPointKey, validateDefaultValue, validateContext, validateTargetingKey, validateInitOptions, } from './domain/validation.js';
|
|
19
|
-
export type { Validated } from './domain/validation.js';
|
|
20
|
-
export type { BackendAdapter, AdapterResolution, AdapterRuntimeFeatures, RegisterTargetOptions, RegisterTargetResult, ResolveOptions, } from './application/ports.js';
|
|
21
|
-
export type { TargetKind } from './domain/target.js';
|
|
22
|
-
export { InMemoryAdapter } from './infrastructure/adapters/inmemory.js';
|
|
23
|
-
export type { InMemoryAdapterOptions, InMemoryFlagDefinition, InMemoryFault } from './infrastructure/adapters/inmemory.js';
|
|
24
|
-
export { FireweaveRemoteAdapter } from './infrastructure/adapters/remote.js';
|
|
25
|
-
export type { FireweaveRemoteAdapterOptions } from './infrastructure/adapters/remote.js';
|
|
26
|
-
export { FireweaveLocalAdapter } from './infrastructure/adapters/local.js';
|
|
27
|
-
export type { FireweaveLocalAdapterOptions } from './infrastructure/adapters/local.js';
|
|
28
|
-
export { DEFAULT_ALLOWED_HOSTS, assertHostAllowed, isLoopbackHostname } from './infrastructure/hosts.js';
|
|
29
|
-
export { FireweaveRuntime, stableStringify, DEFAULT_SHUTDOWN_TIMEOUT_MS } from './application/runtime.js';
|
|
30
|
-
export type { FireweaveRuntimeConfig, EvaluateOptions, ExpectedFlagType } from './application/runtime.js';
|
|
31
|
-
export { FireweaveClient } from './application/client.js';
|
|
32
|
-
export type { ControlPointsApi, FireweaveClientOptions } from './application/client.js';
|
|
33
|
-
export { initFireweave } from './application/mode.js';
|
|
34
|
-
export type { InitFireweaveOptions, InitFireweaveLocalOptions, InitFireweaveRemoteOptions, } from './application/mode.js';
|
package/dist/index.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.initFireweave = exports.FireweaveClient = exports.DEFAULT_SHUTDOWN_TIMEOUT_MS = exports.stableStringify = exports.FireweaveRuntime = exports.isLoopbackHostname = exports.assertHostAllowed = exports.DEFAULT_ALLOWED_HOSTS = exports.FireweaveLocalAdapter = exports.FireweaveRemoteAdapter = exports.InMemoryAdapter = exports.validateInitOptions = exports.validateTargetingKey = exports.validateContext = exports.validateDefaultValue = exports.validateControlPointKey = exports.canonicalizeContext = exports.resolvedContextView = exports.normalizeContextInput = exports.mergeContexts = exports.ALLOWED_FIREWEAVE_CONTEXT_KEYS = exports.DEFAULT_RESERVED_ATTRIBUTE_KEYS = exports.DEFAULT_CONTEXT_LIMITS = exports.isFireweaveError = exports.redactSecrets = exports.ERROR_TAXONOMY = exports.FireweaveError = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* @fireweaveai/server-sdk — Fireweave release-engineering SDK for server runtimes
|
|
6
|
-
* (Node, Bun, Deno).
|
|
7
|
-
*
|
|
8
|
-
* Exactly two v1 capabilities (spec/control-points.md): control points and
|
|
9
|
-
* target registration.
|
|
10
|
-
*
|
|
11
|
-
* Production default: {@link FireweaveRemoteAdapter} (Fireweave project key →
|
|
12
|
-
* fw-server). {@link InMemoryAdapter} for offline work and tests. The backend
|
|
13
|
-
* fw-server forwards to is fw-server's concern — no vendor SDK, key, or host
|
|
14
|
-
* appears in the application process (ADR-0005, ADR-0006).
|
|
15
|
-
*/
|
|
16
|
-
var errors_js_1 = require("./domain/errors.js");
|
|
17
|
-
Object.defineProperty(exports, "FireweaveError", { enumerable: true, get: function () { return errors_js_1.FireweaveError; } });
|
|
18
|
-
Object.defineProperty(exports, "ERROR_TAXONOMY", { enumerable: true, get: function () { return errors_js_1.ERROR_TAXONOMY; } });
|
|
19
|
-
Object.defineProperty(exports, "redactSecrets", { enumerable: true, get: function () { return errors_js_1.redactSecrets; } });
|
|
20
|
-
Object.defineProperty(exports, "isFireweaveError", { enumerable: true, get: function () { return errors_js_1.isFireweaveError; } });
|
|
21
|
-
var context_js_1 = require("./domain/context.js");
|
|
22
|
-
Object.defineProperty(exports, "DEFAULT_CONTEXT_LIMITS", { enumerable: true, get: function () { return context_js_1.DEFAULT_CONTEXT_LIMITS; } });
|
|
23
|
-
Object.defineProperty(exports, "DEFAULT_RESERVED_ATTRIBUTE_KEYS", { enumerable: true, get: function () { return context_js_1.DEFAULT_RESERVED_ATTRIBUTE_KEYS; } });
|
|
24
|
-
Object.defineProperty(exports, "ALLOWED_FIREWEAVE_CONTEXT_KEYS", { enumerable: true, get: function () { return context_js_1.ALLOWED_FIREWEAVE_CONTEXT_KEYS; } });
|
|
25
|
-
Object.defineProperty(exports, "mergeContexts", { enumerable: true, get: function () { return context_js_1.mergeContexts; } });
|
|
26
|
-
Object.defineProperty(exports, "normalizeContextInput", { enumerable: true, get: function () { return context_js_1.normalizeContextInput; } });
|
|
27
|
-
Object.defineProperty(exports, "resolvedContextView", { enumerable: true, get: function () { return context_js_1.resolvedContextView; } });
|
|
28
|
-
var validation_js_1 = require("./domain/validation.js");
|
|
29
|
-
Object.defineProperty(exports, "canonicalizeContext", { enumerable: true, get: function () { return validation_js_1.canonicalizeContext; } });
|
|
30
|
-
Object.defineProperty(exports, "validateControlPointKey", { enumerable: true, get: function () { return validation_js_1.validateControlPointKey; } });
|
|
31
|
-
Object.defineProperty(exports, "validateDefaultValue", { enumerable: true, get: function () { return validation_js_1.validateDefaultValue; } });
|
|
32
|
-
Object.defineProperty(exports, "validateContext", { enumerable: true, get: function () { return validation_js_1.validateContext; } });
|
|
33
|
-
Object.defineProperty(exports, "validateTargetingKey", { enumerable: true, get: function () { return validation_js_1.validateTargetingKey; } });
|
|
34
|
-
Object.defineProperty(exports, "validateInitOptions", { enumerable: true, get: function () { return validation_js_1.validateInitOptions; } });
|
|
35
|
-
var inmemory_js_1 = require("./infrastructure/adapters/inmemory.js");
|
|
36
|
-
Object.defineProperty(exports, "InMemoryAdapter", { enumerable: true, get: function () { return inmemory_js_1.InMemoryAdapter; } });
|
|
37
|
-
var remote_js_1 = require("./infrastructure/adapters/remote.js");
|
|
38
|
-
Object.defineProperty(exports, "FireweaveRemoteAdapter", { enumerable: true, get: function () { return remote_js_1.FireweaveRemoteAdapter; } });
|
|
39
|
-
var local_js_1 = require("./infrastructure/adapters/local.js");
|
|
40
|
-
Object.defineProperty(exports, "FireweaveLocalAdapter", { enumerable: true, get: function () { return local_js_1.FireweaveLocalAdapter; } });
|
|
41
|
-
var hosts_js_1 = require("./infrastructure/hosts.js");
|
|
42
|
-
Object.defineProperty(exports, "DEFAULT_ALLOWED_HOSTS", { enumerable: true, get: function () { return hosts_js_1.DEFAULT_ALLOWED_HOSTS; } });
|
|
43
|
-
Object.defineProperty(exports, "assertHostAllowed", { enumerable: true, get: function () { return hosts_js_1.assertHostAllowed; } });
|
|
44
|
-
Object.defineProperty(exports, "isLoopbackHostname", { enumerable: true, get: function () { return hosts_js_1.isLoopbackHostname; } });
|
|
45
|
-
var runtime_js_1 = require("./application/runtime.js");
|
|
46
|
-
Object.defineProperty(exports, "FireweaveRuntime", { enumerable: true, get: function () { return runtime_js_1.FireweaveRuntime; } });
|
|
47
|
-
Object.defineProperty(exports, "stableStringify", { enumerable: true, get: function () { return runtime_js_1.stableStringify; } });
|
|
48
|
-
Object.defineProperty(exports, "DEFAULT_SHUTDOWN_TIMEOUT_MS", { enumerable: true, get: function () { return runtime_js_1.DEFAULT_SHUTDOWN_TIMEOUT_MS; } });
|
|
49
|
-
var client_js_1 = require("./application/client.js");
|
|
50
|
-
Object.defineProperty(exports, "FireweaveClient", { enumerable: true, get: function () { return client_js_1.FireweaveClient; } });
|
|
51
|
-
var mode_js_1 = require("./application/mode.js");
|
|
52
|
-
Object.defineProperty(exports, "initFireweave", { enumerable: true, get: function () { return mode_js_1.initFireweave; } });
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic in-memory adapter for tests and conformance fixtures.
|
|
3
|
-
* Resolution model mirrors contracts/ fixture flag definitions: flags, variants,
|
|
4
|
-
* payloads, targeting by targeting key / person attributes / groups.
|
|
5
|
-
* Deliberately NO bucketing / percentage logic.
|
|
6
|
-
*/
|
|
7
|
-
import { type FireweaveErrorKind } from '../../domain/errors.js';
|
|
8
|
-
import type { AdapterResolution, AdapterRuntimeFeatures, BackendAdapter, ResolveOptions } from '../../application/ports.js';
|
|
9
|
-
import type { CanonicalContext, DecisionReason, Exposure, JsonValue, Signal } from '../../domain/types.js';
|
|
10
|
-
export interface InMemoryFlagDefinition {
|
|
11
|
-
type: 'boolean' | 'string' | 'integer' | 'float' | 'object';
|
|
12
|
-
enabled: boolean;
|
|
13
|
-
value: JsonValue;
|
|
14
|
-
variant?: string;
|
|
15
|
-
variants?: string[];
|
|
16
|
-
payload?: JsonValue;
|
|
17
|
-
reason?: {
|
|
18
|
-
code?: string;
|
|
19
|
-
condition_index?: number;
|
|
20
|
-
description?: string;
|
|
21
|
-
};
|
|
22
|
-
metadata?: {
|
|
23
|
-
version?: number;
|
|
24
|
-
id?: number;
|
|
25
|
-
};
|
|
26
|
-
/** Canonical reason override (fixtures: "SPLIT"). */
|
|
27
|
-
fireweaveReason?: DecisionReason;
|
|
28
|
-
/** Served from last-good cache (stale scenarios). */
|
|
29
|
-
fromCache?: boolean;
|
|
30
|
-
/** Match only when all listed attributes deep-equal context attributes. */
|
|
31
|
-
matchAttribute?: Record<string, JsonValue>;
|
|
32
|
-
/** Match only when context groups contain these entries. */
|
|
33
|
-
matchGroups?: Record<string, string>;
|
|
34
|
-
/** Match only when person attributes contain these entries. */
|
|
35
|
-
matchPerson?: Record<string, JsonValue>;
|
|
36
|
-
/** Match only this exact targetingKey. */
|
|
37
|
-
matchTargetingKey?: string;
|
|
38
|
-
}
|
|
39
|
-
export interface InMemoryFault {
|
|
40
|
-
kind: FireweaveErrorKind;
|
|
41
|
-
metadata?: Record<string, string | number | boolean>;
|
|
42
|
-
}
|
|
43
|
-
export interface InMemoryAdapterOptions {
|
|
44
|
-
flags?: Record<string, InMemoryFlagDefinition>;
|
|
45
|
-
/** Fault to throw on every resolve (fault-mode conformance without HTTP). */
|
|
46
|
-
fault?: InMemoryFault;
|
|
47
|
-
/** Reject initialize() with this error kind. */
|
|
48
|
-
initError?: FireweaveErrorKind;
|
|
49
|
-
/** Delay initialize() until release() is called (cold-start tests). */
|
|
50
|
-
initGate?: {
|
|
51
|
-
promise: Promise<void>;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
export declare class InMemoryAdapter implements BackendAdapter {
|
|
55
|
-
readonly name: "inmemory";
|
|
56
|
-
private flags;
|
|
57
|
-
private fault;
|
|
58
|
-
private readonly initError;
|
|
59
|
-
private readonly initGate;
|
|
60
|
-
private readonly exposures;
|
|
61
|
-
private readonly signals;
|
|
62
|
-
private closed;
|
|
63
|
-
constructor(options?: InMemoryAdapterOptions);
|
|
64
|
-
setFlags(flags: Record<string, InMemoryFlagDefinition>): void;
|
|
65
|
-
setFault(fault: InMemoryFault | undefined): void;
|
|
66
|
-
initialize(signal?: AbortSignal): Promise<void>;
|
|
67
|
-
resolve(controlPointKey: string, context: CanonicalContext, _options?: ResolveOptions): Promise<AdapterResolution>;
|
|
68
|
-
private matches;
|
|
69
|
-
recordExposure(exposure: Exposure): void;
|
|
70
|
-
getExposures(): readonly Exposure[];
|
|
71
|
-
recordSignal(signal: Signal): void;
|
|
72
|
-
getSignals(): readonly Signal[];
|
|
73
|
-
flush(): Promise<void>;
|
|
74
|
-
shutdown(): Promise<void>;
|
|
75
|
-
isClosed(): boolean;
|
|
76
|
-
features(): AdapterRuntimeFeatures;
|
|
77
|
-
}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InMemoryAdapter = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Deterministic in-memory adapter for tests and conformance fixtures.
|
|
6
|
-
* Resolution model mirrors contracts/ fixture flag definitions: flags, variants,
|
|
7
|
-
* payloads, targeting by targeting key / person attributes / groups.
|
|
8
|
-
* Deliberately NO bucketing / percentage logic.
|
|
9
|
-
*/
|
|
10
|
-
const errors_js_1 = require("../../domain/errors.js");
|
|
11
|
-
function deepEqual(a, b) {
|
|
12
|
-
if (a === b)
|
|
13
|
-
return true;
|
|
14
|
-
if (a === undefined || b === undefined || a === null || b === null)
|
|
15
|
-
return false;
|
|
16
|
-
if (typeof a !== typeof b)
|
|
17
|
-
return false;
|
|
18
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
19
|
-
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
20
|
-
}
|
|
21
|
-
if (typeof a === 'object' && typeof b === 'object' && !Array.isArray(a) && !Array.isArray(b)) {
|
|
22
|
-
const ak = Object.keys(a);
|
|
23
|
-
const bk = Object.keys(b);
|
|
24
|
-
return ak.length === bk.length && ak.every((k) => deepEqual(a[k], b[k]));
|
|
25
|
-
}
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
class InMemoryAdapter {
|
|
29
|
-
name = 'inmemory';
|
|
30
|
-
flags;
|
|
31
|
-
fault;
|
|
32
|
-
initError;
|
|
33
|
-
initGate;
|
|
34
|
-
exposures = [];
|
|
35
|
-
signals = [];
|
|
36
|
-
closed = false;
|
|
37
|
-
constructor(options = {}) {
|
|
38
|
-
this.flags = new Map(Object.entries(options.flags ?? {}));
|
|
39
|
-
this.fault = options.fault;
|
|
40
|
-
this.initError = options.initError;
|
|
41
|
-
this.initGate = options.initGate;
|
|
42
|
-
}
|
|
43
|
-
setFlags(flags) {
|
|
44
|
-
this.flags = new Map(Object.entries(flags));
|
|
45
|
-
}
|
|
46
|
-
setFault(fault) {
|
|
47
|
-
this.fault = fault;
|
|
48
|
-
}
|
|
49
|
-
async initialize(signal) {
|
|
50
|
-
if (this.initError !== undefined) {
|
|
51
|
-
throw new errors_js_1.FireweaveError(this.initError);
|
|
52
|
-
}
|
|
53
|
-
if (this.initGate !== undefined) {
|
|
54
|
-
if (signal?.aborted)
|
|
55
|
-
throw new errors_js_1.FireweaveError('Timeout');
|
|
56
|
-
await this.initGate.promise;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
async resolve(controlPointKey, context, _options) {
|
|
60
|
-
if (this.fault !== undefined) {
|
|
61
|
-
const opts = this.fault.metadata !== undefined ? { metadata: this.fault.metadata } : {};
|
|
62
|
-
throw new errors_js_1.FireweaveError(this.fault.kind, opts);
|
|
63
|
-
}
|
|
64
|
-
const def = this.flags.get(controlPointKey);
|
|
65
|
-
if (def === undefined) {
|
|
66
|
-
return { found: false };
|
|
67
|
-
}
|
|
68
|
-
if (!this.matches(def, context)) {
|
|
69
|
-
return { found: false };
|
|
70
|
-
}
|
|
71
|
-
const resolution = {
|
|
72
|
-
found: true,
|
|
73
|
-
enabled: def.enabled,
|
|
74
|
-
value: def.value,
|
|
75
|
-
flagType: def.type,
|
|
76
|
-
};
|
|
77
|
-
if (def.variant !== undefined)
|
|
78
|
-
resolution.variant = def.variant;
|
|
79
|
-
if (def.fireweaveReason !== undefined)
|
|
80
|
-
resolution.reason = def.fireweaveReason;
|
|
81
|
-
if (def.reason?.code !== undefined)
|
|
82
|
-
resolution.reasonCode = def.reason.code;
|
|
83
|
-
if (def.reason?.condition_index !== undefined)
|
|
84
|
-
resolution.conditionIndex = def.reason.condition_index;
|
|
85
|
-
if (def.metadata?.version !== undefined)
|
|
86
|
-
resolution.version = def.metadata.version;
|
|
87
|
-
if (def.metadata?.id !== undefined)
|
|
88
|
-
resolution.vendorControlPointId = def.metadata.id;
|
|
89
|
-
if (def.payload !== undefined)
|
|
90
|
-
resolution.payload = def.payload;
|
|
91
|
-
if (def.fromCache === true)
|
|
92
|
-
resolution.fromCache = true;
|
|
93
|
-
return resolution;
|
|
94
|
-
}
|
|
95
|
-
matches(def, context) {
|
|
96
|
-
if (def.matchTargetingKey !== undefined && context.targetingKey !== def.matchTargetingKey) {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
if (def.matchAttribute !== undefined) {
|
|
100
|
-
for (const [k, v] of Object.entries(def.matchAttribute)) {
|
|
101
|
-
if (!deepEqual(context.attributes[k], v))
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (def.matchPerson !== undefined) {
|
|
106
|
-
for (const [k, v] of Object.entries(def.matchPerson)) {
|
|
107
|
-
if (!deepEqual(context.attributes[k], v))
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (def.matchGroups !== undefined) {
|
|
112
|
-
for (const [k, v] of Object.entries(def.matchGroups)) {
|
|
113
|
-
if (context.groups?.[k] !== v)
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
recordExposure(exposure) {
|
|
120
|
-
this.exposures.push(exposure);
|
|
121
|
-
}
|
|
122
|
-
getExposures() {
|
|
123
|
-
return this.exposures;
|
|
124
|
-
}
|
|
125
|
-
recordSignal(signal) {
|
|
126
|
-
this.signals.push(signal);
|
|
127
|
-
}
|
|
128
|
-
getSignals() {
|
|
129
|
-
return this.signals;
|
|
130
|
-
}
|
|
131
|
-
async flush() {
|
|
132
|
-
// no-op: exposures stay queryable for tests
|
|
133
|
-
}
|
|
134
|
-
async shutdown() {
|
|
135
|
-
this.closed = true;
|
|
136
|
-
}
|
|
137
|
-
isClosed() {
|
|
138
|
-
return this.closed;
|
|
139
|
-
}
|
|
140
|
-
features() {
|
|
141
|
-
return {
|
|
142
|
-
remoteEvaluation: false,
|
|
143
|
-
localEvaluation: true,
|
|
144
|
-
localOnly: true,
|
|
145
|
-
exposureEmission: true,
|
|
146
|
-
sideEffectFreeReads: true,
|
|
147
|
-
groupAnalytics: true,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
exports.InMemoryAdapter = InMemoryAdapter;
|