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 +135 -0
- package/dist/index.d.cts +182 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.js +103 -0
- package/dist/react/index.cjs +181 -0
- package/dist/react/index.d.cts +180 -0
- package/dist/react/index.d.ts +180 -0
- package/dist/react/index.js +152 -0
- package/package.json +55 -0
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
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|