@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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/application/client.d.ts +77 -0
  4. package/dist/application/client.js +144 -0
  5. package/dist/application/mode.d.ts +70 -0
  6. package/dist/application/mode.js +74 -0
  7. package/dist/application/ports.d.ts +94 -0
  8. package/dist/application/ports.js +2 -0
  9. package/dist/application/runtime.d.ts +101 -0
  10. package/dist/application/runtime.js +346 -0
  11. package/dist/domain/context.d.ts +67 -0
  12. package/dist/domain/context.js +90 -0
  13. package/dist/domain/errors.d.ts +45 -0
  14. package/dist/domain/errors.js +99 -0
  15. package/dist/domain/target.d.ts +2 -0
  16. package/dist/domain/target.js +2 -0
  17. package/dist/domain/types.d.ts +107 -0
  18. package/dist/domain/types.js +2 -0
  19. package/dist/domain/validation.d.ts +124 -0
  20. package/dist/domain/validation.js +355 -0
  21. package/dist/index.d.ts +34 -0
  22. package/dist/index.js +52 -0
  23. package/dist/infrastructure/adapters/inmemory.d.ts +77 -0
  24. package/dist/infrastructure/adapters/inmemory.js +151 -0
  25. package/dist/infrastructure/adapters/local.d.ts +108 -0
  26. package/dist/infrastructure/adapters/local.js +105 -0
  27. package/dist/infrastructure/adapters/remote.d.ts +74 -0
  28. package/dist/infrastructure/adapters/remote.js +373 -0
  29. package/dist/infrastructure/hosts.d.ts +18 -0
  30. package/dist/infrastructure/hosts.js +72 -0
  31. package/dist/register-browser.d.ts +1 -0
  32. package/dist/register-browser.js +7 -0
  33. package/dist/register.d.ts +1 -0
  34. package/dist/register.js +13 -0
  35. package/dist/start/browser.d.ts +12 -0
  36. package/dist/start/browser.js +36 -0
  37. package/dist/start/build-info.d.ts +2 -0
  38. package/dist/start/build-info.js +9 -0
  39. package/dist/start/control-points.d.ts +20 -0
  40. package/dist/start/control-points.js +59 -0
  41. package/dist/start/env.d.ts +11 -0
  42. package/dist/start/env.js +64 -0
  43. package/dist/start/fw.d.ts +29 -0
  44. package/dist/start/fw.js +76 -0
  45. package/dist/start/index.d.ts +23 -0
  46. package/dist/start/index.js +28 -0
  47. package/dist/start/instance.d.ts +17 -0
  48. package/dist/start/instance.js +56 -0
  49. package/dist/start/names.d.ts +22 -0
  50. package/dist/start/names.js +25 -0
  51. package/dist/start/resolve.d.ts +40 -0
  52. package/dist/start/resolve.js +139 -0
  53. package/dist/start/state.d.ts +96 -0
  54. package/dist/start/state.js +303 -0
  55. package/package.json +58 -0
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.envFromBag = envFromBag;
4
+ exports.processEnv = processEnv;
5
+ exports.quiet = quiet;
6
+ /**
7
+ * The ONLY place the SDK reads the process environment.
8
+ *
9
+ * The core SDK reads no environment variables (spec/modes.md). The start
10
+ * profile is the documented exception (docs/adr/0012-start-profile.md), and
11
+ * test/unit/runtime-portability.test.ts pins every env read to this file.
12
+ *
13
+ * Works on Node, Bun and Deno without importing a runtime module: Node and Bun
14
+ * expose `process.env`; Deno exposes `Deno.env.get`, which throws when the
15
+ * process lacks --allow-env. That refusal is reported, not swallowed, so a
16
+ * Deno app never boots with a key it was not allowed to read.
17
+ */
18
+ const index_js_1 = require("../index.js");
19
+ const clean = (value) => {
20
+ if (typeof value !== 'string')
21
+ return undefined;
22
+ const trimmed = value.trim();
23
+ return trimmed === '' ? undefined : trimmed;
24
+ };
25
+ /** Reader over an explicit bag (start({ env }) and tests). Only string values count. */
26
+ function envFromBag(bag) {
27
+ return (name) => clean(bag[name]);
28
+ }
29
+ /** Reader over the running process: Deno.env first, then process.env. */
30
+ function processEnv() {
31
+ const deno = globalThis.Deno;
32
+ if (deno?.env !== undefined) {
33
+ return (name) => {
34
+ try {
35
+ return clean(deno.env?.get(name));
36
+ }
37
+ catch (cause) {
38
+ throw new index_js_1.FireweaveError('Configuration', {
39
+ message: `[fireweave] Deno refused to read ${name}. Run with --allow-env=${name}, or pass the value to start() explicitly.`,
40
+ cause,
41
+ });
42
+ }
43
+ };
44
+ }
45
+ const proc = globalThis.process;
46
+ const env = proc?.env;
47
+ if (env === undefined)
48
+ return () => undefined;
49
+ return (name) => clean(env[name]);
50
+ }
51
+ /**
52
+ * A reader that never throws, for values only needed to word a warning or an
53
+ * error (for example the key a local start ignores).
54
+ */
55
+ function quiet(read) {
56
+ return (name) => {
57
+ try {
58
+ return read(name);
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ };
64
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `fw`: the singleton accessor beside start(). Safe to import anywhere, in any
3
+ * order. Every read awaits start and never throws: if start failed, reads
4
+ * serve the caller's default (or an ERROR decision for the *Details forms).
5
+ */
6
+ import { type ControlPointsApi, type FireweaveClient, type JsonValue, type RegisterTargetResult, type TargetKind } from '../index.js';
7
+ import { type FireweaveStatus } from './state.js';
8
+ /** The same nine read methods as the core client's controlPoints. */
9
+ export type ControlPoints = Pick<ControlPointsApi, 'evaluate' | 'getBooleanValue' | 'getStringValue' | 'getNumberValue' | 'getObjectValue' | 'getBooleanDetails' | 'getStringDetails' | 'getNumberDetails' | 'getObjectDetails'>;
10
+ export interface IdentifyOptions {
11
+ /** Defaults to 'user'. */
12
+ readonly kind?: TargetKind;
13
+ readonly signal?: AbortSignal;
14
+ }
15
+ export interface FireweaveStart {
16
+ /** Read control points: `await fw.controlPoints.getBooleanValue('key', false, { targetingKey })`. */
17
+ readonly controlPoints: ControlPoints;
18
+ /** Register durable targeting facts at sign-in. Resolves { ok }, never throws. */
19
+ identify(targetingKey: string, properties?: Record<string, JsonValue>, options?: IdentifyOptions): Promise<RegisterTargetResult>;
20
+ /** Stable key for reads where the server is the subject (cron, boot). No disk writes. */
21
+ instanceKey(): string;
22
+ /** What start() decided: mode, channel, host, key source. Never includes the key. */
23
+ status(): FireweaveStatus;
24
+ /** The core client, for anything the facade does not cover. Rejects if start failed. */
25
+ client(): Promise<FireweaveClient>;
26
+ /** Flush and close. A later start() begins fresh. */
27
+ shutdown(): Promise<void>;
28
+ }
29
+ export declare const fw: FireweaveStart;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fw = void 0;
4
+ /**
5
+ * `fw`: the singleton accessor beside start(). Safe to import anywhere, in any
6
+ * order. Every read awaits start and never throws: if start failed, reads
7
+ * serve the caller's default (or an ERROR decision for the *Details forms).
8
+ */
9
+ const index_js_1 = require("../index.js");
10
+ const state_js_1 = require("./state.js");
11
+ const startFailure = () => (0, state_js_1.slot)().error ?? new index_js_1.FireweaveError('Configuration', { message: '[fireweave] FireWeave was not started.' });
12
+ const errorDecision = (controlPointKey, defaultValue) => {
13
+ const err = startFailure();
14
+ return {
15
+ controlPointKey,
16
+ value: defaultValue,
17
+ reason: 'ERROR',
18
+ errorKind: err.kind,
19
+ errorCode: err.openFeatureErrorCode,
20
+ errorMessage: err.message,
21
+ metadata: {},
22
+ };
23
+ };
24
+ /** Local mode: a key missing from the control-points object gets its default, with one warning. */
25
+ function noteLocalKey(controlPointKey) {
26
+ const s = (0, state_js_1.slot)();
27
+ const r = s.resolved;
28
+ if (r?.mode !== 'local' || Object.prototype.hasOwnProperty.call(r.controlPoints, controlPointKey))
29
+ return;
30
+ (0, state_js_1.warnOnce)(s, `[fireweave:local] '${controlPointKey}' is not in your control points (src/fireweave/control-points.ts), so it gets its default. Add it there to try it locally.`);
31
+ }
32
+ async function read(controlPointKey, fallback, run) {
33
+ const client = await (0, state_js_1.ensureClient)();
34
+ if (client === undefined)
35
+ return fallback();
36
+ noteLocalKey(controlPointKey);
37
+ return run(client);
38
+ }
39
+ const controlPoints = {
40
+ evaluate: (controlPointKey, expectedType, defaultValue, context, options) => read(controlPointKey, () => errorDecision(controlPointKey, defaultValue), (c) => c.controlPoints.evaluate(controlPointKey, expectedType, defaultValue, context, options)),
41
+ getBooleanValue: (controlPointKey, defaultValue, context) => read(controlPointKey, () => defaultValue, (c) => c.controlPoints.getBooleanValue(controlPointKey, defaultValue, context)),
42
+ getStringValue: (controlPointKey, defaultValue, context) => read(controlPointKey, () => defaultValue, (c) => c.controlPoints.getStringValue(controlPointKey, defaultValue, context)),
43
+ getNumberValue: (controlPointKey, defaultValue, context) => read(controlPointKey, () => defaultValue, (c) => c.controlPoints.getNumberValue(controlPointKey, defaultValue, context)),
44
+ getObjectValue: (controlPointKey, defaultValue, context) => read(controlPointKey, () => defaultValue, (c) => c.controlPoints.getObjectValue(controlPointKey, defaultValue, context)),
45
+ getBooleanDetails: (controlPointKey, defaultValue, context) => read(controlPointKey, () => errorDecision(controlPointKey, defaultValue), (c) => c.controlPoints.getBooleanDetails(controlPointKey, defaultValue, context)),
46
+ getStringDetails: (controlPointKey, defaultValue, context) => read(controlPointKey, () => errorDecision(controlPointKey, defaultValue), (c) => c.controlPoints.getStringDetails(controlPointKey, defaultValue, context)),
47
+ getNumberDetails: (controlPointKey, defaultValue, context) => read(controlPointKey, () => errorDecision(controlPointKey, defaultValue), (c) => c.controlPoints.getNumberDetails(controlPointKey, defaultValue, context)),
48
+ getObjectDetails: (controlPointKey, defaultValue, context) => read(controlPointKey, () => errorDecision(controlPointKey, defaultValue), (c) => c.controlPoints.getObjectDetails(controlPointKey, defaultValue, context)),
49
+ };
50
+ exports.fw = Object.freeze({
51
+ controlPoints: Object.freeze(controlPoints),
52
+ async identify(targetingKey, properties, options = {}) {
53
+ const client = await (0, state_js_1.ensureClient)();
54
+ if (client === undefined)
55
+ return { ok: false };
56
+ try {
57
+ return await client.registerTarget(targetingKey, {
58
+ kind: options.kind ?? 'user',
59
+ ...(properties !== undefined ? { properties } : {}),
60
+ ...(options.signal !== undefined ? { signal: options.signal } : {}),
61
+ });
62
+ }
63
+ catch {
64
+ return { ok: false };
65
+ }
66
+ },
67
+ instanceKey: () => (0, state_js_1.instanceKeyFor)(),
68
+ status: () => (0, state_js_1.currentStatus)(),
69
+ async client() {
70
+ const client = await (0, state_js_1.ensureClient)();
71
+ if (client === undefined)
72
+ throw startFailure();
73
+ return client;
74
+ },
75
+ shutdown: () => (0, state_js_1.shutdownSlot)(),
76
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fireweaveai/server-sdk/start: FireWeave in one import (docs/adr/0012-start-profile.md).
3
+ *
4
+ * // src/fireweave/start.ts (imported first by your entrypoint)
5
+ * import { start } from '@fireweaveai/server-sdk/start';
6
+ * import { controlPoints } from './control-points';
7
+ * start({ controlPoints });
8
+ *
9
+ * // anywhere
10
+ * import { fw } from '@fireweaveai/server-sdk/start';
11
+ * if (await fw.controlPoints.getBooleanValue('new-checkout', false, { targetingKey: user.id })) { … }
12
+ *
13
+ * This layer is built only on the package's public exports; the core
14
+ * (initFireweave and everything in '.') is unchanged and still reads no env.
15
+ */
16
+ export { start, resetForTests } from './state.js';
17
+ export type { StartOptions, StartState, FireweaveStatus } from './state.js';
18
+ export { fw } from './fw.js';
19
+ export type { FireweaveStart, ControlPoints, IdentifyOptions } from './fw.js';
20
+ export { defineControlPoints } from './control-points.js';
21
+ export type { ControlPointDefinition, ControlPointMap } from './control-points.js';
22
+ export type { StartMode, SdkChannel } from './resolve.js';
23
+ export { SDK_VERSION, SDK_CHANNEL } from './build-info.js';
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SDK_CHANNEL = exports.SDK_VERSION = exports.defineControlPoints = exports.fw = exports.resetForTests = exports.start = void 0;
4
+ /**
5
+ * @fireweaveai/server-sdk/start: FireWeave in one import (docs/adr/0012-start-profile.md).
6
+ *
7
+ * // src/fireweave/start.ts (imported first by your entrypoint)
8
+ * import { start } from '@fireweaveai/server-sdk/start';
9
+ * import { controlPoints } from './control-points';
10
+ * start({ controlPoints });
11
+ *
12
+ * // anywhere
13
+ * import { fw } from '@fireweaveai/server-sdk/start';
14
+ * if (await fw.controlPoints.getBooleanValue('new-checkout', false, { targetingKey: user.id })) { … }
15
+ *
16
+ * This layer is built only on the package's public exports; the core
17
+ * (initFireweave and everything in '.') is unchanged and still reads no env.
18
+ */
19
+ var state_js_1 = require("./state.js");
20
+ Object.defineProperty(exports, "start", { enumerable: true, get: function () { return state_js_1.start; } });
21
+ Object.defineProperty(exports, "resetForTests", { enumerable: true, get: function () { return state_js_1.resetForTests; } });
22
+ var fw_js_1 = require("./fw.js");
23
+ Object.defineProperty(exports, "fw", { enumerable: true, get: function () { return fw_js_1.fw; } });
24
+ var control_points_js_1 = require("./control-points.js");
25
+ Object.defineProperty(exports, "defineControlPoints", { enumerable: true, get: function () { return control_points_js_1.defineControlPoints; } });
26
+ var build_info_js_1 = require("./build-info.js");
27
+ Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return build_info_js_1.SDK_VERSION; } });
28
+ Object.defineProperty(exports, "SDK_CHANNEL", { enumerable: true, get: function () { return build_info_js_1.SDK_CHANNEL; } });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * fw.instanceKey(): a stable targeting key for reads where the server itself is
3
+ * the subject (cron, migrations, boot-time decisions). Request reads still pass
4
+ * the user's id.
5
+ *
6
+ * Sources, in order: start({ instanceId }), FIREWEAVE_INSTANCE_ID, then a hash
7
+ * of the host name, then a random id for the life of the process. Nothing is
8
+ * written to disk: in a container the file would not outlive the process anyway.
9
+ */
10
+ import type { EnvReader } from './env.js';
11
+ export interface InstanceKey {
12
+ readonly value: string;
13
+ readonly source: 'option' | 'FIREWEAVE_INSTANCE_ID' | 'host' | 'random';
14
+ }
15
+ /** FNV-1a 64-bit, hex. Stable across runtimes; not a security hash. */
16
+ export declare function fnv1a64(text: string): string;
17
+ export declare function deriveInstanceKey(option: string | undefined, read: EnvReader): InstanceKey;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fnv1a64 = fnv1a64;
4
+ exports.deriveInstanceKey = deriveInstanceKey;
5
+ const env_js_1 = require("./env.js");
6
+ const names_js_1 = require("./names.js");
7
+ /** FNV-1a 64-bit, hex. Stable across runtimes; not a security hash. */
8
+ function fnv1a64(text) {
9
+ let hash = 0xcbf29ce484222325n;
10
+ const prime = 0x100000001b3n;
11
+ for (const byte of new TextEncoder().encode(text)) {
12
+ hash ^= BigInt(byte);
13
+ hash = (hash * prime) & 0xffffffffffffffffn;
14
+ }
15
+ return hash.toString(16).padStart(16, '0');
16
+ }
17
+ /** Host name without importing a runtime module; undefined when the runtime will not say. */
18
+ function hostName(read) {
19
+ const fromEnv = (0, env_js_1.quiet)(read)('HOSTNAME');
20
+ if (fromEnv !== undefined)
21
+ return fromEnv;
22
+ try {
23
+ const deno = globalThis.Deno;
24
+ if (typeof deno?.hostname === 'function')
25
+ return deno.hostname();
26
+ }
27
+ catch {
28
+ // Deno without --allow-sys=hostname: fall through.
29
+ }
30
+ try {
31
+ const proc = globalThis.process;
32
+ const os = proc?.getBuiltinModule?.('node:os');
33
+ const name = os?.hostname?.();
34
+ if (typeof name === 'string' && name !== '')
35
+ return name;
36
+ }
37
+ catch {
38
+ // Runtime without getBuiltinModule: fall through.
39
+ }
40
+ return undefined;
41
+ }
42
+ const randomId = () => {
43
+ const c = globalThis.crypto;
44
+ return c?.randomUUID?.() ?? `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
45
+ };
46
+ function deriveInstanceKey(option, read) {
47
+ if (option !== undefined)
48
+ return { value: option, source: 'option' };
49
+ const fromEnv = (0, env_js_1.quiet)(read)(names_js_1.ENV.instanceId);
50
+ if (fromEnv !== undefined)
51
+ return { value: fromEnv, source: 'FIREWEAVE_INSTANCE_ID' };
52
+ const host = hostName(read);
53
+ if (host !== undefined)
54
+ return { value: `inst_${fnv1a64(host)}`, source: 'host' };
55
+ return { value: `inst_${randomId()}`, source: 'random' };
56
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Every name the start profile reads, in one place, so the README, the
3
+ * initialise skill and the error messages cannot drift apart.
4
+ */
5
+ /** Env vars the start profile reads. Explicit start() options always win. */
6
+ export declare const ENV: Readonly<{
7
+ key: "FIREWEAVE_KEY";
8
+ url: "FIREWEAVE_URL";
9
+ environment: "FIREWEAVE_ENV";
10
+ instanceId: "FIREWEAVE_INSTANCE_ID";
11
+ }>;
12
+ /** Fallback env-name sources for mode inference, after the environment option and FIREWEAVE_ENV. */
13
+ export declare const ENVIRONMENT_FALLBACKS: readonly ["APP_ENV", "NODE_ENV"];
14
+ /** Environment names that mean "local development" when no key is set. Case-insensitive. */
15
+ export declare const DEV_ENVIRONMENTS: ReadonlySet<string>;
16
+ /** fw-server host for each release channel of this package. */
17
+ export declare const CHANNEL_URLS: Readonly<{
18
+ production: "https://app-server.fireweave.ai";
19
+ staging: "https://staging-app-server.fireweave.ai";
20
+ }>;
21
+ /** Hosts always allowed beside a custom endpoint, so local stacks keep working. */
22
+ export declare const LOOPBACK_HOSTS: readonly string[];
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ /**
3
+ * Every name the start profile reads, in one place, so the README, the
4
+ * initialise skill and the error messages cannot drift apart.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.LOOPBACK_HOSTS = exports.CHANNEL_URLS = exports.DEV_ENVIRONMENTS = exports.ENVIRONMENT_FALLBACKS = exports.ENV = void 0;
8
+ /** Env vars the start profile reads. Explicit start() options always win. */
9
+ exports.ENV = Object.freeze({
10
+ key: 'FIREWEAVE_KEY',
11
+ url: 'FIREWEAVE_URL',
12
+ environment: 'FIREWEAVE_ENV',
13
+ instanceId: 'FIREWEAVE_INSTANCE_ID',
14
+ });
15
+ /** Fallback env-name sources for mode inference, after the environment option and FIREWEAVE_ENV. */
16
+ exports.ENVIRONMENT_FALLBACKS = Object.freeze(['APP_ENV', 'NODE_ENV']);
17
+ /** Environment names that mean "local development" when no key is set. Case-insensitive. */
18
+ exports.DEV_ENVIRONMENTS = new Set(['development', 'dev', 'local', 'test']);
19
+ /** fw-server host for each release channel of this package. */
20
+ exports.CHANNEL_URLS = Object.freeze({
21
+ production: 'https://app-server.fireweave.ai',
22
+ staging: 'https://staging-app-server.fireweave.ai',
23
+ });
24
+ /** Hosts always allowed beside a custom endpoint, so local stacks keep working. */
25
+ exports.LOOPBACK_HOSTS = Object.freeze(['localhost', '127.0.0.1', '::1']);
@@ -0,0 +1,40 @@
1
+ import type { EnvReader } from './env.js';
2
+ import { type ControlPointMap } from './control-points.js';
3
+ export type StartMode = 'remote' | 'local';
4
+ export type SdkChannel = 'staging' | 'production';
5
+ export interface ResolveInput {
6
+ readonly mode?: unknown;
7
+ readonly environment?: unknown;
8
+ readonly url?: unknown;
9
+ readonly key?: unknown;
10
+ readonly controlPoints?: unknown;
11
+ }
12
+ export interface BuildInfo {
13
+ readonly version: string;
14
+ readonly channel: SdkChannel;
15
+ }
16
+ export interface ResolvedStart {
17
+ readonly mode: StartMode;
18
+ /** Why this mode: the mode option, a present key, or the environment name. */
19
+ readonly modeSource: 'option' | 'key' | 'environment';
20
+ /** Remote only. */
21
+ readonly url?: string;
22
+ readonly urlSource?: string;
23
+ readonly allowedHosts?: readonly string[];
24
+ /** Remote only. Never logged or printed. */
25
+ readonly key?: string;
26
+ readonly keySource: string;
27
+ /** Set when the environment name was consulted (no key, no mode option). */
28
+ readonly environment?: string;
29
+ readonly environmentSource?: string;
30
+ readonly controlPoints: ControlPointMap;
31
+ readonly channel: SdkChannel;
32
+ readonly sdkVersion: string;
33
+ /** Lines to log once each: ignored keys. */
34
+ readonly warnings: readonly string[];
35
+ }
36
+ /**
37
+ * Resolve start() options against the environment.
38
+ * Throws FireweaveError('Configuration') naming the variable at fault.
39
+ */
40
+ export declare function resolveStart(input: ResolveInput, read: EnvReader, build: BuildInfo): ResolvedStart;
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveStart = resolveStart;
4
+ /**
5
+ * Pure start-profile resolver: options + env + build stamp in, one resolved
6
+ * config out. No I/O, no globals, no timers — every rule here is unit-tested
7
+ * through `resolveStart` alone.
8
+ *
9
+ * Precedence for every value: explicit start() option, then FIREWEAVE_* env,
10
+ * then the default. Reads are lazy:
11
+ * a source is read only if every earlier source was unset, so an explicit
12
+ * option never touches the environment (which matters on Deno).
13
+ */
14
+ const index_js_1 = require("../index.js");
15
+ const env_js_1 = require("./env.js");
16
+ const control_points_js_1 = require("./control-points.js");
17
+ const names_js_1 = require("./names.js");
18
+ const configError = (message) => new index_js_1.FireweaveError('Configuration', { message });
19
+ const optionString = (value, name) => {
20
+ if (value === undefined || value === null)
21
+ return undefined;
22
+ if (typeof value !== 'string')
23
+ throw configError(`[fireweave] start({ ${name} }) must be a string.`);
24
+ const trimmed = value.trim();
25
+ return trimmed === '' ? undefined : trimmed;
26
+ };
27
+ /** First non-empty of: the option, then each env name in order. */
28
+ function pick(option, optionName, names, read) {
29
+ if (option !== undefined)
30
+ return { value: option, source: `start({ ${optionName} })` };
31
+ for (const name of names) {
32
+ const value = read(name);
33
+ if (value !== undefined)
34
+ return { value, source: name };
35
+ }
36
+ return undefined;
37
+ }
38
+ /**
39
+ * Key family check, before any request. Messages name the source, never the
40
+ * value. The redactor masks analytics-vendor prefixes, so the message says
41
+ * "analytics vendor key" instead of quoting one.
42
+ */
43
+ function checkKeyFamily(key, source) {
44
+ if (key.startsWith('fw_public_')) {
45
+ throw configError(`[fireweave] The key from ${source} is a browser key (fw_public_…). Server apps need a project key (project-api-key_…) from Project settings, API keys.`);
46
+ }
47
+ if (/^ph[a-z]_/.test(key)) {
48
+ throw configError(`[fireweave] The key from ${source} is an analytics vendor key, not a FireWeave project key. Use the project key (project-api-key_…).`);
49
+ }
50
+ if (key.startsWith('fw_org_') || key.startsWith('cli_at_')) {
51
+ throw configError(`[fireweave] The key from ${source} is an organisation or CLI token, not a project key. Use the project key (project-api-key_…).`);
52
+ }
53
+ }
54
+ function resolveUrl(input, read, build) {
55
+ const picked = pick(optionString(input.url, 'url'), 'url', [names_js_1.ENV.url], read);
56
+ if (picked === undefined) {
57
+ // The channel host is in DEFAULT_ALLOWED_HOSTS, so no custom allowlist.
58
+ return { url: names_js_1.CHANNEL_URLS[build.channel], urlSource: `SDK channel (${build.channel})` };
59
+ }
60
+ const url = picked.value.replace(/\/+$/, '');
61
+ let hostname;
62
+ try {
63
+ hostname = new URL(url).hostname;
64
+ }
65
+ catch {
66
+ throw configError(`[fireweave] The endpoint from ${picked.source} is not a valid URL.`);
67
+ }
68
+ const allowedHosts = [hostname.replace(/^\[|\]$/g, ''), ...names_js_1.LOOPBACK_HOSTS];
69
+ try {
70
+ (0, index_js_1.assertHostAllowed)(url, allowedHosts);
71
+ }
72
+ catch {
73
+ throw configError(`[fireweave] The endpoint from ${picked.source} must use https (http is allowed only for localhost).`);
74
+ }
75
+ return { url, urlSource: picked.source, allowedHosts };
76
+ }
77
+ function resolveKey(input, read) {
78
+ const picked = pick(optionString(input.key, 'key'), 'key', [names_js_1.ENV.key], read);
79
+ if (picked !== undefined)
80
+ checkKeyFamily(picked.value, picked.source);
81
+ return picked;
82
+ }
83
+ function resolveEnvironment(input, read) {
84
+ return pick(optionString(input.environment, 'environment'), 'environment', [names_js_1.ENV.environment, ...names_js_1.ENVIRONMENT_FALLBACKS], read);
85
+ }
86
+ function noKeyError(env) {
87
+ const where = env === undefined
88
+ ? 'no environment name is set (checked the environment option, FIREWEAVE_ENV, APP_ENV and NODE_ENV)'
89
+ : `the environment is '${env.value}' (from ${env.source}), which is not a development name`;
90
+ return configError(`[fireweave] ${names_js_1.ENV.key} is not set and ${where}. Set ${names_js_1.ENV.key} to the project's server key, or for local development set FIREWEAVE_ENV=development or call start({ mode: 'local' }).`);
91
+ }
92
+ /**
93
+ * Resolve start() options against the environment.
94
+ * Throws FireweaveError('Configuration') naming the variable at fault.
95
+ */
96
+ function resolveStart(input, read, build) {
97
+ const warnings = [];
98
+ const controlPoints = (0, control_points_js_1.normalizeControlPoints)(input.controlPoints);
99
+ const base = { controlPoints, channel: build.channel, sdkVersion: build.version };
100
+ if (input.mode !== undefined && input.mode !== 'remote' && input.mode !== 'local') {
101
+ throw configError(`[fireweave] start({ mode }) must be 'remote' or 'local'.`);
102
+ }
103
+ if (input.mode === 'local') {
104
+ // The key is ignored. Look only to warn, and never fail on a refused read.
105
+ const ignored = resolveKeyQuietly(input, read);
106
+ if (ignored !== undefined) {
107
+ warnings.push(`[fireweave] start({ mode: 'local' }) ignores the key from ${ignored}; nothing is sent to fw-server.`);
108
+ }
109
+ return { ...base, mode: 'local', modeSource: 'option', keySource: 'none', warnings };
110
+ }
111
+ const key = resolveKey(input, read);
112
+ if (input.mode === 'remote') {
113
+ if (key === undefined) {
114
+ throw configError(`[fireweave] start({ mode: 'remote' }) needs a key. Set ${names_js_1.ENV.key} or pass start({ key }).`);
115
+ }
116
+ return { ...base, mode: 'remote', modeSource: 'option', ...resolveUrl(input, read, build), key: key.value, keySource: key.source, warnings };
117
+ }
118
+ if (key !== undefined) {
119
+ return { ...base, mode: 'remote', modeSource: 'key', ...resolveUrl(input, read, build), key: key.value, keySource: key.source, warnings };
120
+ }
121
+ const env = resolveEnvironment(input, read);
122
+ if (env !== undefined && names_js_1.DEV_ENVIRONMENTS.has(env.value.toLowerCase())) {
123
+ return {
124
+ ...base,
125
+ mode: 'local',
126
+ modeSource: 'environment',
127
+ keySource: 'none',
128
+ environment: env.value,
129
+ environmentSource: env.source,
130
+ warnings,
131
+ };
132
+ }
133
+ throw noKeyError(env);
134
+ }
135
+ function resolveKeyQuietly(input, read) {
136
+ if (typeof input.key === 'string' && input.key.trim() !== '')
137
+ return 'start({ key })';
138
+ return (0, env_js_1.quiet)(read)(names_js_1.ENV.key) !== undefined ? names_js_1.ENV.key : undefined;
139
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * start() and the process-wide singleton behind `fw`.
3
+ *
4
+ * One slot per process (or worker thread) at a Symbol.for key on globalThis,
5
+ * so two bundled copies of this package share one client. start() is
6
+ * synchronous: it resolves and validates everything up front, throws a
7
+ * Configuration error for bad config, then hands off to the unchanged core
8
+ * initFireweave(). Reads await readiness and never throw.
9
+ *
10
+ * A read that happens before any start() schedules an env-only start on the
11
+ * next macrotask. An ES module graph without top-level await evaluates in one
12
+ * job, so an entrypoint that imports ./fireweave/start first always wins.
13
+ */
14
+ import { FireweaveError, type FireweaveClient, type FireweaveErrorKind, type FireweaveRemoteAdapterOptions } from '../index.js';
15
+ import { type EnvReader } from './env.js';
16
+ import { type ControlPointMap } from './control-points.js';
17
+ import { type InstanceKey } from './instance.js';
18
+ import { type ResolvedStart, type SdkChannel, type StartMode } from './resolve.js';
19
+ export interface StartOptions {
20
+ /** Control points and their local values; import from src/fireweave/control-points.ts. */
21
+ readonly controlPoints?: ControlPointMap;
22
+ /** Force a mode. Without it: a key means remote; no key means local only in a dev environment. */
23
+ readonly mode?: StartMode;
24
+ /** Environment name used to infer the mode, instead of FIREWEAVE_ENV, APP_ENV or NODE_ENV. */
25
+ readonly environment?: string;
26
+ /** fw-server URL. Default: FIREWEAVE_URL, else this SDK build's channel host. */
27
+ readonly url?: string;
28
+ /** Project key (project-api-key_…). Default: FIREWEAVE_KEY. */
29
+ readonly key?: string;
30
+ /** Stable id for this process in server-subject reads. Default: FIREWEAVE_INSTANCE_ID, else the host. */
31
+ readonly instanceId?: string;
32
+ /** Read these values instead of the process environment (tests, Workers bindings). */
33
+ readonly env?: Readonly<Record<string, unknown>>;
34
+ /** Where [fireweave] lines go. Default: console. Not part of the idempotency check. */
35
+ readonly log?: (line: string) => void;
36
+ /** Transport override for tests. Not part of the idempotency check. */
37
+ readonly fetch?: FireweaveRemoteAdapterOptions['fetch'];
38
+ }
39
+ export type StartState = 'unstarted' | 'starting' | 'ready' | 'failed' | 'shutdown';
40
+ export interface FireweaveStatus {
41
+ readonly state: StartState;
42
+ readonly mode?: StartMode;
43
+ readonly modeSource?: ResolvedStart['modeSource'];
44
+ readonly channel: SdkChannel;
45
+ readonly sdkVersion: string;
46
+ /** fw-server host name only: never a path or a credential. */
47
+ readonly host?: string;
48
+ readonly endpointSource?: string;
49
+ readonly keySource?: string;
50
+ readonly environment?: string;
51
+ readonly controlPointCount?: number;
52
+ /** Why start failed, when it did. Already redacted. */
53
+ readonly error?: string;
54
+ /**
55
+ * The kind of the latest failed fw-server request (Authentication,
56
+ * Authorization, RateLimited, Network, Timeout or BackendUnavailable).
57
+ * Sticky: a later success does not clear it; a fresh start() does.
58
+ */
59
+ readonly lastErrorKind?: FireweaveErrorKind;
60
+ }
61
+ interface Deferred<T> {
62
+ readonly promise: Promise<T>;
63
+ readonly resolve: (value: T | PromiseLike<T>) => void;
64
+ }
65
+ interface Slot {
66
+ readonly protocol: 1;
67
+ state: StartState;
68
+ signature?: string;
69
+ implicit: boolean;
70
+ resolved?: ResolvedStart;
71
+ ready: Deferred<FireweaveClient | undefined>;
72
+ client?: FireweaveClient | undefined;
73
+ error?: FireweaveError;
74
+ implicitScheduled: boolean;
75
+ read?: EnvReader;
76
+ instanceIdOption?: string;
77
+ instance?: InstanceKey;
78
+ log: (line: string) => void;
79
+ readonly warned: Set<string>;
80
+ lastErrorKind?: FireweaveErrorKind;
81
+ /** fw-server failure groups already logged; one line each for the life of the process. */
82
+ readonly diagnosed: Set<string>;
83
+ }
84
+ export declare function slot(): Slot;
85
+ export declare function warnOnce(s: Slot, line: string): void;
86
+ /** Start FireWeave for this process. Synchronous; call once, from src/fireweave/start.ts. */
87
+ export declare function start(options?: StartOptions): void;
88
+ export declare function startWith(options: StartOptions, implicit: boolean): void;
89
+ /** The client once start() settles; undefined when start failed. Schedules an implicit start if none ran. */
90
+ export declare function ensureClient(): Promise<FireweaveClient | undefined>;
91
+ export declare function currentStatus(): FireweaveStatus;
92
+ export declare function instanceKeyFor(): string;
93
+ export declare function shutdownSlot(): Promise<void>;
94
+ /** Test only: shut down and forget the singleton so the next start() begins fresh. */
95
+ export declare function resetForTests(): Promise<void>;
96
+ export {};