@getforma/core 1.0.5 → 1.0.7
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/{chunk-VTPFK5TJ.js → chunk-FJGTMWKY.js} +55 -10
- package/dist/chunk-FJGTMWKY.js.map +1 -0
- package/dist/{chunk-V732ZBCU.cjs → chunk-SZSKW57A.cjs} +55 -10
- package/dist/chunk-SZSKW57A.cjs.map +1 -0
- package/dist/formajs.global.js +53 -8
- package/dist/formajs.global.js.map +1 -1
- package/dist/http.cjs +3 -3
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +3 -3
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +11 -11
- package/dist/index.js +2 -2
- package/dist/runtime.cjs +3 -3
- package/dist/runtime.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-V732ZBCU.cjs.map +0 -1
- package/dist/chunk-VTPFK5TJ.js.map +0 -1
package/dist/http.cjs
CHANGED
|
@@ -11,7 +11,7 @@ function createFetch(url, options) {
|
|
|
11
11
|
let currentController = null;
|
|
12
12
|
function resolveURL() {
|
|
13
13
|
const raw = typeof url === "function" ? url() : url;
|
|
14
|
-
const base = options?.base
|
|
14
|
+
const base = options?.base || (typeof window !== "undefined" ? window.location.origin : "");
|
|
15
15
|
const fullURL = new URL(raw, base || void 0);
|
|
16
16
|
if (options?.params) {
|
|
17
17
|
for (const [key, value] of Object.entries(options.params)) {
|
|
@@ -59,10 +59,10 @@ function createFetch(url, options) {
|
|
|
59
59
|
if (typeof url === "function") {
|
|
60
60
|
chunkDCTOXHPF_cjs.internalEffect(() => {
|
|
61
61
|
url();
|
|
62
|
-
execute();
|
|
62
|
+
void execute();
|
|
63
63
|
});
|
|
64
64
|
} else {
|
|
65
|
-
execute();
|
|
65
|
+
void execute();
|
|
66
66
|
}
|
|
67
67
|
return {
|
|
68
68
|
data,
|
package/dist/http.cjs.map
CHANGED
|
@@ -1 +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"]}
|
|
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 || (typeof window !== 'undefined' ? window.location.origin : '');\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 void execute();\n });\n } else {\n // Kick off the first request immediately\n void 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.js
CHANGED
|
@@ -9,7 +9,7 @@ function createFetch(url, options) {
|
|
|
9
9
|
let currentController = null;
|
|
10
10
|
function resolveURL() {
|
|
11
11
|
const raw = typeof url === "function" ? url() : url;
|
|
12
|
-
const base = options?.base
|
|
12
|
+
const base = options?.base || (typeof window !== "undefined" ? window.location.origin : "");
|
|
13
13
|
const fullURL = new URL(raw, base || void 0);
|
|
14
14
|
if (options?.params) {
|
|
15
15
|
for (const [key, value] of Object.entries(options.params)) {
|
|
@@ -57,10 +57,10 @@ function createFetch(url, options) {
|
|
|
57
57
|
if (typeof url === "function") {
|
|
58
58
|
internalEffect(() => {
|
|
59
59
|
url();
|
|
60
|
-
execute();
|
|
60
|
+
void execute();
|
|
61
61
|
});
|
|
62
62
|
} else {
|
|
63
|
-
execute();
|
|
63
|
+
void execute();
|
|
64
64
|
}
|
|
65
65
|
return {
|
|
66
66
|
data,
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/http/fetch.ts","../src/http/sse.ts","../src/http/ws.ts"],"names":[],"mappings":"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","file":"http.js","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"]}
|
|
1
|
+
{"version":3,"sources":["../src/http/fetch.ts","../src/http/sse.ts","../src/http/ws.ts"],"names":[],"mappings":"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","file":"http.js","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 || (typeof window !== 'undefined' ? window.location.origin : '');\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 void execute();\n });\n } else {\n // Kick off the first request immediately\n void 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/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkSZSKW57A_cjs = require('./chunk-SZSKW57A.cjs');
|
|
4
4
|
var chunkDCTOXHPF_cjs = require('./chunk-DCTOXHPF.cjs');
|
|
5
5
|
var chunk3U57L2TY_cjs = require('./chunk-3U57L2TY.cjs');
|
|
6
6
|
|
|
@@ -26,7 +26,7 @@ function mount(component, container) {
|
|
|
26
26
|
if (target.hasAttribute("data-forma-ssr")) {
|
|
27
27
|
chunkDCTOXHPF_cjs.createRoot((dispose) => {
|
|
28
28
|
disposeRoot = dispose;
|
|
29
|
-
|
|
29
|
+
chunkSZSKW57A_cjs.hydrateIsland(component, target);
|
|
30
30
|
});
|
|
31
31
|
} else {
|
|
32
32
|
const dom = chunkDCTOXHPF_cjs.createRoot((dispose) => {
|
|
@@ -326,7 +326,7 @@ function hydrateIslandRoot(root, id, componentName, hydrateFn, sharedProps) {
|
|
|
326
326
|
root.setAttribute("data-forma-status", "hydrating");
|
|
327
327
|
let activeRoot = root;
|
|
328
328
|
chunkDCTOXHPF_cjs.createRoot((dispose) => {
|
|
329
|
-
activeRoot =
|
|
329
|
+
activeRoot = chunkSZSKW57A_cjs.hydrateIsland(() => hydrateFn(root, props), root);
|
|
330
330
|
activeRoot.__formaDispose = dispose;
|
|
331
331
|
});
|
|
332
332
|
activeRoot.setAttribute("data-forma-status", "active");
|
|
@@ -1016,35 +1016,35 @@ function onMutation(el, handler, options) {
|
|
|
1016
1016
|
|
|
1017
1017
|
Object.defineProperty(exports, "Fragment", {
|
|
1018
1018
|
enumerable: true,
|
|
1019
|
-
get: function () { return
|
|
1019
|
+
get: function () { return chunkSZSKW57A_cjs.Fragment; }
|
|
1020
1020
|
});
|
|
1021
1021
|
Object.defineProperty(exports, "cleanup", {
|
|
1022
1022
|
enumerable: true,
|
|
1023
|
-
get: function () { return
|
|
1023
|
+
get: function () { return chunkSZSKW57A_cjs.cleanup; }
|
|
1024
1024
|
});
|
|
1025
1025
|
Object.defineProperty(exports, "createList", {
|
|
1026
1026
|
enumerable: true,
|
|
1027
|
-
get: function () { return
|
|
1027
|
+
get: function () { return chunkSZSKW57A_cjs.createList; }
|
|
1028
1028
|
});
|
|
1029
1029
|
Object.defineProperty(exports, "createShow", {
|
|
1030
1030
|
enumerable: true,
|
|
1031
|
-
get: function () { return
|
|
1031
|
+
get: function () { return chunkSZSKW57A_cjs.createShow; }
|
|
1032
1032
|
});
|
|
1033
1033
|
Object.defineProperty(exports, "fragment", {
|
|
1034
1034
|
enumerable: true,
|
|
1035
|
-
get: function () { return
|
|
1035
|
+
get: function () { return chunkSZSKW57A_cjs.fragment; }
|
|
1036
1036
|
});
|
|
1037
1037
|
Object.defineProperty(exports, "h", {
|
|
1038
1038
|
enumerable: true,
|
|
1039
|
-
get: function () { return
|
|
1039
|
+
get: function () { return chunkSZSKW57A_cjs.h; }
|
|
1040
1040
|
});
|
|
1041
1041
|
Object.defineProperty(exports, "hydrateIsland", {
|
|
1042
1042
|
enumerable: true,
|
|
1043
|
-
get: function () { return
|
|
1043
|
+
get: function () { return chunkSZSKW57A_cjs.hydrateIsland; }
|
|
1044
1044
|
});
|
|
1045
1045
|
Object.defineProperty(exports, "reconcileList", {
|
|
1046
1046
|
enumerable: true,
|
|
1047
|
-
get: function () { return
|
|
1047
|
+
get: function () { return chunkSZSKW57A_cjs.reconcileList; }
|
|
1048
1048
|
});
|
|
1049
1049
|
Object.defineProperty(exports, "batch", {
|
|
1050
1050
|
enumerable: true,
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { hydrateIsland } from './chunk-
|
|
2
|
-
export { Fragment, cleanup, createList, createShow, fragment, h, hydrateIsland, reconcileList } from './chunk-
|
|
1
|
+
import { hydrateIsland } from './chunk-FJGTMWKY.js';
|
|
2
|
+
export { Fragment, cleanup, createList, createShow, fragment, h, hydrateIsland, reconcileList } from './chunk-FJGTMWKY.js';
|
|
3
3
|
import { internalEffect, createRoot, untrack, createEffect, pushSuspenseContext, popSuspenseContext, __DEV__, reportError, batch } from './chunk-OUVOAYIO.js';
|
|
4
4
|
export { batch, createEffect, createMemo, createReducer, createRef, createResource, createRoot, getBatchDepth, isComputed, isEffect, isEffectScope, isSignal, on, onCleanup, onError, trigger, untrack } from './chunk-OUVOAYIO.js';
|
|
5
5
|
import { createSignal } from './chunk-OZCHIVAZ.js';
|
package/dist/runtime.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkSZSKW57A_cjs = require('./chunk-SZSKW57A.cjs');
|
|
4
4
|
var chunkDCTOXHPF_cjs = require('./chunk-DCTOXHPF.cjs');
|
|
5
5
|
var chunk3U57L2TY_cjs = require('./chunk-3U57L2TY.cjs');
|
|
6
6
|
|
|
@@ -2305,7 +2305,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2305
2305
|
}
|
|
2306
2306
|
const prevNodes = new Set(oldNodes);
|
|
2307
2307
|
if (keyProp) {
|
|
2308
|
-
const result =
|
|
2308
|
+
const result = chunkSZSKW57A_cjs.reconcileList(
|
|
2309
2309
|
el,
|
|
2310
2310
|
oldItems,
|
|
2311
2311
|
rawItems,
|
|
@@ -2335,7 +2335,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2335
2335
|
} else {
|
|
2336
2336
|
const wrapped = rawItems.map((item, i) => ({ __idx: i, __item: item }));
|
|
2337
2337
|
const oldWrapped = oldItems;
|
|
2338
|
-
const result =
|
|
2338
|
+
const result = chunkSZSKW57A_cjs.reconcileList(
|
|
2339
2339
|
el,
|
|
2340
2340
|
oldWrapped,
|
|
2341
2341
|
wrapped,
|
package/dist/runtime.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getforma/core",
|
|
3
3
|
"author": "Forma <victor@getforma.dev>",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.7",
|
|
5
5
|
"description": "Real DOM reactive library — fine-grained signals, islands architecture, SSR hydration. No virtual DOM, no diffing. ~15KB gzipped.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.cjs",
|