@fireweaveai/web-sdk 2.1.0 → 2.1.1-staging.2
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 +105 -0
- package/dist/application/client.d.ts +120 -0
- package/dist/application/client.d.ts.map +1 -0
- package/dist/application/client.js +160 -0
- package/dist/application/client.js.map +1 -0
- package/dist/application/mode.d.ts +100 -0
- package/dist/application/mode.d.ts.map +1 -0
- package/dist/application/mode.js +99 -0
- package/dist/application/mode.js.map +1 -0
- package/dist/{adapter.d.ts → application/ports.d.ts} +19 -4
- package/dist/application/ports.d.ts.map +1 -0
- package/dist/application/ports.js +2 -0
- package/dist/application/ports.js.map +1 -0
- package/dist/{runtime.d.ts → application/runtime.d.ts} +15 -6
- package/dist/application/runtime.d.ts.map +1 -0
- package/dist/{runtime.js → application/runtime.js} +80 -27
- package/dist/application/runtime.js.map +1 -0
- package/dist/domain/context.d.ts +52 -0
- package/dist/domain/context.d.ts.map +1 -0
- package/dist/domain/context.js +68 -0
- package/dist/domain/context.js.map +1 -0
- package/dist/domain/errors.d.ts.map +1 -0
- package/dist/domain/errors.js.map +1 -0
- package/dist/domain/target.d.ts +3 -0
- package/dist/domain/target.d.ts.map +1 -0
- package/dist/domain/target.js +2 -0
- package/dist/domain/target.js.map +1 -0
- package/dist/domain/types.d.ts.map +1 -0
- package/dist/domain/types.js.map +1 -0
- package/dist/domain/validation.d.ts +129 -0
- package/dist/domain/validation.d.ts.map +1 -0
- package/dist/domain/validation.js +350 -0
- package/dist/domain/validation.js.map +1 -0
- package/dist/index.d.ts +37 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -21
- package/dist/index.js.map +1 -1
- package/dist/{adapters → infrastructure/adapters}/inmemory.d.ts +3 -3
- package/dist/infrastructure/adapters/inmemory.d.ts.map +1 -0
- package/dist/{adapters → infrastructure/adapters}/inmemory.js +1 -1
- package/dist/infrastructure/adapters/inmemory.js.map +1 -0
- package/dist/infrastructure/adapters/local.d.ts +80 -0
- package/dist/infrastructure/adapters/local.d.ts.map +1 -0
- package/dist/infrastructure/adapters/local.js +95 -0
- package/dist/infrastructure/adapters/local.js.map +1 -0
- package/dist/{adapters → infrastructure/adapters}/remote.d.ts +2 -2
- package/dist/infrastructure/adapters/remote.d.ts.map +1 -0
- package/dist/{adapters → infrastructure/adapters}/remote.js +9 -15
- package/dist/infrastructure/adapters/remote.js.map +1 -0
- package/dist/infrastructure/hosts.d.ts.map +1 -0
- package/dist/{hosts.js → infrastructure/hosts.js} +1 -1
- package/dist/infrastructure/hosts.js.map +1 -0
- package/package.json +16 -11
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js +0 -2
- package/dist/adapter.js.map +0 -1
- package/dist/adapters/inmemory.d.ts.map +0 -1
- package/dist/adapters/inmemory.js.map +0 -1
- package/dist/adapters/local.d.ts +0 -38
- package/dist/adapters/local.d.ts.map +0 -1
- package/dist/adapters/local.js +0 -51
- package/dist/adapters/local.js.map +0 -1
- package/dist/adapters/remote.d.ts.map +0 -1
- package/dist/adapters/remote.js.map +0 -1
- package/dist/client.d.ts +0 -134
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -0
- package/dist/client.js.map +0 -1
- package/dist/context.d.ts +0 -26
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -106
- package/dist/context.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/hosts.d.ts.map +0 -1
- package/dist/hosts.js.map +0 -1
- package/dist/provider.d.ts +0 -39
- package/dist/provider.d.ts.map +0 -1
- package/dist/provider.js +0 -93
- package/dist/provider.js.map +0 -1
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- /package/dist/{errors.d.ts → domain/errors.d.ts} +0 -0
- /package/dist/{errors.js → domain/errors.js} +0 -0
- /package/dist/{types.d.ts → domain/types.d.ts} +0 -0
- /package/dist/{types.js → domain/types.js} +0 -0
- /package/dist/{hosts.d.ts → infrastructure/hosts.d.ts} +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# @fireweaveai/web-sdk (Web SDK)
|
|
2
|
+
|
|
3
|
+
Fireweave control points for the browser ([ADR-0009](../../docs/adr/0009-browser-control-points.md)) —
|
|
4
|
+
**control points** and **target registration**, the two v1 capabilities
|
|
5
|
+
([spec/control-points.md](../../spec/control-points.md) "Scope of v1"; spec v0.1.0).
|
|
6
|
+
|
|
7
|
+
- **Remote-only and secret-free by construction.** No local evaluation, no vendor SDK dependency, no environment reads, and vendor/secret key shapes are rejected at the door.
|
|
8
|
+
- **Reads are synchronous** — `controlPoints.getBooleanValue(...)` returns a value directly, no `await`, safe inside a render path. `initFireweave` prefetches a decision cache once per context; reads afterward are a pure in-memory lookup.
|
|
9
|
+
- **Bun is the toolchain.** Tested on Bun only — this package ships no server entry point, reads no environment, and imports no runtime built-ins, so Node/Deno are not target runtimes for it.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun add @fireweaveai/web-sdk # or: npm install @fireweaveai/web-sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start (production path)
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { initFireweave } from '@fireweaveai/web-sdk';
|
|
21
|
+
|
|
22
|
+
// apiKey/apiUrl are required, explicit options — the SDK reads no
|
|
23
|
+
// environment variables (spec/modes.md). The apiKey is a Fireweave PROJECT
|
|
24
|
+
// key, public by construction (ADR-0009) — never a secret.
|
|
25
|
+
const fireweave = await initFireweave({
|
|
26
|
+
mode: 'remote',
|
|
27
|
+
apiKey: PUBLIC_FW_PROJECT_API_KEY,
|
|
28
|
+
apiUrl: PUBLIC_FW_API_URL,
|
|
29
|
+
context: { targetingKey: 'anonymous' },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Reads are SYNCHRONOUS — no await, safe inside render.
|
|
33
|
+
const enabled = fireweave.controlPoints.getBooleanValue('new-checkout', false);
|
|
34
|
+
|
|
35
|
+
// At sign-in: register durable targeting facts, then re-prefetch under that id.
|
|
36
|
+
await fireweave.identify('user_42', { kind: 'user', properties: { plan: 'pro' } });
|
|
37
|
+
|
|
38
|
+
await fireweave.shutdown();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick start (offline, local mode)
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { initFireweave } from '@fireweaveai/web-sdk';
|
|
45
|
+
|
|
46
|
+
const fireweave = await initFireweave({
|
|
47
|
+
mode: 'local',
|
|
48
|
+
local: { controlPoints: { 'new-checkout': true } },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// → true
|
|
52
|
+
fireweave.controlPoints.getBooleanValue('new-checkout', false);
|
|
53
|
+
|
|
54
|
+
await fireweave.shutdown();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`initFireweave` is the single entry point (spec/modes.md): `mode` is required and never inferred.
|
|
58
|
+
A bad host or missing credential still fails loudly at `initFireweave()` even though
|
|
59
|
+
`FireweaveWebRuntime.initialize()` itself is deliberately fail-open — a hung or failing prefetch
|
|
60
|
+
must not block app boot ([ADR-0009](../../docs/adr/0009-browser-control-points.md) "Fail-open, not
|
|
61
|
+
fail-silent"). When the initial prefetch race loses to its ceiling, the runtime serves defaults
|
|
62
|
+
with reason `STALE` rather than blocking.
|
|
63
|
+
|
|
64
|
+
## Module layout
|
|
65
|
+
|
|
66
|
+
| Module | Responsibility |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `application/runtime.ts` | `FireweaveWebRuntime` — prefetch-once-per-context cache, lifecycle state, sync reads. |
|
|
69
|
+
| `application/client.ts` | `FireweaveWebClient` — `controlPoints`, `registerTarget`, `identify`. |
|
|
70
|
+
| `application/mode.ts` | `initFireweave` — the single entry point; the only module allowed to import concrete adapters. |
|
|
71
|
+
| `infrastructure/adapters/remote.ts` | `FireweaveRemoteWebAdapter` — the production backend (`/v1/flags/evaluate`, `/v1/targets/register`). |
|
|
72
|
+
| `infrastructure/adapters/inmemory.ts` | Deterministic fixture-driven adapter for tests and conformance. |
|
|
73
|
+
| `infrastructure/adapters/local.ts` | `FireweaveLocalWebAdapter` — the DEV substrate `initFireweave({ mode: 'local' })` builds. |
|
|
74
|
+
| `application/ports.ts` | The `WebBackendAdapter` boundary. |
|
|
75
|
+
| `domain/context.ts` | Merge order, deep copy, bounds, reserved keys. |
|
|
76
|
+
| `domain/errors.ts` | The 15-kind error taxonomy. |
|
|
77
|
+
| `infrastructure/hosts.ts` | SSRF allowlist + secret-key-shape rejection (on by default; https required off-loopback). |
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
The SDK reads no environment variables — every option is an explicit argument to `initFireweave`.
|
|
82
|
+
|
|
83
|
+
| Option | Mode | Description |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| `apiUrl` | `remote` | fw-server base URL (required) |
|
|
86
|
+
| `apiKey` | `remote` | Fireweave **project** key — public by construction, never a secret (required) |
|
|
87
|
+
| `allowedHosts` | `remote` | SSRF allowlist override; defaults to the `apiUrl` host plus loopback |
|
|
88
|
+
| `context` | both | initial evaluation context (e.g. an anonymous `targetingKey`) to prefetch under |
|
|
89
|
+
| `local.controlPoints` | `local` | seeded boolean overrides; a present key resolves `STATIC`, an absent key misses to the caller's default with reason `DEFAULT` |
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
bun install # from sdks/web
|
|
95
|
+
bun run build # emit dist/ (package exports resolve to it)
|
|
96
|
+
bun run verify # typecheck + test + conformance
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Documentation
|
|
100
|
+
|
|
101
|
+
Full docs live in [`docs/`](../../docs/), and [ADR-0009](../../docs/adr/0009-browser-control-points.md) records the browser-specific design (fail-open prefetch, secret-key rejection, sync reads).
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
[MIT](../../LICENSE).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FireweaveWebClient — the Fireweave-native surface, mirroring the server
|
|
3
|
+
* SDK's `FireweaveClient` namespace for namespace (ADR-0003: extensions live
|
|
4
|
+
* beside the OpenFeature client, never inside it).
|
|
5
|
+
*
|
|
6
|
+
* Scope of v1 (spec/control-points.md "Scope of v1"): exactly two
|
|
7
|
+
* capabilities — control points and target registration. Releases,
|
|
8
|
+
* exposures, signals, capabilities discovery and guardrails are out of v1;
|
|
9
|
+
* this client MUST NOT expose them (conformance/surface/control-points.surface.json
|
|
10
|
+
* "mustNotExpose"). The dynamic `invokeCapability` dispatcher and the
|
|
11
|
+
* deprecated `flags` alias survive unchanged.
|
|
12
|
+
*
|
|
13
|
+
* One divergence from the server SDK, and it is intentional: `controlPoints.*`
|
|
14
|
+
* is SYNCHRONOUS here and promise-returning on the server. That follows from
|
|
15
|
+
* the OpenFeature web contract — browser reads happen in render paths — and
|
|
16
|
+
* is recorded in docs/compatibility.md as a surface difference rather than a
|
|
17
|
+
* gap.
|
|
18
|
+
*/
|
|
19
|
+
import { FireweaveError } from '../domain/errors.js';
|
|
20
|
+
import type { ContextInput } from '../domain/context.js';
|
|
21
|
+
import type { FireweaveWebRuntime, ExpectedFlagType } from './runtime.js';
|
|
22
|
+
import type { Decision, JsonValue } from '../domain/types.js';
|
|
23
|
+
import type { RegisterTargetOptions, RegisterTargetResult } from './ports.js';
|
|
24
|
+
export interface ExtensionResult {
|
|
25
|
+
readonly ok: boolean;
|
|
26
|
+
readonly error?: FireweaveError;
|
|
27
|
+
readonly degraded?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Reserved for cross-language surface parity
|
|
31
|
+
* (conformance/surface/control-points.surface.json pins `evaluate(key, type,
|
|
32
|
+
* default, context?, options?)` across every language). Currently INERT on
|
|
33
|
+
* web — accepted and typed, nothing reads it.
|
|
34
|
+
*
|
|
35
|
+
* Node's `EvaluateOptions` carries `signal` (abort an in-flight network
|
|
36
|
+
* call), `includePayload` (attach raw flag payload metadata), and
|
|
37
|
+
* `sendExposure` (opt into exposure emission for this one call). None of
|
|
38
|
+
* those map cleanly onto web's contract: `evaluate` here is a SYNCHRONOUS
|
|
39
|
+
* read of an already-prefetched cache (ADR-0009), so there is no in-flight
|
|
40
|
+
* I/O to abort at read time, and exposure emission is a constructor-level
|
|
41
|
+
* opt-in (`FireweaveWebRuntimeConfig.sendExposure`) rather than a per-call
|
|
42
|
+
* one. The parameter exists so this method's ARITY matches the descriptor
|
|
43
|
+
* every language is pinned to, not because it does anything yet.
|
|
44
|
+
*/
|
|
45
|
+
export interface EvaluateOptions {
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Control-point evaluation on the public client surface — decision-returning,
|
|
49
|
+
* without reaching into the runtime. Never throws; errors surface as ERROR
|
|
50
|
+
* decisions, exactly like the OpenFeature path.
|
|
51
|
+
*/
|
|
52
|
+
export declare class WebControlPointsApi {
|
|
53
|
+
private readonly runtime;
|
|
54
|
+
constructor(runtime: FireweaveWebRuntime);
|
|
55
|
+
evaluate(flagKey: string, expectedType: ExpectedFlagType, defaultValue: JsonValue, context?: ContextInput, _options?: EvaluateOptions): Decision;
|
|
56
|
+
getBooleanValue(flagKey: string, defaultValue: boolean, context?: ContextInput): boolean;
|
|
57
|
+
getStringValue(flagKey: string, defaultValue: string, context?: ContextInput): string;
|
|
58
|
+
getNumberValue(flagKey: string, defaultValue: number, context?: ContextInput): number;
|
|
59
|
+
getObjectValue(flagKey: string, defaultValue: JsonValue, context?: ContextInput): JsonValue;
|
|
60
|
+
/**
|
|
61
|
+
* Detailed reads — the whole {@link Decision} rather than just its value.
|
|
62
|
+
*
|
|
63
|
+
* Same arguments as the `*Value` pair above, so a caller upgrades from one
|
|
64
|
+
* to the other without restructuring the call (spec/control-points.md "The
|
|
65
|
+
* nine methods"). SYNCHRONOUS like every other read here (ADR-0009).
|
|
66
|
+
*/
|
|
67
|
+
getBooleanDetails(flagKey: string, defaultValue: boolean, context?: ContextInput): Decision;
|
|
68
|
+
getStringDetails(flagKey: string, defaultValue: string, context?: ContextInput): Decision;
|
|
69
|
+
getNumberDetails(flagKey: string, defaultValue: number, context?: ContextInput): Decision;
|
|
70
|
+
getObjectDetails(flagKey: string, defaultValue: JsonValue, context?: ContextInput): Decision;
|
|
71
|
+
}
|
|
72
|
+
export interface FireweaveWebClientOptions {
|
|
73
|
+
}
|
|
74
|
+
export declare class FireweaveWebClient {
|
|
75
|
+
readonly runtime: FireweaveWebRuntime;
|
|
76
|
+
readonly controlPoints: WebControlPointsApi;
|
|
77
|
+
/**
|
|
78
|
+
* Control-point evaluation under its former name.
|
|
79
|
+
*
|
|
80
|
+
* @deprecated Renamed to {@link FireweaveWebClient.controlPoints}
|
|
81
|
+
* (ADR-0007). Identical and fully supported —
|
|
82
|
+
* `client.flags === client.controlPoints` — so no migration is required and
|
|
83
|
+
* none is planned. Silent at runtime: the alias is permanent, not scheduled
|
|
84
|
+
* for removal, so there is nothing to warn a caller toward — deprecation is
|
|
85
|
+
* conveyed by this doc comment only (no log, and no env gate to control
|
|
86
|
+
* one, since the SDK reads no environment variables regardless — ADR-0009
|
|
87
|
+
* security rule 3).
|
|
88
|
+
*/
|
|
89
|
+
get flags(): WebControlPointsApi;
|
|
90
|
+
constructor(runtime: FireweaveWebRuntime, _options?: FireweaveWebClientOptions);
|
|
91
|
+
/**
|
|
92
|
+
* Dynamic capability dispatch. Unknown capabilities — currently all of
|
|
93
|
+
* them, v1's SUPPORTED_CAPABILITIES is empty — degrade with
|
|
94
|
+
* UnsupportedCapability, never throw. Any future capability listed in
|
|
95
|
+
* SUPPORTED_CAPABILITIES is lifecycle-gated the same way (ruling 17).
|
|
96
|
+
*/
|
|
97
|
+
invokeCapability(capability: string, _args?: Record<string, JsonValue>): ExtensionResult;
|
|
98
|
+
initialize(context?: ContextInput): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Register durable targeting facts for a target (spec/modes.md).
|
|
101
|
+
*
|
|
102
|
+
* Resolves rather than throwing: this runs in sign-in paths, where a
|
|
103
|
+
* targeting concern must not break authentication. In local mode this
|
|
104
|
+
* records in-process and traces the call; nothing reaches fw-server (see
|
|
105
|
+
* `FireweaveLocalWebAdapter.registerTarget`).
|
|
106
|
+
*/
|
|
107
|
+
registerTarget(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
|
|
108
|
+
/**
|
|
109
|
+
* Sign-in hook: register the user's durable targeting properties, then
|
|
110
|
+
* re-prefetch under that id so percentage ramps bucket on a stable key.
|
|
111
|
+
*
|
|
112
|
+
* Two kinds of property feed a rule and both are needed — DURABLE ones
|
|
113
|
+
* registered here, and PER-REQUEST ones carried in the evaluation context. A
|
|
114
|
+
* rule targeting a property that is never registered AND never sent matches
|
|
115
|
+
* nobody, silently.
|
|
116
|
+
*/
|
|
117
|
+
identify(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
|
|
118
|
+
shutdown(): Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/application/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FireweaveWebClient — the Fireweave-native surface, mirroring the server
|
|
3
|
+
* SDK's `FireweaveClient` namespace for namespace (ADR-0003: extensions live
|
|
4
|
+
* beside the OpenFeature client, never inside it).
|
|
5
|
+
*
|
|
6
|
+
* Scope of v1 (spec/control-points.md "Scope of v1"): exactly two
|
|
7
|
+
* capabilities — control points and target registration. Releases,
|
|
8
|
+
* exposures, signals, capabilities discovery and guardrails are out of v1;
|
|
9
|
+
* this client MUST NOT expose them (conformance/surface/control-points.surface.json
|
|
10
|
+
* "mustNotExpose"). The dynamic `invokeCapability` dispatcher and the
|
|
11
|
+
* deprecated `flags` alias survive unchanged.
|
|
12
|
+
*
|
|
13
|
+
* One divergence from the server SDK, and it is intentional: `controlPoints.*`
|
|
14
|
+
* is SYNCHRONOUS here and promise-returning on the server. That follows from
|
|
15
|
+
* the OpenFeature web contract — browser reads happen in render paths — and
|
|
16
|
+
* is recorded in docs/compatibility.md as a surface difference rather than a
|
|
17
|
+
* gap.
|
|
18
|
+
*/
|
|
19
|
+
import { FireweaveError } from '../domain/errors.js';
|
|
20
|
+
function failure(error, degraded = false) {
|
|
21
|
+
return degraded ? { ok: false, error, degraded: true } : { ok: false, error };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Lifecycle gate for extension calls. Kept for forward compatibility with
|
|
25
|
+
* `invokeCapability` (ruling 17, Go/Java model) even though v1's
|
|
26
|
+
* SUPPORTED_CAPABILITIES is empty and therefore never reaches it today — a
|
|
27
|
+
* future capability re-added to the allowlist is gated the same way without
|
|
28
|
+
* this function needing to be reinvented.
|
|
29
|
+
*/
|
|
30
|
+
function lifecycleGate(runtime) {
|
|
31
|
+
const state = runtime.getState();
|
|
32
|
+
if (state === 'SHUTDOWN')
|
|
33
|
+
return new FireweaveError('AlreadyClosed');
|
|
34
|
+
if (state === 'UNINITIALIZED' || state === 'INITIALIZING')
|
|
35
|
+
return new FireweaveError('NotReady');
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Control-point evaluation on the public client surface — decision-returning,
|
|
40
|
+
* without reaching into the runtime. Never throws; errors surface as ERROR
|
|
41
|
+
* decisions, exactly like the OpenFeature path.
|
|
42
|
+
*/
|
|
43
|
+
export class WebControlPointsApi {
|
|
44
|
+
runtime;
|
|
45
|
+
constructor(runtime) {
|
|
46
|
+
this.runtime = runtime;
|
|
47
|
+
}
|
|
48
|
+
evaluate(flagKey, expectedType, defaultValue, context, _options) {
|
|
49
|
+
return this.runtime.evaluateSync(flagKey, expectedType, defaultValue, context);
|
|
50
|
+
}
|
|
51
|
+
getBooleanValue(flagKey, defaultValue, context) {
|
|
52
|
+
return this.evaluate(flagKey, 'boolean', defaultValue, context).value;
|
|
53
|
+
}
|
|
54
|
+
getStringValue(flagKey, defaultValue, context) {
|
|
55
|
+
return this.evaluate(flagKey, 'string', defaultValue, context).value;
|
|
56
|
+
}
|
|
57
|
+
getNumberValue(flagKey, defaultValue, context) {
|
|
58
|
+
return this.evaluate(flagKey, 'number', defaultValue, context).value;
|
|
59
|
+
}
|
|
60
|
+
getObjectValue(flagKey, defaultValue, context) {
|
|
61
|
+
return this.evaluate(flagKey, 'object', defaultValue, context).value;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Detailed reads — the whole {@link Decision} rather than just its value.
|
|
65
|
+
*
|
|
66
|
+
* Same arguments as the `*Value` pair above, so a caller upgrades from one
|
|
67
|
+
* to the other without restructuring the call (spec/control-points.md "The
|
|
68
|
+
* nine methods"). SYNCHRONOUS like every other read here (ADR-0009).
|
|
69
|
+
*/
|
|
70
|
+
getBooleanDetails(flagKey, defaultValue, context) {
|
|
71
|
+
return this.evaluate(flagKey, 'boolean', defaultValue, context);
|
|
72
|
+
}
|
|
73
|
+
getStringDetails(flagKey, defaultValue, context) {
|
|
74
|
+
return this.evaluate(flagKey, 'string', defaultValue, context);
|
|
75
|
+
}
|
|
76
|
+
getNumberDetails(flagKey, defaultValue, context) {
|
|
77
|
+
return this.evaluate(flagKey, 'number', defaultValue, context);
|
|
78
|
+
}
|
|
79
|
+
getObjectDetails(flagKey, defaultValue, context) {
|
|
80
|
+
return this.evaluate(flagKey, 'object', defaultValue, context);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Names invokeCapability will dispatch instead of degrading with
|
|
85
|
+
* UnsupportedCapability. Empty in v1: releases, exposures, signals,
|
|
86
|
+
* capabilities discovery, and guardrails are all out of scope
|
|
87
|
+
* (spec/control-points.md) and MUST NOT be exposed, so a cut namespace's
|
|
88
|
+
* capability string resolves exactly like any other unknown string.
|
|
89
|
+
*/
|
|
90
|
+
const SUPPORTED_CAPABILITIES = Object.freeze([]);
|
|
91
|
+
export class FireweaveWebClient {
|
|
92
|
+
runtime;
|
|
93
|
+
controlPoints;
|
|
94
|
+
/**
|
|
95
|
+
* Control-point evaluation under its former name.
|
|
96
|
+
*
|
|
97
|
+
* @deprecated Renamed to {@link FireweaveWebClient.controlPoints}
|
|
98
|
+
* (ADR-0007). Identical and fully supported —
|
|
99
|
+
* `client.flags === client.controlPoints` — so no migration is required and
|
|
100
|
+
* none is planned. Silent at runtime: the alias is permanent, not scheduled
|
|
101
|
+
* for removal, so there is nothing to warn a caller toward — deprecation is
|
|
102
|
+
* conveyed by this doc comment only (no log, and no env gate to control
|
|
103
|
+
* one, since the SDK reads no environment variables regardless — ADR-0009
|
|
104
|
+
* security rule 3).
|
|
105
|
+
*/
|
|
106
|
+
get flags() {
|
|
107
|
+
return this.controlPoints;
|
|
108
|
+
}
|
|
109
|
+
constructor(runtime, _options = {}) {
|
|
110
|
+
this.runtime = runtime;
|
|
111
|
+
this.controlPoints = new WebControlPointsApi(runtime);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Dynamic capability dispatch. Unknown capabilities — currently all of
|
|
115
|
+
* them, v1's SUPPORTED_CAPABILITIES is empty — degrade with
|
|
116
|
+
* UnsupportedCapability, never throw. Any future capability listed in
|
|
117
|
+
* SUPPORTED_CAPABILITIES is lifecycle-gated the same way (ruling 17).
|
|
118
|
+
*/
|
|
119
|
+
invokeCapability(capability, _args) {
|
|
120
|
+
if (!SUPPORTED_CAPABILITIES.includes(capability)) {
|
|
121
|
+
return failure(new FireweaveError('UnsupportedCapability'), true);
|
|
122
|
+
}
|
|
123
|
+
const gate = lifecycleGate(this.runtime);
|
|
124
|
+
if (gate !== undefined)
|
|
125
|
+
return failure(gate, true);
|
|
126
|
+
return { ok: true };
|
|
127
|
+
}
|
|
128
|
+
initialize(context) {
|
|
129
|
+
return this.runtime.initialize(context);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Register durable targeting facts for a target (spec/modes.md).
|
|
133
|
+
*
|
|
134
|
+
* Resolves rather than throwing: this runs in sign-in paths, where a
|
|
135
|
+
* targeting concern must not break authentication. In local mode this
|
|
136
|
+
* records in-process and traces the call; nothing reaches fw-server (see
|
|
137
|
+
* `FireweaveLocalWebAdapter.registerTarget`).
|
|
138
|
+
*/
|
|
139
|
+
registerTarget(targetingKey, options = {}) {
|
|
140
|
+
return this.runtime.registerTarget(targetingKey, options);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Sign-in hook: register the user's durable targeting properties, then
|
|
144
|
+
* re-prefetch under that id so percentage ramps bucket on a stable key.
|
|
145
|
+
*
|
|
146
|
+
* Two kinds of property feed a rule and both are needed — DURABLE ones
|
|
147
|
+
* registered here, and PER-REQUEST ones carried in the evaluation context. A
|
|
148
|
+
* rule targeting a property that is never registered AND never sent matches
|
|
149
|
+
* nobody, silently.
|
|
150
|
+
*/
|
|
151
|
+
async identify(targetingKey, options = {}) {
|
|
152
|
+
const result = await this.runtime.registerTarget(targetingKey, options);
|
|
153
|
+
await this.runtime.setContext({ targetingKey });
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
async shutdown() {
|
|
157
|
+
await this.runtime.shutdown();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/application/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* initFireweave — the single SDK entry point (spec/modes.md).
|
|
3
|
+
*
|
|
4
|
+
* `mode` is required and never inferred: a missing or mistyped credential
|
|
5
|
+
* must fail loudly at boot, not silently fall back to local evaluation —
|
|
6
|
+
* that failure mode looks like a green boot and a feature that never ramps.
|
|
7
|
+
* This function's only job is to validate the initialisation-time contract
|
|
8
|
+
* and select the matching adapter; nothing downstream branches on mode again
|
|
9
|
+
* (spec/modes.md "Behaviour per mode" — both adapters implement the same
|
|
10
|
+
* WebBackendAdapter port, so FireweaveWebClient / FireweaveWebRuntime stay
|
|
11
|
+
* mode-blind).
|
|
12
|
+
*
|
|
13
|
+
* Initialisation fails loudly (throws); reads on the returned client never do
|
|
14
|
+
* (spec/control-points.md "initialise is the exception").
|
|
15
|
+
*
|
|
16
|
+
* ## A web-specific wrinkle: `FireweaveWebRuntime.initialize()` never throws
|
|
17
|
+
*
|
|
18
|
+
* Node's `FireweaveRuntime.initialize()` rejects on adapter failure, so
|
|
19
|
+
* node's initFireweave can just `await runtime.initialize()` and let a bad
|
|
20
|
+
* host/credential propagate. Web's runtime is deliberately fail-OPEN at
|
|
21
|
+
* `initialize()` — a hung or failing prefetch must not block app boot
|
|
22
|
+
* (ADR-0009 "Fail-open, not fail-silent"), so it swallows adapter failures
|
|
23
|
+
* into ERROR/STALE state instead of rejecting.
|
|
24
|
+
*
|
|
25
|
+
* That non-throwing contract is correct for TRANSIENT failures (the network
|
|
26
|
+
* happened to be down) but wrong for the four Configuration rows below,
|
|
27
|
+
* which spec/modes.md requires to fail loudly at boot. This module closes
|
|
28
|
+
* that gap itself in two parts: `validateInitOptions` (domain/validation.ts)
|
|
29
|
+
* covers rows 1/2/4 (mode absent/unrecognised, remote apiKey/apiUrl blank,
|
|
30
|
+
* local combined with credentials) exactly like node's `initFireweave` does;
|
|
31
|
+
* `initRemote` covers row 3 (the host allowlist) with a direct, SYNCHRONOUS
|
|
32
|
+
* `assertHostAllowed` call, before ever calling into the runtime — that call
|
|
33
|
+
* is what makes a bad host fail LOUDLY here, because the runtime itself
|
|
34
|
+
* deliberately never throws. A genuinely transient prefetch failure (host is
|
|
35
|
+
* fine, network hiccups) still resolves into ERROR/STALE rather than
|
|
36
|
+
* throwing — that fail-open behaviour is unchanged and is not one of the
|
|
37
|
+
* four rows.
|
|
38
|
+
*/
|
|
39
|
+
import { FireweaveWebClient } from './client.js';
|
|
40
|
+
import type { FireweaveFetchLike } from '../infrastructure/adapters/remote.js';
|
|
41
|
+
import type { ContextInput } from '../domain/context.js';
|
|
42
|
+
export interface InitFireweaveRemoteOptions {
|
|
43
|
+
/** Evaluate against fw-server over the network (spec/remote-protocol.md). */
|
|
44
|
+
readonly mode: 'remote';
|
|
45
|
+
/** Fireweave project key. Public by construction (ADR-0009) — required, never read from the environment. */
|
|
46
|
+
readonly apiKey: string;
|
|
47
|
+
/** fw-server base URL. Required — never read from the environment. */
|
|
48
|
+
readonly apiUrl: string;
|
|
49
|
+
/**
|
|
50
|
+
* SSRF/misconfiguration allowlist override (spec/modes.md "apiUrl fails
|
|
51
|
+
* the host allowlist"). Default: the canonical Fireweave hosts + loopback
|
|
52
|
+
* (`DEFAULT_ALLOWED_HOSTS`). A self-hosted fw-server must list its own
|
|
53
|
+
* host explicitly; `['*']` opts out.
|
|
54
|
+
*/
|
|
55
|
+
readonly allowedHosts?: readonly string[];
|
|
56
|
+
/** Injected fetch (tests). Production uses the runtime's global `fetch`. */
|
|
57
|
+
readonly fetch?: FireweaveFetchLike;
|
|
58
|
+
/** Initial evaluation context (e.g. an anonymous targetingKey) to prefetch under. */
|
|
59
|
+
readonly context?: ContextInput;
|
|
60
|
+
}
|
|
61
|
+
export interface InitFireweaveLocalOptions {
|
|
62
|
+
/** Evaluate against an in-process seeded map; no network (spec/modes.md). */
|
|
63
|
+
readonly mode: 'local';
|
|
64
|
+
readonly local?: {
|
|
65
|
+
/**
|
|
66
|
+
* Per-key boolean overrides — the seeded local map. A present key
|
|
67
|
+
* resolves with reason `STATIC`; an absent key misses so the caller's
|
|
68
|
+
* own default is used. May be empty or omitted entirely.
|
|
69
|
+
*/
|
|
70
|
+
readonly controlPoints?: Record<string, boolean>;
|
|
71
|
+
/**
|
|
72
|
+
* Sink for the `[fireweave:local]` registerTarget trace line
|
|
73
|
+
* (spec/modes.md "registerTarget in local mode"). Defaults to
|
|
74
|
+
* `console.info`.
|
|
75
|
+
*/
|
|
76
|
+
readonly log?: (message: string) => void;
|
|
77
|
+
};
|
|
78
|
+
/** Initial evaluation context (e.g. an anonymous targetingKey) to prefetch under. */
|
|
79
|
+
readonly context?: ContextInput;
|
|
80
|
+
}
|
|
81
|
+
export type InitFireweaveOptions = InitFireweaveRemoteOptions | InitFireweaveLocalOptions;
|
|
82
|
+
/**
|
|
83
|
+
* Build the adapter matching `options.mode` and bring a
|
|
84
|
+
* {@link FireweaveWebClient} up.
|
|
85
|
+
*
|
|
86
|
+
* Throws {@link FireweaveError} (kind `Configuration`) for every row of the
|
|
87
|
+
* initialisation-validation table (spec/modes.md):
|
|
88
|
+
* - `mode` absent or unrecognised
|
|
89
|
+
* - `mode: 'remote'` with `apiKey` or `apiUrl` missing/blank
|
|
90
|
+
* - `apiUrl` fails the host allowlist
|
|
91
|
+
* - `mode: 'local'` with credentials supplied
|
|
92
|
+
*
|
|
93
|
+
* The first, second and fourth rows are `validateInitOptions`'s job
|
|
94
|
+
* (domain/validation.ts, shared discipline with node's `initFireweave`); the
|
|
95
|
+
* third is validated downstream, inside `initRemote`, for the reason the
|
|
96
|
+
* module doc comment explains — `FireweaveWebRuntime.initialize()` itself
|
|
97
|
+
* never throws.
|
|
98
|
+
*/
|
|
99
|
+
export declare function initFireweave(options: InitFireweaveOptions): Promise<FireweaveWebClient>;
|
|
100
|
+
//# sourceMappingURL=mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mode.d.ts","sourceRoot":"","sources":["../../src/application/mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAIjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAG/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,0BAA0B;IACzC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,4GAA4G;IAC5G,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAC;IACpC,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;CACjC;AAED,MAAM,WAAW,yBAAyB;IACxC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf;;;;WAIG;QACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjD;;;;WAIG;QACH,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;KAC1C,CAAC;IACF,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;CACjC;AAED,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,GAAG,yBAAyB,CAAC;AAmC1F;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAK9F"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* initFireweave — the single SDK entry point (spec/modes.md).
|
|
3
|
+
*
|
|
4
|
+
* `mode` is required and never inferred: a missing or mistyped credential
|
|
5
|
+
* must fail loudly at boot, not silently fall back to local evaluation —
|
|
6
|
+
* that failure mode looks like a green boot and a feature that never ramps.
|
|
7
|
+
* This function's only job is to validate the initialisation-time contract
|
|
8
|
+
* and select the matching adapter; nothing downstream branches on mode again
|
|
9
|
+
* (spec/modes.md "Behaviour per mode" — both adapters implement the same
|
|
10
|
+
* WebBackendAdapter port, so FireweaveWebClient / FireweaveWebRuntime stay
|
|
11
|
+
* mode-blind).
|
|
12
|
+
*
|
|
13
|
+
* Initialisation fails loudly (throws); reads on the returned client never do
|
|
14
|
+
* (spec/control-points.md "initialise is the exception").
|
|
15
|
+
*
|
|
16
|
+
* ## A web-specific wrinkle: `FireweaveWebRuntime.initialize()` never throws
|
|
17
|
+
*
|
|
18
|
+
* Node's `FireweaveRuntime.initialize()` rejects on adapter failure, so
|
|
19
|
+
* node's initFireweave can just `await runtime.initialize()` and let a bad
|
|
20
|
+
* host/credential propagate. Web's runtime is deliberately fail-OPEN at
|
|
21
|
+
* `initialize()` — a hung or failing prefetch must not block app boot
|
|
22
|
+
* (ADR-0009 "Fail-open, not fail-silent"), so it swallows adapter failures
|
|
23
|
+
* into ERROR/STALE state instead of rejecting.
|
|
24
|
+
*
|
|
25
|
+
* That non-throwing contract is correct for TRANSIENT failures (the network
|
|
26
|
+
* happened to be down) but wrong for the four Configuration rows below,
|
|
27
|
+
* which spec/modes.md requires to fail loudly at boot. This module closes
|
|
28
|
+
* that gap itself in two parts: `validateInitOptions` (domain/validation.ts)
|
|
29
|
+
* covers rows 1/2/4 (mode absent/unrecognised, remote apiKey/apiUrl blank,
|
|
30
|
+
* local combined with credentials) exactly like node's `initFireweave` does;
|
|
31
|
+
* `initRemote` covers row 3 (the host allowlist) with a direct, SYNCHRONOUS
|
|
32
|
+
* `assertHostAllowed` call, before ever calling into the runtime — that call
|
|
33
|
+
* is what makes a bad host fail LOUDLY here, because the runtime itself
|
|
34
|
+
* deliberately never throws. A genuinely transient prefetch failure (host is
|
|
35
|
+
* fine, network hiccups) still resolves into ERROR/STALE rather than
|
|
36
|
+
* throwing — that fail-open behaviour is unchanged and is not one of the
|
|
37
|
+
* four rows.
|
|
38
|
+
*/
|
|
39
|
+
import { FireweaveWebClient } from './client.js';
|
|
40
|
+
import { FireweaveWebRuntime } from './runtime.js';
|
|
41
|
+
import { FireweaveLocalWebAdapter } from '../infrastructure/adapters/local.js';
|
|
42
|
+
import { FireweaveRemoteWebAdapter } from '../infrastructure/adapters/remote.js';
|
|
43
|
+
import { assertHostAllowed } from '../infrastructure/hosts.js';
|
|
44
|
+
import { validateInitOptions } from '../domain/validation.js';
|
|
45
|
+
async function initLocal(options) {
|
|
46
|
+
const local = options.local ?? {};
|
|
47
|
+
const adapter = new FireweaveLocalWebAdapter({
|
|
48
|
+
devFlags: local.controlPoints ?? {},
|
|
49
|
+
...(local.log !== undefined ? { log: local.log } : {}),
|
|
50
|
+
});
|
|
51
|
+
const runtime = new FireweaveWebRuntime(adapter);
|
|
52
|
+
const client = new FireweaveWebClient(runtime);
|
|
53
|
+
await client.initialize(options.context);
|
|
54
|
+
return client;
|
|
55
|
+
}
|
|
56
|
+
async function initRemote(options) {
|
|
57
|
+
const { apiKey, apiUrl, allowedHosts, fetch } = options;
|
|
58
|
+
// `validateInitOptions` (called by `initFireweave`, below) has already
|
|
59
|
+
// ruled out blank apiKey/apiUrl by the time this runs — only the host
|
|
60
|
+
// allowlist row remains to check here. See the module doc comment: this
|
|
61
|
+
// call — not runtime.initialize() — is what makes a bad host fail LOUDLY
|
|
62
|
+
// here, because the runtime itself deliberately never throws.
|
|
63
|
+
assertHostAllowed(apiUrl, allowedHosts);
|
|
64
|
+
const adapter = new FireweaveRemoteWebAdapter({
|
|
65
|
+
apiUrl,
|
|
66
|
+
apiKey,
|
|
67
|
+
...(allowedHosts !== undefined ? { allowedHosts } : {}),
|
|
68
|
+
...(fetch !== undefined ? { fetch } : {}),
|
|
69
|
+
});
|
|
70
|
+
const runtime = new FireweaveWebRuntime(adapter);
|
|
71
|
+
const client = new FireweaveWebClient(runtime);
|
|
72
|
+
await client.initialize(options.context);
|
|
73
|
+
return client;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build the adapter matching `options.mode` and bring a
|
|
77
|
+
* {@link FireweaveWebClient} up.
|
|
78
|
+
*
|
|
79
|
+
* Throws {@link FireweaveError} (kind `Configuration`) for every row of the
|
|
80
|
+
* initialisation-validation table (spec/modes.md):
|
|
81
|
+
* - `mode` absent or unrecognised
|
|
82
|
+
* - `mode: 'remote'` with `apiKey` or `apiUrl` missing/blank
|
|
83
|
+
* - `apiUrl` fails the host allowlist
|
|
84
|
+
* - `mode: 'local'` with credentials supplied
|
|
85
|
+
*
|
|
86
|
+
* The first, second and fourth rows are `validateInitOptions`'s job
|
|
87
|
+
* (domain/validation.ts, shared discipline with node's `initFireweave`); the
|
|
88
|
+
* third is validated downstream, inside `initRemote`, for the reason the
|
|
89
|
+
* module doc comment explains — `FireweaveWebRuntime.initialize()` itself
|
|
90
|
+
* never throws.
|
|
91
|
+
*/
|
|
92
|
+
export async function initFireweave(options) {
|
|
93
|
+
const validated = validateInitOptions(options);
|
|
94
|
+
if (!validated.ok)
|
|
95
|
+
throw validated.error;
|
|
96
|
+
const validOptions = validated.value;
|
|
97
|
+
return validOptions.mode === 'local' ? initLocal(validOptions) : initRemote(validOptions);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mode.js","sourceRoot":"","sources":["../../src/application/mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AA8C9D,KAAK,UAAU,SAAS,CAAC,OAAkC;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;IAClC,MAAM,OAAO,GAAG,IAAI,wBAAwB,CAAC;QAC3C,QAAQ,EAAE,KAAK,CAAC,aAAa,IAAI,EAAE;QACnC,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvD,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,OAAmC;IAC3D,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IACxD,uEAAuE;IACvE,sEAAsE;IACtE,wEAAwE;IACxE,yEAAyE;IACzE,8DAA8D;IAC9D,iBAAiB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAExC,MAAM,OAAO,GAAG,IAAI,yBAAyB,CAAC;QAC5C,MAAM;QACN,MAAM;QACN,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1C,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,IAAI,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA6B;IAC/D,MAAM,SAAS,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,CAAC,SAAS,CAAC,EAAE;QAAE,MAAM,SAAS,CAAC,KAAK,CAAC;IACzC,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC;IACrC,OAAO,YAAY,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AAC5F,CAAC"}
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* Adapters translate canonical requests to the Fireweave remote protocol; they
|
|
15
15
|
* never see OpenFeature types.
|
|
16
16
|
*/
|
|
17
|
-
import type { CanonicalContext, DecisionReason, Exposure, FlagValueType, JsonValue, Signal } from '
|
|
18
|
-
import type { FireweaveError } from '
|
|
17
|
+
import type { CanonicalContext, DecisionReason, Exposure, FlagValueType, JsonValue, Signal } from '../domain/types.js';
|
|
18
|
+
import type { FireweaveError } from '../domain/errors.js';
|
|
19
|
+
import type { TargetKind } from '../domain/target.js';
|
|
19
20
|
export interface AdapterResolution {
|
|
20
21
|
found: boolean;
|
|
21
22
|
enabled?: boolean;
|
|
@@ -35,7 +36,6 @@ export interface PrefetchOptions {
|
|
|
35
36
|
readonly flagKeys?: readonly string[];
|
|
36
37
|
readonly signal?: AbortSignal;
|
|
37
38
|
}
|
|
38
|
-
export type TargetKind = 'user' | 'device';
|
|
39
39
|
export interface RegisterTargetOptions {
|
|
40
40
|
readonly kind?: TargetKind;
|
|
41
41
|
readonly properties?: Record<string, JsonValue>;
|
|
@@ -63,6 +63,21 @@ export interface AdapterRuntimeFeatures {
|
|
|
63
63
|
}
|
|
64
64
|
export interface WebBackendAdapter {
|
|
65
65
|
readonly name: 'fireweave' | 'inmemory' | 'other';
|
|
66
|
+
/**
|
|
67
|
+
* Miss-reason override for a control point ABSENT from the prefetch result
|
|
68
|
+
* (spec/modes.md "Behaviour per mode": local mode's unknown-key row is
|
|
69
|
+
* `default`/reason `DEFAULT`, not an error — unlike remote's
|
|
70
|
+
* `default`/`ERROR`/`FlagNotFound`).
|
|
71
|
+
*
|
|
72
|
+
* Node's per-call `resolve()` lets a miss carry its own `reason: 'DEFAULT'`
|
|
73
|
+
* on the resolution object itself. Web's adapter returns EVERY decision for
|
|
74
|
+
* a context in one batch (`prefetch`), so there is no per-key resolution
|
|
75
|
+
* object for a key that was never in the batch at all — the seam instead
|
|
76
|
+
* lives on the adapter. `FireweaveLocalWebAdapter` sets this to `'DEFAULT'`;
|
|
77
|
+
* every other adapter leaves it undefined and keeps the FlagNotFound/ERROR
|
|
78
|
+
* path (`FireweaveWebRuntime.evaluateSync` checks it with strict `===`).
|
|
79
|
+
*/
|
|
80
|
+
readonly missReason?: 'DEFAULT';
|
|
66
81
|
/** Bring the backend to a usable state. Reject with FireweaveError on failure. */
|
|
67
82
|
initialize(signal?: AbortSignal): Promise<void>;
|
|
68
83
|
/** Fetch every decision for a context. Throws FireweaveError on transport faults. */
|
|
@@ -84,4 +99,4 @@ export interface WebBackendAdapter {
|
|
|
84
99
|
shutdown(): Promise<void>;
|
|
85
100
|
features(): AdapterRuntimeFeatures;
|
|
86
101
|
}
|
|
87
|
-
//# sourceMappingURL=
|
|
102
|
+
//# sourceMappingURL=ports.d.ts.map
|