@getforma/core 0.9.0 → 1.0.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/README.md +20 -1
- package/dist/chunk-DCTOXHPF.cjs +399 -0
- package/dist/chunk-DCTOXHPF.cjs.map +1 -0
- package/dist/chunk-OUVOAYIO.js +359 -0
- package/dist/chunk-OUVOAYIO.js.map +1 -0
- package/dist/{chunk-YMIMKO4W.cjs → chunk-V732ZBCU.cjs} +119 -511
- package/dist/chunk-V732ZBCU.cjs.map +1 -0
- package/dist/{chunk-N522P3G6.js → chunk-VTPFK5TJ.js} +89 -442
- package/dist/chunk-VTPFK5TJ.js.map +1 -0
- package/dist/forma-runtime-csp.js +1 -1
- package/dist/forma-runtime.js +1 -1
- package/dist/formajs-runtime-hardened.global.js +1 -1
- package/dist/formajs-runtime-hardened.global.js.map +1 -1
- package/dist/formajs-runtime.global.js +1 -1
- package/dist/formajs-runtime.global.js.map +1 -1
- package/dist/formajs.global.js +1 -1
- package/dist/formajs.global.js.map +1 -1
- package/dist/http.cjs +225 -0
- package/dist/http.cjs.map +1 -0
- package/dist/http.d.cts +108 -0
- package/dist/http.d.ts +108 -0
- package/dist/http.js +220 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +71 -607
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -458
- package/dist/index.d.ts +4 -458
- package/dist/index.js +7 -523
- package/dist/index.js.map +1 -1
- package/dist/resource-Cd0cGOxS.d.ts +62 -0
- package/dist/resource-DK98lW5e.d.cts +62 -0
- package/dist/runtime-hardened.cjs +3 -111
- package/dist/runtime-hardened.cjs.map +1 -1
- package/dist/runtime-hardened.js +4 -112
- package/dist/runtime-hardened.js.map +1 -1
- package/dist/runtime.cjs +23 -22
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +2 -1
- package/dist/runtime.js.map +1 -1
- package/dist/server.cjs +179 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +217 -0
- package/dist/server.d.ts +217 -0
- package/dist/server.js +166 -0
- package/dist/server.js.map +1 -0
- package/dist/{signal-B4_wQJHs.d.cts → signal-YlS1kgfh.d.cts} +1 -1
- package/dist/{signal-B4_wQJHs.d.ts → signal-YlS1kgfh.d.ts} +1 -1
- package/dist/storage.cjs +151 -0
- package/dist/storage.cjs.map +1 -0
- package/dist/storage.d.cts +77 -0
- package/dist/storage.d.ts +77 -0
- package/dist/storage.js +147 -0
- package/dist/storage.js.map +1 -0
- package/dist/tc39-compat.d.cts +1 -1
- package/dist/tc39-compat.d.ts +1 -1
- package/package.json +31 -1
- package/dist/chunk-N522P3G6.js.map +0 -1
- package/dist/chunk-YMIMKO4W.cjs.map +0 -1
package/dist/http.cjs
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkDCTOXHPF_cjs = require('./chunk-DCTOXHPF.cjs');
|
|
4
|
+
var chunk3U57L2TY_cjs = require('./chunk-3U57L2TY.cjs');
|
|
5
|
+
|
|
6
|
+
// src/http/fetch.ts
|
|
7
|
+
function createFetch(url, options) {
|
|
8
|
+
const [data, setData] = chunk3U57L2TY_cjs.createSignal(null);
|
|
9
|
+
const [error, setError] = chunk3U57L2TY_cjs.createSignal(null);
|
|
10
|
+
const [loading, setLoading] = chunk3U57L2TY_cjs.createSignal(false);
|
|
11
|
+
let currentController = null;
|
|
12
|
+
function resolveURL() {
|
|
13
|
+
const raw = typeof url === "function" ? url() : url;
|
|
14
|
+
const base = options?.base ?? "";
|
|
15
|
+
const fullURL = new URL(raw, base || void 0);
|
|
16
|
+
if (options?.params) {
|
|
17
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
18
|
+
fullURL.searchParams.set(key, value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return fullURL.toString();
|
|
22
|
+
}
|
|
23
|
+
async function execute() {
|
|
24
|
+
if (currentController) {
|
|
25
|
+
currentController.abort();
|
|
26
|
+
}
|
|
27
|
+
currentController = new AbortController();
|
|
28
|
+
const controller = currentController;
|
|
29
|
+
const timeoutMs = options?.timeout ?? 3e4;
|
|
30
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
31
|
+
setLoading(true);
|
|
32
|
+
setError(null);
|
|
33
|
+
try {
|
|
34
|
+
const resolvedURL = resolveURL();
|
|
35
|
+
const { base: _base, params: _params, timeout: _timeout, transform, ...fetchInit } = options ?? {};
|
|
36
|
+
const response = await fetch(resolvedURL, {
|
|
37
|
+
...fetchInit,
|
|
38
|
+
signal: controller.signal
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
42
|
+
}
|
|
43
|
+
const json = await response.json();
|
|
44
|
+
const transformed = transform ? transform(json) : json;
|
|
45
|
+
setData(transformed);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
51
|
+
} finally {
|
|
52
|
+
clearTimeout(timeoutId);
|
|
53
|
+
if (currentController === controller) {
|
|
54
|
+
currentController = null;
|
|
55
|
+
setLoading(false);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (typeof url === "function") {
|
|
60
|
+
chunkDCTOXHPF_cjs.internalEffect(() => {
|
|
61
|
+
url();
|
|
62
|
+
execute();
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
execute();
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
data,
|
|
69
|
+
error,
|
|
70
|
+
loading,
|
|
71
|
+
refetch: execute,
|
|
72
|
+
abort() {
|
|
73
|
+
if (currentController) {
|
|
74
|
+
currentController.abort();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function fetchJSON(url, options) {
|
|
80
|
+
const response = await fetch(url, options);
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
83
|
+
}
|
|
84
|
+
return await response.json();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/http/sse.ts
|
|
88
|
+
function createSSE(url, options) {
|
|
89
|
+
const [data, setData] = chunk3U57L2TY_cjs.createSignal(null);
|
|
90
|
+
const [error, setError] = chunk3U57L2TY_cjs.createSignal(null);
|
|
91
|
+
const [connected, setConnected] = chunk3U57L2TY_cjs.createSignal(false);
|
|
92
|
+
const source = new EventSource(url, {
|
|
93
|
+
withCredentials: options?.withCredentials ?? false
|
|
94
|
+
});
|
|
95
|
+
source.onopen = () => {
|
|
96
|
+
setConnected(true);
|
|
97
|
+
setError(null);
|
|
98
|
+
};
|
|
99
|
+
const parseMessage = options?.parse ?? ((raw) => {
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(raw);
|
|
102
|
+
} catch {
|
|
103
|
+
return raw;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
source.onmessage = (event) => {
|
|
107
|
+
setData(parseMessage(event.data));
|
|
108
|
+
};
|
|
109
|
+
source.onerror = (event) => {
|
|
110
|
+
setError(event);
|
|
111
|
+
setConnected(false);
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
data,
|
|
115
|
+
error,
|
|
116
|
+
connected,
|
|
117
|
+
close() {
|
|
118
|
+
source.close();
|
|
119
|
+
setConnected(false);
|
|
120
|
+
},
|
|
121
|
+
on(event, handler) {
|
|
122
|
+
const parseEvent = options?.parse ?? ((raw) => {
|
|
123
|
+
try {
|
|
124
|
+
return JSON.parse(raw);
|
|
125
|
+
} catch {
|
|
126
|
+
return raw;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const listener = (e) => {
|
|
130
|
+
handler(parseEvent(e.data));
|
|
131
|
+
};
|
|
132
|
+
source.addEventListener(event, listener);
|
|
133
|
+
return () => {
|
|
134
|
+
source.removeEventListener(event, listener);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/http/ws.ts
|
|
141
|
+
function createWebSocket(url, options) {
|
|
142
|
+
const shouldReconnect = options?.reconnect ?? true;
|
|
143
|
+
const baseInterval = options?.reconnectInterval ?? 1e3;
|
|
144
|
+
const maxReconnects = options?.maxReconnects ?? 5;
|
|
145
|
+
const [data, setData] = chunk3U57L2TY_cjs.createSignal(null);
|
|
146
|
+
const [status, setStatus] = chunk3U57L2TY_cjs.createSignal("connecting");
|
|
147
|
+
const handlers = /* @__PURE__ */ new Set();
|
|
148
|
+
let socket = null;
|
|
149
|
+
let reconnectCount = 0;
|
|
150
|
+
let reconnectTimer = null;
|
|
151
|
+
let permanentlyClosed = false;
|
|
152
|
+
function connect() {
|
|
153
|
+
if (permanentlyClosed) return;
|
|
154
|
+
setStatus("connecting");
|
|
155
|
+
socket = new WebSocket(url, options?.protocols);
|
|
156
|
+
socket.onopen = () => {
|
|
157
|
+
setStatus("open");
|
|
158
|
+
reconnectCount = 0;
|
|
159
|
+
};
|
|
160
|
+
const parseMessage = options?.parse ?? ((raw) => {
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(raw);
|
|
163
|
+
} catch {
|
|
164
|
+
return raw;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
socket.onmessage = (event) => {
|
|
168
|
+
const parsed = parseMessage(event.data);
|
|
169
|
+
setData(parsed);
|
|
170
|
+
for (const handler of handlers) {
|
|
171
|
+
handler(parsed);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
socket.onerror = () => {
|
|
175
|
+
setStatus("error");
|
|
176
|
+
};
|
|
177
|
+
socket.onclose = () => {
|
|
178
|
+
if (permanentlyClosed) {
|
|
179
|
+
setStatus("closed");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
setStatus("closed");
|
|
183
|
+
if (shouldReconnect && reconnectCount < maxReconnects) {
|
|
184
|
+
const delay = baseInterval * Math.pow(2, reconnectCount);
|
|
185
|
+
reconnectCount++;
|
|
186
|
+
reconnectTimer = setTimeout(connect, delay);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
connect();
|
|
191
|
+
return {
|
|
192
|
+
data,
|
|
193
|
+
status,
|
|
194
|
+
send(value) {
|
|
195
|
+
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
196
|
+
socket.send(JSON.stringify(value));
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
close() {
|
|
200
|
+
permanentlyClosed = true;
|
|
201
|
+
if (reconnectTimer !== null) {
|
|
202
|
+
clearTimeout(reconnectTimer);
|
|
203
|
+
reconnectTimer = null;
|
|
204
|
+
}
|
|
205
|
+
if (socket) {
|
|
206
|
+
socket.close();
|
|
207
|
+
socket = null;
|
|
208
|
+
}
|
|
209
|
+
setStatus("closed");
|
|
210
|
+
},
|
|
211
|
+
on(handler) {
|
|
212
|
+
handlers.add(handler);
|
|
213
|
+
return () => {
|
|
214
|
+
handlers.delete(handler);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
exports.createFetch = createFetch;
|
|
221
|
+
exports.createSSE = createSSE;
|
|
222
|
+
exports.createWebSocket = createWebSocket;
|
|
223
|
+
exports.fetchJSON = fetchJSON;
|
|
224
|
+
//# sourceMappingURL=http.cjs.map
|
|
225
|
+
//# sourceMappingURL=http.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/http/fetch.ts","../src/http/sse.ts","../src/http/ws.ts"],"names":["createSignal","internalEffect"],"mappings":"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","file":"http.cjs","sourcesContent":["/**\n * Forma HTTP - Fetch\n *\n * Typed fetch wrapper with reactive signal integration.\n * Zero dependencies — native browser APIs only.\n */\n\nimport { createSignal, internalEffect } from 'forma/reactive';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface FetchOptions<T> extends Omit<RequestInit, 'signal'> {\n base?: string;\n params?: Record<string, string>;\n timeout?: number; // ms, default 30000\n transform?: (data: unknown) => T;\n}\n\nexport interface FetchResult<T> {\n data: () => T | null;\n error: () => Error | null;\n loading: () => boolean;\n refetch: () => Promise<void>;\n abort: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// createFetch — reactive fetch with signals\n// ---------------------------------------------------------------------------\n\n/**\n * Create a reactive fetch that exposes data/error/loading as signals.\n *\n * If `url` is a signal getter (function), an effect auto-refetches when it\n * changes.\n *\n * ```ts\n * const { data, loading, error, refetch, abort } = createFetch<User[]>('/api/users');\n * ```\n */\nexport function createFetch<T>(\n url: string | (() => string),\n options?: FetchOptions<T>,\n): FetchResult<T> {\n const [data, setData] = createSignal<T | null>(null);\n const [error, setError] = createSignal<Error | null>(null);\n const [loading, setLoading] = createSignal<boolean>(false);\n\n let currentController: AbortController | null = null;\n\n /** Resolve the URL string, applying base and params. */\n function resolveURL(): string {\n const raw = typeof url === 'function' ? url() : url;\n const base = options?.base ?? '';\n const fullURL = new URL(raw, base || undefined);\n\n if (options?.params) {\n for (const [key, value] of Object.entries(options.params)) {\n fullURL.searchParams.set(key, value);\n }\n }\n\n return fullURL.toString();\n }\n\n /** Execute a single fetch request. */\n async function execute(): Promise<void> {\n // Abort any in-flight request\n if (currentController) {\n currentController.abort();\n }\n\n currentController = new AbortController();\n const controller = currentController;\n const timeoutMs = options?.timeout ?? 30_000;\n\n // Set up timeout\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n setLoading(true);\n setError(null);\n\n try {\n const resolvedURL = resolveURL();\n\n const { base: _base, params: _params, timeout: _timeout, transform, ...fetchInit } =\n options ?? {};\n\n const response = await fetch(resolvedURL, {\n ...fetchInit,\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const json: unknown = await response.json();\n const transformed = transform ? transform(json) : (json as T);\n setData(transformed);\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n // Ignore aborts — they are intentional or timeouts\n return;\n }\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n clearTimeout(timeoutId);\n // Only update loading if this controller is still current\n if (currentController === controller) {\n currentController = null;\n setLoading(false);\n }\n }\n }\n\n // If url is a reactive getter, set up an effect to auto-refetch.\n if (typeof url === 'function') {\n internalEffect(() => {\n // Read the signal so the effect re-runs when it changes\n url();\n execute();\n });\n } else {\n // Kick off the first request immediately\n execute();\n }\n\n return {\n data,\n error,\n loading,\n refetch: execute,\n abort() {\n if (currentController) {\n currentController.abort();\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// fetchJSON — simple one-shot helper\n// ---------------------------------------------------------------------------\n\n/**\n * One-shot fetch that returns parsed JSON.\n *\n * ```ts\n * const users = await fetchJSON<User[]>('/api/users');\n * ```\n */\nexport async function fetchJSON<T>(url: string, options?: RequestInit): Promise<T> {\n const response = await fetch(url, options);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n return (await response.json()) as T;\n}\n","/**\n * Forma HTTP - Server-Sent Events\n *\n * Reactive SSE wrapper with signal integration.\n * Zero dependencies — native browser APIs only.\n */\n\nimport { createSignal } from 'forma/reactive';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SSEOptions<T = unknown> {\n withCredentials?: boolean;\n headers?: Record<string, string>; // Note: native EventSource does not support custom headers\n /** Custom parser for incoming messages. Defaults to JSON.parse with raw data fallback. */\n parse?: (data: string) => T;\n}\n\nexport interface SSEConnection<T = unknown> {\n data: () => T | null;\n error: () => Event | null;\n connected: () => boolean;\n close: () => void;\n on(event: string, handler: (data: unknown) => void): () => void;\n}\n\n// ---------------------------------------------------------------------------\n// createSSE\n// ---------------------------------------------------------------------------\n\n/**\n * Create a reactive Server-Sent Events connection.\n *\n * ```ts\n * const sse = createSSE<{ message: string }>('/api/events');\n * createEffect(() => {\n * const msg = sse.data();\n * if (msg) console.log(msg.message);\n * });\n * ```\n */\nexport function createSSE<T = unknown>(\n url: string,\n options?: SSEOptions<T>,\n): SSEConnection<T> {\n const [data, setData] = createSignal<T | null>(null);\n const [error, setError] = createSignal<Event | null>(null);\n const [connected, setConnected] = createSignal<boolean>(false);\n\n const source = new EventSource(url, {\n withCredentials: options?.withCredentials ?? false,\n });\n\n source.onopen = () => {\n setConnected(true);\n setError(null);\n };\n\n const parseMessage = options?.parse ?? ((raw: string): T => {\n try { return JSON.parse(raw) as T; }\n catch { return raw as T; }\n });\n\n source.onmessage = (event: MessageEvent) => {\n setData(parseMessage(event.data as string));\n };\n\n source.onerror = (event: Event) => {\n setError(event);\n setConnected(false);\n };\n\n return {\n data,\n error,\n connected,\n\n close(): void {\n source.close();\n setConnected(false);\n },\n\n on(event: string, handler: (data: unknown) => void): () => void {\n const parseEvent = options?.parse ?? ((raw: string) => {\n try { return JSON.parse(raw); }\n catch { return raw; }\n });\n const listener = (e: MessageEvent) => {\n handler(parseEvent(e.data as string));\n };\n source.addEventListener(event, listener as EventListener);\n return () => {\n source.removeEventListener(event, listener as EventListener);\n };\n },\n };\n}\n","/**\n * Forma HTTP - WebSocket\n *\n * Reactive WebSocket wrapper with auto-reconnect and signal integration.\n * Zero dependencies — native browser APIs only.\n */\n\nimport { createSignal } from 'forma/reactive';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type WSStatus = 'connecting' | 'open' | 'closed' | 'error';\n\nexport interface WSOptions<TReceive = unknown> {\n protocols?: string | string[];\n reconnect?: boolean; // default true\n reconnectInterval?: number; // ms, default 1000\n maxReconnects?: number; // default 5\n /** Custom parser for incoming messages. Defaults to JSON.parse with raw data fallback. */\n parse?: (data: string) => TReceive;\n}\n\nexport interface WSConnection<TSend = unknown, TReceive = unknown> {\n data: () => TReceive | null;\n status: () => WSStatus;\n send(data: TSend): void;\n close(): void;\n on(handler: (data: TReceive) => void): () => void;\n}\n\n// ---------------------------------------------------------------------------\n// createWebSocket\n// ---------------------------------------------------------------------------\n\n/**\n * Create a reactive WebSocket connection with auto-reconnect.\n *\n * ```ts\n * const ws = createWebSocket<string, ChatMessage>('wss://chat.example.com');\n * ws.send('hello');\n * createEffect(() => {\n * const msg = ws.data();\n * if (msg) console.log(msg);\n * });\n * ```\n */\nexport function createWebSocket<TSend = unknown, TReceive = unknown>(\n url: string,\n options?: WSOptions<TReceive>,\n): WSConnection<TSend, TReceive> {\n const shouldReconnect = options?.reconnect ?? true;\n const baseInterval = options?.reconnectInterval ?? 1000;\n const maxReconnects = options?.maxReconnects ?? 5;\n\n const [data, setData] = createSignal<TReceive | null>(null);\n const [status, setStatus] = createSignal<WSStatus>('connecting');\n\n const handlers = new Set<(data: TReceive) => void>();\n\n let socket: WebSocket | null = null;\n let reconnectCount = 0;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let permanentlyClosed = false;\n\n function connect(): void {\n if (permanentlyClosed) return;\n\n setStatus('connecting');\n socket = new WebSocket(url, options?.protocols);\n\n socket.onopen = () => {\n setStatus('open');\n reconnectCount = 0; // Reset on successful connection\n };\n\n const parseMessage = options?.parse ?? ((raw: string): TReceive => {\n try { return JSON.parse(raw) as TReceive; }\n catch { return raw as TReceive; }\n });\n\n socket.onmessage = (event: MessageEvent) => {\n const parsed = parseMessage(event.data as string);\n setData(parsed);\n for (const handler of handlers) {\n handler(parsed);\n }\n };\n\n socket.onerror = () => {\n setStatus('error');\n };\n\n socket.onclose = () => {\n if (permanentlyClosed) {\n setStatus('closed');\n return;\n }\n\n setStatus('closed');\n\n if (shouldReconnect && reconnectCount < maxReconnects) {\n // Exponential backoff: baseInterval * 2^reconnectCount\n const delay = baseInterval * Math.pow(2, reconnectCount);\n reconnectCount++;\n reconnectTimer = setTimeout(connect, delay);\n }\n };\n }\n\n // Initiate the first connection\n connect();\n\n return {\n data,\n status,\n\n send(value: TSend): void {\n if (socket && socket.readyState === WebSocket.OPEN) {\n socket.send(JSON.stringify(value));\n }\n },\n\n close(): void {\n permanentlyClosed = true;\n if (reconnectTimer !== null) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n if (socket) {\n socket.close();\n socket = null;\n }\n setStatus('closed');\n },\n\n on(handler: (data: TReceive) => void): () => void {\n handlers.add(handler);\n return () => {\n handlers.delete(handler);\n };\n },\n };\n}\n"]}
|
package/dist/http.d.cts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forma HTTP - Fetch
|
|
3
|
+
*
|
|
4
|
+
* Typed fetch wrapper with reactive signal integration.
|
|
5
|
+
* Zero dependencies — native browser APIs only.
|
|
6
|
+
*/
|
|
7
|
+
interface FetchOptions<T> extends Omit<RequestInit, 'signal'> {
|
|
8
|
+
base?: string;
|
|
9
|
+
params?: Record<string, string>;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
transform?: (data: unknown) => T;
|
|
12
|
+
}
|
|
13
|
+
interface FetchResult<T> {
|
|
14
|
+
data: () => T | null;
|
|
15
|
+
error: () => Error | null;
|
|
16
|
+
loading: () => boolean;
|
|
17
|
+
refetch: () => Promise<void>;
|
|
18
|
+
abort: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a reactive fetch that exposes data/error/loading as signals.
|
|
22
|
+
*
|
|
23
|
+
* If `url` is a signal getter (function), an effect auto-refetches when it
|
|
24
|
+
* changes.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* const { data, loading, error, refetch, abort } = createFetch<User[]>('/api/users');
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function createFetch<T>(url: string | (() => string), options?: FetchOptions<T>): FetchResult<T>;
|
|
31
|
+
/**
|
|
32
|
+
* One-shot fetch that returns parsed JSON.
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const users = await fetchJSON<User[]>('/api/users');
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function fetchJSON<T>(url: string, options?: RequestInit): Promise<T>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Forma HTTP - Server-Sent Events
|
|
42
|
+
*
|
|
43
|
+
* Reactive SSE wrapper with signal integration.
|
|
44
|
+
* Zero dependencies — native browser APIs only.
|
|
45
|
+
*/
|
|
46
|
+
interface SSEOptions<T = unknown> {
|
|
47
|
+
withCredentials?: boolean;
|
|
48
|
+
headers?: Record<string, string>;
|
|
49
|
+
/** Custom parser for incoming messages. Defaults to JSON.parse with raw data fallback. */
|
|
50
|
+
parse?: (data: string) => T;
|
|
51
|
+
}
|
|
52
|
+
interface SSEConnection<T = unknown> {
|
|
53
|
+
data: () => T | null;
|
|
54
|
+
error: () => Event | null;
|
|
55
|
+
connected: () => boolean;
|
|
56
|
+
close: () => void;
|
|
57
|
+
on(event: string, handler: (data: unknown) => void): () => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a reactive Server-Sent Events connection.
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* const sse = createSSE<{ message: string }>('/api/events');
|
|
64
|
+
* createEffect(() => {
|
|
65
|
+
* const msg = sse.data();
|
|
66
|
+
* if (msg) console.log(msg.message);
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function createSSE<T = unknown>(url: string, options?: SSEOptions<T>): SSEConnection<T>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Forma HTTP - WebSocket
|
|
74
|
+
*
|
|
75
|
+
* Reactive WebSocket wrapper with auto-reconnect and signal integration.
|
|
76
|
+
* Zero dependencies — native browser APIs only.
|
|
77
|
+
*/
|
|
78
|
+
type WSStatus = 'connecting' | 'open' | 'closed' | 'error';
|
|
79
|
+
interface WSOptions<TReceive = unknown> {
|
|
80
|
+
protocols?: string | string[];
|
|
81
|
+
reconnect?: boolean;
|
|
82
|
+
reconnectInterval?: number;
|
|
83
|
+
maxReconnects?: number;
|
|
84
|
+
/** Custom parser for incoming messages. Defaults to JSON.parse with raw data fallback. */
|
|
85
|
+
parse?: (data: string) => TReceive;
|
|
86
|
+
}
|
|
87
|
+
interface WSConnection<TSend = unknown, TReceive = unknown> {
|
|
88
|
+
data: () => TReceive | null;
|
|
89
|
+
status: () => WSStatus;
|
|
90
|
+
send(data: TSend): void;
|
|
91
|
+
close(): void;
|
|
92
|
+
on(handler: (data: TReceive) => void): () => void;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create a reactive WebSocket connection with auto-reconnect.
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* const ws = createWebSocket<string, ChatMessage>('wss://chat.example.com');
|
|
99
|
+
* ws.send('hello');
|
|
100
|
+
* createEffect(() => {
|
|
101
|
+
* const msg = ws.data();
|
|
102
|
+
* if (msg) console.log(msg);
|
|
103
|
+
* });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
declare function createWebSocket<TSend = unknown, TReceive = unknown>(url: string, options?: WSOptions<TReceive>): WSConnection<TSend, TReceive>;
|
|
107
|
+
|
|
108
|
+
export { type FetchOptions, type FetchResult, type SSEConnection, type SSEOptions, type WSConnection, type WSOptions, type WSStatus, createFetch, createSSE, createWebSocket, fetchJSON };
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forma HTTP - Fetch
|
|
3
|
+
*
|
|
4
|
+
* Typed fetch wrapper with reactive signal integration.
|
|
5
|
+
* Zero dependencies — native browser APIs only.
|
|
6
|
+
*/
|
|
7
|
+
interface FetchOptions<T> extends Omit<RequestInit, 'signal'> {
|
|
8
|
+
base?: string;
|
|
9
|
+
params?: Record<string, string>;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
transform?: (data: unknown) => T;
|
|
12
|
+
}
|
|
13
|
+
interface FetchResult<T> {
|
|
14
|
+
data: () => T | null;
|
|
15
|
+
error: () => Error | null;
|
|
16
|
+
loading: () => boolean;
|
|
17
|
+
refetch: () => Promise<void>;
|
|
18
|
+
abort: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a reactive fetch that exposes data/error/loading as signals.
|
|
22
|
+
*
|
|
23
|
+
* If `url` is a signal getter (function), an effect auto-refetches when it
|
|
24
|
+
* changes.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* const { data, loading, error, refetch, abort } = createFetch<User[]>('/api/users');
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function createFetch<T>(url: string | (() => string), options?: FetchOptions<T>): FetchResult<T>;
|
|
31
|
+
/**
|
|
32
|
+
* One-shot fetch that returns parsed JSON.
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const users = await fetchJSON<User[]>('/api/users');
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function fetchJSON<T>(url: string, options?: RequestInit): Promise<T>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Forma HTTP - Server-Sent Events
|
|
42
|
+
*
|
|
43
|
+
* Reactive SSE wrapper with signal integration.
|
|
44
|
+
* Zero dependencies — native browser APIs only.
|
|
45
|
+
*/
|
|
46
|
+
interface SSEOptions<T = unknown> {
|
|
47
|
+
withCredentials?: boolean;
|
|
48
|
+
headers?: Record<string, string>;
|
|
49
|
+
/** Custom parser for incoming messages. Defaults to JSON.parse with raw data fallback. */
|
|
50
|
+
parse?: (data: string) => T;
|
|
51
|
+
}
|
|
52
|
+
interface SSEConnection<T = unknown> {
|
|
53
|
+
data: () => T | null;
|
|
54
|
+
error: () => Event | null;
|
|
55
|
+
connected: () => boolean;
|
|
56
|
+
close: () => void;
|
|
57
|
+
on(event: string, handler: (data: unknown) => void): () => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a reactive Server-Sent Events connection.
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* const sse = createSSE<{ message: string }>('/api/events');
|
|
64
|
+
* createEffect(() => {
|
|
65
|
+
* const msg = sse.data();
|
|
66
|
+
* if (msg) console.log(msg.message);
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function createSSE<T = unknown>(url: string, options?: SSEOptions<T>): SSEConnection<T>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Forma HTTP - WebSocket
|
|
74
|
+
*
|
|
75
|
+
* Reactive WebSocket wrapper with auto-reconnect and signal integration.
|
|
76
|
+
* Zero dependencies — native browser APIs only.
|
|
77
|
+
*/
|
|
78
|
+
type WSStatus = 'connecting' | 'open' | 'closed' | 'error';
|
|
79
|
+
interface WSOptions<TReceive = unknown> {
|
|
80
|
+
protocols?: string | string[];
|
|
81
|
+
reconnect?: boolean;
|
|
82
|
+
reconnectInterval?: number;
|
|
83
|
+
maxReconnects?: number;
|
|
84
|
+
/** Custom parser for incoming messages. Defaults to JSON.parse with raw data fallback. */
|
|
85
|
+
parse?: (data: string) => TReceive;
|
|
86
|
+
}
|
|
87
|
+
interface WSConnection<TSend = unknown, TReceive = unknown> {
|
|
88
|
+
data: () => TReceive | null;
|
|
89
|
+
status: () => WSStatus;
|
|
90
|
+
send(data: TSend): void;
|
|
91
|
+
close(): void;
|
|
92
|
+
on(handler: (data: TReceive) => void): () => void;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create a reactive WebSocket connection with auto-reconnect.
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* const ws = createWebSocket<string, ChatMessage>('wss://chat.example.com');
|
|
99
|
+
* ws.send('hello');
|
|
100
|
+
* createEffect(() => {
|
|
101
|
+
* const msg = ws.data();
|
|
102
|
+
* if (msg) console.log(msg);
|
|
103
|
+
* });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
declare function createWebSocket<TSend = unknown, TReceive = unknown>(url: string, options?: WSOptions<TReceive>): WSConnection<TSend, TReceive>;
|
|
107
|
+
|
|
108
|
+
export { type FetchOptions, type FetchResult, type SSEConnection, type SSEOptions, type WSConnection, type WSOptions, type WSStatus, createFetch, createSSE, createWebSocket, fetchJSON };
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { internalEffect } from './chunk-OUVOAYIO.js';
|
|
2
|
+
import { createSignal } from './chunk-OZCHIVAZ.js';
|
|
3
|
+
|
|
4
|
+
// src/http/fetch.ts
|
|
5
|
+
function createFetch(url, options) {
|
|
6
|
+
const [data, setData] = createSignal(null);
|
|
7
|
+
const [error, setError] = createSignal(null);
|
|
8
|
+
const [loading, setLoading] = createSignal(false);
|
|
9
|
+
let currentController = null;
|
|
10
|
+
function resolveURL() {
|
|
11
|
+
const raw = typeof url === "function" ? url() : url;
|
|
12
|
+
const base = options?.base ?? "";
|
|
13
|
+
const fullURL = new URL(raw, base || void 0);
|
|
14
|
+
if (options?.params) {
|
|
15
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
16
|
+
fullURL.searchParams.set(key, value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return fullURL.toString();
|
|
20
|
+
}
|
|
21
|
+
async function execute() {
|
|
22
|
+
if (currentController) {
|
|
23
|
+
currentController.abort();
|
|
24
|
+
}
|
|
25
|
+
currentController = new AbortController();
|
|
26
|
+
const controller = currentController;
|
|
27
|
+
const timeoutMs = options?.timeout ?? 3e4;
|
|
28
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
29
|
+
setLoading(true);
|
|
30
|
+
setError(null);
|
|
31
|
+
try {
|
|
32
|
+
const resolvedURL = resolveURL();
|
|
33
|
+
const { base: _base, params: _params, timeout: _timeout, transform, ...fetchInit } = options ?? {};
|
|
34
|
+
const response = await fetch(resolvedURL, {
|
|
35
|
+
...fetchInit,
|
|
36
|
+
signal: controller.signal
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
const json = await response.json();
|
|
42
|
+
const transformed = transform ? transform(json) : json;
|
|
43
|
+
setData(transformed);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
49
|
+
} finally {
|
|
50
|
+
clearTimeout(timeoutId);
|
|
51
|
+
if (currentController === controller) {
|
|
52
|
+
currentController = null;
|
|
53
|
+
setLoading(false);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (typeof url === "function") {
|
|
58
|
+
internalEffect(() => {
|
|
59
|
+
url();
|
|
60
|
+
execute();
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
execute();
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
data,
|
|
67
|
+
error,
|
|
68
|
+
loading,
|
|
69
|
+
refetch: execute,
|
|
70
|
+
abort() {
|
|
71
|
+
if (currentController) {
|
|
72
|
+
currentController.abort();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function fetchJSON(url, options) {
|
|
78
|
+
const response = await fetch(url, options);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
81
|
+
}
|
|
82
|
+
return await response.json();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/http/sse.ts
|
|
86
|
+
function createSSE(url, options) {
|
|
87
|
+
const [data, setData] = createSignal(null);
|
|
88
|
+
const [error, setError] = createSignal(null);
|
|
89
|
+
const [connected, setConnected] = createSignal(false);
|
|
90
|
+
const source = new EventSource(url, {
|
|
91
|
+
withCredentials: options?.withCredentials ?? false
|
|
92
|
+
});
|
|
93
|
+
source.onopen = () => {
|
|
94
|
+
setConnected(true);
|
|
95
|
+
setError(null);
|
|
96
|
+
};
|
|
97
|
+
const parseMessage = options?.parse ?? ((raw) => {
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(raw);
|
|
100
|
+
} catch {
|
|
101
|
+
return raw;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
source.onmessage = (event) => {
|
|
105
|
+
setData(parseMessage(event.data));
|
|
106
|
+
};
|
|
107
|
+
source.onerror = (event) => {
|
|
108
|
+
setError(event);
|
|
109
|
+
setConnected(false);
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
data,
|
|
113
|
+
error,
|
|
114
|
+
connected,
|
|
115
|
+
close() {
|
|
116
|
+
source.close();
|
|
117
|
+
setConnected(false);
|
|
118
|
+
},
|
|
119
|
+
on(event, handler) {
|
|
120
|
+
const parseEvent = options?.parse ?? ((raw) => {
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(raw);
|
|
123
|
+
} catch {
|
|
124
|
+
return raw;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
const listener = (e) => {
|
|
128
|
+
handler(parseEvent(e.data));
|
|
129
|
+
};
|
|
130
|
+
source.addEventListener(event, listener);
|
|
131
|
+
return () => {
|
|
132
|
+
source.removeEventListener(event, listener);
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/http/ws.ts
|
|
139
|
+
function createWebSocket(url, options) {
|
|
140
|
+
const shouldReconnect = options?.reconnect ?? true;
|
|
141
|
+
const baseInterval = options?.reconnectInterval ?? 1e3;
|
|
142
|
+
const maxReconnects = options?.maxReconnects ?? 5;
|
|
143
|
+
const [data, setData] = createSignal(null);
|
|
144
|
+
const [status, setStatus] = createSignal("connecting");
|
|
145
|
+
const handlers = /* @__PURE__ */ new Set();
|
|
146
|
+
let socket = null;
|
|
147
|
+
let reconnectCount = 0;
|
|
148
|
+
let reconnectTimer = null;
|
|
149
|
+
let permanentlyClosed = false;
|
|
150
|
+
function connect() {
|
|
151
|
+
if (permanentlyClosed) return;
|
|
152
|
+
setStatus("connecting");
|
|
153
|
+
socket = new WebSocket(url, options?.protocols);
|
|
154
|
+
socket.onopen = () => {
|
|
155
|
+
setStatus("open");
|
|
156
|
+
reconnectCount = 0;
|
|
157
|
+
};
|
|
158
|
+
const parseMessage = options?.parse ?? ((raw) => {
|
|
159
|
+
try {
|
|
160
|
+
return JSON.parse(raw);
|
|
161
|
+
} catch {
|
|
162
|
+
return raw;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
socket.onmessage = (event) => {
|
|
166
|
+
const parsed = parseMessage(event.data);
|
|
167
|
+
setData(parsed);
|
|
168
|
+
for (const handler of handlers) {
|
|
169
|
+
handler(parsed);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
socket.onerror = () => {
|
|
173
|
+
setStatus("error");
|
|
174
|
+
};
|
|
175
|
+
socket.onclose = () => {
|
|
176
|
+
if (permanentlyClosed) {
|
|
177
|
+
setStatus("closed");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
setStatus("closed");
|
|
181
|
+
if (shouldReconnect && reconnectCount < maxReconnects) {
|
|
182
|
+
const delay = baseInterval * Math.pow(2, reconnectCount);
|
|
183
|
+
reconnectCount++;
|
|
184
|
+
reconnectTimer = setTimeout(connect, delay);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
connect();
|
|
189
|
+
return {
|
|
190
|
+
data,
|
|
191
|
+
status,
|
|
192
|
+
send(value) {
|
|
193
|
+
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
194
|
+
socket.send(JSON.stringify(value));
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
close() {
|
|
198
|
+
permanentlyClosed = true;
|
|
199
|
+
if (reconnectTimer !== null) {
|
|
200
|
+
clearTimeout(reconnectTimer);
|
|
201
|
+
reconnectTimer = null;
|
|
202
|
+
}
|
|
203
|
+
if (socket) {
|
|
204
|
+
socket.close();
|
|
205
|
+
socket = null;
|
|
206
|
+
}
|
|
207
|
+
setStatus("closed");
|
|
208
|
+
},
|
|
209
|
+
on(handler) {
|
|
210
|
+
handlers.add(handler);
|
|
211
|
+
return () => {
|
|
212
|
+
handlers.delete(handler);
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export { createFetch, createSSE, createWebSocket, fetchJSON };
|
|
219
|
+
//# sourceMappingURL=http.js.map
|
|
220
|
+
//# sourceMappingURL=http.js.map
|