@fireweaveai/web-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 +52 -0
- package/dist/application/client.d.ts +104 -0
- package/dist/application/client.js +147 -0
- package/dist/application/mode.d.ts +99 -0
- package/dist/application/mode.js +101 -0
- package/dist/application/ports.d.ts +101 -0
- package/dist/application/ports.js +2 -0
- package/dist/application/runtime.d.ts +70 -0
- package/dist/application/runtime.js +348 -0
- package/dist/build/node/define.d.ts +14 -0
- package/dist/build/node/define.js +38 -0
- package/dist/build/node/shared.d.ts +19 -0
- package/dist/build/node/shared.js +39 -0
- package/dist/build/node/vite.d.ts +53 -0
- package/dist/build/node/vite.js +106 -0
- package/dist/build/src/start/build-info.d.ts +2 -0
- package/dist/build/src/start/build-info.js +9 -0
- package/dist/build/src/start/names.d.ts +38 -0
- package/dist/build/src/start/names.js +41 -0
- package/dist/build/src/start/policy.d.ts +70 -0
- package/dist/build/src/start/policy.js +128 -0
- package/dist/domain/context.d.ts +51 -0
- package/dist/domain/context.js +72 -0
- package/dist/domain/errors.d.ts +36 -0
- package/dist/domain/errors.js +97 -0
- package/dist/domain/target.d.ts +2 -0
- package/dist/domain/target.js +2 -0
- package/dist/domain/types.d.ts +89 -0
- package/dist/domain/types.js +10 -0
- package/dist/domain/validation.d.ts +128 -0
- package/dist/domain/validation.js +358 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +66 -0
- package/dist/infrastructure/adapters/inmemory.d.ts +50 -0
- package/dist/infrastructure/adapters/inmemory.js +94 -0
- package/dist/infrastructure/adapters/local.d.ts +79 -0
- package/dist/infrastructure/adapters/local.js +98 -0
- package/dist/infrastructure/adapters/remote.d.ts +44 -0
- package/dist/infrastructure/adapters/remote.js +329 -0
- package/dist/infrastructure/hosts.d.ts +10 -0
- package/dist/infrastructure/hosts.js +84 -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 +24 -0
- package/dist/start/control-points.js +61 -0
- package/dist/start/fw.d.ts +42 -0
- package/dist/start/fw.js +137 -0
- package/dist/start/identity.d.ts +19 -0
- package/dist/start/identity.js +93 -0
- package/dist/start/index.d.ts +34 -0
- package/dist/start/index.js +38 -0
- package/dist/start/names.d.ts +38 -0
- package/dist/start/names.js +41 -0
- package/dist/start/policy.d.ts +70 -0
- package/dist/start/policy.js +128 -0
- package/dist/start/state.d.ts +103 -0
- package/dist/start/state.js +375 -0
- package/package.json +68 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initFireweave = exports.WebControlPointsApi = exports.FireweaveWebClient = exports.DEFAULT_FLAGS_READY_TIMEOUT_MS = exports.FireweaveWebRuntime = exports.FireweaveLocalWebAdapter = exports.InMemoryWebAdapter = exports.FireweaveRemoteWebAdapter = exports.isLoopbackHostname = exports.assertNotSecretKey = exports.assertHostAllowed = exports.DEFAULT_ALLOWED_HOSTS = exports.validateInitOptions = exports.validateTargetingKey = exports.validateDefaultValue = exports.validateControlPointKey = exports.validateContext = exports.matchesExpectedType = exports.canonicalizeContext = exports.mergeContexts = exports.DEFAULT_RESERVED_ATTRIBUTE_KEYS = exports.DEFAULT_CONTEXT_LIMITS = exports.isFireweaveError = exports.ERROR_TAXONOMY = exports.FireweaveError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* @fireweaveai/web-sdk — Fireweave control points for the browser (ADR-0009).
|
|
6
|
+
*
|
|
7
|
+
* Remote-only and secret-free by construction: it never evaluates locally, has
|
|
8
|
+
* no vendor SDK dependency, reads no environment, and rejects vendor/secret key
|
|
9
|
+
* shapes at the door. Credentials are passed in explicitly by the embedding app.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { initFireweave } from '@fireweaveai/web-sdk';
|
|
13
|
+
*
|
|
14
|
+
* const fw = await initFireweave({
|
|
15
|
+
* mode: 'remote',
|
|
16
|
+
* apiKey,
|
|
17
|
+
* apiUrl,
|
|
18
|
+
* context: { targetingKey: 'anonymous' },
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Reads are SYNCHRONOUS — no await, safe inside render.
|
|
22
|
+
* const on = fw.controlPoints.getBooleanValue('new-checkout', false);
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* `src/` is layered domain/ · application/ · infrastructure/
|
|
26
|
+
* (test/unit/architecture-layers.test.ts guards the boundary): `domain/` is
|
|
27
|
+
* pure data + validation with no outward imports; `application/` wires
|
|
28
|
+
* domain types into the runtime/client surface, with `mode.ts` as the sole
|
|
29
|
+
* composition root allowed to reach into `infrastructure/`; `infrastructure/`
|
|
30
|
+
* holds the concrete adapters and the host allowlist.
|
|
31
|
+
*/
|
|
32
|
+
var errors_js_1 = require("./domain/errors.js");
|
|
33
|
+
Object.defineProperty(exports, "FireweaveError", { enumerable: true, get: function () { return errors_js_1.FireweaveError; } });
|
|
34
|
+
Object.defineProperty(exports, "ERROR_TAXONOMY", { enumerable: true, get: function () { return errors_js_1.ERROR_TAXONOMY; } });
|
|
35
|
+
Object.defineProperty(exports, "isFireweaveError", { enumerable: true, get: function () { return errors_js_1.isFireweaveError; } });
|
|
36
|
+
var context_js_1 = require("./domain/context.js");
|
|
37
|
+
Object.defineProperty(exports, "DEFAULT_CONTEXT_LIMITS", { enumerable: true, get: function () { return context_js_1.DEFAULT_CONTEXT_LIMITS; } });
|
|
38
|
+
Object.defineProperty(exports, "DEFAULT_RESERVED_ATTRIBUTE_KEYS", { enumerable: true, get: function () { return context_js_1.DEFAULT_RESERVED_ATTRIBUTE_KEYS; } });
|
|
39
|
+
Object.defineProperty(exports, "mergeContexts", { enumerable: true, get: function () { return context_js_1.mergeContexts; } });
|
|
40
|
+
var validation_js_1 = require("./domain/validation.js");
|
|
41
|
+
Object.defineProperty(exports, "canonicalizeContext", { enumerable: true, get: function () { return validation_js_1.canonicalizeContext; } });
|
|
42
|
+
Object.defineProperty(exports, "matchesExpectedType", { enumerable: true, get: function () { return validation_js_1.matchesExpectedType; } });
|
|
43
|
+
Object.defineProperty(exports, "validateContext", { enumerable: true, get: function () { return validation_js_1.validateContext; } });
|
|
44
|
+
Object.defineProperty(exports, "validateControlPointKey", { enumerable: true, get: function () { return validation_js_1.validateControlPointKey; } });
|
|
45
|
+
Object.defineProperty(exports, "validateDefaultValue", { enumerable: true, get: function () { return validation_js_1.validateDefaultValue; } });
|
|
46
|
+
Object.defineProperty(exports, "validateTargetingKey", { enumerable: true, get: function () { return validation_js_1.validateTargetingKey; } });
|
|
47
|
+
Object.defineProperty(exports, "validateInitOptions", { enumerable: true, get: function () { return validation_js_1.validateInitOptions; } });
|
|
48
|
+
var hosts_js_1 = require("./infrastructure/hosts.js");
|
|
49
|
+
Object.defineProperty(exports, "DEFAULT_ALLOWED_HOSTS", { enumerable: true, get: function () { return hosts_js_1.DEFAULT_ALLOWED_HOSTS; } });
|
|
50
|
+
Object.defineProperty(exports, "assertHostAllowed", { enumerable: true, get: function () { return hosts_js_1.assertHostAllowed; } });
|
|
51
|
+
Object.defineProperty(exports, "assertNotSecretKey", { enumerable: true, get: function () { return hosts_js_1.assertNotSecretKey; } });
|
|
52
|
+
Object.defineProperty(exports, "isLoopbackHostname", { enumerable: true, get: function () { return hosts_js_1.isLoopbackHostname; } });
|
|
53
|
+
var remote_js_1 = require("./infrastructure/adapters/remote.js");
|
|
54
|
+
Object.defineProperty(exports, "FireweaveRemoteWebAdapter", { enumerable: true, get: function () { return remote_js_1.FireweaveRemoteWebAdapter; } });
|
|
55
|
+
var inmemory_js_1 = require("./infrastructure/adapters/inmemory.js");
|
|
56
|
+
Object.defineProperty(exports, "InMemoryWebAdapter", { enumerable: true, get: function () { return inmemory_js_1.InMemoryWebAdapter; } });
|
|
57
|
+
var local_js_1 = require("./infrastructure/adapters/local.js");
|
|
58
|
+
Object.defineProperty(exports, "FireweaveLocalWebAdapter", { enumerable: true, get: function () { return local_js_1.FireweaveLocalWebAdapter; } });
|
|
59
|
+
var runtime_js_1 = require("./application/runtime.js");
|
|
60
|
+
Object.defineProperty(exports, "FireweaveWebRuntime", { enumerable: true, get: function () { return runtime_js_1.FireweaveWebRuntime; } });
|
|
61
|
+
Object.defineProperty(exports, "DEFAULT_FLAGS_READY_TIMEOUT_MS", { enumerable: true, get: function () { return runtime_js_1.DEFAULT_FLAGS_READY_TIMEOUT_MS; } });
|
|
62
|
+
var client_js_1 = require("./application/client.js");
|
|
63
|
+
Object.defineProperty(exports, "FireweaveWebClient", { enumerable: true, get: function () { return client_js_1.FireweaveWebClient; } });
|
|
64
|
+
Object.defineProperty(exports, "WebControlPointsApi", { enumerable: true, get: function () { return client_js_1.WebControlPointsApi; } });
|
|
65
|
+
var mode_js_1 = require("./application/mode.js");
|
|
66
|
+
Object.defineProperty(exports, "initFireweave", { enumerable: true, get: function () { return mode_js_1.initFireweave; } });
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory adapter — offline evaluation for tests and conformance fixtures.
|
|
3
|
+
*
|
|
4
|
+
* Declares control points up front and serves them from memory. Fault
|
|
5
|
+
* injection exists so the conformance suite can drive the error taxonomy
|
|
6
|
+
* without a network or a stub server.
|
|
7
|
+
*/
|
|
8
|
+
import { type FireweaveErrorKind } from '../../domain/errors.js';
|
|
9
|
+
import type { AdapterResolution, AdapterRuntimeFeatures, PrefetchOptions, PrefetchResult, WebBackendAdapter } from '../../application/ports.js';
|
|
10
|
+
import type { CanonicalContext, Exposure, FlagValueType, JsonValue, Signal } from '../../domain/types.js';
|
|
11
|
+
export interface InMemoryFlagDefinition {
|
|
12
|
+
readonly type: FlagValueType;
|
|
13
|
+
readonly enabled: boolean;
|
|
14
|
+
readonly value: JsonValue;
|
|
15
|
+
readonly variant?: string;
|
|
16
|
+
readonly payload?: JsonValue;
|
|
17
|
+
/** Only resolve for this targeting key; otherwise the control point misses. */
|
|
18
|
+
readonly matchTargetingKey?: string;
|
|
19
|
+
readonly fireweaveReason?: AdapterResolution['reason'];
|
|
20
|
+
}
|
|
21
|
+
export interface InMemoryFault {
|
|
22
|
+
readonly kind: FireweaveErrorKind;
|
|
23
|
+
/** Throw on initialize rather than on prefetch. */
|
|
24
|
+
readonly onInitialize?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface InMemoryWebAdapterOptions {
|
|
27
|
+
readonly flags?: Record<string, InMemoryFlagDefinition>;
|
|
28
|
+
readonly fault?: InMemoryFault;
|
|
29
|
+
}
|
|
30
|
+
export declare class InMemoryWebAdapter implements WebBackendAdapter {
|
|
31
|
+
readonly name: "inmemory";
|
|
32
|
+
private flags;
|
|
33
|
+
private fault;
|
|
34
|
+
private readonly exposures;
|
|
35
|
+
private readonly signals;
|
|
36
|
+
private closed;
|
|
37
|
+
constructor(options?: InMemoryWebAdapterOptions);
|
|
38
|
+
setFlags(flags: Record<string, InMemoryFlagDefinition>): void;
|
|
39
|
+
setFault(fault: InMemoryFault | undefined): void;
|
|
40
|
+
initialize(_signal?: AbortSignal): Promise<void>;
|
|
41
|
+
prefetch(context: CanonicalContext, options?: PrefetchOptions): Promise<PrefetchResult>;
|
|
42
|
+
recordExposure(exposure: Exposure): void;
|
|
43
|
+
getExposures(): readonly Exposure[];
|
|
44
|
+
recordSignal(signal: Signal): void;
|
|
45
|
+
getSignals(): readonly Signal[];
|
|
46
|
+
flush(): Promise<void>;
|
|
47
|
+
shutdown(): Promise<void>;
|
|
48
|
+
isClosed(): boolean;
|
|
49
|
+
features(): AdapterRuntimeFeatures;
|
|
50
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InMemoryWebAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* In-memory adapter — offline evaluation for tests and conformance fixtures.
|
|
6
|
+
*
|
|
7
|
+
* Declares control points up front and serves them from memory. Fault
|
|
8
|
+
* injection exists so the conformance suite can drive the error taxonomy
|
|
9
|
+
* without a network or a stub server.
|
|
10
|
+
*/
|
|
11
|
+
const errors_js_1 = require("../../domain/errors.js");
|
|
12
|
+
class InMemoryWebAdapter {
|
|
13
|
+
name = 'inmemory';
|
|
14
|
+
flags;
|
|
15
|
+
fault;
|
|
16
|
+
exposures = [];
|
|
17
|
+
signals = [];
|
|
18
|
+
closed = false;
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.flags = new Map(Object.entries(options.flags ?? {}));
|
|
21
|
+
this.fault = options.fault;
|
|
22
|
+
}
|
|
23
|
+
setFlags(flags) {
|
|
24
|
+
this.flags = new Map(Object.entries(flags));
|
|
25
|
+
}
|
|
26
|
+
setFault(fault) {
|
|
27
|
+
this.fault = fault;
|
|
28
|
+
}
|
|
29
|
+
async initialize(_signal) {
|
|
30
|
+
if (this.fault?.onInitialize === true)
|
|
31
|
+
throw new errors_js_1.FireweaveError(this.fault.kind);
|
|
32
|
+
}
|
|
33
|
+
async prefetch(context, options) {
|
|
34
|
+
if (this.fault !== undefined && this.fault.onInitialize !== true) {
|
|
35
|
+
throw new errors_js_1.FireweaveError(this.fault.kind);
|
|
36
|
+
}
|
|
37
|
+
const wanted = options?.controlPointKeys;
|
|
38
|
+
const out = new Map();
|
|
39
|
+
for (const [key, def] of this.flags) {
|
|
40
|
+
if (wanted !== undefined && wanted.length > 0 && !wanted.includes(key))
|
|
41
|
+
continue;
|
|
42
|
+
if (def.matchTargetingKey !== undefined && context.targetingKey !== def.matchTargetingKey) {
|
|
43
|
+
out.set(key, { found: false });
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const resolution = {
|
|
47
|
+
found: true,
|
|
48
|
+
enabled: def.enabled,
|
|
49
|
+
value: def.value,
|
|
50
|
+
flagType: def.type,
|
|
51
|
+
};
|
|
52
|
+
if (def.variant !== undefined)
|
|
53
|
+
resolution.variant = def.variant;
|
|
54
|
+
if (def.payload !== undefined)
|
|
55
|
+
resolution.payload = def.payload;
|
|
56
|
+
if (def.fireweaveReason !== undefined)
|
|
57
|
+
resolution.reason = def.fireweaveReason;
|
|
58
|
+
out.set(key, resolution);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
recordExposure(exposure) {
|
|
63
|
+
this.exposures.push(exposure);
|
|
64
|
+
}
|
|
65
|
+
getExposures() {
|
|
66
|
+
return this.exposures;
|
|
67
|
+
}
|
|
68
|
+
recordSignal(signal) {
|
|
69
|
+
this.signals.push(signal);
|
|
70
|
+
}
|
|
71
|
+
getSignals() {
|
|
72
|
+
return this.signals;
|
|
73
|
+
}
|
|
74
|
+
async flush() {
|
|
75
|
+
// no-op: telemetry stays queryable for tests
|
|
76
|
+
}
|
|
77
|
+
async shutdown() {
|
|
78
|
+
this.closed = true;
|
|
79
|
+
}
|
|
80
|
+
isClosed() {
|
|
81
|
+
return this.closed;
|
|
82
|
+
}
|
|
83
|
+
features() {
|
|
84
|
+
return {
|
|
85
|
+
remoteEvaluation: false,
|
|
86
|
+
localEvaluation: false,
|
|
87
|
+
localOnly: true,
|
|
88
|
+
exposureEmission: true,
|
|
89
|
+
sideEffectFreeReads: true,
|
|
90
|
+
groupAnalytics: false,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.InMemoryWebAdapter = InMemoryWebAdapter;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local development adapter for the browser — the DEV substrate of a
|
|
3
|
+
* scaffolded web harness, and the exact peer of the server SDK's
|
|
4
|
+
* `FireweaveLocalAdapter`.
|
|
5
|
+
*
|
|
6
|
+
* A key present in `devFlags` resolves with reason `STATIC`; every other key
|
|
7
|
+
* misses, which the runtime turns into the caller's own default. No network,
|
|
8
|
+
* no credentials, nothing to configure.
|
|
9
|
+
*
|
|
10
|
+
* Call-site defaults stay `false` under RAMP-1. Never write `flag(key, true)`
|
|
11
|
+
* to dogfood locally — that same `true` is the production fallback when a
|
|
12
|
+
* control point is absent from the provider, so it silently ships the ON
|
|
13
|
+
* branch. Use `devFlags`.
|
|
14
|
+
*/
|
|
15
|
+
import type { AdapterRuntimeFeatures, PrefetchOptions, PrefetchResult, RegisterTargetOptions, RegisterTargetResult, WebBackendAdapter } from '../../application/ports.js';
|
|
16
|
+
import type { TargetKind } from '../../domain/target.js';
|
|
17
|
+
import type { CanonicalContext } from '../../domain/types.js';
|
|
18
|
+
export interface FireweaveLocalWebAdapterOptions {
|
|
19
|
+
readonly devFlags?: Record<string, boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Sink for the `[fireweave:local]` registerTarget trace line
|
|
22
|
+
* (spec/modes.md "registerTarget in local mode"). Defaults to
|
|
23
|
+
* `console.info`.
|
|
24
|
+
*/
|
|
25
|
+
readonly log?: (message: string) => void;
|
|
26
|
+
}
|
|
27
|
+
/** A target recorded by {@link FireweaveLocalWebAdapter.registerTarget}. */
|
|
28
|
+
export interface LocalRegisteredTarget {
|
|
29
|
+
readonly targetingKey: string;
|
|
30
|
+
readonly kind: TargetKind;
|
|
31
|
+
readonly properties: Record<string, unknown>;
|
|
32
|
+
readonly environment?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare class FireweaveLocalWebAdapter implements WebBackendAdapter {
|
|
35
|
+
/** Not `inmemory` — that name belongs to the fixture adapter. */
|
|
36
|
+
readonly name: "other";
|
|
37
|
+
/**
|
|
38
|
+
* spec/modes.md "Behaviour per mode": local's unknown-key row is
|
|
39
|
+
* `default`/reason `DEFAULT`, not an error. This is the strict `===` seam
|
|
40
|
+
* `FireweaveWebRuntime.evaluateSync` checks on a cache miss.
|
|
41
|
+
*/
|
|
42
|
+
readonly missReason: "DEFAULT";
|
|
43
|
+
private readonly devFlags;
|
|
44
|
+
private readonly log;
|
|
45
|
+
private readonly targets;
|
|
46
|
+
private closed;
|
|
47
|
+
constructor(options?: FireweaveLocalWebAdapterOptions);
|
|
48
|
+
initialize(_signal?: AbortSignal): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* `enabled: true` alongside `reason: 'STATIC'` is deliberate: reporting
|
|
51
|
+
* `enabled: false` for an override of `false` would make the runtime label
|
|
52
|
+
* the decision `DISABLED`, which means "exists but switched off upstream" —
|
|
53
|
+
* not what a local override expresses.
|
|
54
|
+
*/
|
|
55
|
+
prefetch(_context: CanonicalContext, _options?: PrefetchOptions): Promise<PrefetchResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Records the target in-process and traces it, rather than reporting
|
|
58
|
+
* `UnsupportedCapability`.
|
|
59
|
+
*
|
|
60
|
+
* The capability could instead report Unsupported so a dev harness could
|
|
61
|
+
* not *silently* look registered — but the failure being guarded against
|
|
62
|
+
* is a developer believing their targeting works because nothing objected.
|
|
63
|
+
* A recorded target plus an explicit `[fireweave:local]` line preserves
|
|
64
|
+
* that guarantee by a different route: nothing is silent, and local dev
|
|
65
|
+
* can exercise targeting rules offline instead of only in production
|
|
66
|
+
* (spec/modes.md "registerTarget in local mode").
|
|
67
|
+
*
|
|
68
|
+
* The trace names the mode, so a line appearing in a production log is
|
|
69
|
+
* itself the signal that something booted in local mode by mistake.
|
|
70
|
+
*
|
|
71
|
+
* No network call is made and nothing reaches fw-server.
|
|
72
|
+
*/
|
|
73
|
+
registerTarget(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
|
|
74
|
+
/** Targets recorded this process, for assertions and dev inspection. */
|
|
75
|
+
getRegisteredTargets(): readonly LocalRegisteredTarget[];
|
|
76
|
+
shutdown(): Promise<void>;
|
|
77
|
+
isClosed(): boolean;
|
|
78
|
+
features(): AdapterRuntimeFeatures;
|
|
79
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FireweaveLocalWebAdapter = void 0;
|
|
4
|
+
class FireweaveLocalWebAdapter {
|
|
5
|
+
/** Not `inmemory` — that name belongs to the fixture adapter. */
|
|
6
|
+
name = 'other';
|
|
7
|
+
/**
|
|
8
|
+
* spec/modes.md "Behaviour per mode": local's unknown-key row is
|
|
9
|
+
* `default`/reason `DEFAULT`, not an error. This is the strict `===` seam
|
|
10
|
+
* `FireweaveWebRuntime.evaluateSync` checks on a cache miss.
|
|
11
|
+
*/
|
|
12
|
+
missReason = 'DEFAULT';
|
|
13
|
+
devFlags;
|
|
14
|
+
log;
|
|
15
|
+
targets = new Map();
|
|
16
|
+
closed = false;
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.devFlags = { ...(options.devFlags ?? {}) };
|
|
19
|
+
this.log = options.log ?? ((m) => console.info(m));
|
|
20
|
+
}
|
|
21
|
+
async initialize(_signal) {
|
|
22
|
+
// Nothing to connect to.
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `enabled: true` alongside `reason: 'STATIC'` is deliberate: reporting
|
|
26
|
+
* `enabled: false` for an override of `false` would make the runtime label
|
|
27
|
+
* the decision `DISABLED`, which means "exists but switched off upstream" —
|
|
28
|
+
* not what a local override expresses.
|
|
29
|
+
*/
|
|
30
|
+
async prefetch(_context, _options) {
|
|
31
|
+
const out = new Map();
|
|
32
|
+
for (const [key, value] of Object.entries(this.devFlags)) {
|
|
33
|
+
out.set(key, {
|
|
34
|
+
found: true,
|
|
35
|
+
enabled: true,
|
|
36
|
+
value,
|
|
37
|
+
variant: value ? 'on' : 'off',
|
|
38
|
+
reason: 'STATIC',
|
|
39
|
+
flagType: 'boolean',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Records the target in-process and traces it, rather than reporting
|
|
46
|
+
* `UnsupportedCapability`.
|
|
47
|
+
*
|
|
48
|
+
* The capability could instead report Unsupported so a dev harness could
|
|
49
|
+
* not *silently* look registered — but the failure being guarded against
|
|
50
|
+
* is a developer believing their targeting works because nothing objected.
|
|
51
|
+
* A recorded target plus an explicit `[fireweave:local]` line preserves
|
|
52
|
+
* that guarantee by a different route: nothing is silent, and local dev
|
|
53
|
+
* can exercise targeting rules offline instead of only in production
|
|
54
|
+
* (spec/modes.md "registerTarget in local mode").
|
|
55
|
+
*
|
|
56
|
+
* The trace names the mode, so a line appearing in a production log is
|
|
57
|
+
* itself the signal that something booted in local mode by mistake.
|
|
58
|
+
*
|
|
59
|
+
* No network call is made and nothing reaches fw-server.
|
|
60
|
+
*/
|
|
61
|
+
async registerTarget(targetingKey, options = {}) {
|
|
62
|
+
const kind = options.kind ?? 'user';
|
|
63
|
+
const properties = { ...(options.properties ?? {}) };
|
|
64
|
+
const target = {
|
|
65
|
+
targetingKey,
|
|
66
|
+
kind,
|
|
67
|
+
properties,
|
|
68
|
+
...(options.environment === undefined ? {} : { environment: options.environment }),
|
|
69
|
+
};
|
|
70
|
+
this.targets.set(targetingKey, target);
|
|
71
|
+
this.log(`[fireweave:local] registerTarget ${kind} ${targetingKey} ` +
|
|
72
|
+
`${JSON.stringify(properties)} — recorded in-process, NOT sent to fw-server`);
|
|
73
|
+
return { ok: true };
|
|
74
|
+
}
|
|
75
|
+
/** Targets recorded this process, for assertions and dev inspection. */
|
|
76
|
+
getRegisteredTargets() {
|
|
77
|
+
return [...this.targets.values()];
|
|
78
|
+
}
|
|
79
|
+
async shutdown() {
|
|
80
|
+
this.closed = true;
|
|
81
|
+
}
|
|
82
|
+
isClosed() {
|
|
83
|
+
return this.closed;
|
|
84
|
+
}
|
|
85
|
+
features() {
|
|
86
|
+
return {
|
|
87
|
+
remoteEvaluation: false,
|
|
88
|
+
localEvaluation: true,
|
|
89
|
+
localOnly: true,
|
|
90
|
+
// No exposure sink exists locally; claiming otherwise would advertise
|
|
91
|
+
// emission that silently goes nowhere.
|
|
92
|
+
exposureEmission: false,
|
|
93
|
+
sideEffectFreeReads: true,
|
|
94
|
+
groupAnalytics: false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.FireweaveLocalWebAdapter = FireweaveLocalWebAdapter;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { AdapterRuntimeFeatures, PrefetchOptions, PrefetchResult, RegisterTargetOptions, RegisterTargetResult, WebBackendAdapter } from '../../application/ports.js';
|
|
2
|
+
import type { CanonicalContext, Exposure, Signal } from '../../domain/types.js';
|
|
3
|
+
export type FireweaveFetchLike = typeof fetch;
|
|
4
|
+
export interface FireweaveRemoteWebAdapterOptions {
|
|
5
|
+
/** fw-server base URL, e.g. `https://app-server.fireweave.ai`. */
|
|
6
|
+
readonly apiUrl: string;
|
|
7
|
+
/** Fireweave project key. Public by construction — must be scoped accordingly. */
|
|
8
|
+
readonly apiKey: string;
|
|
9
|
+
/** SSRF/misconfiguration allowlist override. `['*']` opts out. */
|
|
10
|
+
readonly allowedHosts?: readonly string[];
|
|
11
|
+
readonly requestTimeoutMs?: number;
|
|
12
|
+
/** Injected fetch (tests). Defaults to the global. */
|
|
13
|
+
readonly fetch?: FireweaveFetchLike;
|
|
14
|
+
}
|
|
15
|
+
export declare class FireweaveRemoteWebAdapter implements WebBackendAdapter {
|
|
16
|
+
readonly name: "fireweave";
|
|
17
|
+
private readonly apiUrl;
|
|
18
|
+
private readonly apiKey;
|
|
19
|
+
private readonly allowedHosts;
|
|
20
|
+
private readonly requestTimeoutMs;
|
|
21
|
+
private readonly fetchImpl;
|
|
22
|
+
private readonly pending;
|
|
23
|
+
private ready;
|
|
24
|
+
private closed;
|
|
25
|
+
constructor(options: FireweaveRemoteWebAdapterOptions);
|
|
26
|
+
initialize(_signal?: AbortSignal): Promise<void>;
|
|
27
|
+
prefetch(context: CanonicalContext, options?: PrefetchOptions): Promise<PrefetchResult>;
|
|
28
|
+
registerTarget(targetingKey: string, options?: RegisterTargetOptions): Promise<RegisterTargetResult>;
|
|
29
|
+
recordExposure(exposure: Exposure): void;
|
|
30
|
+
recordSignal(signal: Signal): void;
|
|
31
|
+
/**
|
|
32
|
+
* `beacon: true` is the unload path. It must not await anything the browser
|
|
33
|
+
* will cancel, so it uses `sendBeacon` when available and a `keepalive`
|
|
34
|
+
* fetch otherwise, and it never re-queues on failure — there is no later.
|
|
35
|
+
*/
|
|
36
|
+
flush(options?: {
|
|
37
|
+
beacon?: boolean;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
private sendBeaconOrKeepalive;
|
|
40
|
+
shutdown(): Promise<void>;
|
|
41
|
+
features(): AdapterRuntimeFeatures;
|
|
42
|
+
private toResolution;
|
|
43
|
+
private requestJson;
|
|
44
|
+
}
|