fluidsoul-js 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FluidsoulClient: () => FluidsoulClient,
24
+ FluidsoulError: () => FluidsoulError,
25
+ createFluidsoulConfig: () => createFluidsoulConfig,
26
+ getContext: () => getContext,
27
+ refreshContext: () => refreshContext,
28
+ sendEvent: () => sendEvent
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/types.ts
33
+ var FluidsoulError = class extends Error {
34
+ constructor(message, status, endpoint, body) {
35
+ super(message);
36
+ this.status = status;
37
+ this.endpoint = endpoint;
38
+ this.body = body;
39
+ this.name = "FluidsoulError";
40
+ }
41
+ };
42
+
43
+ // src/client.ts
44
+ function buildHeaders(config, requestId) {
45
+ return {
46
+ "Authorization": `Bearer ${config.token}`,
47
+ "Content-Type": "application/json",
48
+ "X-Workspace-Id": config.workspaceId,
49
+ ...requestId ? { "X-Request-Id": requestId } : {}
50
+ };
51
+ }
52
+ async function assertOk(res, endpoint) {
53
+ if (!res.ok) {
54
+ const body = await res.text().catch(() => "");
55
+ throw new FluidsoulError(
56
+ `fluidsoul request failed: ${endpoint} returned ${res.status}`,
57
+ res.status,
58
+ endpoint,
59
+ body
60
+ );
61
+ }
62
+ }
63
+ async function sendEvent(config, input, requestId) {
64
+ const endpoint = `${config.apiUrl}/v1/events/ingest`;
65
+ const res = await fetch(endpoint, {
66
+ method: "POST",
67
+ headers: buildHeaders(config, requestId),
68
+ body: JSON.stringify({
69
+ user_id: input.userId,
70
+ event_type: input.eventType,
71
+ event_time: (/* @__PURE__ */ new Date()).toISOString(),
72
+ metadata: input.metadata ?? {}
73
+ })
74
+ });
75
+ await assertOk(res, endpoint);
76
+ }
77
+ async function refreshContext(config, userId, requestId) {
78
+ const endpoint = `${config.apiUrl}/v1/context/refresh`;
79
+ const res = await fetch(endpoint, {
80
+ method: "POST",
81
+ headers: buildHeaders(config, requestId),
82
+ body: JSON.stringify({
83
+ user_id: userId,
84
+ refresh_reason: "new_events"
85
+ })
86
+ });
87
+ await assertOk(res, endpoint);
88
+ }
89
+ async function getContext(config, userId, requestId) {
90
+ const endpoint = `${config.apiUrl}/v1/context/users/${encodeURIComponent(userId)}`;
91
+ const res = await fetch(endpoint, {
92
+ method: "GET",
93
+ headers: buildHeaders(config, requestId)
94
+ });
95
+ if (res.status === 404) return null;
96
+ await assertOk(res, endpoint);
97
+ return res.json();
98
+ }
99
+
100
+ // src/helpers.ts
101
+ function createFluidsoulConfig(overrides) {
102
+ const env = typeof globalThis !== "undefined" && "process" in globalThis ? globalThis.process : void 0;
103
+ const vars = env?.env ?? {};
104
+ return {
105
+ apiUrl: overrides?.apiUrl ?? vars.FLUIDSOUL_API_URL ?? vars.NEXT_PUBLIC_FLUIDSOUL_API_URL ?? "http://localhost:4000",
106
+ token: overrides?.token ?? vars.FLUIDSOUL_API_TOKEN ?? vars.NEXT_PUBLIC_FLUIDSOUL_API_TOKEN ?? "",
107
+ workspaceId: overrides?.workspaceId ?? vars.FLUIDSOUL_WORKSPACE_ID ?? vars.NEXT_PUBLIC_FLUIDSOUL_WORKSPACE_ID ?? ""
108
+ };
109
+ }
110
+ var FluidsoulClient = class {
111
+ constructor(config) {
112
+ this.config = config;
113
+ }
114
+ /** Send a user action event. */
115
+ async sendEvent(input, requestId) {
116
+ return sendEvent(this.config, input, requestId);
117
+ }
118
+ /** Trigger a context refresh for a user. */
119
+ async refreshContext(userId, requestId) {
120
+ return refreshContext(this.config, userId, requestId);
121
+ }
122
+ /** Get the current computed context for a user. Returns null if not found. */
123
+ async getContext(userId, requestId) {
124
+ return getContext(this.config, userId, requestId);
125
+ }
126
+ };
127
+ // Annotate the CommonJS export names for ESM import in node:
128
+ 0 && (module.exports = {
129
+ FluidsoulClient,
130
+ FluidsoulError,
131
+ createFluidsoulConfig,
132
+ getContext,
133
+ refreshContext,
134
+ sendEvent
135
+ });
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Configuration required to connect to a fluidsoul API server.
3
+ * Store these values in environment variables — never hardcode them.
4
+ */
5
+ interface FluidsoulConfig {
6
+ /** URL of the fluidsoul API server. e.g. "https://api.fluidsoul.dev" or "http://localhost:4000" */
7
+ apiUrl: string;
8
+ /** Bearer token for your workspace. Set via fluidsoul CLI: `fluidsoul token set <token>` */
9
+ token: string;
10
+ /** Your workspace ID (UUID). Found in your fluidsoul dashboard or config file. */
11
+ workspaceId: string;
12
+ }
13
+ /**
14
+ * The computed context profile for a user.
15
+ * fluidsoul produces this by running its rules engine over the user's event history.
16
+ * Use it to decide what your UI should show, hide, prioritize, or pre-configure.
17
+ */
18
+ interface UserContext {
19
+ user_id: string;
20
+ /**
21
+ * What kind of user this is, inferred from their behavior.
22
+ * The possible values depend on your workspace's rules config dimensions.
23
+ * Examples: "b2b_focused", "analyst", "power_user", "new_user", "hybrid"
24
+ */
25
+ user_category: string;
26
+ /**
27
+ * The dominant mode/value this user prefers based on their activity.
28
+ * Derived from the primary dimension in your rules config.
29
+ */
30
+ preferred_mode: string;
31
+ /**
32
+ * Features ordered by how relevant they are to this user.
33
+ * Use this to decide what to surface first in your UI.
34
+ * e.g. ["discovery", "lead_management", "analytics", "scanning"]
35
+ */
36
+ feature_priorities: string[];
37
+ /**
38
+ * Suggested default settings to pre-configure for this user.
39
+ * Keys and values depend on your workspace's rules config.
40
+ * e.g. { "default_view": "reports", "default_period": "weekly" }
41
+ */
42
+ recommended_defaults: Record<string, string>;
43
+ /** How actively this user engages with the app. */
44
+ engagement_level: "low" | "moderate" | "high";
45
+ /**
46
+ * How experienced this user is with the app.
47
+ * - "new" — show onboarding hints and tooltips
48
+ * - "intermediate" — show hints for unfamiliar features only
49
+ * - "power_user" — hide onboarding, show advanced options
50
+ */
51
+ user_maturity: "new" | "intermediate" | "power_user";
52
+ /**
53
+ * How confident fluidsoul is about each inference, as a number between 0 and 1.
54
+ * e.g. { "user_category": 0.85, "preferred_mode": 0.92 }
55
+ */
56
+ confidence_scores: Record<string, number>;
57
+ /**
58
+ * Plain-English reasons explaining why the context looks the way it does.
59
+ * e.g. ["Classified as 'analyst' based on 12 analyst and 3 executive signals"]
60
+ * Show these to developers and power users for transparency.
61
+ */
62
+ explainability: string[];
63
+ /**
64
+ * A natural-language summary of this user, generated by an LLM.
65
+ * Null if no LLM is configured or inference hasn't run yet.
66
+ */
67
+ user_narrative?: string | null;
68
+ /**
69
+ * How this context was computed.
70
+ * - "rules" — deterministic rules engine only
71
+ * - "rules+llm" — rules engine + LLM-generated narrative
72
+ */
73
+ inference_source?: "rules" | "rules+llm";
74
+ /** ISO 8601 timestamp of when this context was last computed. */
75
+ last_refreshed_at: string;
76
+ }
77
+ /** Input for sending a user action event to fluidsoul. */
78
+ interface SendEventInput {
79
+ /** The ID of the user who performed the action. Must match how you identify users in your app. */
80
+ userId: string;
81
+ /**
82
+ * What the user did. Use consistent names across your app.
83
+ * Define event types that match your workspace's signal conditions.
84
+ */
85
+ eventType: string;
86
+ /** Optional extra details about the event. e.g. { platform: "linkedin" } or { feature_name: "analytics" } */
87
+ metadata?: Record<string, unknown>;
88
+ }
89
+ /** Error thrown when a fluidsoul API call fails. */
90
+ declare class FluidsoulError extends Error {
91
+ readonly status: number;
92
+ readonly endpoint: string;
93
+ readonly body: string;
94
+ constructor(message: string, status: number, endpoint: string, body: string);
95
+ }
96
+
97
+ /**
98
+ * fluidsoul core HTTP client.
99
+ *
100
+ * Framework-agnostic — works in any environment that supports the Fetch API:
101
+ * browsers, Node.js 18+, Electron, VS Code extensions, Figma plugins, Photoshop UXP.
102
+ *
103
+ * If you're using React, prefer the hooks in "fluidsoul-js/react" which wrap this client
104
+ * with state management. Use this client directly for server-side calls (Next.js
105
+ * server components / route handlers) or non-React environments.
106
+ */
107
+
108
+ /**
109
+ * Send a user action event to fluidsoul.
110
+ *
111
+ * Call this fire-and-forget whenever a user does something meaningful in your app.
112
+ * fluidsoul accumulates these events and uses them to build the user's context profile.
113
+ *
114
+ * @example
115
+ * // In a server action or route handler — never call this from browser code directly
116
+ * await sendEvent(fluidsoulConfig, { userId, eventType: "b2b_mode_used" })
117
+ */
118
+ declare function sendEvent(config: FluidsoulConfig, input: SendEventInput, requestId?: string): Promise<void>;
119
+ /**
120
+ * Trigger a context refresh for a user.
121
+ *
122
+ * This tells fluidsoul to re-run its rules engine over all of the user's events and
123
+ * update their context profile. Call this after sending a batch of events, or at
124
+ * natural session boundaries (end of session, start of a new session).
125
+ *
126
+ * You don't need to call this after every single event — batch events and refresh
127
+ * periodically for best performance.
128
+ */
129
+ declare function refreshContext(config: FluidsoulConfig, userId: string, requestId?: string): Promise<void>;
130
+ /**
131
+ * Get the current context profile for a user.
132
+ *
133
+ * Returns the user's computed context — who they are, what they prefer, what features
134
+ * to surface. Use this to configure your UI before rendering.
135
+ *
136
+ * Returns null if no context exists yet for this user (they haven't sent any events,
137
+ * or context hasn't been refreshed yet). Always handle the null case by showing
138
+ * your default layout.
139
+ *
140
+ * @example
141
+ * const context = await getContext(fluidsoulConfig, userId)
142
+ * const featureOrder = context?.feature_priorities ?? ["scan", "leads", "analytics"]
143
+ */
144
+ declare function getContext(config: FluidsoulConfig, userId: string, requestId?: string): Promise<UserContext | null>;
145
+
146
+ /**
147
+ * Create a fluidsoul config from environment variables.
148
+ * Reads FLUIDSOUL_API_URL, FLUIDSOUL_API_TOKEN, and FLUIDSOUL_WORKSPACE_ID.
149
+ *
150
+ * Works in Node.js and any environment where `process.env` is available.
151
+ * For browser environments, use NEXT_PUBLIC_ prefixed variables.
152
+ */
153
+ declare function createFluidsoulConfig(overrides?: Partial<FluidsoulConfig>): FluidsoulConfig;
154
+ /**
155
+ * A class-based wrapper around the fluidsoul SDK functions.
156
+ * Useful when you want to pass a single client instance around.
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const client = new FluidsoulClient({
161
+ * apiUrl: "https://api.fluidsoul.dev",
162
+ * token: process.vars.FLUIDSOUL_API_TOKEN!,
163
+ * workspaceId: process.vars.FLUIDSOUL_WORKSPACE_ID!,
164
+ * });
165
+ *
166
+ * await client.sendEvent({ userId: "user_123", eventType: "page_viewed" });
167
+ * await client.refreshContext("user_123");
168
+ * const ctx = await client.getContext("user_123");
169
+ * ```
170
+ */
171
+ declare class FluidsoulClient {
172
+ private readonly config;
173
+ constructor(config: FluidsoulConfig);
174
+ /** Send a user action event. */
175
+ sendEvent(input: SendEventInput, requestId?: string): Promise<void>;
176
+ /** Trigger a context refresh for a user. */
177
+ refreshContext(userId: string, requestId?: string): Promise<void>;
178
+ /** Get the current computed context for a user. Returns null if not found. */
179
+ getContext(userId: string, requestId?: string): Promise<UserContext | null>;
180
+ }
181
+
182
+ export { FluidsoulClient, type FluidsoulConfig, FluidsoulError, type SendEventInput, type UserContext, createFluidsoulConfig, getContext, refreshContext, sendEvent };
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Configuration required to connect to a fluidsoul API server.
3
+ * Store these values in environment variables — never hardcode them.
4
+ */
5
+ interface FluidsoulConfig {
6
+ /** URL of the fluidsoul API server. e.g. "https://api.fluidsoul.dev" or "http://localhost:4000" */
7
+ apiUrl: string;
8
+ /** Bearer token for your workspace. Set via fluidsoul CLI: `fluidsoul token set <token>` */
9
+ token: string;
10
+ /** Your workspace ID (UUID). Found in your fluidsoul dashboard or config file. */
11
+ workspaceId: string;
12
+ }
13
+ /**
14
+ * The computed context profile for a user.
15
+ * fluidsoul produces this by running its rules engine over the user's event history.
16
+ * Use it to decide what your UI should show, hide, prioritize, or pre-configure.
17
+ */
18
+ interface UserContext {
19
+ user_id: string;
20
+ /**
21
+ * What kind of user this is, inferred from their behavior.
22
+ * The possible values depend on your workspace's rules config dimensions.
23
+ * Examples: "b2b_focused", "analyst", "power_user", "new_user", "hybrid"
24
+ */
25
+ user_category: string;
26
+ /**
27
+ * The dominant mode/value this user prefers based on their activity.
28
+ * Derived from the primary dimension in your rules config.
29
+ */
30
+ preferred_mode: string;
31
+ /**
32
+ * Features ordered by how relevant they are to this user.
33
+ * Use this to decide what to surface first in your UI.
34
+ * e.g. ["discovery", "lead_management", "analytics", "scanning"]
35
+ */
36
+ feature_priorities: string[];
37
+ /**
38
+ * Suggested default settings to pre-configure for this user.
39
+ * Keys and values depend on your workspace's rules config.
40
+ * e.g. { "default_view": "reports", "default_period": "weekly" }
41
+ */
42
+ recommended_defaults: Record<string, string>;
43
+ /** How actively this user engages with the app. */
44
+ engagement_level: "low" | "moderate" | "high";
45
+ /**
46
+ * How experienced this user is with the app.
47
+ * - "new" — show onboarding hints and tooltips
48
+ * - "intermediate" — show hints for unfamiliar features only
49
+ * - "power_user" — hide onboarding, show advanced options
50
+ */
51
+ user_maturity: "new" | "intermediate" | "power_user";
52
+ /**
53
+ * How confident fluidsoul is about each inference, as a number between 0 and 1.
54
+ * e.g. { "user_category": 0.85, "preferred_mode": 0.92 }
55
+ */
56
+ confidence_scores: Record<string, number>;
57
+ /**
58
+ * Plain-English reasons explaining why the context looks the way it does.
59
+ * e.g. ["Classified as 'analyst' based on 12 analyst and 3 executive signals"]
60
+ * Show these to developers and power users for transparency.
61
+ */
62
+ explainability: string[];
63
+ /**
64
+ * A natural-language summary of this user, generated by an LLM.
65
+ * Null if no LLM is configured or inference hasn't run yet.
66
+ */
67
+ user_narrative?: string | null;
68
+ /**
69
+ * How this context was computed.
70
+ * - "rules" — deterministic rules engine only
71
+ * - "rules+llm" — rules engine + LLM-generated narrative
72
+ */
73
+ inference_source?: "rules" | "rules+llm";
74
+ /** ISO 8601 timestamp of when this context was last computed. */
75
+ last_refreshed_at: string;
76
+ }
77
+ /** Input for sending a user action event to fluidsoul. */
78
+ interface SendEventInput {
79
+ /** The ID of the user who performed the action. Must match how you identify users in your app. */
80
+ userId: string;
81
+ /**
82
+ * What the user did. Use consistent names across your app.
83
+ * Define event types that match your workspace's signal conditions.
84
+ */
85
+ eventType: string;
86
+ /** Optional extra details about the event. e.g. { platform: "linkedin" } or { feature_name: "analytics" } */
87
+ metadata?: Record<string, unknown>;
88
+ }
89
+ /** Error thrown when a fluidsoul API call fails. */
90
+ declare class FluidsoulError extends Error {
91
+ readonly status: number;
92
+ readonly endpoint: string;
93
+ readonly body: string;
94
+ constructor(message: string, status: number, endpoint: string, body: string);
95
+ }
96
+
97
+ /**
98
+ * fluidsoul core HTTP client.
99
+ *
100
+ * Framework-agnostic — works in any environment that supports the Fetch API:
101
+ * browsers, Node.js 18+, Electron, VS Code extensions, Figma plugins, Photoshop UXP.
102
+ *
103
+ * If you're using React, prefer the hooks in "fluidsoul-js/react" which wrap this client
104
+ * with state management. Use this client directly for server-side calls (Next.js
105
+ * server components / route handlers) or non-React environments.
106
+ */
107
+
108
+ /**
109
+ * Send a user action event to fluidsoul.
110
+ *
111
+ * Call this fire-and-forget whenever a user does something meaningful in your app.
112
+ * fluidsoul accumulates these events and uses them to build the user's context profile.
113
+ *
114
+ * @example
115
+ * // In a server action or route handler — never call this from browser code directly
116
+ * await sendEvent(fluidsoulConfig, { userId, eventType: "b2b_mode_used" })
117
+ */
118
+ declare function sendEvent(config: FluidsoulConfig, input: SendEventInput, requestId?: string): Promise<void>;
119
+ /**
120
+ * Trigger a context refresh for a user.
121
+ *
122
+ * This tells fluidsoul to re-run its rules engine over all of the user's events and
123
+ * update their context profile. Call this after sending a batch of events, or at
124
+ * natural session boundaries (end of session, start of a new session).
125
+ *
126
+ * You don't need to call this after every single event — batch events and refresh
127
+ * periodically for best performance.
128
+ */
129
+ declare function refreshContext(config: FluidsoulConfig, userId: string, requestId?: string): Promise<void>;
130
+ /**
131
+ * Get the current context profile for a user.
132
+ *
133
+ * Returns the user's computed context — who they are, what they prefer, what features
134
+ * to surface. Use this to configure your UI before rendering.
135
+ *
136
+ * Returns null if no context exists yet for this user (they haven't sent any events,
137
+ * or context hasn't been refreshed yet). Always handle the null case by showing
138
+ * your default layout.
139
+ *
140
+ * @example
141
+ * const context = await getContext(fluidsoulConfig, userId)
142
+ * const featureOrder = context?.feature_priorities ?? ["scan", "leads", "analytics"]
143
+ */
144
+ declare function getContext(config: FluidsoulConfig, userId: string, requestId?: string): Promise<UserContext | null>;
145
+
146
+ /**
147
+ * Create a fluidsoul config from environment variables.
148
+ * Reads FLUIDSOUL_API_URL, FLUIDSOUL_API_TOKEN, and FLUIDSOUL_WORKSPACE_ID.
149
+ *
150
+ * Works in Node.js and any environment where `process.env` is available.
151
+ * For browser environments, use NEXT_PUBLIC_ prefixed variables.
152
+ */
153
+ declare function createFluidsoulConfig(overrides?: Partial<FluidsoulConfig>): FluidsoulConfig;
154
+ /**
155
+ * A class-based wrapper around the fluidsoul SDK functions.
156
+ * Useful when you want to pass a single client instance around.
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const client = new FluidsoulClient({
161
+ * apiUrl: "https://api.fluidsoul.dev",
162
+ * token: process.vars.FLUIDSOUL_API_TOKEN!,
163
+ * workspaceId: process.vars.FLUIDSOUL_WORKSPACE_ID!,
164
+ * });
165
+ *
166
+ * await client.sendEvent({ userId: "user_123", eventType: "page_viewed" });
167
+ * await client.refreshContext("user_123");
168
+ * const ctx = await client.getContext("user_123");
169
+ * ```
170
+ */
171
+ declare class FluidsoulClient {
172
+ private readonly config;
173
+ constructor(config: FluidsoulConfig);
174
+ /** Send a user action event. */
175
+ sendEvent(input: SendEventInput, requestId?: string): Promise<void>;
176
+ /** Trigger a context refresh for a user. */
177
+ refreshContext(userId: string, requestId?: string): Promise<void>;
178
+ /** Get the current computed context for a user. Returns null if not found. */
179
+ getContext(userId: string, requestId?: string): Promise<UserContext | null>;
180
+ }
181
+
182
+ export { FluidsoulClient, type FluidsoulConfig, FluidsoulError, type SendEventInput, type UserContext, createFluidsoulConfig, getContext, refreshContext, sendEvent };
package/dist/index.js ADDED
@@ -0,0 +1,103 @@
1
+ // src/types.ts
2
+ var FluidsoulError = class extends Error {
3
+ constructor(message, status, endpoint, body) {
4
+ super(message);
5
+ this.status = status;
6
+ this.endpoint = endpoint;
7
+ this.body = body;
8
+ this.name = "FluidsoulError";
9
+ }
10
+ };
11
+
12
+ // src/client.ts
13
+ function buildHeaders(config, requestId) {
14
+ return {
15
+ "Authorization": `Bearer ${config.token}`,
16
+ "Content-Type": "application/json",
17
+ "X-Workspace-Id": config.workspaceId,
18
+ ...requestId ? { "X-Request-Id": requestId } : {}
19
+ };
20
+ }
21
+ async function assertOk(res, endpoint) {
22
+ if (!res.ok) {
23
+ const body = await res.text().catch(() => "");
24
+ throw new FluidsoulError(
25
+ `fluidsoul request failed: ${endpoint} returned ${res.status}`,
26
+ res.status,
27
+ endpoint,
28
+ body
29
+ );
30
+ }
31
+ }
32
+ async function sendEvent(config, input, requestId) {
33
+ const endpoint = `${config.apiUrl}/v1/events/ingest`;
34
+ const res = await fetch(endpoint, {
35
+ method: "POST",
36
+ headers: buildHeaders(config, requestId),
37
+ body: JSON.stringify({
38
+ user_id: input.userId,
39
+ event_type: input.eventType,
40
+ event_time: (/* @__PURE__ */ new Date()).toISOString(),
41
+ metadata: input.metadata ?? {}
42
+ })
43
+ });
44
+ await assertOk(res, endpoint);
45
+ }
46
+ async function refreshContext(config, userId, requestId) {
47
+ const endpoint = `${config.apiUrl}/v1/context/refresh`;
48
+ const res = await fetch(endpoint, {
49
+ method: "POST",
50
+ headers: buildHeaders(config, requestId),
51
+ body: JSON.stringify({
52
+ user_id: userId,
53
+ refresh_reason: "new_events"
54
+ })
55
+ });
56
+ await assertOk(res, endpoint);
57
+ }
58
+ async function getContext(config, userId, requestId) {
59
+ const endpoint = `${config.apiUrl}/v1/context/users/${encodeURIComponent(userId)}`;
60
+ const res = await fetch(endpoint, {
61
+ method: "GET",
62
+ headers: buildHeaders(config, requestId)
63
+ });
64
+ if (res.status === 404) return null;
65
+ await assertOk(res, endpoint);
66
+ return res.json();
67
+ }
68
+
69
+ // src/helpers.ts
70
+ function createFluidsoulConfig(overrides) {
71
+ const env = typeof globalThis !== "undefined" && "process" in globalThis ? globalThis.process : void 0;
72
+ const vars = env?.env ?? {};
73
+ return {
74
+ apiUrl: overrides?.apiUrl ?? vars.FLUIDSOUL_API_URL ?? vars.NEXT_PUBLIC_FLUIDSOUL_API_URL ?? "http://localhost:4000",
75
+ token: overrides?.token ?? vars.FLUIDSOUL_API_TOKEN ?? vars.NEXT_PUBLIC_FLUIDSOUL_API_TOKEN ?? "",
76
+ workspaceId: overrides?.workspaceId ?? vars.FLUIDSOUL_WORKSPACE_ID ?? vars.NEXT_PUBLIC_FLUIDSOUL_WORKSPACE_ID ?? ""
77
+ };
78
+ }
79
+ var FluidsoulClient = class {
80
+ constructor(config) {
81
+ this.config = config;
82
+ }
83
+ /** Send a user action event. */
84
+ async sendEvent(input, requestId) {
85
+ return sendEvent(this.config, input, requestId);
86
+ }
87
+ /** Trigger a context refresh for a user. */
88
+ async refreshContext(userId, requestId) {
89
+ return refreshContext(this.config, userId, requestId);
90
+ }
91
+ /** Get the current computed context for a user. Returns null if not found. */
92
+ async getContext(userId, requestId) {
93
+ return getContext(this.config, userId, requestId);
94
+ }
95
+ };
96
+ export {
97
+ FluidsoulClient,
98
+ FluidsoulError,
99
+ createFluidsoulConfig,
100
+ getContext,
101
+ refreshContext,
102
+ sendEvent
103
+ };
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ FluidsoulProvider: () => FluidsoulProvider,
24
+ useFluidsoulContext: () => useFluidsoulContext,
25
+ useFluidsoulEvents: () => useFluidsoulEvents
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/react/FluidsoulProvider.tsx
30
+ var import_react = require("react");
31
+ var import_jsx_runtime = require("react/jsx-runtime");
32
+ var FluidsoulContext = (0, import_react.createContext)(null);
33
+ function FluidsoulProvider({ apiUrl, token, workspaceId, children }) {
34
+ const config = { apiUrl, token, workspaceId };
35
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FluidsoulContext.Provider, { value: config, children });
36
+ }
37
+ function useFluidsoulConfig() {
38
+ const config = (0, import_react.useContext)(FluidsoulContext);
39
+ if (!config) {
40
+ throw new Error(
41
+ "useFluidsoulConfig: no FluidsoulProvider found. Wrap your component tree with <FluidsoulProvider>."
42
+ );
43
+ }
44
+ return config;
45
+ }
46
+
47
+ // src/react/useFluidsoulContext.ts
48
+ var import_react2 = require("react");
49
+
50
+ // src/types.ts
51
+ var FluidsoulError = class extends Error {
52
+ constructor(message, status, endpoint, body) {
53
+ super(message);
54
+ this.status = status;
55
+ this.endpoint = endpoint;
56
+ this.body = body;
57
+ this.name = "FluidsoulError";
58
+ }
59
+ };
60
+
61
+ // src/client.ts
62
+ function buildHeaders(config, requestId) {
63
+ return {
64
+ "Authorization": `Bearer ${config.token}`,
65
+ "Content-Type": "application/json",
66
+ "X-Workspace-Id": config.workspaceId,
67
+ ...requestId ? { "X-Request-Id": requestId } : {}
68
+ };
69
+ }
70
+ async function assertOk(res, endpoint) {
71
+ if (!res.ok) {
72
+ const body = await res.text().catch(() => "");
73
+ throw new FluidsoulError(
74
+ `fluidsoul request failed: ${endpoint} returned ${res.status}`,
75
+ res.status,
76
+ endpoint,
77
+ body
78
+ );
79
+ }
80
+ }
81
+ async function sendEvent(config, input, requestId) {
82
+ const endpoint = `${config.apiUrl}/v1/events/ingest`;
83
+ const res = await fetch(endpoint, {
84
+ method: "POST",
85
+ headers: buildHeaders(config, requestId),
86
+ body: JSON.stringify({
87
+ user_id: input.userId,
88
+ event_type: input.eventType,
89
+ event_time: (/* @__PURE__ */ new Date()).toISOString(),
90
+ metadata: input.metadata ?? {}
91
+ })
92
+ });
93
+ await assertOk(res, endpoint);
94
+ }
95
+ async function refreshContext(config, userId, requestId) {
96
+ const endpoint = `${config.apiUrl}/v1/context/refresh`;
97
+ const res = await fetch(endpoint, {
98
+ method: "POST",
99
+ headers: buildHeaders(config, requestId),
100
+ body: JSON.stringify({
101
+ user_id: userId,
102
+ refresh_reason: "new_events"
103
+ })
104
+ });
105
+ await assertOk(res, endpoint);
106
+ }
107
+ async function getContext(config, userId, requestId) {
108
+ const endpoint = `${config.apiUrl}/v1/context/users/${encodeURIComponent(userId)}`;
109
+ const res = await fetch(endpoint, {
110
+ method: "GET",
111
+ headers: buildHeaders(config, requestId)
112
+ });
113
+ if (res.status === 404) return null;
114
+ await assertOk(res, endpoint);
115
+ return res.json();
116
+ }
117
+
118
+ // src/react/useFluidsoulContext.ts
119
+ function useFluidsoulContext(userId) {
120
+ const config = useFluidsoulConfig();
121
+ const [context, setContext] = (0, import_react2.useState)(null);
122
+ const [loading, setLoading] = (0, import_react2.useState)(true);
123
+ const [error, setError] = (0, import_react2.useState)(null);
124
+ const mountedRef = (0, import_react2.useRef)(true);
125
+ (0, import_react2.useEffect)(() => {
126
+ mountedRef.current = true;
127
+ return () => {
128
+ mountedRef.current = false;
129
+ };
130
+ }, []);
131
+ const fetchContext = (0, import_react2.useCallback)(async () => {
132
+ if (!mountedRef.current) return;
133
+ setLoading(true);
134
+ setError(null);
135
+ try {
136
+ const result = await getContext(config, userId);
137
+ if (mountedRef.current) {
138
+ setContext(result);
139
+ }
140
+ } catch (err) {
141
+ if (mountedRef.current) {
142
+ setError(err instanceof Error ? err : new Error(String(err)));
143
+ }
144
+ } finally {
145
+ if (mountedRef.current) {
146
+ setLoading(false);
147
+ }
148
+ }
149
+ }, [config, userId]);
150
+ (0, import_react2.useEffect)(() => {
151
+ void fetchContext();
152
+ }, [fetchContext]);
153
+ const refresh = (0, import_react2.useCallback)(async () => {
154
+ try {
155
+ await refreshContext(config, userId);
156
+ } catch {
157
+ }
158
+ await fetchContext();
159
+ }, [config, userId, fetchContext]);
160
+ return { context, loading, error, refresh };
161
+ }
162
+
163
+ // src/react/useFluidsoulEvents.ts
164
+ var import_react3 = require("react");
165
+ function useFluidsoulEvents() {
166
+ const config = useFluidsoulConfig();
167
+ const sendEvent2 = (0, import_react3.useCallback)(
168
+ (input) => {
169
+ void sendEvent(config, input).catch(() => {
170
+ });
171
+ },
172
+ [config]
173
+ );
174
+ return { sendEvent: sendEvent2 };
175
+ }
176
+ // Annotate the CommonJS export names for ESM import in node:
177
+ 0 && (module.exports = {
178
+ FluidsoulProvider,
179
+ useFluidsoulContext,
180
+ useFluidsoulEvents
181
+ });
@@ -0,0 +1,180 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ /**
5
+ * Configuration required to connect to a fluidsoul API server.
6
+ * Store these values in environment variables — never hardcode them.
7
+ */
8
+ interface FluidsoulConfig {
9
+ /** URL of the fluidsoul API server. e.g. "https://api.fluidsoul.dev" or "http://localhost:4000" */
10
+ apiUrl: string;
11
+ /** Bearer token for your workspace. Set via fluidsoul CLI: `fluidsoul token set <token>` */
12
+ token: string;
13
+ /** Your workspace ID (UUID). Found in your fluidsoul dashboard or config file. */
14
+ workspaceId: string;
15
+ }
16
+ /**
17
+ * The computed context profile for a user.
18
+ * fluidsoul produces this by running its rules engine over the user's event history.
19
+ * Use it to decide what your UI should show, hide, prioritize, or pre-configure.
20
+ */
21
+ interface UserContext {
22
+ user_id: string;
23
+ /**
24
+ * What kind of user this is, inferred from their behavior.
25
+ * The possible values depend on your workspace's rules config dimensions.
26
+ * Examples: "b2b_focused", "analyst", "power_user", "new_user", "hybrid"
27
+ */
28
+ user_category: string;
29
+ /**
30
+ * The dominant mode/value this user prefers based on their activity.
31
+ * Derived from the primary dimension in your rules config.
32
+ */
33
+ preferred_mode: string;
34
+ /**
35
+ * Features ordered by how relevant they are to this user.
36
+ * Use this to decide what to surface first in your UI.
37
+ * e.g. ["discovery", "lead_management", "analytics", "scanning"]
38
+ */
39
+ feature_priorities: string[];
40
+ /**
41
+ * Suggested default settings to pre-configure for this user.
42
+ * Keys and values depend on your workspace's rules config.
43
+ * e.g. { "default_view": "reports", "default_period": "weekly" }
44
+ */
45
+ recommended_defaults: Record<string, string>;
46
+ /** How actively this user engages with the app. */
47
+ engagement_level: "low" | "moderate" | "high";
48
+ /**
49
+ * How experienced this user is with the app.
50
+ * - "new" — show onboarding hints and tooltips
51
+ * - "intermediate" — show hints for unfamiliar features only
52
+ * - "power_user" — hide onboarding, show advanced options
53
+ */
54
+ user_maturity: "new" | "intermediate" | "power_user";
55
+ /**
56
+ * How confident fluidsoul is about each inference, as a number between 0 and 1.
57
+ * e.g. { "user_category": 0.85, "preferred_mode": 0.92 }
58
+ */
59
+ confidence_scores: Record<string, number>;
60
+ /**
61
+ * Plain-English reasons explaining why the context looks the way it does.
62
+ * e.g. ["Classified as 'analyst' based on 12 analyst and 3 executive signals"]
63
+ * Show these to developers and power users for transparency.
64
+ */
65
+ explainability: string[];
66
+ /**
67
+ * A natural-language summary of this user, generated by an LLM.
68
+ * Null if no LLM is configured or inference hasn't run yet.
69
+ */
70
+ user_narrative?: string | null;
71
+ /**
72
+ * How this context was computed.
73
+ * - "rules" — deterministic rules engine only
74
+ * - "rules+llm" — rules engine + LLM-generated narrative
75
+ */
76
+ inference_source?: "rules" | "rules+llm";
77
+ /** ISO 8601 timestamp of when this context was last computed. */
78
+ last_refreshed_at: string;
79
+ }
80
+ /** Input for sending a user action event to fluidsoul. */
81
+ interface SendEventInput {
82
+ /** The ID of the user who performed the action. Must match how you identify users in your app. */
83
+ userId: string;
84
+ /**
85
+ * What the user did. Use consistent names across your app.
86
+ * Define event types that match your workspace's signal conditions.
87
+ */
88
+ eventType: string;
89
+ /** Optional extra details about the event. e.g. { platform: "linkedin" } or { feature_name: "analytics" } */
90
+ metadata?: Record<string, unknown>;
91
+ }
92
+ /** Error thrown when a fluidsoul API call fails. */
93
+ declare class FluidsoulError extends Error {
94
+ readonly status: number;
95
+ readonly endpoint: string;
96
+ readonly body: string;
97
+ constructor(message: string, status: number, endpoint: string, body: string);
98
+ }
99
+
100
+ interface FluidsoulProviderProps extends FluidsoulConfig {
101
+ children: React.ReactNode;
102
+ }
103
+ declare function FluidsoulProvider({ apiUrl, token, workspaceId, children }: FluidsoulProviderProps): react_jsx_runtime.JSX.Element;
104
+
105
+ /**
106
+ * useFluidsoulContext — fetch and subscribe to a user's fluidsoul context profile.
107
+ *
108
+ * Drop this hook into any component that needs to adapt its UI based on who
109
+ * the current user is. The hook fetches on mount and re-fetches whenever
110
+ * the userId changes.
111
+ *
112
+ * Requires <FluidsoulProvider> somewhere above this component in the tree.
113
+ *
114
+ * @example
115
+ * function Dashboard({ userId }: { userId: string }) {
116
+ * const { context, loading, error } = useFluidsoulContext(userId)
117
+ *
118
+ * if (loading) return <Spinner />
119
+ *
120
+ * const features = context?.feature_priorities ?? ["scan", "leads", "analytics"]
121
+ * const showOnboarding = !context || context.user_maturity === "new"
122
+ *
123
+ * return (
124
+ * <div>
125
+ * {showOnboarding && <OnboardingBanner />}
126
+ * <FeatureGrid features={features} />
127
+ * </div>
128
+ * )
129
+ * }
130
+ */
131
+
132
+ interface UseFluidsoulContextResult {
133
+ /** The user's current context profile. Null if not yet loaded or user has no context. */
134
+ context: UserContext | null;
135
+ /** True while the initial fetch is in progress. */
136
+ loading: boolean;
137
+ /** Set if the fetch failed. Your UI should fall back to its default layout. */
138
+ error: FluidsoulError | Error | null;
139
+ /**
140
+ * Manually trigger a context refresh then re-fetch.
141
+ * Call this after sending a batch of events (e.g., end of session).
142
+ * Returns a promise that resolves when the refresh and re-fetch are complete.
143
+ */
144
+ refresh: () => Promise<void>;
145
+ }
146
+ declare function useFluidsoulContext(userId: string): UseFluidsoulContextResult;
147
+
148
+ /**
149
+ * useFluidsoulEvents — send user action events from any React component.
150
+ *
151
+ * Returns a `sendEvent` function that fires events to fluidsoul fire-and-forget style
152
+ * (it never blocks your UI or throws — failures are silently ignored so your app
153
+ * always keeps working even if fluidsoul is temporarily unavailable).
154
+ *
155
+ * Requires <FluidsoulProvider> somewhere above this component in the tree.
156
+ *
157
+ * @example
158
+ * function ScanButton({ userId }: { userId: string }) {
159
+ * const { sendEvent } = useFluidsoulEvents()
160
+ *
161
+ * function handleScan() {
162
+ * sendEvent({ userId, eventType: "scan_triggered", metadata: { scan_type: "b2b", platform: "linkedin" } })
163
+ * // Continue immediately — don't wait for fluidsoul
164
+ * startScan()
165
+ * }
166
+ *
167
+ * return <button onClick={handleScan}>Scan</button>
168
+ * }
169
+ */
170
+
171
+ interface UseFluidsoulEventsResult {
172
+ /**
173
+ * Send a user action event to fluidsoul.
174
+ * Fire-and-forget: call it and move on. Never awaited, never throws.
175
+ */
176
+ sendEvent: (input: SendEventInput) => void;
177
+ }
178
+ declare function useFluidsoulEvents(): UseFluidsoulEventsResult;
179
+
180
+ export { type FluidsoulConfig, FluidsoulError, FluidsoulProvider, type SendEventInput, type UserContext, useFluidsoulContext, useFluidsoulEvents };
@@ -0,0 +1,180 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ /**
5
+ * Configuration required to connect to a fluidsoul API server.
6
+ * Store these values in environment variables — never hardcode them.
7
+ */
8
+ interface FluidsoulConfig {
9
+ /** URL of the fluidsoul API server. e.g. "https://api.fluidsoul.dev" or "http://localhost:4000" */
10
+ apiUrl: string;
11
+ /** Bearer token for your workspace. Set via fluidsoul CLI: `fluidsoul token set <token>` */
12
+ token: string;
13
+ /** Your workspace ID (UUID). Found in your fluidsoul dashboard or config file. */
14
+ workspaceId: string;
15
+ }
16
+ /**
17
+ * The computed context profile for a user.
18
+ * fluidsoul produces this by running its rules engine over the user's event history.
19
+ * Use it to decide what your UI should show, hide, prioritize, or pre-configure.
20
+ */
21
+ interface UserContext {
22
+ user_id: string;
23
+ /**
24
+ * What kind of user this is, inferred from their behavior.
25
+ * The possible values depend on your workspace's rules config dimensions.
26
+ * Examples: "b2b_focused", "analyst", "power_user", "new_user", "hybrid"
27
+ */
28
+ user_category: string;
29
+ /**
30
+ * The dominant mode/value this user prefers based on their activity.
31
+ * Derived from the primary dimension in your rules config.
32
+ */
33
+ preferred_mode: string;
34
+ /**
35
+ * Features ordered by how relevant they are to this user.
36
+ * Use this to decide what to surface first in your UI.
37
+ * e.g. ["discovery", "lead_management", "analytics", "scanning"]
38
+ */
39
+ feature_priorities: string[];
40
+ /**
41
+ * Suggested default settings to pre-configure for this user.
42
+ * Keys and values depend on your workspace's rules config.
43
+ * e.g. { "default_view": "reports", "default_period": "weekly" }
44
+ */
45
+ recommended_defaults: Record<string, string>;
46
+ /** How actively this user engages with the app. */
47
+ engagement_level: "low" | "moderate" | "high";
48
+ /**
49
+ * How experienced this user is with the app.
50
+ * - "new" — show onboarding hints and tooltips
51
+ * - "intermediate" — show hints for unfamiliar features only
52
+ * - "power_user" — hide onboarding, show advanced options
53
+ */
54
+ user_maturity: "new" | "intermediate" | "power_user";
55
+ /**
56
+ * How confident fluidsoul is about each inference, as a number between 0 and 1.
57
+ * e.g. { "user_category": 0.85, "preferred_mode": 0.92 }
58
+ */
59
+ confidence_scores: Record<string, number>;
60
+ /**
61
+ * Plain-English reasons explaining why the context looks the way it does.
62
+ * e.g. ["Classified as 'analyst' based on 12 analyst and 3 executive signals"]
63
+ * Show these to developers and power users for transparency.
64
+ */
65
+ explainability: string[];
66
+ /**
67
+ * A natural-language summary of this user, generated by an LLM.
68
+ * Null if no LLM is configured or inference hasn't run yet.
69
+ */
70
+ user_narrative?: string | null;
71
+ /**
72
+ * How this context was computed.
73
+ * - "rules" — deterministic rules engine only
74
+ * - "rules+llm" — rules engine + LLM-generated narrative
75
+ */
76
+ inference_source?: "rules" | "rules+llm";
77
+ /** ISO 8601 timestamp of when this context was last computed. */
78
+ last_refreshed_at: string;
79
+ }
80
+ /** Input for sending a user action event to fluidsoul. */
81
+ interface SendEventInput {
82
+ /** The ID of the user who performed the action. Must match how you identify users in your app. */
83
+ userId: string;
84
+ /**
85
+ * What the user did. Use consistent names across your app.
86
+ * Define event types that match your workspace's signal conditions.
87
+ */
88
+ eventType: string;
89
+ /** Optional extra details about the event. e.g. { platform: "linkedin" } or { feature_name: "analytics" } */
90
+ metadata?: Record<string, unknown>;
91
+ }
92
+ /** Error thrown when a fluidsoul API call fails. */
93
+ declare class FluidsoulError extends Error {
94
+ readonly status: number;
95
+ readonly endpoint: string;
96
+ readonly body: string;
97
+ constructor(message: string, status: number, endpoint: string, body: string);
98
+ }
99
+
100
+ interface FluidsoulProviderProps extends FluidsoulConfig {
101
+ children: React.ReactNode;
102
+ }
103
+ declare function FluidsoulProvider({ apiUrl, token, workspaceId, children }: FluidsoulProviderProps): react_jsx_runtime.JSX.Element;
104
+
105
+ /**
106
+ * useFluidsoulContext — fetch and subscribe to a user's fluidsoul context profile.
107
+ *
108
+ * Drop this hook into any component that needs to adapt its UI based on who
109
+ * the current user is. The hook fetches on mount and re-fetches whenever
110
+ * the userId changes.
111
+ *
112
+ * Requires <FluidsoulProvider> somewhere above this component in the tree.
113
+ *
114
+ * @example
115
+ * function Dashboard({ userId }: { userId: string }) {
116
+ * const { context, loading, error } = useFluidsoulContext(userId)
117
+ *
118
+ * if (loading) return <Spinner />
119
+ *
120
+ * const features = context?.feature_priorities ?? ["scan", "leads", "analytics"]
121
+ * const showOnboarding = !context || context.user_maturity === "new"
122
+ *
123
+ * return (
124
+ * <div>
125
+ * {showOnboarding && <OnboardingBanner />}
126
+ * <FeatureGrid features={features} />
127
+ * </div>
128
+ * )
129
+ * }
130
+ */
131
+
132
+ interface UseFluidsoulContextResult {
133
+ /** The user's current context profile. Null if not yet loaded or user has no context. */
134
+ context: UserContext | null;
135
+ /** True while the initial fetch is in progress. */
136
+ loading: boolean;
137
+ /** Set if the fetch failed. Your UI should fall back to its default layout. */
138
+ error: FluidsoulError | Error | null;
139
+ /**
140
+ * Manually trigger a context refresh then re-fetch.
141
+ * Call this after sending a batch of events (e.g., end of session).
142
+ * Returns a promise that resolves when the refresh and re-fetch are complete.
143
+ */
144
+ refresh: () => Promise<void>;
145
+ }
146
+ declare function useFluidsoulContext(userId: string): UseFluidsoulContextResult;
147
+
148
+ /**
149
+ * useFluidsoulEvents — send user action events from any React component.
150
+ *
151
+ * Returns a `sendEvent` function that fires events to fluidsoul fire-and-forget style
152
+ * (it never blocks your UI or throws — failures are silently ignored so your app
153
+ * always keeps working even if fluidsoul is temporarily unavailable).
154
+ *
155
+ * Requires <FluidsoulProvider> somewhere above this component in the tree.
156
+ *
157
+ * @example
158
+ * function ScanButton({ userId }: { userId: string }) {
159
+ * const { sendEvent } = useFluidsoulEvents()
160
+ *
161
+ * function handleScan() {
162
+ * sendEvent({ userId, eventType: "scan_triggered", metadata: { scan_type: "b2b", platform: "linkedin" } })
163
+ * // Continue immediately — don't wait for fluidsoul
164
+ * startScan()
165
+ * }
166
+ *
167
+ * return <button onClick={handleScan}>Scan</button>
168
+ * }
169
+ */
170
+
171
+ interface UseFluidsoulEventsResult {
172
+ /**
173
+ * Send a user action event to fluidsoul.
174
+ * Fire-and-forget: call it and move on. Never awaited, never throws.
175
+ */
176
+ sendEvent: (input: SendEventInput) => void;
177
+ }
178
+ declare function useFluidsoulEvents(): UseFluidsoulEventsResult;
179
+
180
+ export { type FluidsoulConfig, FluidsoulError, FluidsoulProvider, type SendEventInput, type UserContext, useFluidsoulContext, useFluidsoulEvents };
@@ -0,0 +1,152 @@
1
+ // src/react/FluidsoulProvider.tsx
2
+ import { createContext, useContext } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var FluidsoulContext = createContext(null);
5
+ function FluidsoulProvider({ apiUrl, token, workspaceId, children }) {
6
+ const config = { apiUrl, token, workspaceId };
7
+ return /* @__PURE__ */ jsx(FluidsoulContext.Provider, { value: config, children });
8
+ }
9
+ function useFluidsoulConfig() {
10
+ const config = useContext(FluidsoulContext);
11
+ if (!config) {
12
+ throw new Error(
13
+ "useFluidsoulConfig: no FluidsoulProvider found. Wrap your component tree with <FluidsoulProvider>."
14
+ );
15
+ }
16
+ return config;
17
+ }
18
+
19
+ // src/react/useFluidsoulContext.ts
20
+ import { useCallback, useEffect, useRef, useState } from "react";
21
+
22
+ // src/types.ts
23
+ var FluidsoulError = class extends Error {
24
+ constructor(message, status, endpoint, body) {
25
+ super(message);
26
+ this.status = status;
27
+ this.endpoint = endpoint;
28
+ this.body = body;
29
+ this.name = "FluidsoulError";
30
+ }
31
+ };
32
+
33
+ // src/client.ts
34
+ function buildHeaders(config, requestId) {
35
+ return {
36
+ "Authorization": `Bearer ${config.token}`,
37
+ "Content-Type": "application/json",
38
+ "X-Workspace-Id": config.workspaceId,
39
+ ...requestId ? { "X-Request-Id": requestId } : {}
40
+ };
41
+ }
42
+ async function assertOk(res, endpoint) {
43
+ if (!res.ok) {
44
+ const body = await res.text().catch(() => "");
45
+ throw new FluidsoulError(
46
+ `fluidsoul request failed: ${endpoint} returned ${res.status}`,
47
+ res.status,
48
+ endpoint,
49
+ body
50
+ );
51
+ }
52
+ }
53
+ async function sendEvent(config, input, requestId) {
54
+ const endpoint = `${config.apiUrl}/v1/events/ingest`;
55
+ const res = await fetch(endpoint, {
56
+ method: "POST",
57
+ headers: buildHeaders(config, requestId),
58
+ body: JSON.stringify({
59
+ user_id: input.userId,
60
+ event_type: input.eventType,
61
+ event_time: (/* @__PURE__ */ new Date()).toISOString(),
62
+ metadata: input.metadata ?? {}
63
+ })
64
+ });
65
+ await assertOk(res, endpoint);
66
+ }
67
+ async function refreshContext(config, userId, requestId) {
68
+ const endpoint = `${config.apiUrl}/v1/context/refresh`;
69
+ const res = await fetch(endpoint, {
70
+ method: "POST",
71
+ headers: buildHeaders(config, requestId),
72
+ body: JSON.stringify({
73
+ user_id: userId,
74
+ refresh_reason: "new_events"
75
+ })
76
+ });
77
+ await assertOk(res, endpoint);
78
+ }
79
+ async function getContext(config, userId, requestId) {
80
+ const endpoint = `${config.apiUrl}/v1/context/users/${encodeURIComponent(userId)}`;
81
+ const res = await fetch(endpoint, {
82
+ method: "GET",
83
+ headers: buildHeaders(config, requestId)
84
+ });
85
+ if (res.status === 404) return null;
86
+ await assertOk(res, endpoint);
87
+ return res.json();
88
+ }
89
+
90
+ // src/react/useFluidsoulContext.ts
91
+ function useFluidsoulContext(userId) {
92
+ const config = useFluidsoulConfig();
93
+ const [context, setContext] = useState(null);
94
+ const [loading, setLoading] = useState(true);
95
+ const [error, setError] = useState(null);
96
+ const mountedRef = useRef(true);
97
+ useEffect(() => {
98
+ mountedRef.current = true;
99
+ return () => {
100
+ mountedRef.current = false;
101
+ };
102
+ }, []);
103
+ const fetchContext = useCallback(async () => {
104
+ if (!mountedRef.current) return;
105
+ setLoading(true);
106
+ setError(null);
107
+ try {
108
+ const result = await getContext(config, userId);
109
+ if (mountedRef.current) {
110
+ setContext(result);
111
+ }
112
+ } catch (err) {
113
+ if (mountedRef.current) {
114
+ setError(err instanceof Error ? err : new Error(String(err)));
115
+ }
116
+ } finally {
117
+ if (mountedRef.current) {
118
+ setLoading(false);
119
+ }
120
+ }
121
+ }, [config, userId]);
122
+ useEffect(() => {
123
+ void fetchContext();
124
+ }, [fetchContext]);
125
+ const refresh = useCallback(async () => {
126
+ try {
127
+ await refreshContext(config, userId);
128
+ } catch {
129
+ }
130
+ await fetchContext();
131
+ }, [config, userId, fetchContext]);
132
+ return { context, loading, error, refresh };
133
+ }
134
+
135
+ // src/react/useFluidsoulEvents.ts
136
+ import { useCallback as useCallback2 } from "react";
137
+ function useFluidsoulEvents() {
138
+ const config = useFluidsoulConfig();
139
+ const sendEvent2 = useCallback2(
140
+ (input) => {
141
+ void sendEvent(config, input).catch(() => {
142
+ });
143
+ },
144
+ [config]
145
+ );
146
+ return { sendEvent: sendEvent2 };
147
+ }
148
+ export {
149
+ FluidsoulProvider,
150
+ useFluidsoulContext,
151
+ useFluidsoulEvents
152
+ };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "fluidsoul-js",
3
+ "version": "0.1.0",
4
+ "description": "Adaptive user context for every app. Classify users, compute engagement, recommend features — from behavior events. Official JavaScript/TypeScript SDK.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ },
13
+ "./react": {
14
+ "types": "./dist/react/index.d.ts",
15
+ "import": "./dist/react/index.js",
16
+ "require": "./dist/react/index.cjs"
17
+ }
18
+ },
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "files": ["dist"],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "typecheck": "tsc --noEmit",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "keywords": [
29
+ "user-context",
30
+ "adaptive-ui",
31
+ "personalization",
32
+ "behavior-analytics",
33
+ "rules-engine",
34
+ "react",
35
+ "next.js"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/linden-hub/fluidsoul"
40
+ },
41
+ "homepage": "https://fluidsoul.dev",
42
+ "peerDependencies": {
43
+ "react": ">=18"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "react": {
47
+ "optional": true
48
+ }
49
+ },
50
+ "devDependencies": {
51
+ "@types/react": "^18.0.0",
52
+ "tsup": "^8.0.0",
53
+ "typescript": "^5.0.0"
54
+ }
55
+ }