@getforma/core 1.0.6 → 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/http.cjs CHANGED
@@ -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 || (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 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
@@ -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 || (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 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 chunkV732ZBCU_cjs = require('./chunk-V732ZBCU.cjs');
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
- chunkV732ZBCU_cjs.hydrateIsland(component, target);
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 = chunkV732ZBCU_cjs.hydrateIsland(() => hydrateFn(root, props), root);
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 chunkV732ZBCU_cjs.Fragment; }
1019
+ get: function () { return chunkSZSKW57A_cjs.Fragment; }
1020
1020
  });
1021
1021
  Object.defineProperty(exports, "cleanup", {
1022
1022
  enumerable: true,
1023
- get: function () { return chunkV732ZBCU_cjs.cleanup; }
1023
+ get: function () { return chunkSZSKW57A_cjs.cleanup; }
1024
1024
  });
1025
1025
  Object.defineProperty(exports, "createList", {
1026
1026
  enumerable: true,
1027
- get: function () { return chunkV732ZBCU_cjs.createList; }
1027
+ get: function () { return chunkSZSKW57A_cjs.createList; }
1028
1028
  });
1029
1029
  Object.defineProperty(exports, "createShow", {
1030
1030
  enumerable: true,
1031
- get: function () { return chunkV732ZBCU_cjs.createShow; }
1031
+ get: function () { return chunkSZSKW57A_cjs.createShow; }
1032
1032
  });
1033
1033
  Object.defineProperty(exports, "fragment", {
1034
1034
  enumerable: true,
1035
- get: function () { return chunkV732ZBCU_cjs.fragment; }
1035
+ get: function () { return chunkSZSKW57A_cjs.fragment; }
1036
1036
  });
1037
1037
  Object.defineProperty(exports, "h", {
1038
1038
  enumerable: true,
1039
- get: function () { return chunkV732ZBCU_cjs.h; }
1039
+ get: function () { return chunkSZSKW57A_cjs.h; }
1040
1040
  });
1041
1041
  Object.defineProperty(exports, "hydrateIsland", {
1042
1042
  enumerable: true,
1043
- get: function () { return chunkV732ZBCU_cjs.hydrateIsland; }
1043
+ get: function () { return chunkSZSKW57A_cjs.hydrateIsland; }
1044
1044
  });
1045
1045
  Object.defineProperty(exports, "reconcileList", {
1046
1046
  enumerable: true,
1047
- get: function () { return chunkV732ZBCU_cjs.reconcileList; }
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-VTPFK5TJ.js';
2
- export { Fragment, cleanup, createList, createShow, fragment, h, hydrateIsland, reconcileList } from './chunk-VTPFK5TJ.js';
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 chunkV732ZBCU_cjs = require('./chunk-V732ZBCU.cjs');
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 = chunkV732ZBCU_cjs.reconcileList(
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 = chunkV732ZBCU_cjs.reconcileList(
2338
+ const result = chunkSZSKW57A_cjs.reconcileList(
2339
2339
  el,
2340
2340
  oldWrapped,
2341
2341
  wrapped,
package/dist/runtime.js CHANGED
@@ -1,4 +1,4 @@
1
- import { reconcileList } from './chunk-VTPFK5TJ.js';
1
+ import { reconcileList } from './chunk-FJGTMWKY.js';
2
2
  import { internalEffect, batch } from './chunk-OUVOAYIO.js';
3
3
  import { createSignal, createComputed } from './chunk-OZCHIVAZ.js';
4
4
 
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.6",
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",