@fireweaveai/server-sdk-cjs 0.0.0-reserve.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/application/client.d.ts +77 -0
- package/dist/application/client.js +144 -0
- package/dist/application/mode.d.ts +70 -0
- package/dist/application/mode.js +74 -0
- package/dist/application/ports.d.ts +94 -0
- package/dist/application/ports.js +2 -0
- package/dist/application/runtime.d.ts +101 -0
- package/dist/application/runtime.js +346 -0
- package/dist/domain/context.d.ts +67 -0
- package/dist/domain/context.js +90 -0
- package/dist/domain/errors.d.ts +45 -0
- package/dist/domain/errors.js +99 -0
- package/dist/domain/target.d.ts +2 -0
- package/dist/domain/target.js +2 -0
- package/dist/domain/types.d.ts +107 -0
- package/dist/domain/types.js +2 -0
- package/dist/domain/validation.d.ts +124 -0
- package/dist/domain/validation.js +355 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +52 -0
- package/dist/infrastructure/adapters/inmemory.d.ts +77 -0
- package/dist/infrastructure/adapters/inmemory.js +151 -0
- package/dist/infrastructure/adapters/local.d.ts +108 -0
- package/dist/infrastructure/adapters/local.js +105 -0
- package/dist/infrastructure/adapters/remote.d.ts +74 -0
- package/dist/infrastructure/adapters/remote.js +373 -0
- package/dist/infrastructure/hosts.d.ts +18 -0
- package/dist/infrastructure/hosts.js +72 -0
- package/dist/register-browser.d.ts +1 -0
- package/dist/register-browser.js +7 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +13 -0
- package/dist/start/browser.d.ts +12 -0
- package/dist/start/browser.js +36 -0
- package/dist/start/build-info.d.ts +2 -0
- package/dist/start/build-info.js +9 -0
- package/dist/start/control-points.d.ts +20 -0
- package/dist/start/control-points.js +59 -0
- package/dist/start/env.d.ts +11 -0
- package/dist/start/env.js +64 -0
- package/dist/start/fw.d.ts +29 -0
- package/dist/start/fw.js +76 -0
- package/dist/start/index.d.ts +23 -0
- package/dist/start/index.js +28 -0
- package/dist/start/instance.d.ts +17 -0
- package/dist/start/instance.js +56 -0
- package/dist/start/names.d.ts +22 -0
- package/dist/start/names.js +25 -0
- package/dist/start/resolve.d.ts +40 -0
- package/dist/start/resolve.js +139 -0
- package/dist/start/state.d.ts +96 -0
- package/dist/start/state.js +303 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fireweave AI, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
> **This is the CommonJS build of [`@fireweaveai/server-sdk`](https://www.npmjs.com/package/@fireweaveai/server-sdk).**
|
|
2
|
+
> Use it only when your project cannot load ES modules (see
|
|
3
|
+
> [Module formats](https://github.com/FireWeave-HQ/fireweave-sdk/blob/main/docs/module-formats.md)).
|
|
4
|
+
> Same code, same version, same import paths. ES modules are the long-term format;
|
|
5
|
+
> this package will be retired in a future major release.
|
|
6
|
+
|
|
7
|
+
<a href="https://app.fireweave.ai">
|
|
8
|
+
<picture>
|
|
9
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/FireWeave-HQ/fireweave-sdk/main/.github/assets/banner-dark.png">
|
|
10
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/FireWeave-HQ/fireweave-sdk/main/.github/assets/banner-light.png">
|
|
11
|
+
<img alt="Fireweave — agents write the code. We watch every rollout, under control." src="https://raw.githubusercontent.com/FireWeave-HQ/fireweave-sdk/main/.github/assets/banner-light.png" width="100%">
|
|
12
|
+
</picture>
|
|
13
|
+
</a>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="https://www.npmjs.com/package/@fireweaveai/server-sdk"><img alt="node" src="https://img.shields.io/npm/v/@fireweaveai/server-sdk?label=node&logo=nodedotjs&style=flat&labelColor=0E0E10&color=ED5502&logoColor=white"></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
**Fireweave is the AI release engineer for teams shipping with coding agents.** Every change rolls out behind a control point, one step at a time, with guardrails watching real users — and if something breaks, Fireweave pauses or rolls it back before most users ever see it. It's the only way to ship as fast as your agents write code, without the fear of breaking production.
|
|
20
|
+
|
|
21
|
+
## Quick start · Node.js · Bun · Deno
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @fireweaveai/server-sdk
|
|
25
|
+
# bun add @fireweaveai/server-sdk · deno add npm:@fireweaveai/server-sdk
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { initFireweave } from '@fireweaveai/server-sdk';
|
|
30
|
+
|
|
31
|
+
const fireweave = await initFireweave({
|
|
32
|
+
mode: 'remote',
|
|
33
|
+
apiUrl: process.env.FW_API_URL!,
|
|
34
|
+
apiKey: process.env.FW_PROJECT_API_KEY!,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Once per user: the facts your targeting rules match on.
|
|
38
|
+
await fireweave.registerTarget('user_42', { kind: 'user', properties: { plan: 'pro' } });
|
|
39
|
+
|
|
40
|
+
// Per request: evaluate a control point.
|
|
41
|
+
const enabled = await fireweave.controlPoints.getBooleanValue('new-checkout', false, {
|
|
42
|
+
targetingKey: 'user_42',
|
|
43
|
+
});
|
|
44
|
+
console.log('new-checkout:', enabled);
|
|
45
|
+
|
|
46
|
+
await fireweave.shutdown();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Learn more
|
|
50
|
+
|
|
51
|
+
[Concepts](https://github.com/FireWeave-HQ/fireweave-sdk/blob/main/docs/concepts.md) · [Identity](https://github.com/FireWeave-HQ/fireweave-sdk/blob/main/docs/identity.md) · [Testing](https://github.com/FireWeave-HQ/fireweave-sdk/blob/main/docs/testing.md) · [Troubleshooting](https://github.com/FireWeave-HQ/fireweave-sdk/blob/main/docs/troubleshooting.md) · [Module formats (CommonJS)](https://github.com/FireWeave-HQ/fireweave-sdk/blob/main/docs/module-formats.md) · [All SDKs](https://github.com/FireWeave-HQ/fireweave-sdk#quick-start)
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
[MIT](https://github.com/FireWeave-HQ/fireweave-sdk/blob/main/LICENSE)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FireweaveClient — control-point evaluation and target registration
|
|
3
|
+
* (spec/control-points.md): the only two v1 capabilities. Facade methods
|
|
4
|
+
* degrade instead of throwing.
|
|
5
|
+
*/
|
|
6
|
+
import { type FireweaveErrorKind } from '../domain/errors.js';
|
|
7
|
+
import type { EvaluateOptions, ExpectedFlagType, FireweaveRuntime } from './runtime.js';
|
|
8
|
+
import type { ContextInput } from '../domain/context.js';
|
|
9
|
+
import type { RegisterTargetOptions, RegisterTargetResult } from './ports.js';
|
|
10
|
+
import type { Decision, JsonValue } from '../domain/types.js';
|
|
11
|
+
export interface ExtensionResult {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
errorKind?: FireweaveErrorKind;
|
|
14
|
+
errorCode?: string;
|
|
15
|
+
errorMessage?: string;
|
|
16
|
+
degraded?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Control-point evaluation on the public client surface (ruling 16):
|
|
20
|
+
* Decision-returning evaluation without reaching into the runtime. Never throws
|
|
21
|
+
* — errors surface as ERROR decisions, exactly like the OpenFeature path.
|
|
22
|
+
*
|
|
23
|
+
* "Control point" is the Fireweave product noun (ADR-0007), on the wire too
|
|
24
|
+
* (ADR-0013).
|
|
25
|
+
*/
|
|
26
|
+
export declare class ControlPointsApi {
|
|
27
|
+
private readonly runtime;
|
|
28
|
+
constructor(runtime: FireweaveRuntime);
|
|
29
|
+
/** Evaluate a flag to a canonical Decision (detailed evaluation). */
|
|
30
|
+
evaluate(controlPointKey: string, expectedType: ExpectedFlagType, defaultValue: JsonValue, context?: ContextInput, options?: EvaluateOptions): Promise<Decision>;
|
|
31
|
+
getBooleanValue(controlPointKey: string, defaultValue: boolean, context?: ContextInput): Promise<boolean>;
|
|
32
|
+
getStringValue(controlPointKey: string, defaultValue: string, context?: ContextInput): Promise<string>;
|
|
33
|
+
getNumberValue(controlPointKey: string, defaultValue: number, context?: ContextInput): Promise<number>;
|
|
34
|
+
getObjectValue(controlPointKey: string, defaultValue: JsonValue, context?: ContextInput): Promise<JsonValue>;
|
|
35
|
+
/**
|
|
36
|
+
* Detailed reads — the whole {@link Decision} rather than just its value.
|
|
37
|
+
*
|
|
38
|
+
* Same arguments as the `*Value` pair above, so a caller upgrades from one to
|
|
39
|
+
* the other without restructuring the call. Required by
|
|
40
|
+
* `spec/control-points.md`; before ADR-0010 the only route to `reason`,
|
|
41
|
+
* `variant` or `error` was the OpenFeature provider's `resolve*Evaluation`,
|
|
42
|
+
* so detailed evaluation was unreachable without installing OpenFeature.
|
|
43
|
+
*/
|
|
44
|
+
getBooleanDetails(controlPointKey: string, defaultValue: boolean, context?: ContextInput): Promise<Decision>;
|
|
45
|
+
getStringDetails(controlPointKey: string, defaultValue: string, context?: ContextInput): Promise<Decision>;
|
|
46
|
+
getNumberDetails(controlPointKey: string, defaultValue: number, context?: ContextInput): Promise<Decision>;
|
|
47
|
+
getObjectDetails(controlPointKey: string, defaultValue: JsonValue, context?: ContextInput): Promise<Decision>;
|
|
48
|
+
}
|
|
49
|
+
export interface FireweaveClientOptions {
|
|
50
|
+
}
|
|
51
|
+
export declare class FireweaveClient {
|
|
52
|
+
readonly runtime: FireweaveRuntime;
|
|
53
|
+
readonly controlPoints: ControlPointsApi;
|
|
54
|
+
constructor(runtime: FireweaveRuntime, _options?: FireweaveClientOptions);
|
|
55
|
+
/**
|
|
56
|
+
* Register durable targeting facts for a target (spec/modes.md).
|
|
57
|
+
*
|
|
58
|
+
* Resolves `{ ok: false }` rather than throwing: this runs in sign-in paths,
|
|
59
|
+
* where a targeting concern must not break authentication. A careful caller
|
|
60
|
+
* logs a false — a silently unregistered target is how targeting rules end
|
|
61
|
+
* up matching nobody.
|
|
62
|
+
*
|
|
63
|
+
* In local mode this records in-process and traces the call; nothing reaches
|
|
64
|
+
* fw-server. See `FireweaveLocalAdapter.registerTarget`.
|
|
65
|
+
*/
|
|
66
|
+
registerTarget(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Dynamic capability dispatch. Unknown capabilities — currently all of
|
|
69
|
+
* them, v1's SUPPORTED_CAPABILITIES is empty — degrade with
|
|
70
|
+
* UnsupportedCapability, never throw (fixture
|
|
71
|
+
* ext-unsupported-capability-degrade). Any future capability listed in
|
|
72
|
+
* SUPPORTED_CAPABILITIES is lifecycle-gated the same way (ruling 17).
|
|
73
|
+
*/
|
|
74
|
+
invokeCapability(capability: string, _args?: Record<string, JsonValue>): ExtensionResult;
|
|
75
|
+
initialize(signal?: AbortSignal): Promise<void>;
|
|
76
|
+
shutdown(): Promise<void>;
|
|
77
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FireweaveClient = exports.ControlPointsApi = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* FireweaveClient — control-point evaluation and target registration
|
|
6
|
+
* (spec/control-points.md): the only two v1 capabilities. Facade methods
|
|
7
|
+
* degrade instead of throwing.
|
|
8
|
+
*/
|
|
9
|
+
const errors_js_1 = require("../domain/errors.js");
|
|
10
|
+
const failure = (err, degraded = false) => ({
|
|
11
|
+
ok: false,
|
|
12
|
+
errorKind: err.kind,
|
|
13
|
+
errorCode: err.openFeatureErrorCode,
|
|
14
|
+
errorMessage: err.message,
|
|
15
|
+
...(degraded ? { degraded: true } : {}),
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Lifecycle gate for extension calls (ruling 17, Go/Java model; fixture
|
|
19
|
+
* ext-lifecycle-gating): READY/STALE proceed; post-shutdown degrades
|
|
20
|
+
* AlreadyClosed; pre-ready degrades UnsupportedCapability. Gated methods never
|
|
21
|
+
* throw — failures come back as structured, degraded results.
|
|
22
|
+
*/
|
|
23
|
+
const lifecycleGate = (runtime) => {
|
|
24
|
+
switch (runtime.getState()) {
|
|
25
|
+
case 'READY':
|
|
26
|
+
case 'STALE':
|
|
27
|
+
return undefined;
|
|
28
|
+
case 'SHUTDOWN':
|
|
29
|
+
return new errors_js_1.FireweaveError('AlreadyClosed');
|
|
30
|
+
default:
|
|
31
|
+
return new errors_js_1.FireweaveError('UnsupportedCapability');
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Control-point evaluation on the public client surface (ruling 16):
|
|
36
|
+
* Decision-returning evaluation without reaching into the runtime. Never throws
|
|
37
|
+
* — errors surface as ERROR decisions, exactly like the OpenFeature path.
|
|
38
|
+
*
|
|
39
|
+
* "Control point" is the Fireweave product noun (ADR-0007), on the wire too
|
|
40
|
+
* (ADR-0013).
|
|
41
|
+
*/
|
|
42
|
+
class ControlPointsApi {
|
|
43
|
+
runtime;
|
|
44
|
+
constructor(runtime) {
|
|
45
|
+
this.runtime = runtime;
|
|
46
|
+
}
|
|
47
|
+
/** Evaluate a flag to a canonical Decision (detailed evaluation). */
|
|
48
|
+
evaluate(controlPointKey, expectedType, defaultValue, context, options) {
|
|
49
|
+
return this.runtime.evaluate(controlPointKey, expectedType, defaultValue, context, options ?? {});
|
|
50
|
+
}
|
|
51
|
+
async getBooleanValue(controlPointKey, defaultValue, context) {
|
|
52
|
+
const decision = await this.evaluate(controlPointKey, 'boolean', defaultValue, context);
|
|
53
|
+
return decision.value;
|
|
54
|
+
}
|
|
55
|
+
async getStringValue(controlPointKey, defaultValue, context) {
|
|
56
|
+
const decision = await this.evaluate(controlPointKey, 'string', defaultValue, context);
|
|
57
|
+
return decision.value;
|
|
58
|
+
}
|
|
59
|
+
async getNumberValue(controlPointKey, defaultValue, context) {
|
|
60
|
+
const decision = await this.evaluate(controlPointKey, 'number', defaultValue, context);
|
|
61
|
+
return decision.value;
|
|
62
|
+
}
|
|
63
|
+
async getObjectValue(controlPointKey, defaultValue, context) {
|
|
64
|
+
const decision = await this.evaluate(controlPointKey, 'object', defaultValue, context);
|
|
65
|
+
return decision.value;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Detailed reads — the whole {@link Decision} rather than just its value.
|
|
69
|
+
*
|
|
70
|
+
* Same arguments as the `*Value` pair above, so a caller upgrades from one to
|
|
71
|
+
* the other without restructuring the call. Required by
|
|
72
|
+
* `spec/control-points.md`; before ADR-0010 the only route to `reason`,
|
|
73
|
+
* `variant` or `error` was the OpenFeature provider's `resolve*Evaluation`,
|
|
74
|
+
* so detailed evaluation was unreachable without installing OpenFeature.
|
|
75
|
+
*/
|
|
76
|
+
getBooleanDetails(controlPointKey, defaultValue, context) {
|
|
77
|
+
return this.evaluate(controlPointKey, 'boolean', defaultValue, context);
|
|
78
|
+
}
|
|
79
|
+
getStringDetails(controlPointKey, defaultValue, context) {
|
|
80
|
+
return this.evaluate(controlPointKey, 'string', defaultValue, context);
|
|
81
|
+
}
|
|
82
|
+
getNumberDetails(controlPointKey, defaultValue, context) {
|
|
83
|
+
return this.evaluate(controlPointKey, 'number', defaultValue, context);
|
|
84
|
+
}
|
|
85
|
+
getObjectDetails(controlPointKey, defaultValue, context) {
|
|
86
|
+
return this.evaluate(controlPointKey, 'object', defaultValue, context);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.ControlPointsApi = ControlPointsApi;
|
|
90
|
+
/**
|
|
91
|
+
* Names invokeCapability will dispatch instead of degrading with
|
|
92
|
+
* UnsupportedCapability. Empty in v1: releases, exposures, signals,
|
|
93
|
+
* capabilities discovery, and guardrails are all out of scope
|
|
94
|
+
* (spec/control-points.md) and MUST NOT be exposed, so a cut namespace's
|
|
95
|
+
* capability string resolves exactly like any other unknown string.
|
|
96
|
+
*/
|
|
97
|
+
const SUPPORTED_CAPABILITIES = Object.freeze([]);
|
|
98
|
+
class FireweaveClient {
|
|
99
|
+
runtime;
|
|
100
|
+
controlPoints;
|
|
101
|
+
constructor(runtime, _options = {}) {
|
|
102
|
+
this.runtime = runtime;
|
|
103
|
+
this.controlPoints = new ControlPointsApi(runtime);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Register durable targeting facts for a target (spec/modes.md).
|
|
107
|
+
*
|
|
108
|
+
* Resolves `{ ok: false }` rather than throwing: this runs in sign-in paths,
|
|
109
|
+
* where a targeting concern must not break authentication. A careful caller
|
|
110
|
+
* logs a false — a silently unregistered target is how targeting rules end
|
|
111
|
+
* up matching nobody.
|
|
112
|
+
*
|
|
113
|
+
* In local mode this records in-process and traces the call; nothing reaches
|
|
114
|
+
* fw-server. See `FireweaveLocalAdapter.registerTarget`.
|
|
115
|
+
*/
|
|
116
|
+
registerTarget(targetingKey, options = {}) {
|
|
117
|
+
return this.runtime.registerTarget(targetingKey, options);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Dynamic capability dispatch. Unknown capabilities — currently all of
|
|
121
|
+
* them, v1's SUPPORTED_CAPABILITIES is empty — degrade with
|
|
122
|
+
* UnsupportedCapability, never throw (fixture
|
|
123
|
+
* ext-unsupported-capability-degrade). Any future capability listed in
|
|
124
|
+
* SUPPORTED_CAPABILITIES is lifecycle-gated the same way (ruling 17).
|
|
125
|
+
*/
|
|
126
|
+
invokeCapability(capability, _args) {
|
|
127
|
+
if (!SUPPORTED_CAPABILITIES.includes(capability)) {
|
|
128
|
+
return failure(new errors_js_1.FireweaveError('UnsupportedCapability'), true);
|
|
129
|
+
}
|
|
130
|
+
if (capability !== 'capabilities.get') {
|
|
131
|
+
const gateError = lifecycleGate(this.runtime);
|
|
132
|
+
if (gateError !== undefined)
|
|
133
|
+
return failure(gateError, true);
|
|
134
|
+
}
|
|
135
|
+
return { ok: true };
|
|
136
|
+
}
|
|
137
|
+
async initialize(signal) {
|
|
138
|
+
await this.runtime.initialize(signal);
|
|
139
|
+
}
|
|
140
|
+
async shutdown() {
|
|
141
|
+
await this.runtime.shutdown();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.FireweaveClient = FireweaveClient;
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
* BackendAdapter port, so FireweaveClient / FireweaveRuntime stay mode-blind).
|
|
11
|
+
*
|
|
12
|
+
* Initialisation fails loudly (throws); reads on the returned client never do
|
|
13
|
+
* (spec/control-points.md "initialise is the exception"). The validation
|
|
14
|
+
* itself lives in `validateInitOptions` (validation.ts), which returns
|
|
15
|
+
* `Validated<T>` like every other validator — this function is what converts
|
|
16
|
+
* a failed `Validated` into the throw spec/modes.md requires.
|
|
17
|
+
*/
|
|
18
|
+
import { FireweaveClient } from './client.js';
|
|
19
|
+
import type { FireweaveRemoteAdapterOptions } from '../infrastructure/adapters/remote.js';
|
|
20
|
+
export interface InitFireweaveRemoteOptions {
|
|
21
|
+
/** Evaluate against fw-server over the network (spec/remote-protocol.md). */
|
|
22
|
+
readonly mode: 'remote';
|
|
23
|
+
/** Fireweave project/runtime key (project-api-key_…). Required — never read from env. */
|
|
24
|
+
readonly apiKey: string;
|
|
25
|
+
/** fw-server base URL. Required — never read from env. */
|
|
26
|
+
readonly apiUrl: string;
|
|
27
|
+
/**
|
|
28
|
+
* SSRF allowlist override (spec/modes.md "apiUrl fails the host allowlist").
|
|
29
|
+
* Default: the canonical Fireweave hosts + loopback (`DEFAULT_ALLOWED_HOSTS`).
|
|
30
|
+
* A self-hosted fw-server must list its own host explicitly; `['*']` opts out.
|
|
31
|
+
*/
|
|
32
|
+
readonly allowedHosts?: readonly string[];
|
|
33
|
+
/** Injected transport (tests). Production uses the runtime's global `fetch`. */
|
|
34
|
+
readonly fetch?: FireweaveRemoteAdapterOptions['fetch'];
|
|
35
|
+
}
|
|
36
|
+
export interface InitFireweaveLocalOptions {
|
|
37
|
+
/** Evaluate against an in-process seeded map; no network (spec/modes.md). */
|
|
38
|
+
readonly mode: 'local';
|
|
39
|
+
readonly local?: {
|
|
40
|
+
/**
|
|
41
|
+
* Per-key boolean overrides — the seeded local map. A present key
|
|
42
|
+
* resolves with reason `STATIC`; an absent key misses so the caller's own
|
|
43
|
+
* default is used. May be empty or omitted entirely.
|
|
44
|
+
*/
|
|
45
|
+
readonly controlPoints?: Record<string, boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* Sink for the `[fireweave:local]` registerTarget trace line
|
|
48
|
+
* (spec/modes.md "registerTarget in local mode"). Defaults to
|
|
49
|
+
* `console.info`.
|
|
50
|
+
*/
|
|
51
|
+
readonly log?: (message: string) => void;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export type InitFireweaveOptions = InitFireweaveRemoteOptions | InitFireweaveLocalOptions;
|
|
55
|
+
/**
|
|
56
|
+
* Build the adapter matching `options.mode` and bring a {@link FireweaveClient}
|
|
57
|
+
* to READY.
|
|
58
|
+
*
|
|
59
|
+
* Throws {@link FireweaveError} (kind `Configuration`) for every row of the
|
|
60
|
+
* initialisation-validation table (spec/modes.md):
|
|
61
|
+
* - `mode` absent or unrecognised
|
|
62
|
+
* - `mode: 'remote'` with `apiKey` or `apiUrl` missing/blank
|
|
63
|
+
* - `apiUrl` fails the host allowlist
|
|
64
|
+
* - `mode: 'local'` with credentials supplied
|
|
65
|
+
*
|
|
66
|
+
* The first, second and fourth rows are `validateInitOptions`'s job; the
|
|
67
|
+
* third is validated downstream, when `runtime.initialize()` brings the
|
|
68
|
+
* adapter up (`FireweaveRuntime`'s own config validation, `hosts.ts`).
|
|
69
|
+
*/
|
|
70
|
+
export declare function initFireweave(options: InitFireweaveOptions): Promise<FireweaveClient>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initFireweave = initFireweave;
|
|
4
|
+
/**
|
|
5
|
+
* initFireweave — the single SDK entry point (spec/modes.md).
|
|
6
|
+
*
|
|
7
|
+
* `mode` is required and never inferred: a missing or mistyped credential
|
|
8
|
+
* must fail loudly at boot, not silently fall back to local evaluation —
|
|
9
|
+
* that failure mode looks like a green boot and a feature that never ramps.
|
|
10
|
+
* This function's only job is to validate the initialisation-time contract
|
|
11
|
+
* and select the matching adapter; nothing downstream branches on mode again
|
|
12
|
+
* (spec/modes.md "Behaviour per mode" — both adapters implement the same
|
|
13
|
+
* BackendAdapter port, so FireweaveClient / FireweaveRuntime stay mode-blind).
|
|
14
|
+
*
|
|
15
|
+
* Initialisation fails loudly (throws); reads on the returned client never do
|
|
16
|
+
* (spec/control-points.md "initialise is the exception"). The validation
|
|
17
|
+
* itself lives in `validateInitOptions` (validation.ts), which returns
|
|
18
|
+
* `Validated<T>` like every other validator — this function is what converts
|
|
19
|
+
* a failed `Validated` into the throw spec/modes.md requires.
|
|
20
|
+
*/
|
|
21
|
+
const client_js_1 = require("./client.js");
|
|
22
|
+
const runtime_js_1 = require("./runtime.js");
|
|
23
|
+
const local_js_1 = require("../infrastructure/adapters/local.js");
|
|
24
|
+
const remote_js_1 = require("../infrastructure/adapters/remote.js");
|
|
25
|
+
const validation_js_1 = require("../domain/validation.js");
|
|
26
|
+
async function initLocal(options) {
|
|
27
|
+
const local = options.local ?? {};
|
|
28
|
+
const adapter = new local_js_1.FireweaveLocalAdapter({
|
|
29
|
+
devFlags: local.controlPoints ?? {},
|
|
30
|
+
...(local.log !== undefined ? { log: local.log } : {}),
|
|
31
|
+
});
|
|
32
|
+
const runtime = new runtime_js_1.FireweaveRuntime(adapter);
|
|
33
|
+
await runtime.initialize();
|
|
34
|
+
return new client_js_1.FireweaveClient(runtime);
|
|
35
|
+
}
|
|
36
|
+
async function initRemote(options) {
|
|
37
|
+
const { apiKey, apiUrl, allowedHosts, fetch } = options;
|
|
38
|
+
const adapter = new remote_js_1.FireweaveRemoteAdapter({
|
|
39
|
+
apiKey,
|
|
40
|
+
apiUrl,
|
|
41
|
+
...(allowedHosts !== undefined ? { allowedHosts } : {}),
|
|
42
|
+
...(fetch !== undefined ? { fetch } : {}),
|
|
43
|
+
});
|
|
44
|
+
const runtime = new runtime_js_1.FireweaveRuntime(adapter, {
|
|
45
|
+
// The same host the adapter will actually call, gated through the
|
|
46
|
+
// canonical allowlist (hosts.ts) before any network I/O happens.
|
|
47
|
+
host: apiUrl,
|
|
48
|
+
...(allowedHosts !== undefined ? { allowedHosts } : {}),
|
|
49
|
+
});
|
|
50
|
+
await runtime.initialize();
|
|
51
|
+
return new client_js_1.FireweaveClient(runtime);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the adapter matching `options.mode` and bring a {@link FireweaveClient}
|
|
55
|
+
* to READY.
|
|
56
|
+
*
|
|
57
|
+
* Throws {@link FireweaveError} (kind `Configuration`) for every row of the
|
|
58
|
+
* initialisation-validation table (spec/modes.md):
|
|
59
|
+
* - `mode` absent or unrecognised
|
|
60
|
+
* - `mode: 'remote'` with `apiKey` or `apiUrl` missing/blank
|
|
61
|
+
* - `apiUrl` fails the host allowlist
|
|
62
|
+
* - `mode: 'local'` with credentials supplied
|
|
63
|
+
*
|
|
64
|
+
* The first, second and fourth rows are `validateInitOptions`'s job; the
|
|
65
|
+
* third is validated downstream, when `runtime.initialize()` brings the
|
|
66
|
+
* adapter up (`FireweaveRuntime`'s own config validation, `hosts.ts`).
|
|
67
|
+
*/
|
|
68
|
+
async function initFireweave(options) {
|
|
69
|
+
const validated = (0, validation_js_1.validateInitOptions)(options);
|
|
70
|
+
if (!validated.ok)
|
|
71
|
+
throw validated.error;
|
|
72
|
+
const validOptions = validated.value;
|
|
73
|
+
return validOptions.mode === 'local' ? initLocal(validOptions) : initRemote(validOptions);
|
|
74
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { FireweaveError } from '../domain/errors.js';
|
|
2
|
+
import type { CanonicalContext, DecisionReason, Exposure, FlagValueType, JsonValue, Signal } from '../domain/types.js';
|
|
3
|
+
import type { TargetKind } from '../domain/target.js';
|
|
4
|
+
export interface RegisterTargetOptions {
|
|
5
|
+
/** Defaults to 'user'. */
|
|
6
|
+
kind?: TargetKind;
|
|
7
|
+
/** Durable targeting facts: plan, beta membership, region, device model. */
|
|
8
|
+
properties?: Record<string, JsonValue>;
|
|
9
|
+
/** Client-declared environment (production, staging, …). */
|
|
10
|
+
environment?: string;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
export interface RegisterTargetResult {
|
|
14
|
+
/**
|
|
15
|
+
* `false` means the target was NOT registered — rules that depend on its
|
|
16
|
+
* properties will not match until a later attempt succeeds. Callers in a
|
|
17
|
+
* login path normally ignore this; a careful caller logs it, because a
|
|
18
|
+
* silently unregistered target is exactly how targeting rules end up
|
|
19
|
+
* matching nobody.
|
|
20
|
+
*/
|
|
21
|
+
readonly ok: boolean;
|
|
22
|
+
readonly error?: FireweaveError;
|
|
23
|
+
}
|
|
24
|
+
/** Result of a backend flag resolution (success path; faults throw FireweaveError). */
|
|
25
|
+
export interface AdapterResolution {
|
|
26
|
+
found: boolean;
|
|
27
|
+
/** Flag enabled state; false ⇒ DISABLED reason with the flag's stored value. */
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
value?: JsonValue;
|
|
30
|
+
variant?: string;
|
|
31
|
+
/** Declared flag type when known (in-memory fixtures); adapters may omit. */
|
|
32
|
+
flagType?: FlagValueType | 'integer' | 'float';
|
|
33
|
+
/**
|
|
34
|
+
* Adapter-suggested reason override (e.g. SPLIT, STALE) on a `found: true`
|
|
35
|
+
* resolution. On a `found: false` miss, `reason: 'DEFAULT'` instead signals
|
|
36
|
+
* the runtime to return the caller's default with reason `DEFAULT` — not an
|
|
37
|
+
* error — per local mode's unknown-key row (spec/modes.md; see
|
|
38
|
+
* FireweaveLocalAdapter.resolve).
|
|
39
|
+
*/
|
|
40
|
+
reason?: DecisionReason;
|
|
41
|
+
/** Vendor reason code (e.g. "condition_match"). */
|
|
42
|
+
reasonCode?: string;
|
|
43
|
+
/** Vendor condition index when the vendor reports one. */
|
|
44
|
+
conditionIndex?: number;
|
|
45
|
+
/** Flag definition version. */
|
|
46
|
+
version?: number;
|
|
47
|
+
/** Vendor-side numeric flag id. */
|
|
48
|
+
vendorControlPointId?: number;
|
|
49
|
+
/** Optional flag payload (object or pre-serialized JSON string). */
|
|
50
|
+
payload?: JsonValue;
|
|
51
|
+
/** True when the /flags response reported quota limiting. */
|
|
52
|
+
quotaLimited?: boolean;
|
|
53
|
+
/** True when served from a stale/last-good cache. */
|
|
54
|
+
fromCache?: boolean;
|
|
55
|
+
}
|
|
56
|
+
export interface ResolveOptions {
|
|
57
|
+
signal?: AbortSignal;
|
|
58
|
+
}
|
|
59
|
+
export interface AdapterRuntimeFeatures {
|
|
60
|
+
remoteEvaluation?: boolean;
|
|
61
|
+
localEvaluation?: boolean;
|
|
62
|
+
localOnly?: boolean;
|
|
63
|
+
exposureEmission?: boolean;
|
|
64
|
+
sideEffectFreeReads?: boolean;
|
|
65
|
+
groupAnalytics?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Backend adapter boundary. Adapters translate
|
|
69
|
+
* canonical requests to vendor protocols; they never see OpenFeature types.
|
|
70
|
+
*/
|
|
71
|
+
export interface BackendAdapter {
|
|
72
|
+
/** Third-party adapters use 'other'; the vendor is fw-server's concern. */
|
|
73
|
+
readonly name: 'inmemory' | 'fireweave' | 'other';
|
|
74
|
+
/** Bring the backend to a usable state. Reject with FireweaveError on failure. */
|
|
75
|
+
initialize(signal?: AbortSignal): Promise<void>;
|
|
76
|
+
/** Resolve one flag. Throws FireweaveError for transport/auth/parse faults. */
|
|
77
|
+
resolve(controlPointKey: string, context: CanonicalContext, options?: ResolveOptions): Promise<AdapterResolution>;
|
|
78
|
+
/**
|
|
79
|
+
* Register a user or device so rules can target its durable properties
|
|
80
|
+
* (optional capability). Resolves with `ok: false` rather than throwing —
|
|
81
|
+
* registration sits in login paths and must not break sign-in.
|
|
82
|
+
*/
|
|
83
|
+
registerTarget?(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
|
|
84
|
+
/** Record an exposure event (optional capability). */
|
|
85
|
+
recordExposure?(exposure: Exposure): void;
|
|
86
|
+
/** Deliver a telemetry signal to the backend sink (optional capability, ruling 17). */
|
|
87
|
+
recordSignal?(signal: Signal): void;
|
|
88
|
+
/** Flush buffered telemetry/exposures. */
|
|
89
|
+
flush?(): Promise<void>;
|
|
90
|
+
/** Release resources; must be idempotent. */
|
|
91
|
+
shutdown(): Promise<void>;
|
|
92
|
+
/** Runtime feature capabilities contributed by this adapter. */
|
|
93
|
+
features(): AdapterRuntimeFeatures;
|
|
94
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type ContextInput, type ContextLimits } from '../domain/context.js';
|
|
2
|
+
import { type ExpectedFlagType } from '../domain/validation.js';
|
|
3
|
+
import type { BackendAdapter, RegisterTargetOptions, RegisterTargetResult } from './ports.js';
|
|
4
|
+
import type { CanonicalContext, Decision, JsonValue, LifecycleState } from '../domain/types.js';
|
|
5
|
+
export type { ExpectedFlagType } from '../domain/validation.js';
|
|
6
|
+
export interface FireweaveRuntimeConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Fireweave project/runtime key (`project-api-key_…`). Adapters may also read
|
|
9
|
+
* it from their own options.
|
|
10
|
+
*/
|
|
11
|
+
projectApiKey?: string;
|
|
12
|
+
/** Backend host (must be http(s) and pass the allowlist). */
|
|
13
|
+
host?: string;
|
|
14
|
+
/**
|
|
15
|
+
* SSRF allowlist: hostnames the SDK may talk to. Empty/undefined ⇒ the
|
|
16
|
+
* canonical Fireweave default allowlist + loopback (DEFAULT_ALLOWED_HOSTS).
|
|
17
|
+
* Custom/self-hosted endpoints must be listed explicitly; ['*'] opts out.
|
|
18
|
+
*/
|
|
19
|
+
allowedHosts?: readonly string[];
|
|
20
|
+
/** Require targetingKey on every evaluation. */
|
|
21
|
+
requireTargetingKey?: boolean;
|
|
22
|
+
/** Context bound overrides (defaults are the ratified spec bounds). */
|
|
23
|
+
limits?: Partial<ContextLimits>;
|
|
24
|
+
/** Additional reserved attribute keys. */
|
|
25
|
+
reservedAttributeKeys?: readonly string[];
|
|
26
|
+
/** Shutdown flush deadline (ms). Default DEFAULT_SHUTDOWN_TIMEOUT_MS. */
|
|
27
|
+
shutdownTimeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
/** Default bound on shutdown/flush (matches capabilities.get()). */
|
|
30
|
+
export declare const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10000;
|
|
31
|
+
export interface EvaluateOptions {
|
|
32
|
+
/** Attach flag payload as fireweave.payload metadata (sorted-key JSON string). */
|
|
33
|
+
includePayload?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* When true, emit a Fireweave-owned exposure for a successful evaluation
|
|
36
|
+
* (H-4 / ADR-0001 §23 errata / ruling 20). Default `false` — phase-one
|
|
37
|
+
* evaluate is side-effect-free. Vendor `$feature_flag_called` stays
|
|
38
|
+
* suppressed on the local snapshot path (RB-2); Fireweave owns emission
|
|
39
|
+
* and dedup when opted in.
|
|
40
|
+
*/
|
|
41
|
+
sendExposure?: boolean;
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
}
|
|
44
|
+
/** JSON stringify with deterministic (sorted) key order at every level. */
|
|
45
|
+
export declare function stableStringify(value: JsonValue): string;
|
|
46
|
+
export declare class FireweaveRuntime {
|
|
47
|
+
readonly adapter: BackendAdapter;
|
|
48
|
+
readonly config: FireweaveRuntimeConfig;
|
|
49
|
+
private readonly contextPolicy;
|
|
50
|
+
private state;
|
|
51
|
+
private globalContext;
|
|
52
|
+
private clientContext;
|
|
53
|
+
private initPromise;
|
|
54
|
+
private readonly stateListeners;
|
|
55
|
+
/** Dedup window for evaluate/OF-path exposures (cleared on flush / shutdown). */
|
|
56
|
+
private readonly evaluateExposureSeen;
|
|
57
|
+
constructor(adapter: BackendAdapter, config?: FireweaveRuntimeConfig);
|
|
58
|
+
/** Clear evaluate-path exposure dedup (M-2 / clear-on-flush lifecycle). */
|
|
59
|
+
clearEvaluateExposureDedup(): void;
|
|
60
|
+
getState(): LifecycleState;
|
|
61
|
+
onStateChange(listener: (state: LifecycleState) => void): void;
|
|
62
|
+
private setState;
|
|
63
|
+
/** Mark definitions/cache staleness (adapter-driven). */
|
|
64
|
+
markStale(): void;
|
|
65
|
+
setGlobalContext(context: ContextInput | undefined): void;
|
|
66
|
+
setClientContext(context: ContextInput | undefined): void;
|
|
67
|
+
/**
|
|
68
|
+
* Initialize the runtime: validate config, then bring the adapter up.
|
|
69
|
+
* Rejects with FireweaveError; Configuration errors put the runtime in FATAL.
|
|
70
|
+
*/
|
|
71
|
+
initialize(signal?: AbortSignal): Promise<void>;
|
|
72
|
+
shutdown(): Promise<void>;
|
|
73
|
+
/** Canonicalize + validate the merged context (throws FireweaveError). */
|
|
74
|
+
resolveContext(invocationContext: ContextInput | undefined): CanonicalContext;
|
|
75
|
+
/**
|
|
76
|
+
* Evaluate a flag to a canonical Decision. Never throws.
|
|
77
|
+
*
|
|
78
|
+
* Validates in the fixed order spec/control-points.md "Validation, before
|
|
79
|
+
* any I/O" names, stopping at the first failure: (1) key, (2) default vs
|
|
80
|
+
* type, (3) context, (4) lifecycle. Only once all four pass does this
|
|
81
|
+
* reach the adapter (the one I/O call in this method).
|
|
82
|
+
*/
|
|
83
|
+
evaluate(controlPointKey: string, expectedType: ExpectedFlagType, defaultValue: JsonValue, invocationContext?: ContextInput, options?: EvaluateOptions): Promise<Decision>;
|
|
84
|
+
/**
|
|
85
|
+
* Register a user or device so flag rules can target its DURABLE properties.
|
|
86
|
+
*
|
|
87
|
+
* Call once per login / device provisioning with the facts that outlive a
|
|
88
|
+
* request (plan, beta membership, region, device model), then send the same
|
|
89
|
+
* `targetingKey` on evaluation. Per-request context attributes still override
|
|
90
|
+
* the registered properties for a single evaluation — the two identity paths
|
|
91
|
+
* compose (spec/remote-protocol.md § Two identity paths).
|
|
92
|
+
*
|
|
93
|
+
* Resolves with `ok: false` instead of throwing: this runs in sign-in paths.
|
|
94
|
+
* Adapters without the capability (in-memory, local dev) report
|
|
95
|
+
* `Unsupported` so a dev harness does not silently look registered.
|
|
96
|
+
*/
|
|
97
|
+
registerTarget(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
|
|
98
|
+
private emitEvaluateExposure;
|
|
99
|
+
private lifecycleError;
|
|
100
|
+
private errorDecision;
|
|
101
|
+
}
|