@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.
Files changed (89) hide show
  1. package/README.md +105 -0
  2. package/dist/application/client.d.ts +120 -0
  3. package/dist/application/client.d.ts.map +1 -0
  4. package/dist/application/client.js +160 -0
  5. package/dist/application/client.js.map +1 -0
  6. package/dist/application/mode.d.ts +100 -0
  7. package/dist/application/mode.d.ts.map +1 -0
  8. package/dist/application/mode.js +99 -0
  9. package/dist/application/mode.js.map +1 -0
  10. package/dist/{adapter.d.ts → application/ports.d.ts} +19 -4
  11. package/dist/application/ports.d.ts.map +1 -0
  12. package/dist/application/ports.js +2 -0
  13. package/dist/application/ports.js.map +1 -0
  14. package/dist/{runtime.d.ts → application/runtime.d.ts} +15 -6
  15. package/dist/application/runtime.d.ts.map +1 -0
  16. package/dist/{runtime.js → application/runtime.js} +80 -27
  17. package/dist/application/runtime.js.map +1 -0
  18. package/dist/domain/context.d.ts +52 -0
  19. package/dist/domain/context.d.ts.map +1 -0
  20. package/dist/domain/context.js +68 -0
  21. package/dist/domain/context.js.map +1 -0
  22. package/dist/domain/errors.d.ts.map +1 -0
  23. package/dist/domain/errors.js.map +1 -0
  24. package/dist/domain/target.d.ts +3 -0
  25. package/dist/domain/target.d.ts.map +1 -0
  26. package/dist/domain/target.js +2 -0
  27. package/dist/domain/target.js.map +1 -0
  28. package/dist/domain/types.d.ts.map +1 -0
  29. package/dist/domain/types.js.map +1 -0
  30. package/dist/domain/validation.d.ts +129 -0
  31. package/dist/domain/validation.d.ts.map +1 -0
  32. package/dist/domain/validation.js +350 -0
  33. package/dist/domain/validation.js.map +1 -0
  34. package/dist/index.d.ts +37 -31
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +25 -21
  37. package/dist/index.js.map +1 -1
  38. package/dist/{adapters → infrastructure/adapters}/inmemory.d.ts +3 -3
  39. package/dist/infrastructure/adapters/inmemory.d.ts.map +1 -0
  40. package/dist/{adapters → infrastructure/adapters}/inmemory.js +1 -1
  41. package/dist/infrastructure/adapters/inmemory.js.map +1 -0
  42. package/dist/infrastructure/adapters/local.d.ts +80 -0
  43. package/dist/infrastructure/adapters/local.d.ts.map +1 -0
  44. package/dist/infrastructure/adapters/local.js +95 -0
  45. package/dist/infrastructure/adapters/local.js.map +1 -0
  46. package/dist/{adapters → infrastructure/adapters}/remote.d.ts +2 -2
  47. package/dist/infrastructure/adapters/remote.d.ts.map +1 -0
  48. package/dist/{adapters → infrastructure/adapters}/remote.js +9 -15
  49. package/dist/infrastructure/adapters/remote.js.map +1 -0
  50. package/dist/infrastructure/hosts.d.ts.map +1 -0
  51. package/dist/{hosts.js → infrastructure/hosts.js} +1 -1
  52. package/dist/infrastructure/hosts.js.map +1 -0
  53. package/package.json +16 -11
  54. package/dist/adapter.d.ts.map +0 -1
  55. package/dist/adapter.js +0 -2
  56. package/dist/adapter.js.map +0 -1
  57. package/dist/adapters/inmemory.d.ts.map +0 -1
  58. package/dist/adapters/inmemory.js.map +0 -1
  59. package/dist/adapters/local.d.ts +0 -38
  60. package/dist/adapters/local.d.ts.map +0 -1
  61. package/dist/adapters/local.js +0 -51
  62. package/dist/adapters/local.js.map +0 -1
  63. package/dist/adapters/remote.d.ts.map +0 -1
  64. package/dist/adapters/remote.js.map +0 -1
  65. package/dist/client.d.ts +0 -134
  66. package/dist/client.d.ts.map +0 -1
  67. package/dist/client.js +0 -0
  68. package/dist/client.js.map +0 -1
  69. package/dist/context.d.ts +0 -26
  70. package/dist/context.d.ts.map +0 -1
  71. package/dist/context.js +0 -106
  72. package/dist/context.js.map +0 -1
  73. package/dist/errors.d.ts.map +0 -1
  74. package/dist/errors.js.map +0 -1
  75. package/dist/hosts.d.ts.map +0 -1
  76. package/dist/hosts.js.map +0 -1
  77. package/dist/provider.d.ts +0 -39
  78. package/dist/provider.d.ts.map +0 -1
  79. package/dist/provider.js +0 -93
  80. package/dist/provider.js.map +0 -1
  81. package/dist/runtime.d.ts.map +0 -1
  82. package/dist/runtime.js.map +0 -1
  83. package/dist/types.d.ts.map +0 -1
  84. package/dist/types.js.map +0 -1
  85. /package/dist/{errors.d.ts → domain/errors.d.ts} +0 -0
  86. /package/dist/{errors.js → domain/errors.js} +0 -0
  87. /package/dist/{types.d.ts → domain/types.d.ts} +0 -0
  88. /package/dist/{types.js → domain/types.js} +0 -0
  89. /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":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAYrD,SAAS,OAAO,CAAC,KAAqB,EAAE,QAAQ,GAAG,KAAK;IACtD,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAChF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,OAA4B;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IACjC,IAAI,KAAK,KAAK,UAAU;QAAE,OAAO,IAAI,cAAc,CAAC,eAAe,CAAC,CAAC;IACrE,IAAI,KAAK,KAAK,eAAe,IAAI,KAAK,KAAK,cAAc;QAAE,OAAO,IAAI,cAAc,CAAC,UAAU,CAAC,CAAC;IACjG,OAAO,SAAS,CAAC;AACnB,CAAC;AAoBD;;;;GAIG;AACH,MAAM,OAAO,mBAAmB;IACb,OAAO,CAAsB;IAE9C,YAAY,OAA4B;QACtC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,QAAQ,CACN,OAAe,EACf,YAA8B,EAC9B,YAAuB,EACvB,OAAsB,EACtB,QAA0B;QAE1B,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IACjF,CAAC;IAED,eAAe,CAAC,OAAe,EAAE,YAAqB,EAAE,OAAsB;QAC5E,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,KAAgB,CAAC;IACnF,CAAC;IAED,cAAc,CAAC,OAAe,EAAE,YAAoB,EAAE,OAAsB;QAC1E,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,KAAe,CAAC;IACjF,CAAC;IAED,cAAc,CAAC,OAAe,EAAE,YAAoB,EAAE,OAAsB;QAC1E,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,KAAe,CAAC;IACjF,CAAC;IAED,cAAc,CAAC,OAAe,EAAE,YAAuB,EAAE,OAAsB;QAC7E,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC;IACvE,CAAC;IAED;;;;;;OAMG;IACH,iBAAiB,CAAC,OAAe,EAAE,YAAqB,EAAE,OAAsB;QAC9E,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IAClE,CAAC;IAED,gBAAgB,CAAC,OAAe,EAAE,YAAoB,EAAE,OAAsB;QAC5E,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IACjE,CAAC;IAED,gBAAgB,CAAC,OAAe,EAAE,YAAoB,EAAE,OAAsB;QAC5E,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IACjE,CAAC;IAED,gBAAgB,CAAC,OAAe,EAAE,YAAuB,EAAE,OAAsB;QAC/E,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IACjE,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,sBAAsB,GAAsB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAIpE,MAAM,OAAO,kBAAkB;IACpB,OAAO,CAAsB;IAC7B,aAAa,CAAsB;IAE5C;;;;;;;;;;;OAWG;IACH,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,YAAY,OAA4B,EAAE,WAAsC,EAAE;QAChF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACxD,CAAC;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,UAAkB,EAAE,KAAiC;QACpE,IAAI,CAAC,sBAAsB,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YACjD,OAAO,OAAO,CAAC,IAAI,cAAc,CAAC,uBAAuB,CAAC,EAAE,IAAI,CAAC,CAAC;QACpE,CAAC;QACD,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,UAAU,CAAC,OAAsB;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED;;;;;;;OAOG;IACH,cAAc,CACZ,YAAoB,EACpB,UAAiC,EAAE;QAEnC,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,QAAQ,CACZ,YAAoB,EACpB,UAAiC,EAAE;QAEnC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACxE,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;QAChD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IAChC,CAAC;CACF"}
@@ -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 './types.js';
18
- import type { FireweaveError } from './errors.js';
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=adapter.d.ts.map
102
+ //# sourceMappingURL=ports.d.ts.map