@joai/core 0.2.0 → 0.4.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.mjs CHANGED
@@ -18,7 +18,32 @@ const getAppBaseUrl = (env) => {
18
18
  if (env === "testnet") return "https://testnet.joai.ai";
19
19
  return "https://joai.ai";
20
20
  };
21
+ const getDigestBaseUrl = (env) => {
22
+ if (env === "local") return "http://localhost:3005";
23
+ if (env === "devnet") return "https://devnet-digests.joai.ai";
24
+ if (env === "testnet") return "https://testnet-digests.joai.ai";
25
+ return "https://digests.joai.ai";
26
+ };
27
+ const getMarketplaceBaseUrl = (env) => {
28
+ if (env === "local") return "http://localhost:3006";
29
+ if (env === "devnet") return "https://devnet-marketplace.joai.ai";
30
+ if (env === "testnet") return "https://testnet-marketplace.joai.ai";
31
+ return "https://marketplace.joai.ai";
32
+ };
33
+ const getAgentsBaseUrl = (env) => {
34
+ if (env === "local") return "http://localhost:3007";
35
+ if (env === "devnet") return "https://devnet-agents.joai.ai";
36
+ if (env === "testnet") return "https://testnet-agents.joai.ai";
37
+ return "https://agents.joai.ai";
38
+ };
21
39
  const getMcpServerAgentUrl = (env, agent) => `${toSubdomainBaseUrl(env, "cortex")}/mcp/agents/${agent}`;
40
+ const getSitesRootDomain = (env) => {
41
+ if (env === "local") return "sites.joai--api.test";
42
+ if (env === "devnet") return "devnet-sites.joai.ai";
43
+ if (env === "testnet") return "testnet-sites.joai.ai";
44
+ return "sites.joai.ai";
45
+ };
46
+ const getSiteUrl = (env, teamSlug, brandSlug) => `https://${getSitesRootDomain(env)}/${teamSlug}/${brandSlug}`;
22
47
  //#endregion
23
48
  //#region src/config/constants.ts
24
49
  const JoaiUrls = {
@@ -142,7 +167,7 @@ const sanitizeAlphanumeric = (val) => val.replace(/[^a-z0-9]/gi, "");
142
167
  const sanitizeNumeric = (val) => {
143
168
  return (val.split(".").length > 2 && val.endsWith(".") ? val.slice(0, -1) : val).replace(/[^0-9\.]+/g, "");
144
169
  };
145
- const toNameInitials = (name) => name.split(" ").map((word) => word[0]).join("");
170
+ const toNameInitials = (name) => name ? name.split(" ").map((word) => word[0]).join("").toUpperCase().slice(0, 2) : "?";
146
171
  const toPreviewText = (text, maxChars = 100) => {
147
172
  if (!text) return "";
148
173
  let sanitized = text.replace(/<\/?(h[1-6])[^>]*>/gi, " - ").replace(/<\/?(p|div|ul|ol|li|br|hr)[^>]*>/gi, " ").replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim();
@@ -167,6 +192,11 @@ const abbreviateNumber = (val) => {
167
192
  const suffix = SI_SYMBOL[tier];
168
193
  return (val / Math.pow(10, tier * 3)).toFixed(1) + suffix;
169
194
  };
195
+ const toCurrencyDisplay = (valueInCents, currency = "USD") => new Intl.NumberFormat("en-US", {
196
+ style: "currency",
197
+ currency,
198
+ currencyDisplay: "symbol"
199
+ }).format(valueInCents / 100);
170
200
  const toHexEncodedNonce = (nonce) => {
171
201
  const hex = nonce.toString(16);
172
202
  return hex.length % 2 ? "0" + hex : hex;
@@ -203,11 +233,22 @@ const withRetry = async (fn, validate, { maxRetries = 5, retryDelay = 1e3 } = {}
203
233
  //#endregion
204
234
  //#region src/helpers/asset.ts
205
235
  const AssetBaseUrl = "https://raw.githubusercontent.com/JoAiHQ/assets/main";
236
+ const WarpsBaseUrl = "https://raw.githubusercontent.com/JoAiHQ/warps/main";
206
237
  const getAssetAppLogoUrl = (name) => `${AssetBaseUrl}/apps/logos/${name}`;
207
238
  const getAssetChainLogoUrl = (name) => `${AssetBaseUrl}/chains/logos/${name}`;
208
239
  const getAssetTokenLogoUrl = (name) => `${AssetBaseUrl}/tokens/logos/${name}`;
209
240
  const getAssetWalletLogoUrl = (name) => `${AssetBaseUrl}/wallets/logos/${name}`;
210
241
  const getAssetPartnerLogoUrl = (name) => `${AssetBaseUrl}/partners/logos/${name}`;
242
+ const getWarpScreenshotUrl = (brandSlug, warpAlias, variant = "light") => {
243
+ return `${WarpsBaseUrl}/warps/${brandSlug}/images/${warpAlias}${variant === "light" ? "" : `-${variant}`}.png`;
244
+ };
245
+ //#endregion
246
+ //#region src/helpers/warp.ts
247
+ function resolveWarpText(text, locale = "en") {
248
+ if (!text) return "";
249
+ if (typeof text === "string") return text;
250
+ return text[locale] ?? text.en ?? Object.values(text)[0] ?? "";
251
+ }
211
252
  //#endregion
212
253
  //#region src/budget/types.ts
213
254
  const PeriodTypeLabels = {
@@ -277,6 +318,14 @@ const OgImageColors = {
277
318
  }
278
319
  };
279
320
  //#endregion
321
+ //#region src/ogimage/helpers.ts
322
+ const OG_BASE = "https://gen.joai.ai/image/og";
323
+ function ogImageUrl(type, params) {
324
+ switch (type) {
325
+ case "meeting": return `${OG_BASE}/meeting/${params.code}`;
326
+ }
327
+ }
328
+ //#endregion
280
329
  //#region src/skill/types.ts
281
330
  const SkillType = {
282
331
  Knowledge: "knowledge",
@@ -300,4 +349,4 @@ const SkillScope = {
300
349
  Team: "team"
301
350
  };
302
351
  //#endregion
303
- export { ContractCategoryDisplayNames, DefaultCodeMetadata, HttpService, JoaiUrls, OG_IMAGE_CONFIG, OG_IMAGE_HEIGHT, OG_IMAGE_WIDTH, OgImageColors, PeriodTypeLabels, PeriodTypeOptions, SkillMediaBindingRole, SkillScope, SkillType, abbreviateNumber, getApiBaseUrl, getAppBaseUrl, getAssetAppLogoUrl, getAssetChainLogoUrl, getAssetPartnerLogoUrl, getAssetTokenLogoUrl, getAssetWalletLogoUrl, getCortexBaseUrl, getMcpServerAgentUrl, isFileFormatAudio, isFileFormatImage, isFileFormatVideo, sanitizeAlphanumeric, sanitizeNumeric, toHexEncodedNonce, toNameInitials, toNftId, toPreviewText, withRetry };
352
+ export { ContractCategoryDisplayNames, DefaultCodeMetadata, HttpService, JoaiUrls, OG_IMAGE_CONFIG, OG_IMAGE_HEIGHT, OG_IMAGE_WIDTH, OgImageColors, PeriodTypeLabels, PeriodTypeOptions, SkillMediaBindingRole, SkillScope, SkillType, abbreviateNumber, getAgentsBaseUrl, getApiBaseUrl, getAppBaseUrl, getAssetAppLogoUrl, getAssetChainLogoUrl, getAssetPartnerLogoUrl, getAssetTokenLogoUrl, getAssetWalletLogoUrl, getCortexBaseUrl, getDigestBaseUrl, getMarketplaceBaseUrl, getMcpServerAgentUrl, getSiteUrl, getSitesRootDomain, getWarpScreenshotUrl, isFileFormatAudio, isFileFormatImage, isFileFormatVideo, ogImageUrl, resolveWarpText, sanitizeAlphanumeric, sanitizeNumeric, toCurrencyDisplay, toHexEncodedNonce, toNameInitials, toNftId, toPreviewText, withRetry };
@@ -0,0 +1,103 @@
1
+ import { n as IHttpService } from "../types-C4eYYzWF.mjs";
2
+ import * as react from "react";
3
+ import { ReactNode } from "react";
4
+
5
+ //#region src/react/types.d.ts
6
+ type EmbedChatItem = {
7
+ content: string;
8
+ byUser: boolean;
9
+ streaming?: boolean;
10
+ error?: boolean;
11
+ };
12
+ //#endregion
13
+ //#region src/react/guest.d.ts
14
+ declare function getOrCreateGuestId(storageKey?: string): string;
15
+ declare function loadRoom(storageKey: string): string | null;
16
+ declare function saveRoom(storageKey: string, roomId: string): void;
17
+ declare function clearRoom(storageKey: string): void;
18
+ //#endregion
19
+ //#region src/react/stream-connection.d.ts
20
+ type StreamDataHandler = (data: unknown) => void;
21
+ declare function openEventSourceStream(url: string, onData: StreamDataHandler, options?: {
22
+ withCredentials?: boolean;
23
+ onError?: (e: unknown) => void;
24
+ onConnectionError?: (e: Event | null) => void;
25
+ }): () => void;
26
+ //#endregion
27
+ //#region src/react/components.d.ts
28
+ type ListClassNames = {
29
+ container?: string;
30
+ userBubble?: string;
31
+ agentBubble?: string;
32
+ errorBubble?: string;
33
+ thinkingWrapper?: string;
34
+ dot?: string;
35
+ };
36
+ type EmbedChatListProps = {
37
+ items: EmbedChatItem[];
38
+ agentName: string;
39
+ awaitingResponse: boolean;
40
+ classNames?: ListClassNames;
41
+ renderEmptyState?: () => ReactNode;
42
+ renderAgentContent?: (item: EmbedChatItem) => ReactNode;
43
+ };
44
+ declare function EmbedChatList({
45
+ items,
46
+ agentName,
47
+ awaitingResponse,
48
+ classNames,
49
+ renderEmptyState,
50
+ renderAgentContent
51
+ }: EmbedChatListProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react.JSX.Element | null | undefined;
52
+ type EmbedChatInputProps = {
53
+ onSend: (message: string) => void;
54
+ disabled: boolean;
55
+ placeholder?: string;
56
+ maxLength?: number;
57
+ classNames?: {
58
+ container?: string;
59
+ textarea?: string;
60
+ };
61
+ renderSendButton: (props: {
62
+ onClick: () => void;
63
+ disabled: boolean;
64
+ }) => ReactNode;
65
+ };
66
+ declare function EmbedChatInput({
67
+ onSend,
68
+ disabled,
69
+ placeholder,
70
+ maxLength,
71
+ classNames,
72
+ renderSendButton
73
+ }: EmbedChatInputProps): react.JSX.Element;
74
+ //#endregion
75
+ //#region src/react/use-embed-chat.d.ts
76
+ type Options = {
77
+ agentUuid: string | null;
78
+ http: IHttpService;
79
+ cortexBaseUrl: string;
80
+ guestId: string;
81
+ roomStorageKey: string;
82
+ enabled?: boolean;
83
+ extraPayload?: Record<string, unknown>;
84
+ };
85
+ declare function useEmbedChat({
86
+ agentUuid,
87
+ http,
88
+ cortexBaseUrl,
89
+ guestId,
90
+ roomStorageKey,
91
+ enabled,
92
+ extraPayload
93
+ }: Options): {
94
+ items: EmbedChatItem[];
95
+ setItems: react.Dispatch<react.SetStateAction<EmbedChatItem[]>>;
96
+ sending: boolean;
97
+ awaitingResponse: boolean;
98
+ roomId: string | null;
99
+ setRoomId: react.Dispatch<react.SetStateAction<string | null>>;
100
+ send: (message: string) => Promise<void>;
101
+ };
102
+ //#endregion
103
+ export { EmbedChatInput, EmbedChatItem, EmbedChatList, StreamDataHandler, clearRoom, getOrCreateGuestId, loadRoom, openEventSourceStream, saveRoom, useEmbedChat };
@@ -0,0 +1,269 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/react/guest.ts
4
+ function safeGet(key) {
5
+ try {
6
+ return localStorage.getItem(key);
7
+ } catch {
8
+ return null;
9
+ }
10
+ }
11
+ function safeSet(key, value) {
12
+ try {
13
+ localStorage.setItem(key, value);
14
+ } catch {}
15
+ }
16
+ function safeRemove(key) {
17
+ try {
18
+ localStorage.removeItem(key);
19
+ } catch {}
20
+ }
21
+ function getOrCreateGuestId(storageKey = "joai_embed_guest") {
22
+ if (typeof window === "undefined") return "";
23
+ const existing = safeGet(storageKey);
24
+ if (existing) return existing;
25
+ const id = crypto.randomUUID();
26
+ safeSet(storageKey, id);
27
+ return id;
28
+ }
29
+ function loadRoom(storageKey) {
30
+ return safeGet(storageKey);
31
+ }
32
+ function saveRoom(storageKey, roomId) {
33
+ safeSet(storageKey, roomId);
34
+ }
35
+ function clearRoom(storageKey) {
36
+ safeRemove(storageKey);
37
+ }
38
+ //#endregion
39
+ //#region src/react/stream-connection.ts
40
+ function openEventSourceStream(url, onData, options) {
41
+ const es = new EventSource(url, { withCredentials: options?.withCredentials ?? false });
42
+ es.onmessage = (event) => {
43
+ try {
44
+ onData(JSON.parse(event.data));
45
+ } catch (e) {
46
+ options?.onError?.(e);
47
+ }
48
+ };
49
+ es.onerror = (event) => {
50
+ options?.onConnectionError?.(event);
51
+ };
52
+ return () => es.close();
53
+ }
54
+ //#endregion
55
+ //#region src/react/components.tsx
56
+ function EmbedChatList({ items, agentName, awaitingResponse, classNames = {}, renderEmptyState, renderAgentContent }) {
57
+ const containerRef = useRef(null);
58
+ useEffect(() => {
59
+ const el = containerRef.current;
60
+ if (!el) return;
61
+ if (el.scrollHeight - el.scrollTop - el.clientHeight < 120) el.scrollTop = el.scrollHeight;
62
+ }, [items, awaitingResponse]);
63
+ if (items.length === 0 && !awaitingResponse) return renderEmptyState ? renderEmptyState() : /* @__PURE__ */ jsx("div", {
64
+ className: classNames.container,
65
+ children: /* @__PURE__ */ jsxs("p", { children: [
66
+ "Send ",
67
+ agentName,
68
+ " a message to start chatting"
69
+ ] })
70
+ });
71
+ return /* @__PURE__ */ jsxs("div", {
72
+ ref: containerRef,
73
+ className: classNames.container,
74
+ role: "log",
75
+ "aria-live": "polite",
76
+ children: [items.map((item, i) => /* @__PURE__ */ jsx(EmbedChatBubble, {
77
+ item,
78
+ classNames,
79
+ renderAgentContent
80
+ }, i)), awaitingResponse && /* @__PURE__ */ jsx(EmbedThinkingIndicator, {
81
+ wrapperClassName: classNames.thinkingWrapper,
82
+ dotClassName: classNames.dot
83
+ })]
84
+ });
85
+ }
86
+ function EmbedChatBubble({ item, classNames, renderAgentContent }) {
87
+ if (item.byUser) return /* @__PURE__ */ jsx("div", {
88
+ className: classNames.userBubble,
89
+ children: item.content
90
+ });
91
+ return /* @__PURE__ */ jsx("div", {
92
+ className: item.error ? classNames.errorBubble : classNames.agentBubble,
93
+ children: renderAgentContent ? renderAgentContent(item) : /* @__PURE__ */ jsxs(Fragment, { children: [item.content, item.streaming && /* @__PURE__ */ jsx("span", { className: "inline-block w-1 h-3 ml-1 bg-current animate-pulse rounded-full" })] })
94
+ });
95
+ }
96
+ function EmbedThinkingIndicator({ wrapperClassName, dotClassName }) {
97
+ return /* @__PURE__ */ jsx("div", {
98
+ className: wrapperClassName,
99
+ children: /* @__PURE__ */ jsxs("span", {
100
+ className: "inline-flex gap-1",
101
+ children: [
102
+ /* @__PURE__ */ jsx("span", {
103
+ className: dotClassName,
104
+ style: { animationDelay: "0ms" }
105
+ }),
106
+ /* @__PURE__ */ jsx("span", {
107
+ className: dotClassName,
108
+ style: { animationDelay: "150ms" }
109
+ }),
110
+ /* @__PURE__ */ jsx("span", {
111
+ className: dotClassName,
112
+ style: { animationDelay: "300ms" }
113
+ })
114
+ ]
115
+ })
116
+ });
117
+ }
118
+ function EmbedChatInput({ onSend, disabled, placeholder = "Type a message...", maxLength = 4e3, classNames = {}, renderSendButton }) {
119
+ const [text, setText] = useState("");
120
+ const textareaRef = useRef(null);
121
+ function handleSend() {
122
+ const trimmed = text.trim();
123
+ if (!trimmed || disabled) return;
124
+ onSend(trimmed);
125
+ setText("");
126
+ if (textareaRef.current) textareaRef.current.style.height = "auto";
127
+ }
128
+ function handleKeyDown(e) {
129
+ if (e.key === "Enter" && !e.shiftKey) {
130
+ e.preventDefault();
131
+ handleSend();
132
+ }
133
+ }
134
+ function handleInput() {
135
+ if (textareaRef.current) {
136
+ textareaRef.current.style.height = "auto";
137
+ textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 120) + "px";
138
+ }
139
+ }
140
+ return /* @__PURE__ */ jsxs("div", {
141
+ className: classNames.container,
142
+ children: [/* @__PURE__ */ jsx("textarea", {
143
+ ref: textareaRef,
144
+ value: text,
145
+ onChange: (e) => setText(e.target.value),
146
+ onKeyDown: handleKeyDown,
147
+ onInput: handleInput,
148
+ placeholder,
149
+ "aria-label": "Message input",
150
+ maxLength,
151
+ rows: 1,
152
+ disabled,
153
+ className: classNames.textarea
154
+ }), renderSendButton({
155
+ onClick: handleSend,
156
+ disabled: disabled || !text.trim()
157
+ })]
158
+ });
159
+ }
160
+ //#endregion
161
+ //#region src/react/use-embed-chat.ts
162
+ function useEmbedChat({ agentUuid, http, cortexBaseUrl, guestId, roomStorageKey, enabled = true, extraPayload }) {
163
+ const [items, setItems] = useState([]);
164
+ const [sending, setSending] = useState(false);
165
+ const [awaitingResponse, setAwaitingResponse] = useState(false);
166
+ const [roomId, setRoomId] = useState(null);
167
+ const roomIdRef = useRef(null);
168
+ roomIdRef.current = roomId;
169
+ const extraPayloadRef = useRef(extraPayload);
170
+ extraPayloadRef.current = extraPayload;
171
+ useEffect(() => {
172
+ if (!roomId) return;
173
+ return openEventSourceStream(`${cortexBaseUrl}/agents/stream/${roomId}`, (raw) => {
174
+ const data = raw;
175
+ if (data.type === "response:chunk") {
176
+ setAwaitingResponse(false);
177
+ setItems((prev) => {
178
+ const last = prev[prev.length - 1];
179
+ if (last && last.streaming && !last.byUser) {
180
+ const updated = [...prev];
181
+ updated[updated.length - 1] = {
182
+ ...last,
183
+ content: last.content + (data.content ?? "")
184
+ };
185
+ return updated;
186
+ }
187
+ return [...prev, {
188
+ content: data.content ?? "",
189
+ byUser: false,
190
+ streaming: true
191
+ }];
192
+ });
193
+ } else if (data.type === "response") {
194
+ setAwaitingResponse(false);
195
+ setItems((prev) => {
196
+ const updated = [...prev];
197
+ const lastIdx = updated.length - 1;
198
+ if (lastIdx >= 0 && updated[lastIdx].streaming) updated[lastIdx] = {
199
+ ...updated[lastIdx],
200
+ content: data.content ?? updated[lastIdx].content,
201
+ streaming: false
202
+ };
203
+ else updated.push({
204
+ content: data.content ?? "",
205
+ byUser: false
206
+ });
207
+ return updated;
208
+ });
209
+ } else if (data.type === "complete") setItems((prev) => prev.map((item) => item.streaming ? {
210
+ ...item,
211
+ streaming: false
212
+ } : item));
213
+ });
214
+ }, [roomId, cortexBaseUrl]);
215
+ return {
216
+ items,
217
+ setItems,
218
+ sending,
219
+ awaitingResponse,
220
+ roomId,
221
+ setRoomId,
222
+ send: useCallback(async (message) => {
223
+ if (!enabled || sending || !agentUuid) return;
224
+ setItems((prev) => [...prev, {
225
+ content: message,
226
+ byUser: true
227
+ }]);
228
+ setSending(true);
229
+ try {
230
+ const res = await http.post(`integrations/embed/${agentUuid}/execute`, {
231
+ message,
232
+ guestId,
233
+ ...extraPayloadRef.current
234
+ });
235
+ if (!res.ok) {
236
+ setItems((prev) => [...prev, {
237
+ content: "Something went wrong. Please try again.",
238
+ byUser: false,
239
+ error: true
240
+ }]);
241
+ return;
242
+ }
243
+ setAwaitingResponse(true);
244
+ const newRoomId = res.data?.roomId;
245
+ if (newRoomId && !roomIdRef.current) {
246
+ setRoomId(newRoomId);
247
+ saveRoom(roomStorageKey, newRoomId);
248
+ }
249
+ } catch {
250
+ setItems((prev) => [...prev, {
251
+ content: "Something went wrong. Please try again.",
252
+ byUser: false,
253
+ error: true
254
+ }]);
255
+ } finally {
256
+ setSending(false);
257
+ }
258
+ }, [
259
+ enabled,
260
+ sending,
261
+ agentUuid,
262
+ http,
263
+ guestId,
264
+ roomStorageKey
265
+ ])
266
+ };
267
+ }
268
+ //#endregion
269
+ export { EmbedChatInput, EmbedChatList, clearRoom, getOrCreateGuestId, loadRoom, openEventSourceStream, saveRoom, useEmbedChat };
@@ -0,0 +1,31 @@
1
+ //#region src/http/types.d.ts
2
+ interface IHttpService {
3
+ readonly baseUrl: string;
4
+ readonly authToken: string | null;
5
+ onNetworkError: (error: Error) => void;
6
+ onUnauthorized: (response: IAppResponse<any>) => void;
7
+ onSuccessfulResponse: (response: IAppResponse<any>) => void;
8
+ onMaintenanceMode: (response: IAppResponse<any>) => void;
9
+ get: <T>(url: string, options?: IRequestOptions) => Promise<IAppResponse<T>>;
10
+ post: <T>(url: string, data?: any, options?: IRequestOptions) => Promise<IAppResponse<T>>;
11
+ put: <T>(url: string, data?: any, options?: IRequestOptions) => Promise<IAppResponse<T>>;
12
+ delete: <T>(url: string, options?: IRequestOptions) => Promise<IAppResponse<T>>;
13
+ }
14
+ interface IAppResponse<T> {
15
+ data: T;
16
+ meta?: any;
17
+ body: any;
18
+ errors: any;
19
+ ok: boolean;
20
+ paymentRequired: boolean;
21
+ noContent: boolean;
22
+ original: Response;
23
+ }
24
+ interface IRequestOptions {
25
+ isJson?: boolean;
26
+ headers?: {
27
+ [name: string]: string;
28
+ };
29
+ }
30
+ //#endregion
31
+ export { IHttpService as n, IRequestOptions as r, IAppResponse as t };
package/package.json CHANGED
@@ -1,14 +1,19 @@
1
1
  {
2
2
  "name": "@joai/core",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "JoAi shared types, HTTP service, and config",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.mjs",
8
+ "types": "./dist/index.d.mts",
8
9
  "exports": {
9
10
  ".": {
10
11
  "import": "./dist/index.mjs",
11
12
  "types": "./dist/index.d.mts"
13
+ },
14
+ "./react": {
15
+ "import": "./dist/react/index.mjs",
16
+ "types": "./dist/react/index.d.mts"
12
17
  }
13
18
  },
14
19
  "files": [
@@ -21,10 +26,17 @@
21
26
  "test:watch": "jest --watch"
22
27
  },
23
28
  "peerDependencies": {
24
- "@joai/warps": ">=4.0.0"
29
+ "@joai/warps": ">=4.0.0",
30
+ "react": ">=18.0.0"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "react": {
34
+ "optional": true
35
+ }
25
36
  },
26
37
  "devDependencies": {
27
38
  "@types/jest": "^30.0.0",
39
+ "@types/react": "^19.2.14",
28
40
  "jest": "^30.3.0",
29
41
  "ts-jest": "^29.4.6",
30
42
  "tsdown": "^0.21.0",