@alepha/react 0.13.6 → 0.13.8

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.
Files changed (37) hide show
  1. package/dist/auth/index.browser.js +5 -5
  2. package/dist/auth/index.browser.js.map +1 -1
  3. package/dist/auth/index.d.ts +105 -103
  4. package/dist/auth/index.js +5 -5
  5. package/dist/auth/index.js.map +1 -1
  6. package/dist/core/index.browser.js +407 -142
  7. package/dist/core/index.browser.js.map +1 -1
  8. package/dist/core/index.d.ts +144 -116
  9. package/dist/core/index.js +409 -145
  10. package/dist/core/index.js.map +1 -1
  11. package/dist/core/index.native.js +24 -2
  12. package/dist/core/index.native.js.map +1 -1
  13. package/dist/form/index.d.ts +14 -6
  14. package/dist/form/index.js +32 -12
  15. package/dist/form/index.js.map +1 -1
  16. package/dist/head/index.d.ts +18 -18
  17. package/dist/head/index.js +5 -1
  18. package/dist/head/index.js.map +1 -1
  19. package/dist/i18n/index.d.ts +25 -25
  20. package/dist/i18n/index.js +4 -3
  21. package/dist/i18n/index.js.map +1 -1
  22. package/dist/websocket/index.d.ts +1 -1
  23. package/package.json +22 -23
  24. package/src/auth/hooks/useAuth.ts +1 -0
  25. package/src/auth/services/ReactAuth.ts +6 -4
  26. package/src/core/components/ErrorViewer.tsx +378 -130
  27. package/src/core/components/NestedView.tsx +16 -11
  28. package/src/core/contexts/AlephaProvider.tsx +41 -0
  29. package/src/core/contexts/RouterLayerContext.ts +2 -0
  30. package/src/core/hooks/useAction.ts +4 -1
  31. package/src/core/index.shared.ts +1 -0
  32. package/src/core/primitives/$page.ts +15 -2
  33. package/src/core/providers/ReactPageProvider.ts +6 -7
  34. package/src/core/providers/ReactServerProvider.ts +2 -6
  35. package/src/form/services/FormModel.ts +81 -26
  36. package/src/head/index.ts +2 -1
  37. package/src/i18n/providers/I18nProvider.ts +4 -2
@@ -1,12 +1,34 @@
1
- import { $module, AlephaError, Atom } from "alepha";
1
+ import { $module, Alepha, AlephaError, Atom } from "alepha";
2
2
  import { AlephaDateTime, DateTimeProvider } from "alepha/datetime";
3
3
  import { AlephaServerLinks, LinkProvider } from "alepha/server/links";
4
4
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
5
+ import { jsx } from "react/jsx-runtime";
5
6
  import { HttpClient } from "alepha/server";
6
7
 
7
8
  //#region ../../src/core/contexts/AlephaContext.ts
8
9
  const AlephaContext = createContext(void 0);
9
10
 
11
+ //#endregion
12
+ //#region ../../src/core/contexts/AlephaProvider.tsx
13
+ /**
14
+ * AlephaProvider component to initialize and provide Alepha instance to the app.
15
+ * This isn't recommended for apps using alepha/react/router, as Router will handle this for you.
16
+ */
17
+ const AlephaProvider = (props) => {
18
+ const alepha = useMemo(() => Alepha.create(), []);
19
+ const [started, setStarted] = useState(false);
20
+ const [error, setError] = useState();
21
+ useEffect(() => {
22
+ alepha.start().then(() => setStarted(true)).catch((err) => setError(err));
23
+ }, [alepha]);
24
+ if (error) return props.onError(error);
25
+ if (!started) return props.onLoading();
26
+ return /* @__PURE__ */ jsx(AlephaContext.Provider, {
27
+ value: alepha,
28
+ children: props.children
29
+ });
30
+ };
31
+
10
32
  //#endregion
11
33
  //#region ../../src/core/hooks/useAlepha.ts
12
34
  /**
@@ -377,5 +399,5 @@ const AlephaReact = $module({
377
399
  });
378
400
 
379
401
  //#endregion
380
- export { AlephaContext, AlephaReact, ssrSchemaLoading, useAction, useAlepha, useClient, useEvents, useInject, useSchema, useStore };
402
+ export { AlephaContext, AlephaProvider, AlephaReact, ssrSchemaLoading, useAction, useAlepha, useClient, useEvents, useInject, useSchema, useStore };
381
403
  //# sourceMappingURL=index.native.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.native.js","names":["error","subs: Function[]","schema"],"sources":["../../src/core/contexts/AlephaContext.ts","../../src/core/hooks/useAlepha.ts","../../src/core/hooks/useInject.ts","../../src/core/hooks/useAction.ts","../../src/core/hooks/useClient.ts","../../src/core/hooks/useEvents.ts","../../src/core/hooks/useSchema.ts","../../src/core/hooks/useStore.ts","../../src/core/index.native.ts"],"sourcesContent":["import type { Alepha } from \"alepha\";\nimport { createContext } from \"react\";\n\nexport const AlephaContext = createContext<Alepha | undefined>(undefined);\n","import { type Alepha, AlephaError } from \"alepha\";\nimport { useContext } from \"react\";\nimport { AlephaContext } from \"../contexts/AlephaContext.ts\";\n\n/**\n * Main Alepha hook.\n *\n * It provides access to the Alepha instance within a React component.\n *\n * With Alepha, you can access the core functionalities of the framework:\n *\n * - alepha.state() for state management\n * - alepha.inject() for dependency injection\n * - alepha.events.emit() for event handling\n * etc...\n */\nexport const useAlepha = (): Alepha => {\n const alepha = useContext(AlephaContext);\n if (!alepha) {\n throw new AlephaError(\n \"Hook 'useAlepha()' must be used within an AlephaContext.Provider\",\n );\n }\n\n return alepha;\n};\n","import type { Service } from \"alepha\";\nimport { useMemo } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Hook to inject a service instance.\n * It's a wrapper of `useAlepha().inject(service)` with a memoization.\n */\nexport const useInject = <T extends object>(service: Service<T>): T => {\n const alepha = useAlepha();\n return useMemo(() => alepha.inject(service), []);\n};\n","import {\n DateTimeProvider,\n type DurationLike,\n type Interval,\n type Timeout,\n} from \"alepha/datetime\";\nimport {\n type DependencyList,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\nimport { useInject } from \"./useInject.ts\";\n\n/**\n * Hook for handling async actions with automatic error handling and event emission.\n *\n * By default, prevents concurrent executions - if an action is running and you call it again,\n * the second call will be ignored. Use `debounce` option to delay execution instead.\n *\n * Emits lifecycle events:\n * - `react:action:begin` - When action starts\n * - `react:action:success` - When action completes successfully\n * - `react:action:error` - When action throws an error\n * - `react:action:end` - Always emitted at the end\n *\n * @example Basic usage\n * ```tsx\n * const action = useAction({\n * handler: async (data) => {\n * await api.save(data);\n * }\n * }, []);\n *\n * <button onClick={() => action.run(data)} disabled={action.loading}>\n * Save\n * </button>\n * ```\n *\n * @example With debounce (search input)\n * ```tsx\n * const search = useAction({\n * handler: async (query: string) => {\n * await api.search(query);\n * },\n * debounce: 300 // Wait 300ms after last call\n * }, []);\n *\n * <input onChange={(e) => search.run(e.target.value)} />\n * ```\n *\n * @example Run on component mount\n * ```tsx\n * const fetchData = useAction({\n * handler: async () => {\n * const data = await api.getData();\n * return data;\n * },\n * runOnInit: true // Runs once when component mounts\n * }, []);\n * ```\n *\n * @example Run periodically (polling)\n * ```tsx\n * const pollStatus = useAction({\n * handler: async () => {\n * const status = await api.getStatus();\n * return status;\n * },\n * runEvery: 5000 // Run every 5 seconds\n * }, []);\n *\n * // Or with duration tuple\n * const pollStatus = useAction({\n * handler: async () => {\n * const status = await api.getStatus();\n * return status;\n * },\n * runEvery: [30, 'seconds'] // Run every 30 seconds\n * }, []);\n * ```\n *\n * @example With AbortController\n * ```tsx\n * const fetch = useAction({\n * handler: async (url, { signal }) => {\n * const response = await fetch(url, { signal });\n * return response.json();\n * }\n * }, []);\n * // Automatically cancelled on unmount or when new request starts\n * ```\n *\n * @example With error handling\n * ```tsx\n * const deleteAction = useAction({\n * handler: async (id: string) => {\n * await api.delete(id);\n * },\n * onError: (error) => {\n * if (error.code === 'NOT_FOUND') {\n * // Custom error handling\n * }\n * }\n * }, []);\n *\n * {deleteAction.error && <div>Error: {deleteAction.error.message}</div>}\n * ```\n *\n * @example Global error handling\n * ```tsx\n * // In your root app setup\n * alepha.events.on(\"react:action:error\", ({ error }) => {\n * toast.danger(error.message);\n * Sentry.captureException(error);\n * });\n * ```\n */\nexport function useAction<Args extends any[], Result = void>(\n options: UseActionOptions<Args, Result>,\n deps: DependencyList,\n): UseActionReturn<Args, Result> {\n const alepha = useAlepha();\n const dateTimeProvider = useInject(DateTimeProvider);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>();\n const isExecutingRef = useRef(false);\n const debounceTimerRef = useRef<Timeout | undefined>(undefined);\n const abortControllerRef = useRef<AbortController | undefined>(undefined);\n const isMountedRef = useRef(true);\n const intervalRef = useRef<Interval | undefined>(undefined);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n isMountedRef.current = false;\n\n // clear debounce timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = undefined;\n }\n\n // clear interval\n if (intervalRef.current) {\n dateTimeProvider.clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n\n // abort in-flight request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = undefined;\n }\n };\n }, []);\n\n const executeAction = useCallback(\n async (...args: Args): Promise<Result | undefined> => {\n // Prevent concurrent executions\n if (isExecutingRef.current) {\n return;\n }\n\n // Abort previous request if still running\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n // Create new AbortController for this request\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n isExecutingRef.current = true;\n setLoading(true);\n setError(undefined);\n\n await alepha.events.emit(\"react:action:begin\", {\n type: \"custom\",\n id: options.id,\n });\n\n try {\n // Pass abort signal as last argument to handler\n const result = await options.handler(...args, {\n signal: abortController.signal,\n } as any);\n\n // Only update state if still mounted and not aborted\n if (!isMountedRef.current || abortController.signal.aborted) {\n return;\n }\n\n await alepha.events.emit(\"react:action:success\", {\n type: \"custom\",\n id: options.id,\n });\n\n if (options.onSuccess) {\n await options.onSuccess(result);\n }\n\n return result;\n } catch (err) {\n // Ignore abort errors\n if (err instanceof Error && err.name === \"AbortError\") {\n return;\n }\n\n // Only update state if still mounted\n if (!isMountedRef.current) {\n return;\n }\n\n const error = err as Error;\n setError(error);\n\n await alepha.events.emit(\"react:action:error\", {\n type: \"custom\",\n id: options.id,\n error,\n });\n\n if (options.onError) {\n await options.onError(error);\n } else {\n // Re-throw if no custom error handler\n throw error;\n }\n } finally {\n isExecutingRef.current = false;\n setLoading(false);\n\n await alepha.events.emit(\"react:action:end\", {\n type: \"custom\",\n id: options.id,\n });\n\n // Clean up abort controller\n if (abortControllerRef.current === abortController) {\n abortControllerRef.current = undefined;\n }\n }\n },\n [...deps, options.id, options.onError, options.onSuccess],\n );\n\n const handler = useCallback(\n async (...args: Args): Promise<Result | undefined> => {\n if (options.debounce) {\n // clear existing timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n }\n\n // Set new timer\n return new Promise((resolve) => {\n debounceTimerRef.current = dateTimeProvider.createTimeout(\n async () => {\n const result = await executeAction(...args);\n resolve(result);\n },\n options.debounce ?? 0,\n );\n });\n }\n\n return executeAction(...args);\n },\n [executeAction, options.debounce],\n );\n\n const cancel = useCallback(() => {\n // clear debounce timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = undefined;\n }\n\n // abort in-flight request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = undefined;\n }\n\n // reset state\n if (isMountedRef.current) {\n isExecutingRef.current = false;\n setLoading(false);\n }\n }, []);\n\n // Run action on mount if runOnInit is true\n useEffect(() => {\n if (options.runOnInit) {\n handler(...([] as any));\n }\n }, deps);\n\n // Run action periodically if runEvery is specified\n useEffect(() => {\n if (!options.runEvery) {\n return;\n }\n\n // Set up interval\n intervalRef.current = dateTimeProvider.createInterval(\n () => handler(...([] as any)),\n options.runEvery,\n true,\n );\n\n // cleanup on unmount or when runEvery changes\n return () => {\n if (intervalRef.current) {\n dateTimeProvider.clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n };\n }, [handler, options.runEvery]);\n\n return {\n run: handler,\n loading,\n error,\n cancel,\n };\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Context object passed as the last argument to action handlers.\n * Contains an AbortSignal that can be used to cancel the request.\n */\nexport interface ActionContext {\n /**\n * AbortSignal that can be passed to fetch or other async operations.\n * The signal will be aborted when:\n * - The component unmounts\n * - A new action is triggered (cancels previous)\n * - The cancel() method is called\n *\n * @example\n * ```tsx\n * const action = useAction({\n * handler: async (url, { signal }) => {\n * const response = await fetch(url, { signal });\n * return response.json();\n * }\n * }, []);\n * ```\n */\n signal: AbortSignal;\n}\n\nexport interface UseActionOptions<Args extends any[] = any[], Result = any> {\n /**\n * The async action handler function.\n * Receives the action arguments plus an ActionContext as the last parameter.\n */\n handler: (...args: [...Args, ActionContext]) => Promise<Result>;\n\n /**\n * Custom error handler. If provided, prevents default error re-throw.\n */\n onError?: (error: Error) => void | Promise<void>;\n\n /**\n * Custom success handler.\n */\n onSuccess?: (result: Result) => void | Promise<void>;\n\n /**\n * Optional identifier for this action (useful for debugging/analytics)\n */\n id?: string;\n\n /**\n * Debounce delay in milliseconds. If specified, the action will only execute\n * after the specified delay has passed since the last call. Useful for search inputs\n * or other high-frequency events.\n *\n * @example\n * ```tsx\n * // Execute search 300ms after user stops typing\n * const search = useAction({ handler: search, debounce: 300 }, [])\n * ```\n */\n debounce?: number;\n\n /**\n * If true, the action will be executed once when the component mounts.\n *\n * @example\n * ```tsx\n * const fetchData = useAction({\n * handler: async () => await api.getData(),\n * runOnInit: true\n * }, []);\n * ```\n */\n runOnInit?: boolean;\n\n /**\n * If specified, the action will be executed periodically at the given interval.\n * The interval is specified as a DurationLike value (number in ms, Duration object, or [number, unit] tuple).\n *\n * @example\n * ```tsx\n * // Run every 5 seconds\n * const poll = useAction({\n * handler: async () => await api.poll(),\n * runEvery: 5000\n * }, []);\n * ```\n *\n * @example\n * ```tsx\n * // Run every 1 minute\n * const poll = useAction({\n * handler: async () => await api.poll(),\n * runEvery: [1, 'minute']\n * }, []);\n * ```\n */\n runEvery?: DurationLike;\n}\n\nexport interface UseActionReturn<Args extends any[], Result> {\n /**\n * Execute the action with the provided arguments.\n *\n * @example\n * ```tsx\n * const action = useAction({ handler: async (data) => { ... } }, []);\n * action.run(data);\n * ```\n */\n run: (...args: Args) => Promise<Result | undefined>;\n\n /**\n * Loading state - true when action is executing.\n */\n loading: boolean;\n\n /**\n * Error state - contains error if action failed, undefined otherwise.\n */\n error?: Error;\n\n /**\n * Cancel any pending debounced action or abort the current in-flight request.\n *\n * @example\n * ```tsx\n * const action = useAction({ ... }, []);\n *\n * <button onClick={action.cancel} disabled={!action.loading}>\n * Cancel\n * </button>\n * ```\n */\n cancel: () => void;\n}\n","import {\n type ClientScope,\n type HttpVirtualClient,\n LinkProvider,\n} from \"alepha/server/links\";\nimport { useInject } from \"./useInject.ts\";\n\n/**\n * Hook to get a virtual client for the specified scope.\n *\n * It's the React-hook version of `$client()`, from `AlephaServerLinks` module.\n */\nexport const useClient = <T extends object>(\n scope?: ClientScope,\n): HttpVirtualClient<T> => {\n return useInject(LinkProvider).client<T>(scope);\n};\n","import type { Async, Hook, Hooks } from \"alepha\";\nimport { type DependencyList, useEffect } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Allow subscribing to multiple Alepha events. See {@link Hooks} for available events.\n *\n * useEvents is fully typed to ensure correct event callback signatures.\n *\n * @example\n * ```tsx\n * useEvents(\n * {\n * \"react:transition:begin\": (ev) => {\n * console.log(\"Transition began to:\", ev.to);\n * },\n * \"react:transition:error\": {\n * priority: \"first\",\n * callback: (ev) => {\n * console.error(\"Transition error:\", ev.error);\n * },\n * },\n * },\n * [],\n * );\n * ```\n */\nexport const useEvents = (opts: UseEvents, deps: DependencyList) => {\n const alepha = useAlepha();\n\n useEffect(() => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n const subs: Function[] = [];\n for (const [name, hook] of Object.entries(opts)) {\n subs.push(alepha.events.on(name as any, hook as any));\n }\n\n return () => {\n for (const clear of subs) {\n clear();\n }\n };\n }, deps);\n};\n\ntype UseEvents = {\n [T in keyof Hooks]?: Hook<T> | ((payload: Hooks[T]) => Async<void>);\n};\n","import type { Alepha } from \"alepha\";\nimport {\n type FetchOptions,\n HttpClient,\n type RequestConfigSchema,\n} from \"alepha/server\";\nimport { LinkProvider, type VirtualAction } from \"alepha/server/links\";\nimport { useEffect, useState } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\nimport { useInject } from \"./useInject.ts\";\n\nexport const useSchema = <TConfig extends RequestConfigSchema>(\n action: VirtualAction<TConfig>,\n): UseSchemaReturn<TConfig> => {\n const name = action.name;\n const alepha = useAlepha();\n const httpClient = useInject(HttpClient);\n const [schema, setSchema] = useState<UseSchemaReturn<TConfig>>(\n ssrSchemaLoading(alepha, name) as UseSchemaReturn<TConfig>,\n );\n\n useEffect(() => {\n if (!schema.loading) {\n return;\n }\n\n const opts: FetchOptions = {\n localCache: true,\n };\n\n httpClient\n .fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, opts)\n .then((it) => setSchema(it.data as UseSchemaReturn<TConfig>));\n }, [name]);\n\n return schema;\n};\n\nexport type UseSchemaReturn<TConfig extends RequestConfigSchema> = TConfig & {\n loading: boolean;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Get an action schema during server-side rendering (SSR) or client-side rendering (CSR).\n */\nexport const ssrSchemaLoading = (alepha: Alepha, name: string) => {\n // server-side rendering (SSR) context\n if (!alepha.isBrowser()) {\n // get user links\n const linkProvider = alepha.inject(LinkProvider);\n\n // check if user can access the link\n const can = linkProvider\n .getServerLinks()\n .find((link) => link.name === name);\n\n // yes!\n if (can) {\n // user-links have no schema, so we need to get it from the provider\n const schema = linkProvider.links.find((it) => it.name === name)?.schema;\n\n // oh, we have a schema!\n if (schema) {\n // attach to user link, it will be used in the client during hydration\n can.schema = schema;\n return schema;\n }\n }\n\n return { loading: true };\n }\n\n // browser side rendering (CSR) context\n // check if we have the schema already loaded\n const schema = alepha\n .inject(LinkProvider)\n .links.find((it) => it.name === name)?.schema;\n\n // yes!\n if (schema) {\n return schema;\n }\n\n // no, we need to load it\n return { loading: true };\n};\n","import type { State, Static, TAtomObject } from \"alepha\";\nimport { Atom } from \"alepha\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Hook to access and mutate the Alepha state.\n */\nfunction useStore<T extends TAtomObject>(\n target: Atom<T>,\n defaultValue?: Static<T>,\n): UseStoreReturn<Static<T>>;\nfunction useStore<Key extends keyof State>(\n target: Key,\n defaultValue?: State[Key],\n): UseStoreReturn<State[Key]>;\nfunction useStore(target: any, defaultValue?: any): any {\n const alepha = useAlepha();\n\n useMemo(() => {\n if (defaultValue != null && alepha.store.get(target) == null) {\n alepha.store.set(target, defaultValue);\n }\n }, [defaultValue]);\n\n const [state, setState] = useState(alepha.store.get(target));\n\n useEffect(() => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n const key = target instanceof Atom ? target.key : target;\n\n return alepha.events.on(\"state:mutate\", (ev) => {\n if (ev.key === key) {\n setState(ev.value);\n }\n });\n }, []);\n\n return [\n state,\n (value: any) => {\n alepha.store.set(target, value);\n },\n ] as const;\n}\n\nexport type UseStoreReturn<T> = [T, (value: T) => void];\n\nexport { useStore };\n","import { $module } from \"alepha\";\nimport { AlephaDateTime } from \"alepha/datetime\";\nimport { AlephaServerLinks } from \"alepha/server/links\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./index.shared.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaReact = $module({\n name: \"alepha.react\",\n primitives: [],\n services: [\n\n ],\n register: (alepha) =>\n alepha\n .with(AlephaDateTime)\n .with(AlephaServerLinks)\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.native.js","names":["error","subs: Function[]","schema"],"sources":["../../src/core/contexts/AlephaContext.ts","../../src/core/contexts/AlephaProvider.tsx","../../src/core/hooks/useAlepha.ts","../../src/core/hooks/useInject.ts","../../src/core/hooks/useAction.ts","../../src/core/hooks/useClient.ts","../../src/core/hooks/useEvents.ts","../../src/core/hooks/useSchema.ts","../../src/core/hooks/useStore.ts","../../src/core/index.native.ts"],"sourcesContent":["import type { Alepha } from \"alepha\";\nimport { createContext } from \"react\";\n\nexport const AlephaContext = createContext<Alepha | undefined>(undefined);\n","import { Alepha } from \"alepha\";\nimport { type ReactNode, useEffect, useMemo, useState } from \"react\";\nimport { AlephaContext } from \"./AlephaContext.ts\";\n\nexport interface AlephaProviderProps {\n children: ReactNode;\n onError: (error: Error) => ReactNode;\n onLoading: () => ReactNode;\n}\n\n/**\n * AlephaProvider component to initialize and provide Alepha instance to the app.\n * This isn't recommended for apps using alepha/react/router, as Router will handle this for you.\n */\nexport const AlephaProvider = (props: AlephaProviderProps) => {\n const alepha = useMemo(() => Alepha.create(), []);\n\n const [started, setStarted] = useState(false);\n const [error, setError] = useState<Error | undefined>();\n\n useEffect(() => {\n alepha\n .start()\n .then(() => setStarted(true))\n .catch((err) => setError(err));\n }, [alepha]);\n\n if (error) {\n return props.onError(error);\n }\n\n if (!started) {\n return props.onLoading();\n }\n\n return (\n <AlephaContext.Provider value={alepha}>\n {props.children}\n </AlephaContext.Provider>\n );\n};\n","import { type Alepha, AlephaError } from \"alepha\";\nimport { useContext } from \"react\";\nimport { AlephaContext } from \"../contexts/AlephaContext.ts\";\n\n/**\n * Main Alepha hook.\n *\n * It provides access to the Alepha instance within a React component.\n *\n * With Alepha, you can access the core functionalities of the framework:\n *\n * - alepha.state() for state management\n * - alepha.inject() for dependency injection\n * - alepha.events.emit() for event handling\n * etc...\n */\nexport const useAlepha = (): Alepha => {\n const alepha = useContext(AlephaContext);\n if (!alepha) {\n throw new AlephaError(\n \"Hook 'useAlepha()' must be used within an AlephaContext.Provider\",\n );\n }\n\n return alepha;\n};\n","import type { Service } from \"alepha\";\nimport { useMemo } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Hook to inject a service instance.\n * It's a wrapper of `useAlepha().inject(service)` with a memoization.\n */\nexport const useInject = <T extends object>(service: Service<T>): T => {\n const alepha = useAlepha();\n return useMemo(() => alepha.inject(service), []);\n};\n","import {\n DateTimeProvider,\n type DurationLike,\n type Interval,\n type Timeout,\n} from \"alepha/datetime\";\nimport {\n type DependencyList,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\nimport { useInject } from \"./useInject.ts\";\nimport type { Async } from \"alepha\";\n\n/**\n * Hook for handling async actions with automatic error handling and event emission.\n *\n * By default, prevents concurrent executions - if an action is running and you call it again,\n * the second call will be ignored. Use `debounce` option to delay execution instead.\n *\n * Emits lifecycle events:\n * - `react:action:begin` - When action starts\n * - `react:action:success` - When action completes successfully\n * - `react:action:error` - When action throws an error\n * - `react:action:end` - Always emitted at the end\n *\n * @example Basic usage\n * ```tsx\n * const action = useAction({\n * handler: async (data) => {\n * await api.save(data);\n * }\n * }, []);\n *\n * <button onClick={() => action.run(data)} disabled={action.loading}>\n * Save\n * </button>\n * ```\n *\n * @example With debounce (search input)\n * ```tsx\n * const search = useAction({\n * handler: async (query: string) => {\n * await api.search(query);\n * },\n * debounce: 300 // Wait 300ms after last call\n * }, []);\n *\n * <input onChange={(e) => search.run(e.target.value)} />\n * ```\n *\n * @example Run on component mount\n * ```tsx\n * const fetchData = useAction({\n * handler: async () => {\n * const data = await api.getData();\n * return data;\n * },\n * runOnInit: true // Runs once when component mounts\n * }, []);\n * ```\n *\n * @example Run periodically (polling)\n * ```tsx\n * const pollStatus = useAction({\n * handler: async () => {\n * const status = await api.getStatus();\n * return status;\n * },\n * runEvery: 5000 // Run every 5 seconds\n * }, []);\n *\n * // Or with duration tuple\n * const pollStatus = useAction({\n * handler: async () => {\n * const status = await api.getStatus();\n * return status;\n * },\n * runEvery: [30, 'seconds'] // Run every 30 seconds\n * }, []);\n * ```\n *\n * @example With AbortController\n * ```tsx\n * const fetch = useAction({\n * handler: async (url, { signal }) => {\n * const response = await fetch(url, { signal });\n * return response.json();\n * }\n * }, []);\n * // Automatically cancelled on unmount or when new request starts\n * ```\n *\n * @example With error handling\n * ```tsx\n * const deleteAction = useAction({\n * handler: async (id: string) => {\n * await api.delete(id);\n * },\n * onError: (error) => {\n * if (error.code === 'NOT_FOUND') {\n * // Custom error handling\n * }\n * }\n * }, []);\n *\n * {deleteAction.error && <div>Error: {deleteAction.error.message}</div>}\n * ```\n *\n * @example Global error handling\n * ```tsx\n * // In your root app setup\n * alepha.events.on(\"react:action:error\", ({ error }) => {\n * toast.danger(error.message);\n * Sentry.captureException(error);\n * });\n * ```\n */\nexport function useAction<Args extends any[], Result = void>(\n options: UseActionOptions<Args, Result>,\n deps: DependencyList,\n): UseActionReturn<Args, Result> {\n const alepha = useAlepha();\n const dateTimeProvider = useInject(DateTimeProvider);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>();\n const isExecutingRef = useRef(false);\n const debounceTimerRef = useRef<Timeout | undefined>(undefined);\n const abortControllerRef = useRef<AbortController | undefined>(undefined);\n const isMountedRef = useRef(true);\n const intervalRef = useRef<Interval | undefined>(undefined);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n isMountedRef.current = false;\n\n // clear debounce timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = undefined;\n }\n\n // clear interval\n if (intervalRef.current) {\n dateTimeProvider.clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n\n // abort in-flight request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = undefined;\n }\n };\n }, []);\n\n const executeAction = useCallback(\n async (...args: Args): Promise<Result | undefined> => {\n // Prevent concurrent executions\n if (isExecutingRef.current) {\n return;\n }\n\n // Abort previous request if still running\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n // Create new AbortController for this request\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n isExecutingRef.current = true;\n setLoading(true);\n setError(undefined);\n\n await alepha.events.emit(\"react:action:begin\", {\n type: \"custom\",\n id: options.id,\n });\n\n try {\n // Pass abort signal as last argument to handler\n const result = await options.handler(...args, {\n signal: abortController.signal,\n } as any);\n\n // Only update state if still mounted and not aborted\n if (!isMountedRef.current || abortController.signal.aborted) {\n return;\n }\n\n await alepha.events.emit(\"react:action:success\", {\n type: \"custom\",\n id: options.id,\n });\n\n if (options.onSuccess) {\n await options.onSuccess(result);\n }\n\n return result;\n } catch (err) {\n // Ignore abort errors\n if (err instanceof Error && err.name === \"AbortError\") {\n return;\n }\n\n // Only update state if still mounted\n if (!isMountedRef.current) {\n return;\n }\n\n const error = err as Error;\n setError(error);\n\n await alepha.events.emit(\"react:action:error\", {\n type: \"custom\",\n id: options.id,\n error,\n });\n\n if (options.onError) {\n await options.onError(error);\n } else {\n // Re-throw if no custom error handler\n throw error;\n }\n } finally {\n isExecutingRef.current = false;\n setLoading(false);\n\n await alepha.events.emit(\"react:action:end\", {\n type: \"custom\",\n id: options.id,\n });\n\n // Clean up abort controller\n if (abortControllerRef.current === abortController) {\n abortControllerRef.current = undefined;\n }\n }\n },\n [...deps, options.id, options.onError, options.onSuccess],\n );\n\n const handler = useCallback(\n async (...args: Args): Promise<Result | undefined> => {\n if (options.debounce) {\n // clear existing timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n }\n\n // Set new timer\n return new Promise((resolve) => {\n debounceTimerRef.current = dateTimeProvider.createTimeout(\n async () => {\n const result = await executeAction(...args);\n resolve(result);\n },\n options.debounce ?? 0,\n );\n });\n }\n\n return executeAction(...args);\n },\n [executeAction, options.debounce],\n );\n\n const cancel = useCallback(() => {\n // clear debounce timer\n if (debounceTimerRef.current) {\n dateTimeProvider.clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = undefined;\n }\n\n // abort in-flight request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = undefined;\n }\n\n // reset state\n if (isMountedRef.current) {\n isExecutingRef.current = false;\n setLoading(false);\n }\n }, []);\n\n // Run action on mount if runOnInit is true\n useEffect(() => {\n if (options.runOnInit) {\n handler(...([] as any));\n }\n }, deps);\n\n // Run action periodically if runEvery is specified\n useEffect(() => {\n if (!options.runEvery) {\n return;\n }\n\n // Set up interval\n intervalRef.current = dateTimeProvider.createInterval(\n () => handler(...([] as any)),\n options.runEvery,\n true,\n );\n\n // cleanup on unmount or when runEvery changes\n return () => {\n if (intervalRef.current) {\n dateTimeProvider.clearInterval(intervalRef.current);\n intervalRef.current = undefined;\n }\n };\n }, [handler, options.runEvery]);\n\n return {\n run: handler,\n loading,\n error,\n cancel,\n };\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Context object passed as the last argument to action handlers.\n * Contains an AbortSignal that can be used to cancel the request.\n */\nexport interface ActionContext {\n /**\n * AbortSignal that can be passed to fetch or other async operations.\n * The signal will be aborted when:\n * - The component unmounts\n * - A new action is triggered (cancels previous)\n * - The cancel() method is called\n *\n * @example\n * ```tsx\n * const action = useAction({\n * handler: async (url, { signal }) => {\n * const response = await fetch(url, { signal });\n * return response.json();\n * }\n * }, []);\n * ```\n */\n signal: AbortSignal;\n}\n\nexport interface UseActionOptions<Args extends any[] = any[], Result = any> {\n /**\n * The async action handler function.\n * Receives the action arguments plus an ActionContext as the last parameter.\n */\n handler: (...args: [...Args, ActionContext]) => Async<Result>;\n\n /**\n * Custom error handler. If provided, prevents default error re-throw.\n */\n onError?: (error: Error) => void | Promise<void>;\n\n /**\n * Custom success handler.\n */\n onSuccess?: (result: Result) => void | Promise<void>;\n\n /**\n * Optional identifier for this action (useful for debugging/analytics)\n */\n id?: string;\n\n name?: string;\n\n /**\n * Debounce delay in milliseconds. If specified, the action will only execute\n * after the specified delay has passed since the last call. Useful for search inputs\n * or other high-frequency events.\n *\n * @example\n * ```tsx\n * // Execute search 300ms after user stops typing\n * const search = useAction({ handler: search, debounce: 300 }, [])\n * ```\n */\n debounce?: number;\n\n /**\n * If true, the action will be executed once when the component mounts.\n *\n * @example\n * ```tsx\n * const fetchData = useAction({\n * handler: async () => await api.getData(),\n * runOnInit: true\n * }, []);\n * ```\n */\n runOnInit?: boolean;\n\n /**\n * If specified, the action will be executed periodically at the given interval.\n * The interval is specified as a DurationLike value (number in ms, Duration object, or [number, unit] tuple).\n *\n * @example\n * ```tsx\n * // Run every 5 seconds\n * const poll = useAction({\n * handler: async () => await api.poll(),\n * runEvery: 5000\n * }, []);\n * ```\n *\n * @example\n * ```tsx\n * // Run every 1 minute\n * const poll = useAction({\n * handler: async () => await api.poll(),\n * runEvery: [1, 'minute']\n * }, []);\n * ```\n */\n runEvery?: DurationLike;\n}\n\nexport interface UseActionReturn<Args extends any[], Result> {\n /**\n * Execute the action with the provided arguments.\n *\n * @example\n * ```tsx\n * const action = useAction({ handler: async (data) => { ... } }, []);\n * action.run(data);\n * ```\n */\n run: (...args: Args) => Promise<Result | undefined>;\n\n /**\n * Loading state - true when action is executing.\n */\n loading: boolean;\n\n /**\n * Error state - contains error if action failed, undefined otherwise.\n */\n error?: Error;\n\n /**\n * Cancel any pending debounced action or abort the current in-flight request.\n *\n * @example\n * ```tsx\n * const action = useAction({ ... }, []);\n *\n * <button onClick={action.cancel} disabled={!action.loading}>\n * Cancel\n * </button>\n * ```\n */\n cancel: () => void;\n}\n","import {\n type ClientScope,\n type HttpVirtualClient,\n LinkProvider,\n} from \"alepha/server/links\";\nimport { useInject } from \"./useInject.ts\";\n\n/**\n * Hook to get a virtual client for the specified scope.\n *\n * It's the React-hook version of `$client()`, from `AlephaServerLinks` module.\n */\nexport const useClient = <T extends object>(\n scope?: ClientScope,\n): HttpVirtualClient<T> => {\n return useInject(LinkProvider).client<T>(scope);\n};\n","import type { Async, Hook, Hooks } from \"alepha\";\nimport { type DependencyList, useEffect } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Allow subscribing to multiple Alepha events. See {@link Hooks} for available events.\n *\n * useEvents is fully typed to ensure correct event callback signatures.\n *\n * @example\n * ```tsx\n * useEvents(\n * {\n * \"react:transition:begin\": (ev) => {\n * console.log(\"Transition began to:\", ev.to);\n * },\n * \"react:transition:error\": {\n * priority: \"first\",\n * callback: (ev) => {\n * console.error(\"Transition error:\", ev.error);\n * },\n * },\n * },\n * [],\n * );\n * ```\n */\nexport const useEvents = (opts: UseEvents, deps: DependencyList) => {\n const alepha = useAlepha();\n\n useEffect(() => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n const subs: Function[] = [];\n for (const [name, hook] of Object.entries(opts)) {\n subs.push(alepha.events.on(name as any, hook as any));\n }\n\n return () => {\n for (const clear of subs) {\n clear();\n }\n };\n }, deps);\n};\n\ntype UseEvents = {\n [T in keyof Hooks]?: Hook<T> | ((payload: Hooks[T]) => Async<void>);\n};\n","import type { Alepha } from \"alepha\";\nimport {\n type FetchOptions,\n HttpClient,\n type RequestConfigSchema,\n} from \"alepha/server\";\nimport { LinkProvider, type VirtualAction } from \"alepha/server/links\";\nimport { useEffect, useState } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\nimport { useInject } from \"./useInject.ts\";\n\nexport const useSchema = <TConfig extends RequestConfigSchema>(\n action: VirtualAction<TConfig>,\n): UseSchemaReturn<TConfig> => {\n const name = action.name;\n const alepha = useAlepha();\n const httpClient = useInject(HttpClient);\n const [schema, setSchema] = useState<UseSchemaReturn<TConfig>>(\n ssrSchemaLoading(alepha, name) as UseSchemaReturn<TConfig>,\n );\n\n useEffect(() => {\n if (!schema.loading) {\n return;\n }\n\n const opts: FetchOptions = {\n localCache: true,\n };\n\n httpClient\n .fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, opts)\n .then((it) => setSchema(it.data as UseSchemaReturn<TConfig>));\n }, [name]);\n\n return schema;\n};\n\nexport type UseSchemaReturn<TConfig extends RequestConfigSchema> = TConfig & {\n loading: boolean;\n};\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * Get an action schema during server-side rendering (SSR) or client-side rendering (CSR).\n */\nexport const ssrSchemaLoading = (alepha: Alepha, name: string) => {\n // server-side rendering (SSR) context\n if (!alepha.isBrowser()) {\n // get user links\n const linkProvider = alepha.inject(LinkProvider);\n\n // check if user can access the link\n const can = linkProvider\n .getServerLinks()\n .find((link) => link.name === name);\n\n // yes!\n if (can) {\n // user-links have no schema, so we need to get it from the provider\n const schema = linkProvider.links.find((it) => it.name === name)?.schema;\n\n // oh, we have a schema!\n if (schema) {\n // attach to user link, it will be used in the client during hydration\n can.schema = schema;\n return schema;\n }\n }\n\n return { loading: true };\n }\n\n // browser side rendering (CSR) context\n // check if we have the schema already loaded\n const schema = alepha\n .inject(LinkProvider)\n .links.find((it) => it.name === name)?.schema;\n\n // yes!\n if (schema) {\n return schema;\n }\n\n // no, we need to load it\n return { loading: true };\n};\n","import type { State, Static, TAtomObject } from \"alepha\";\nimport { Atom } from \"alepha\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useAlepha } from \"./useAlepha.ts\";\n\n/**\n * Hook to access and mutate the Alepha state.\n */\nfunction useStore<T extends TAtomObject>(\n target: Atom<T>,\n defaultValue?: Static<T>,\n): UseStoreReturn<Static<T>>;\nfunction useStore<Key extends keyof State>(\n target: Key,\n defaultValue?: State[Key],\n): UseStoreReturn<State[Key]>;\nfunction useStore(target: any, defaultValue?: any): any {\n const alepha = useAlepha();\n\n useMemo(() => {\n if (defaultValue != null && alepha.store.get(target) == null) {\n alepha.store.set(target, defaultValue);\n }\n }, [defaultValue]);\n\n const [state, setState] = useState(alepha.store.get(target));\n\n useEffect(() => {\n if (!alepha.isBrowser()) {\n return;\n }\n\n const key = target instanceof Atom ? target.key : target;\n\n return alepha.events.on(\"state:mutate\", (ev) => {\n if (ev.key === key) {\n setState(ev.value);\n }\n });\n }, []);\n\n return [\n state,\n (value: any) => {\n alepha.store.set(target, value);\n },\n ] as const;\n}\n\nexport type UseStoreReturn<T> = [T, (value: T) => void];\n\nexport { useStore };\n","import { $module } from \"alepha\";\nimport { AlephaDateTime } from \"alepha/datetime\";\nimport { AlephaServerLinks } from \"alepha/server/links\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./index.shared.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport const AlephaReact = $module({\n name: \"alepha.react\",\n primitives: [],\n services: [\n\n ],\n register: (alepha) =>\n alepha\n .with(AlephaDateTime)\n .with(AlephaServerLinks)\n});\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import * as alepha8 from "alepha";
2
- import { Alepha, LogLevel, LoggerInterface, Static, TObject, TSchema, TypeBoxError } from "alepha";
2
+ import { Alepha, LogLevel, LoggerInterface, Static, TArray, TObject, TSchema, TypeBoxError } from "alepha";
3
3
  import { InputHTMLAttributes, ReactNode } from "react";
4
4
  import dayjsDuration from "dayjs/plugin/duration.js";
5
5
  import DayjsApi, { Dayjs, ManipulateType, PluginFunc } from "dayjs";
@@ -182,7 +182,7 @@ declare const envSchema: alepha8.TObject<{
182
182
  /**
183
183
  * Built-in log formats.
184
184
  * - "json" - JSON format, useful for structured logging and log aggregation. {@link JsonFormatterProvider}
185
- * - "pretty" - Simple text format, human-readable, with colors. {@link SimpleFormatterProvider}
185
+ * - "pretty" - Simple text format, human-readable, with colors. {@link PrettyFormatterProvider}
186
186
  * - "raw" - Raw format, no formatting, just the message. {@link RawFormatterProvider}
187
187
  */
188
188
  LOG_FORMAT: alepha8.TOptional<alepha8.TUnsafe<"json" | "pretty" | "raw">>;
@@ -249,7 +249,7 @@ declare class FormModel<T extends TObject> {
249
249
  protected createInputFromSchema<T extends TObject>(name: keyof Static<T> & string, options: FormCtrlOptions<T>, schema: TObject, required: boolean, context: {
250
250
  parent: string;
251
251
  store: Record<string, any>;
252
- }): InputField;
252
+ }): BaseInputField;
253
253
  /**
254
254
  * Convert an input value to the correct type based on the schema.
255
255
  * Handles raw DOM values (strings, booleans from checkboxes, Files, etc.)
@@ -257,18 +257,26 @@ declare class FormModel<T extends TObject> {
257
257
  protected getValueFromInput(input: any, schema: TSchema): any;
258
258
  protected valueToInputEntry(value: any): string | number | boolean;
259
259
  }
260
- type SchemaToInput<T extends TObject> = { [K in keyof T["properties"]]: T["properties"][K] extends TObject ? SchemaToInput<T["properties"][K]> : InputField };
260
+ type SchemaToInput<T extends TObject> = { [K in keyof T["properties"]]: InputField<T["properties"][K]> };
261
261
  interface FormEventLike {
262
262
  preventDefault?: () => void;
263
263
  stopPropagation?: () => void;
264
264
  }
265
- interface InputField {
265
+ type InputField<T extends TSchema> = T extends TObject ? ObjectInputField<T> : T extends TArray<infer U> ? ArrayInputField<U> : BaseInputField;
266
+ interface BaseInputField {
266
267
  path: string;
267
268
  required: boolean;
268
269
  props: InputHTMLAttributesLike;
269
270
  schema: TSchema;
270
271
  set: (value: any) => void;
271
272
  form: FormModel<any>;
273
+ items?: any;
274
+ }
275
+ interface ObjectInputField<T extends TObject> extends BaseInputField {
276
+ items: SchemaToInput<T>;
277
+ }
278
+ interface ArrayInputField<T extends TSchema> extends BaseInputField {
279
+ items: Array<InputField<T>>;
272
280
  }
273
281
  type InputHTMLAttributesLike = Pick<InputHTMLAttributes<unknown>, "id" | "name" | "type" | "value" | "defaultValue" | "required" | "maxLength" | "minLength" | "aria-label" | "autoComplete"> & {
274
282
  value?: any;
@@ -415,5 +423,5 @@ declare module "alepha" {
415
423
  */
416
424
  declare const AlephaReactForm: alepha8.Service<alepha8.Module>;
417
425
  //#endregion
418
- export { AlephaReactForm, FormCtrlOptions, FormEventLike, FormModel, FormState, FormValidationError, InputField, InputHTMLAttributesLike, SchemaToInput, UseFormStateReturn, useForm, useFormState };
426
+ export { AlephaReactForm, ArrayInputField, BaseInputField, FormCtrlOptions, FormEventLike, FormModel, FormState, FormValidationError, InputField, InputHTMLAttributesLike, ObjectInputField, SchemaToInput, UseFormStateReturn, useForm, useFormState };
419
427
  //# sourceMappingURL=index.d.ts.map
@@ -200,16 +200,10 @@ var FormModel = class {
200
200
  currentObjectLevel[finalPropertyKey] = value;
201
201
  }
202
202
  createProxyFromSchema(options, schema, context) {
203
- const parent = context.parent || "";
203
+ context.parent;
204
204
  return new Proxy({}, { get: (_, prop) => {
205
205
  if (!options.schema || !t.schema.isObject(schema)) return {};
206
- if (prop in schema.properties) {
207
- if (t.schema.isObject(schema.properties[prop])) return this.createProxyFromSchema(options, schema.properties[prop], {
208
- parent: parent ? `${parent}.${prop}` : prop,
209
- store: context.store
210
- });
211
- return this.createInputFromSchema(prop, options, schema, schema.required?.includes(prop) || false, context);
212
- }
206
+ if (prop in schema.properties) return this.createInputFromSchema(prop, options, schema, schema.required?.includes(prop) || false, context);
213
207
  } });
214
208
  }
215
209
  createInputFromSchema(name, options, schema, required, context) {
@@ -235,11 +229,13 @@ var FormModel = class {
235
229
  id: this.id,
236
230
  path,
237
231
  value: typedValue
238
- });
232
+ }, { catch: true });
239
233
  if (sync) {
240
234
  const inputElement = window.document.querySelector(`[data-path="${path}"]`);
241
- if (inputElement instanceof HTMLInputElement) if (t.schema.isBoolean(field)) inputElement.checked = Boolean(value);
242
- else inputElement.value = value;
235
+ if (inputElement instanceof HTMLInputElement) if (t.schema.isBoolean(field)) {
236
+ inputElement.value = value;
237
+ inputElement.checked = Boolean(value);
238
+ } else inputElement.value = value;
243
239
  }
244
240
  };
245
241
  const attr = {
@@ -254,7 +250,10 @@ var FormModel = class {
254
250
  set(event, false);
255
251
  return;
256
252
  }
257
- if (t.schema.isBoolean(field)) set(event.target.checked, false);
253
+ if (t.schema.isBoolean(field)) if (event.target.value === "true") set(true, false);
254
+ else if (event.target.value === "false") set(false, false);
255
+ else if (event.target.value === "") set(void 0, false);
256
+ else set(event.target.checked, false);
258
257
  else set(event.target.value, false);
259
258
  }
260
259
  };
@@ -285,6 +284,27 @@ var FormModel = class {
285
284
  const customAttr = options.onCreateField(name, field);
286
285
  Object.assign(attr, customAttr);
287
286
  }
287
+ if (t.schema.isObject(field)) return {
288
+ path,
289
+ props: attr,
290
+ schema: field,
291
+ set,
292
+ form: this,
293
+ required,
294
+ items: this.createProxyFromSchema(options, field, {
295
+ parent: key,
296
+ store: context.store
297
+ })
298
+ };
299
+ if (t.schema.isArray(field)) return {
300
+ path,
301
+ props: attr,
302
+ schema: field,
303
+ set,
304
+ form: this,
305
+ required,
306
+ items: []
307
+ };
288
308
  return {
289
309
  path,
290
310
  props: attr,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["listeners: Function[]","id: string","options: FormCtrlOptions<T>","values: Record<string, any>","attr: InputHTMLAttributesLike"],"sources":["../../src/form/hooks/useFormState.ts","../../src/form/components/FormState.tsx","../../src/form/services/FormModel.ts","../../src/form/hooks/useForm.ts","../../src/form/errors/FormValidationError.ts","../../src/form/index.ts"],"sourcesContent":["import { useAlepha } from \"@alepha/react\";\nimport type { FormModel } from \"../services/FormModel.ts\";\nimport { type TObject, TypeBoxError } from \"alepha\";\nimport { useEffect, useState } from \"react\";\n\nexport interface UseFormStateReturn {\n loading: boolean;\n dirty: boolean;\n values?: Record<string, any>;\n error?: Error;\n}\n\nexport const useFormState = <\n T extends TObject,\n Keys extends keyof UseFormStateReturn,\n>(\n target: FormModel<T> | { form: FormModel<T>; path: string },\n _events: Keys[] = [\"loading\", \"dirty\", \"error\"] as Keys[],\n): Pick<UseFormStateReturn, Keys> => {\n const alepha = useAlepha();\n const events = _events as string[];\n\n const [dirty, setDirty] = useState(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [values, setValues] = useState<Record<string, any> | undefined>(\n undefined,\n );\n\n const form = \"form\" in target ? target.form : target;\n const path = \"path\" in target ? target.path : undefined;\n\n const hasValues = events.includes(\"values\");\n const hasErrors = events.includes(\"error\");\n const hasDirty = events.includes(\"dirty\");\n const hasLoading = events.includes(\"loading\");\n\n useEffect(() => {\n const listeners: Function[] = [];\n\n if (hasErrors || hasValues || hasDirty) {\n listeners.push(\n alepha.events.on(\"form:change\", (event) => {\n if (event.id === form.id) {\n if (!path || event.path === path) {\n if (hasDirty) {\n setDirty(true);\n }\n if (hasErrors) {\n setError(undefined);\n }\n }\n if (hasValues) {\n setValues(form.currentValues);\n }\n }\n }),\n );\n }\n\n if (hasValues) {\n listeners.push(\n alepha.events.on(\"form:reset\", (event) => {\n if (event.id === form.id) {\n setValues(event.values);\n }\n }),\n );\n }\n\n if (hasLoading) {\n listeners.push(\n alepha.events.on(\"form:submit:begin\", (event) => {\n if (event.id === form.id) {\n setLoading(true);\n }\n }),\n alepha.events.on(\"form:submit:end\", (event) => {\n if (event.id === form.id) {\n setLoading(false);\n }\n }),\n );\n }\n\n if (hasValues || hasDirty) {\n listeners.push(\n alepha.events.on(\"form:submit:success\", (event) => {\n if (event.id === form.id) {\n if (hasValues) {\n setValues(event.values);\n }\n if (hasDirty) {\n setDirty(false);\n }\n }\n }),\n );\n }\n\n if (hasErrors) {\n listeners.push(\n alepha.events.on(\"form:submit:error\", (event) => {\n if (event.id === form.id) {\n if (\n !path ||\n (event.error instanceof TypeBoxError &&\n event.error.value.path === path)\n ) {\n setError(event.error);\n }\n }\n }),\n );\n }\n\n return () => {\n for (const unsub of listeners) {\n unsub();\n }\n };\n }, []);\n\n return {\n dirty,\n loading,\n error,\n values,\n } as Pick<UseFormStateReturn, Keys>;\n};\n","import type { TObject } from \"alepha\";\nimport type { ReactNode } from \"react\";\nimport { useFormState } from \"../hooks/useFormState.ts\";\nimport type { FormModel } from \"../services/FormModel.ts\";\n\nconst FormState = <T extends TObject>(props: {\n form: FormModel<T>;\n children: (state: { loading: boolean; dirty: boolean }) => ReactNode;\n}) => {\n const formState = useFormState(props.form);\n return props.children({\n loading: formState.loading,\n dirty: formState.dirty,\n });\n};\n\nexport default FormState;\n","import {\n $inject,\n Alepha,\n type Static,\n type TObject,\n type TSchema,\n t,\n} from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { ChangeEvent, InputHTMLAttributes } from \"react\";\n\n/**\n * FormModel is a dynamic form handler that generates form inputs based on a provided TypeBox schema.\n * It manages form state, handles input changes, and processes form submissions with validation.\n *\n * It means to be injected and used within React components to provide a structured way to create and manage forms.\n *\n * @see {@link useForm}\n */\nexport class FormModel<T extends TObject> {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly values: Record<string, any> = {};\n protected submitInProgress = false;\n\n public input: SchemaToInput<T>;\n\n public get submitting(): boolean {\n return this.submitInProgress;\n }\n\n constructor(\n public readonly id: string,\n public readonly options: FormCtrlOptions<T>,\n ) {\n this.options = options;\n\n if (options.initialValues) {\n this.values = this.alepha.codec.decode(\n options.schema,\n options.initialValues,\n ) as Record<string, any>;\n }\n\n this.input = this.createProxyFromSchema(options, options.schema, {\n store: this.values,\n parent: \"\",\n });\n }\n\n public get element(): HTMLFormElement {\n return window.document.getElementById(this.id)! as HTMLFormElement;\n }\n\n public get currentValues(): Record<string, any> {\n return this.restructureValues(this.values);\n }\n\n public get props() {\n return {\n id: this.id,\n noValidate: true,\n onSubmit: (ev?: FormEventLike) => {\n ev?.preventDefault?.();\n this.submit();\n },\n onReset: (event: FormEventLike) => this.reset(event),\n };\n }\n\n public readonly reset = (event: FormEventLike) => {\n // clear values in place to maintain proxy reference\n for (const key in this.values) {\n delete this.values[key];\n }\n\n this.options.onReset?.();\n\n return this.alepha.events.emit(\n \"form:reset\",\n {\n id: this.id,\n values: this.values,\n },\n {\n catch: true,\n },\n );\n };\n\n public readonly submit = async () => {\n if (this.submitInProgress) {\n this.log.warn(\n \"Form submission already in progress, ignoring duplicate submit.\",\n );\n return;\n }\n\n // emit both action and form events\n await this.alepha.events.emit(\"react:action:begin\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:begin\", {\n id: this.id,\n });\n\n this.submitInProgress = true;\n\n const options = this.options;\n const form = this.element;\n const args = {\n form,\n };\n\n try {\n let values: Record<string, any> = this.restructureValues(this.values);\n\n if (t.schema.isSchema(options.schema)) {\n values = this.alepha.codec.decode(options.schema, values) as Record<\n string,\n any\n >;\n }\n\n await options.handler(values as any, args);\n\n await this.alepha.events.emit(\"react:action:success\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:success\", {\n id: this.id,\n values,\n });\n } catch (error) {\n this.log.error(\"Form submission error:\", error);\n\n options.onError?.(error as Error, args);\n\n await this.alepha.events.emit(\"react:action:error\", {\n type: \"form\",\n id: this.id,\n error: error as Error,\n });\n await this.alepha.events.emit(\"form:submit:error\", {\n error: error as Error,\n id: this.id,\n });\n } finally {\n this.submitInProgress = false;\n }\n\n await this.alepha.events.emit(\"react:action:end\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:end\", {\n id: this.id,\n });\n };\n\n /**\n * Restructures flat keys like \"address.city\" into nested objects like { address: { city: ... } }\n * Values are already typed from onChange, so no conversion is needed.\n */\n protected restructureValues(store: Record<string, any>): Record<string, any> {\n const values: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(store)) {\n if (key.includes(\".\")) {\n // nested object: restructure flat key to nested structure\n this.restructureNestedValue(values, key, value);\n } else {\n // value is already typed, just copy it\n values[key] = value;\n }\n }\n\n return values;\n }\n\n /**\n * Helper to restructure a flat key like \"address.city\" into nested object structure.\n * The value is already typed, so we just assign it to the nested path.\n */\n protected restructureNestedValue(\n values: Record<string, any>,\n key: string,\n value: any,\n ) {\n const pathSegments = key.split(\".\");\n const finalPropertyKey = pathSegments.pop();\n if (!finalPropertyKey) {\n return;\n }\n\n let currentObjectLevel = values;\n\n // traverse/create the nested structure\n for (const segment of pathSegments) {\n currentObjectLevel[segment] ??= {};\n currentObjectLevel = currentObjectLevel[segment];\n }\n\n // value is already typed from onChange, just assign it\n currentObjectLevel[finalPropertyKey] = value;\n }\n\n protected createProxyFromSchema<T extends TObject>(\n options: FormCtrlOptions<T>,\n schema: TSchema,\n context: {\n parent: string;\n store: Record<string, any>;\n },\n ): SchemaToInput<T> {\n const parent = context.parent || \"\";\n return new Proxy<SchemaToInput<T>>({} as SchemaToInput<T>, {\n get: (_, prop: string) => {\n if (!options.schema || !t.schema.isObject(schema)) {\n return {};\n }\n if (prop in schema.properties) {\n if (t.schema.isObject(schema.properties[prop])) {\n return this.createProxyFromSchema(\n options,\n schema.properties[prop],\n {\n parent: parent ? `${parent}.${prop}` : prop,\n store: context.store,\n },\n );\n }\n return this.createInputFromSchema<T>(\n prop as keyof Static<T> & string,\n options,\n schema,\n schema.required?.includes(prop as string) || false,\n context,\n );\n }\n },\n });\n }\n\n protected createInputFromSchema<T extends TObject>(\n name: keyof Static<T> & string,\n options: FormCtrlOptions<T>,\n schema: TObject,\n required: boolean,\n context: {\n parent: string;\n store: Record<string, any>;\n },\n ): InputField {\n const parent = context.parent || \"\";\n const field = schema.properties?.[name];\n if (!field) {\n return {\n path: \"\",\n required,\n props: {} as InputHTMLAttributes<unknown>,\n schema: schema,\n set: () => {},\n form: this,\n };\n }\n\n const isRequired = schema.required?.includes(name) ?? false;\n\n const key = parent ? `${parent}.${name}` : name;\n const path = `/${key.replaceAll(\".\", \"/\")}`;\n\n const set = (value: any, sync = true) => {\n // Convert to typed value immediately based on schema\n const typedValue = this.getValueFromInput(value, field);\n\n if (context.store[key] === typedValue) {\n // no change, do not update\n // return;\n }\n\n context.store[key] = typedValue;\n\n if (options.onChange) {\n options.onChange(key, typedValue, context.store);\n }\n\n this.alepha.events.emit(\"form:change\", {\n id: this.id,\n path: path,\n value: typedValue,\n });\n\n if (sync) {\n const inputElement = window.document.querySelector(\n `[data-path=\"${path}\"]`,\n );\n if (inputElement instanceof HTMLInputElement) {\n if (t.schema.isBoolean(field)) {\n inputElement.checked = Boolean(value);\n } else {\n inputElement.value = value;\n }\n }\n }\n };\n\n const attr: InputHTMLAttributesLike = {\n name: key,\n autoComplete: \"off\",\n onChange: (event: ChangeEvent<HTMLInputElement> | string | number) => {\n if (typeof event === \"string\") {\n // If the event is a string, it means it's a direct value change\n set(event, false);\n return;\n }\n\n if (typeof event === \"number\") {\n // Some inputs might return number directly\n set(event, false);\n return;\n }\n\n if (t.schema.isBoolean(field)) {\n set(event.target.checked, false);\n } else {\n set(event.target.value, false);\n }\n },\n };\n\n (attr as any)[\"data-path\"] = path;\n\n if (options.id) {\n attr.id = `${options.id}-${key}`;\n (attr as any)[\"data-testid\"] = attr.id;\n }\n\n if (t.schema.isString(field)) {\n if (field.maxLength != null) {\n attr.maxLength = Number(field.maxLength);\n }\n\n if (field.minLength != null) {\n attr.minLength = Number(field.minLength);\n }\n }\n\n if (options.initialValues?.[name] != null) {\n attr.defaultValue = this.valueToInputEntry(options.initialValues[name]);\n } else if (\"default\" in field && field.default != null) {\n attr.defaultValue = this.valueToInputEntry(field.default);\n }\n\n if (isRequired) {\n attr.required = true;\n }\n\n if (\"description\" in field && typeof field.description === \"string\") {\n attr[\"aria-label\"] = field.description;\n }\n\n if (t.schema.isInteger(field) || t.schema.isNumber(field)) {\n attr.type = \"number\";\n } else if (name === \"password\") {\n attr.type = \"password\";\n } else if (name === \"email\") {\n attr.type = \"email\";\n } else if (name === \"url\") {\n attr.type = \"url\";\n } else if (t.schema.isString(field)) {\n if (field.format === \"binary\") {\n attr.type = \"file\";\n } else if (field.format === \"date\") {\n attr.type = \"date\";\n } else if (field.format === \"time\") {\n attr.type = \"time\";\n } else if (field.format === \"date-time\") {\n attr.type = \"datetime-local\";\n } else {\n attr.type = \"text\";\n }\n } else if (t.schema.isBoolean(field)) {\n attr.type = \"checkbox\";\n }\n\n if (options.onCreateField) {\n const customAttr = options.onCreateField(name, field);\n Object.assign(attr, customAttr);\n }\n\n return {\n path,\n props: attr,\n schema: field,\n set,\n form: this,\n required,\n };\n }\n\n /**\n * Convert an input value to the correct type based on the schema.\n * Handles raw DOM values (strings, booleans from checkboxes, Files, etc.)\n */\n protected getValueFromInput(input: any, schema: TSchema): any {\n if (input instanceof File) {\n // for file inputs, return the File object directly\n if (t.schema.isString(schema) && schema.format === \"binary\") {\n return input;\n }\n // for now, ignore other formats\n return null;\n }\n\n if (t.schema.isBoolean(schema)) {\n return !!input;\n }\n\n if (t.schema.isNumber(schema)) {\n const num = Number(input);\n return Number.isNaN(num) ? null : num;\n }\n\n if (t.schema.isString(schema)) {\n if (schema.format === \"date\") {\n return new Date(input).toISOString().slice(0, 10); // For date input\n }\n if (schema.format === \"time\") {\n return new Date(`1970-01-01T${input}`).toISOString().slice(11, 16); // For time input\n }\n if (schema.format === \"date-time\") {\n return new Date(input).toISOString(); // For datetime-local input\n }\n return String(input);\n }\n\n return input; // fallback for other types\n }\n\n protected valueToInputEntry(value: any): string | number | boolean {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n if (typeof value === \"boolean\") {\n return value;\n }\n\n if (typeof value === \"number\") {\n return value;\n }\n\n if (typeof value === \"string\") {\n return value;\n }\n\n if (value instanceof Date) {\n return value.toISOString().slice(0, 16); // For datetime-local input\n }\n\n return value;\n }\n}\n\nexport type SchemaToInput<T extends TObject> = {\n [K in keyof T[\"properties\"]]: T[\"properties\"][K] extends TObject\n ? SchemaToInput<T[\"properties\"][K]>\n : InputField;\n};\n\nexport interface FormEventLike {\n preventDefault?: () => void;\n stopPropagation?: () => void;\n}\n\nexport interface InputField {\n path: string;\n required: boolean;\n props: InputHTMLAttributesLike;\n schema: TSchema;\n set: (value: any) => void;\n form: FormModel<any>;\n}\n\nexport type InputHTMLAttributesLike = Pick<\n InputHTMLAttributes<unknown>,\n | \"id\"\n | \"name\"\n | \"type\"\n | \"value\"\n | \"defaultValue\"\n | \"required\"\n | \"maxLength\"\n | \"minLength\"\n | \"aria-label\"\n | \"autoComplete\"\n> & {\n value?: any;\n defaultValue?: any;\n onChange?: (event: any) => void;\n};\n\nexport type FormCtrlOptions<T extends TObject> = {\n /**\n * The schema defining the structure and validation rules for the form.\n * This should be a TypeBox schema object.\n */\n schema: T;\n\n /**\n * Callback function to handle form submission.\n * This function will receive the parsed and validated form values.\n */\n handler: (values: Static<T>, args: { form: HTMLFormElement }) => unknown;\n\n /**\n * Optional initial values for the form fields.\n * This can be used to pre-populate the form with existing data.\n */\n initialValues?: Partial<Static<T>>;\n\n /**\n * Optional function to create custom field attributes.\n * This can be used to add custom validation, styles, or other attributes.\n */\n onCreateField?: (\n name: keyof Static<T> & string,\n schema: TSchema,\n ) => InputHTMLAttributes<unknown>;\n\n /**\n * If defined, this will generate a unique ID for each field, prefixed with this string.\n *\n * > \"username\" with id=\"form-123\" will become \"form-123-username\".\n *\n * If omitted, IDs will not be generated.\n */\n id?: string;\n\n onError?: (error: Error, args: { form: HTMLFormElement }) => void;\n\n onChange?: (key: string, value: any, store: Record<string, any>) => void;\n\n onReset?: () => void;\n};\n","import { useAlepha } from \"@alepha/react\";\nimport type { TObject } from \"alepha\";\nimport { useId, useMemo } from \"react\";\nimport { type FormCtrlOptions, FormModel } from \"../services/FormModel.ts\";\n\n/**\n * Custom hook to create a form with validation and field management.\n * This hook uses TypeBox schemas to define the structure and validation rules for the form.\n * It provides a way to handle form submission, field creation, and value management.\n *\n * @example\n * ```tsx\n * import { t } from \"alepha\";\n *\n * const form = useForm({\n * schema: t.object({\n * username: t.text(),\n * password: t.text(),\n * }),\n * handler: (values) => {\n * console.log(\"Form submitted with values:\", values);\n * },\n * });\n *\n * return (\n * <form {...form.props}>\n * <input {...form.input.username.props} />\n * <input {...form.input.password.props} />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * ```\n */\nexport const useForm = <T extends TObject>(\n options: FormCtrlOptions<T>,\n deps: any[] = [],\n): FormModel<T> => {\n const alepha = useAlepha();\n const formId = useId();\n\n return useMemo(() => {\n return alepha.inject(FormModel<T>, {\n lifetime: \"transient\",\n args: [options.id || formId, options],\n });\n }, deps);\n};\n","import { TypeBoxError } from \"alepha\";\n\nexport class FormValidationError extends TypeBoxError {\n readonly name = \"ValidationError\";\n\n constructor(\n options: {\n message: string;\n path: string;\n }\n ) {\n super({\n message: options.message,\n instancePath: options.path,\n schemaPath: \"\",\n keyword: \"not\",\n params: {},\n });\n }\n}\n","import { $module } from \"alepha\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport { default as FormState } from \"./components/FormState.tsx\";\nexport * from \"./hooks/useForm.ts\";\nexport * from \"./hooks/useFormState.ts\";\nexport * from \"./services/FormModel.ts\";\nexport * from \"./errors/FormValidationError.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"form:change\": { id: string; path: string; value: any };\n \"form:reset\": { id: string; values: Record<string, any> };\n \"form:submit:begin\": { id: string };\n \"form:submit:success\": { id: string; values: Record<string, any> };\n \"form:submit:error\": { id: string; error: Error };\n \"form:submit:end\": { id: string };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * React hooks for managing forms in Alepha applications.\n *\n * This module provides a set of hooks to simplify form handling, validation, and submission in React applications built with Alepha.\n *\n * It includes:\n * - `useForm`: A hook for managing form state, validation, and submission.\n *\n * @see {@link useForm}\n * @module alepha.react.form\n */\nexport const AlephaReactForm = $module({\n name: \"alepha.react.form\",\n});\n"],"mappings":";;;;;;AAYA,MAAa,gBAIX,QACA,UAAkB;CAAC;CAAW;CAAS;CAAQ,KACZ;CACnC,MAAM,SAAS,WAAW;CAC1B,MAAM,SAAS;CAEf,MAAM,CAAC,OAAO,YAAY,SAAS,MAAM;CACzC,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAA4B,OAAU;CAChE,MAAM,CAAC,QAAQ,aAAa,SAC1B,OACD;CAED,MAAM,OAAO,UAAU,SAAS,OAAO,OAAO;CAC9C,MAAM,OAAO,UAAU,SAAS,OAAO,OAAO;CAE9C,MAAM,YAAY,OAAO,SAAS,SAAS;CAC3C,MAAM,YAAY,OAAO,SAAS,QAAQ;CAC1C,MAAM,WAAW,OAAO,SAAS,QAAQ;CACzC,MAAM,aAAa,OAAO,SAAS,UAAU;AAE7C,iBAAgB;EACd,MAAMA,YAAwB,EAAE;AAEhC,MAAI,aAAa,aAAa,SAC5B,WAAU,KACR,OAAO,OAAO,GAAG,gBAAgB,UAAU;AACzC,OAAI,MAAM,OAAO,KAAK,IAAI;AACxB,QAAI,CAAC,QAAQ,MAAM,SAAS,MAAM;AAChC,SAAI,SACF,UAAS,KAAK;AAEhB,SAAI,UACF,UAAS,OAAU;;AAGvB,QAAI,UACF,WAAU,KAAK,cAAc;;IAGjC,CACH;AAGH,MAAI,UACF,WAAU,KACR,OAAO,OAAO,GAAG,eAAe,UAAU;AACxC,OAAI,MAAM,OAAO,KAAK,GACpB,WAAU,MAAM,OAAO;IAEzB,CACH;AAGH,MAAI,WACF,WAAU,KACR,OAAO,OAAO,GAAG,sBAAsB,UAAU;AAC/C,OAAI,MAAM,OAAO,KAAK,GACpB,YAAW,KAAK;IAElB,EACF,OAAO,OAAO,GAAG,oBAAoB,UAAU;AAC7C,OAAI,MAAM,OAAO,KAAK,GACpB,YAAW,MAAM;IAEnB,CACH;AAGH,MAAI,aAAa,SACf,WAAU,KACR,OAAO,OAAO,GAAG,wBAAwB,UAAU;AACjD,OAAI,MAAM,OAAO,KAAK,IAAI;AACxB,QAAI,UACF,WAAU,MAAM,OAAO;AAEzB,QAAI,SACF,UAAS,MAAM;;IAGnB,CACH;AAGH,MAAI,UACF,WAAU,KACR,OAAO,OAAO,GAAG,sBAAsB,UAAU;AAC/C,OAAI,MAAM,OAAO,KAAK,IACpB;QACE,CAAC,QACA,MAAM,iBAAiB,gBACtB,MAAM,MAAM,MAAM,SAAS,KAE7B,UAAS,MAAM,MAAM;;IAGzB,CACH;AAGH,eAAa;AACX,QAAK,MAAM,SAAS,UAClB,QAAO;;IAGV,EAAE,CAAC;AAEN,QAAO;EACL;EACA;EACA;EACA;EACD;;;;;AC3HH,MAAM,aAAgC,UAGhC;CACJ,MAAM,YAAY,aAAa,MAAM,KAAK;AAC1C,QAAO,MAAM,SAAS;EACpB,SAAS,UAAU;EACnB,OAAO,UAAU;EAClB,CAAC;;AAGJ,wBAAe;;;;;;;;;;;;ACGf,IAAa,YAAb,MAA0C;CACxC,AAAmB,MAAM,SAAS;CAClC,AAAmB,SAAS,QAAQ,OAAO;CAC3C,AAAmB,SAA8B,EAAE;CACnD,AAAU,mBAAmB;CAE7B,AAAO;CAEP,IAAW,aAAsB;AAC/B,SAAO,KAAK;;CAGd,YACE,AAAgBC,IAChB,AAAgBC,SAChB;EAFgB;EACA;AAEhB,OAAK,UAAU;AAEf,MAAI,QAAQ,cACV,MAAK,SAAS,KAAK,OAAO,MAAM,OAC9B,QAAQ,QACR,QAAQ,cACT;AAGH,OAAK,QAAQ,KAAK,sBAAsB,SAAS,QAAQ,QAAQ;GAC/D,OAAO,KAAK;GACZ,QAAQ;GACT,CAAC;;CAGJ,IAAW,UAA2B;AACpC,SAAO,OAAO,SAAS,eAAe,KAAK,GAAG;;CAGhD,IAAW,gBAAqC;AAC9C,SAAO,KAAK,kBAAkB,KAAK,OAAO;;CAG5C,IAAW,QAAQ;AACjB,SAAO;GACL,IAAI,KAAK;GACT,YAAY;GACZ,WAAW,OAAuB;AAChC,QAAI,kBAAkB;AACtB,SAAK,QAAQ;;GAEf,UAAU,UAAyB,KAAK,MAAM,MAAM;GACrD;;CAGH,AAAgB,SAAS,UAAyB;AAEhD,OAAK,MAAM,OAAO,KAAK,OACrB,QAAO,KAAK,OAAO;AAGrB,OAAK,QAAQ,WAAW;AAExB,SAAO,KAAK,OAAO,OAAO,KACxB,cACA;GACE,IAAI,KAAK;GACT,QAAQ,KAAK;GACd,EACD,EACE,OAAO,MACR,CACF;;CAGH,AAAgB,SAAS,YAAY;AACnC,MAAI,KAAK,kBAAkB;AACzB,QAAK,IAAI,KACP,kEACD;AACD;;AAIF,QAAM,KAAK,OAAO,OAAO,KAAK,sBAAsB;GAClD,MAAM;GACN,IAAI,KAAK;GACV,CAAC;AACF,QAAM,KAAK,OAAO,OAAO,KAAK,qBAAqB,EACjD,IAAI,KAAK,IACV,CAAC;AAEF,OAAK,mBAAmB;EAExB,MAAM,UAAU,KAAK;EAErB,MAAM,OAAO,EACX,MAFW,KAAK,SAGjB;AAED,MAAI;GACF,IAAIC,SAA8B,KAAK,kBAAkB,KAAK,OAAO;AAErE,OAAI,EAAE,OAAO,SAAS,QAAQ,OAAO,CACnC,UAAS,KAAK,OAAO,MAAM,OAAO,QAAQ,QAAQ,OAAO;AAM3D,SAAM,QAAQ,QAAQ,QAAe,KAAK;AAE1C,SAAM,KAAK,OAAO,OAAO,KAAK,wBAAwB;IACpD,MAAM;IACN,IAAI,KAAK;IACV,CAAC;AACF,SAAM,KAAK,OAAO,OAAO,KAAK,uBAAuB;IACnD,IAAI,KAAK;IACT;IACD,CAAC;WACK,OAAO;AACd,QAAK,IAAI,MAAM,0BAA0B,MAAM;AAE/C,WAAQ,UAAU,OAAgB,KAAK;AAEvC,SAAM,KAAK,OAAO,OAAO,KAAK,sBAAsB;IAClD,MAAM;IACN,IAAI,KAAK;IACF;IACR,CAAC;AACF,SAAM,KAAK,OAAO,OAAO,KAAK,qBAAqB;IAC1C;IACP,IAAI,KAAK;IACV,CAAC;YACM;AACR,QAAK,mBAAmB;;AAG1B,QAAM,KAAK,OAAO,OAAO,KAAK,oBAAoB;GAChD,MAAM;GACN,IAAI,KAAK;GACV,CAAC;AACF,QAAM,KAAK,OAAO,OAAO,KAAK,mBAAmB,EAC/C,IAAI,KAAK,IACV,CAAC;;;;;;CAOJ,AAAU,kBAAkB,OAAiD;EAC3E,MAAMA,SAA8B,EAAE;AAEtC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC9C,KAAI,IAAI,SAAS,IAAI,CAEnB,MAAK,uBAAuB,QAAQ,KAAK,MAAM;MAG/C,QAAO,OAAO;AAIlB,SAAO;;;;;;CAOT,AAAU,uBACR,QACA,KACA,OACA;EACA,MAAM,eAAe,IAAI,MAAM,IAAI;EACnC,MAAM,mBAAmB,aAAa,KAAK;AAC3C,MAAI,CAAC,iBACH;EAGF,IAAI,qBAAqB;AAGzB,OAAK,MAAM,WAAW,cAAc;AAClC,sBAAmB,aAAa,EAAE;AAClC,wBAAqB,mBAAmB;;AAI1C,qBAAmB,oBAAoB;;CAGzC,AAAU,sBACR,SACA,QACA,SAIkB;EAClB,MAAM,SAAS,QAAQ,UAAU;AACjC,SAAO,IAAI,MAAwB,EAAE,EAAsB,EACzD,MAAM,GAAG,SAAiB;AACxB,OAAI,CAAC,QAAQ,UAAU,CAAC,EAAE,OAAO,SAAS,OAAO,CAC/C,QAAO,EAAE;AAEX,OAAI,QAAQ,OAAO,YAAY;AAC7B,QAAI,EAAE,OAAO,SAAS,OAAO,WAAW,MAAM,CAC5C,QAAO,KAAK,sBACV,SACA,OAAO,WAAW,OAClB;KACE,QAAQ,SAAS,GAAG,OAAO,GAAG,SAAS;KACvC,OAAO,QAAQ;KAChB,CACF;AAEH,WAAO,KAAK,sBACV,MACA,SACA,QACA,OAAO,UAAU,SAAS,KAAe,IAAI,OAC7C,QACD;;KAGN,CAAC;;CAGJ,AAAU,sBACR,MACA,SACA,QACA,UACA,SAIY;EACZ,MAAM,SAAS,QAAQ,UAAU;EACjC,MAAM,QAAQ,OAAO,aAAa;AAClC,MAAI,CAAC,MACH,QAAO;GACL,MAAM;GACN;GACA,OAAO,EAAE;GACD;GACR,WAAW;GACX,MAAM;GACP;EAGH,MAAM,aAAa,OAAO,UAAU,SAAS,KAAK,IAAI;EAEtD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS;EAC3C,MAAM,OAAO,IAAI,IAAI,WAAW,KAAK,IAAI;EAEzC,MAAM,OAAO,OAAY,OAAO,SAAS;GAEvC,MAAM,aAAa,KAAK,kBAAkB,OAAO,MAAM;AAEvD,OAAI,QAAQ,MAAM,SAAS,YAAY;AAKvC,WAAQ,MAAM,OAAO;AAErB,OAAI,QAAQ,SACV,SAAQ,SAAS,KAAK,YAAY,QAAQ,MAAM;AAGlD,QAAK,OAAO,OAAO,KAAK,eAAe;IACrC,IAAI,KAAK;IACH;IACN,OAAO;IACR,CAAC;AAEF,OAAI,MAAM;IACR,MAAM,eAAe,OAAO,SAAS,cACnC,eAAe,KAAK,IACrB;AACD,QAAI,wBAAwB,iBAC1B,KAAI,EAAE,OAAO,UAAU,MAAM,CAC3B,cAAa,UAAU,QAAQ,MAAM;QAErC,cAAa,QAAQ;;;EAM7B,MAAMC,OAAgC;GACpC,MAAM;GACN,cAAc;GACd,WAAW,UAA2D;AACpE,QAAI,OAAO,UAAU,UAAU;AAE7B,SAAI,OAAO,MAAM;AACjB;;AAGF,QAAI,OAAO,UAAU,UAAU;AAE7B,SAAI,OAAO,MAAM;AACjB;;AAGF,QAAI,EAAE,OAAO,UAAU,MAAM,CAC3B,KAAI,MAAM,OAAO,SAAS,MAAM;QAEhC,KAAI,MAAM,OAAO,OAAO,MAAM;;GAGnC;AAED,EAAC,KAAa,eAAe;AAE7B,MAAI,QAAQ,IAAI;AACd,QAAK,KAAK,GAAG,QAAQ,GAAG,GAAG;AAC3B,GAAC,KAAa,iBAAiB,KAAK;;AAGtC,MAAI,EAAE,OAAO,SAAS,MAAM,EAAE;AAC5B,OAAI,MAAM,aAAa,KACrB,MAAK,YAAY,OAAO,MAAM,UAAU;AAG1C,OAAI,MAAM,aAAa,KACrB,MAAK,YAAY,OAAO,MAAM,UAAU;;AAI5C,MAAI,QAAQ,gBAAgB,SAAS,KACnC,MAAK,eAAe,KAAK,kBAAkB,QAAQ,cAAc,MAAM;WAC9D,aAAa,SAAS,MAAM,WAAW,KAChD,MAAK,eAAe,KAAK,kBAAkB,MAAM,QAAQ;AAG3D,MAAI,WACF,MAAK,WAAW;AAGlB,MAAI,iBAAiB,SAAS,OAAO,MAAM,gBAAgB,SACzD,MAAK,gBAAgB,MAAM;AAG7B,MAAI,EAAE,OAAO,UAAU,MAAM,IAAI,EAAE,OAAO,SAAS,MAAM,CACvD,MAAK,OAAO;WACH,SAAS,WAClB,MAAK,OAAO;WACH,SAAS,QAClB,MAAK,OAAO;WACH,SAAS,MAClB,MAAK,OAAO;WACH,EAAE,OAAO,SAAS,MAAM,CACjC,KAAI,MAAM,WAAW,SACnB,MAAK,OAAO;WACH,MAAM,WAAW,OAC1B,MAAK,OAAO;WACH,MAAM,WAAW,OAC1B,MAAK,OAAO;WACH,MAAM,WAAW,YAC1B,MAAK,OAAO;MAEZ,MAAK,OAAO;WAEL,EAAE,OAAO,UAAU,MAAM,CAClC,MAAK,OAAO;AAGd,MAAI,QAAQ,eAAe;GACzB,MAAM,aAAa,QAAQ,cAAc,MAAM,MAAM;AACrD,UAAO,OAAO,MAAM,WAAW;;AAGjC,SAAO;GACL;GACA,OAAO;GACP,QAAQ;GACR;GACA,MAAM;GACN;GACD;;;;;;CAOH,AAAU,kBAAkB,OAAY,QAAsB;AAC5D,MAAI,iBAAiB,MAAM;AAEzB,OAAI,EAAE,OAAO,SAAS,OAAO,IAAI,OAAO,WAAW,SACjD,QAAO;AAGT,UAAO;;AAGT,MAAI,EAAE,OAAO,UAAU,OAAO,CAC5B,QAAO,CAAC,CAAC;AAGX,MAAI,EAAE,OAAO,SAAS,OAAO,EAAE;GAC7B,MAAM,MAAM,OAAO,MAAM;AACzB,UAAO,OAAO,MAAM,IAAI,GAAG,OAAO;;AAGpC,MAAI,EAAE,OAAO,SAAS,OAAO,EAAE;AAC7B,OAAI,OAAO,WAAW,OACpB,QAAO,IAAI,KAAK,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG;AAEnD,OAAI,OAAO,WAAW,OACpB,yBAAO,IAAI,KAAK,cAAc,QAAQ,EAAC,aAAa,CAAC,MAAM,IAAI,GAAG;AAEpE,OAAI,OAAO,WAAW,YACpB,QAAO,IAAI,KAAK,MAAM,CAAC,aAAa;AAEtC,UAAO,OAAO,MAAM;;AAGtB,SAAO;;CAGT,AAAU,kBAAkB,OAAuC;AACjE,MAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO;AAGT,MAAI,OAAO,UAAU,UACnB,QAAO;AAGT,MAAI,OAAO,UAAU,SACnB,QAAO;AAGT,MAAI,OAAO,UAAU,SACnB,QAAO;AAGT,MAAI,iBAAiB,KACnB,QAAO,MAAM,aAAa,CAAC,MAAM,GAAG,GAAG;AAGzC,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9aX,MAAa,WACX,SACA,OAAc,EAAE,KACC;CACjB,MAAM,SAAS,WAAW;CAC1B,MAAM,SAAS,OAAO;AAEtB,QAAO,cAAc;AACnB,SAAO,OAAO,OAAO,WAAc;GACjC,UAAU;GACV,MAAM,CAAC,QAAQ,MAAM,QAAQ,QAAQ;GACtC,CAAC;IACD,KAAK;;;;;AC3CV,IAAa,sBAAb,cAAyC,aAAa;CACpD,AAAS,OAAO;CAEhB,YACE,SAIA;AACA,QAAM;GACJ,SAAS,QAAQ;GACjB,cAAc,QAAQ;GACtB,YAAY;GACZ,SAAS;GACT,QAAQ,EAAE;GACX,CAAC;;;;;;;;;;;;;;;;;ACmBN,MAAa,kBAAkB,QAAQ,EACrC,MAAM,qBACP,CAAC"}
1
+ {"version":3,"file":"index.js","names":["listeners: Function[]","id: string","options: FormCtrlOptions<T>","values: Record<string, any>","attr: InputHTMLAttributesLike"],"sources":["../../src/form/hooks/useFormState.ts","../../src/form/components/FormState.tsx","../../src/form/services/FormModel.ts","../../src/form/hooks/useForm.ts","../../src/form/errors/FormValidationError.ts","../../src/form/index.ts"],"sourcesContent":["import { useAlepha } from \"@alepha/react\";\nimport type { FormModel } from \"../services/FormModel.ts\";\nimport { type TObject, TypeBoxError } from \"alepha\";\nimport { useEffect, useState } from \"react\";\n\nexport interface UseFormStateReturn {\n loading: boolean;\n dirty: boolean;\n values?: Record<string, any>;\n error?: Error;\n}\n\nexport const useFormState = <\n T extends TObject,\n Keys extends keyof UseFormStateReturn,\n>(\n target: FormModel<T> | { form: FormModel<T>; path: string },\n _events: Keys[] = [\"loading\", \"dirty\", \"error\"] as Keys[],\n): Pick<UseFormStateReturn, Keys> => {\n const alepha = useAlepha();\n const events = _events as string[];\n\n const [dirty, setDirty] = useState(false);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [values, setValues] = useState<Record<string, any> | undefined>(\n undefined,\n );\n\n const form = \"form\" in target ? target.form : target;\n const path = \"path\" in target ? target.path : undefined;\n\n const hasValues = events.includes(\"values\");\n const hasErrors = events.includes(\"error\");\n const hasDirty = events.includes(\"dirty\");\n const hasLoading = events.includes(\"loading\");\n\n useEffect(() => {\n const listeners: Function[] = [];\n\n if (hasErrors || hasValues || hasDirty) {\n listeners.push(\n alepha.events.on(\"form:change\", (event) => {\n if (event.id === form.id) {\n if (!path || event.path === path) {\n if (hasDirty) {\n setDirty(true);\n }\n if (hasErrors) {\n setError(undefined);\n }\n }\n if (hasValues) {\n setValues(form.currentValues);\n }\n }\n }),\n );\n }\n\n if (hasValues) {\n listeners.push(\n alepha.events.on(\"form:reset\", (event) => {\n if (event.id === form.id) {\n setValues(event.values);\n }\n }),\n );\n }\n\n if (hasLoading) {\n listeners.push(\n alepha.events.on(\"form:submit:begin\", (event) => {\n if (event.id === form.id) {\n setLoading(true);\n }\n }),\n alepha.events.on(\"form:submit:end\", (event) => {\n if (event.id === form.id) {\n setLoading(false);\n }\n }),\n );\n }\n\n if (hasValues || hasDirty) {\n listeners.push(\n alepha.events.on(\"form:submit:success\", (event) => {\n if (event.id === form.id) {\n if (hasValues) {\n setValues(event.values);\n }\n if (hasDirty) {\n setDirty(false);\n }\n }\n }),\n );\n }\n\n if (hasErrors) {\n listeners.push(\n alepha.events.on(\"form:submit:error\", (event) => {\n if (event.id === form.id) {\n if (\n !path ||\n (event.error instanceof TypeBoxError &&\n event.error.value.path === path)\n ) {\n setError(event.error);\n }\n }\n }),\n );\n }\n\n return () => {\n for (const unsub of listeners) {\n unsub();\n }\n };\n }, []);\n\n return {\n dirty,\n loading,\n error,\n values,\n } as Pick<UseFormStateReturn, Keys>;\n};\n","import type { TObject } from \"alepha\";\nimport type { ReactNode } from \"react\";\nimport { useFormState } from \"../hooks/useFormState.ts\";\nimport type { FormModel } from \"../services/FormModel.ts\";\n\nconst FormState = <T extends TObject>(props: {\n form: FormModel<T>;\n children: (state: { loading: boolean; dirty: boolean }) => ReactNode;\n}) => {\n const formState = useFormState(props.form);\n return props.children({\n loading: formState.loading,\n dirty: formState.dirty,\n });\n};\n\nexport default FormState;\n","import type { TArray } from \"alepha\";\nimport { $inject, Alepha, type Static, t, type TObject, type TSchema, } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport type { ChangeEvent, InputHTMLAttributes } from \"react\";\n\n/**\n * FormModel is a dynamic form handler that generates form inputs based on a provided TypeBox schema.\n * It manages form state, handles input changes, and processes form submissions with validation.\n *\n * It means to be injected and used within React components to provide a structured way to create and manage forms.\n *\n * @see {@link useForm}\n */\nexport class FormModel<T extends TObject> {\n protected readonly log = $logger();\n protected readonly alepha = $inject(Alepha);\n protected readonly values: Record<string, any> = {};\n protected submitInProgress = false;\n\n public input: SchemaToInput<T>;\n\n public get submitting(): boolean {\n return this.submitInProgress;\n }\n\n constructor(\n public readonly id: string,\n public readonly options: FormCtrlOptions<T>,\n ) {\n this.options = options;\n\n if (options.initialValues) {\n this.values = this.alepha.codec.decode(\n options.schema,\n options.initialValues,\n ) as Record<string, any>;\n }\n\n this.input = this.createProxyFromSchema(options, options.schema, {\n store: this.values,\n parent: \"\",\n });\n }\n\n public get element(): HTMLFormElement {\n return window.document.getElementById(this.id)! as HTMLFormElement;\n }\n\n public get currentValues(): Record<string, any> {\n return this.restructureValues(this.values);\n }\n\n public get props() {\n return {\n id: this.id,\n noValidate: true,\n onSubmit: (ev?: FormEventLike) => {\n ev?.preventDefault?.();\n this.submit();\n },\n onReset: (event: FormEventLike) => this.reset(event),\n };\n }\n\n public readonly reset = (event: FormEventLike) => {\n // clear values in place to maintain proxy reference\n for (const key in this.values) {\n delete this.values[key];\n }\n\n this.options.onReset?.();\n\n return this.alepha.events.emit(\n \"form:reset\",\n {\n id: this.id,\n values: this.values,\n },\n {\n catch: true,\n },\n );\n };\n\n public readonly submit = async () => {\n if (this.submitInProgress) {\n this.log.warn(\n \"Form submission already in progress, ignoring duplicate submit.\",\n );\n return;\n }\n\n // emit both action and form events\n await this.alepha.events.emit(\"react:action:begin\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:begin\", {\n id: this.id,\n });\n\n this.submitInProgress = true;\n\n const options = this.options;\n const form = this.element;\n const args = {\n form,\n };\n\n try {\n let values: Record<string, any> = this.restructureValues(this.values);\n\n if (t.schema.isSchema(options.schema)) {\n values = this.alepha.codec.decode(options.schema, values) as Record<\n string,\n any\n >;\n }\n\n await options.handler(values as any, args);\n\n await this.alepha.events.emit(\"react:action:success\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:success\", {\n id: this.id,\n values,\n });\n } catch (error) {\n this.log.error(\"Form submission error:\", error);\n\n options.onError?.(error as Error, args);\n\n await this.alepha.events.emit(\"react:action:error\", {\n type: \"form\",\n id: this.id,\n error: error as Error,\n });\n await this.alepha.events.emit(\"form:submit:error\", {\n error: error as Error,\n id: this.id,\n });\n } finally {\n this.submitInProgress = false;\n }\n\n await this.alepha.events.emit(\"react:action:end\", {\n type: \"form\",\n id: this.id,\n });\n await this.alepha.events.emit(\"form:submit:end\", {\n id: this.id,\n });\n };\n\n /**\n * Restructures flat keys like \"address.city\" into nested objects like { address: { city: ... } }\n * Values are already typed from onChange, so no conversion is needed.\n */\n protected restructureValues(store: Record<string, any>): Record<string, any> {\n const values: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(store)) {\n if (key.includes(\".\")) {\n // nested object: restructure flat key to nested structure\n this.restructureNestedValue(values, key, value);\n } else {\n // value is already typed, just copy it\n values[key] = value;\n }\n }\n\n return values;\n }\n\n /**\n * Helper to restructure a flat key like \"address.city\" into nested object structure.\n * The value is already typed, so we just assign it to the nested path.\n */\n protected restructureNestedValue(\n values: Record<string, any>,\n key: string,\n value: any,\n ) {\n const pathSegments = key.split(\".\");\n const finalPropertyKey = pathSegments.pop();\n if (!finalPropertyKey) {\n return;\n }\n\n let currentObjectLevel = values;\n\n // traverse/create the nested structure\n for (const segment of pathSegments) {\n currentObjectLevel[segment] ??= {};\n currentObjectLevel = currentObjectLevel[segment];\n }\n\n // value is already typed from onChange, just assign it\n currentObjectLevel[finalPropertyKey] = value;\n }\n\n protected createProxyFromSchema<T extends TObject>(\n options: FormCtrlOptions<T>,\n schema: TSchema,\n context: {\n parent: string;\n store: Record<string, any>;\n },\n ): SchemaToInput<T> {\n const parent = context.parent || \"\";\n return new Proxy<SchemaToInput<T>>({} as SchemaToInput<T>, {\n get: (_, prop: string) => {\n if (!options.schema || !t.schema.isObject(schema)) {\n return {};\n }\n\n if (prop in schema.properties) {\n\n // // it's a nested object, create another proxy\n // if (t.schema.isObject(schema.properties[prop])) {\n // return this.createProxyFromSchema(\n // options,\n // schema.properties[prop],\n // {\n // parent: parent ? `${parent}.${prop}` : prop,\n // store: context.store,\n // },\n // );\n // }\n\n return this.createInputFromSchema<T>(\n prop as keyof Static<T> & string,\n options,\n schema,\n schema.required?.includes(prop as string) || false,\n context,\n );\n }\n },\n });\n }\n\n protected createInputFromSchema<T extends TObject>(\n name: keyof Static<T> & string,\n options: FormCtrlOptions<T>,\n schema: TObject,\n required: boolean,\n context: {\n parent: string;\n store: Record<string, any>;\n },\n ): BaseInputField {\n const parent = context.parent || \"\";\n const field = schema.properties?.[name];\n if (!field) {\n return {\n path: \"\",\n required,\n props: {} as InputHTMLAttributes<unknown>,\n schema: schema,\n set: () => {},\n form: this,\n };\n }\n\n const isRequired = schema.required?.includes(name) ?? false;\n const key = parent ? `${parent}.${name}` : name;\n const path = `/${key.replaceAll(\".\", \"/\")}`;\n\n const set = (value: any, sync = true) => {\n // Convert to typed value immediately based on schema\n const typedValue = this.getValueFromInput(value, field);\n\n if (context.store[key] === typedValue) {\n // no change, do not update\n // return; <- disabled for now, as some inputs may need to sync even if value is same\n }\n\n context.store[key] = typedValue;\n\n if (options.onChange) {\n options.onChange(key, typedValue, context.store);\n }\n\n this.alepha.events.emit(\"form:change\", {\n id: this.id,\n path: path,\n value: typedValue,\n }, {\n catch: true\n });\n\n if (sync) {\n const inputElement = window.document.querySelector(\n `[data-path=\"${path}\"]`,\n );\n if (inputElement instanceof HTMLInputElement) {\n if (t.schema.isBoolean(field)) {\n inputElement.value = value;\n inputElement.checked = Boolean(value);\n } else {\n inputElement.value = value;\n }\n }\n }\n };\n\n const attr: InputHTMLAttributesLike = {\n name: key,\n autoComplete: \"off\",\n onChange: (event: ChangeEvent<HTMLInputElement> | string | number) => {\n if (typeof event === \"string\") {\n // If the event is a string, it means it's a direct value change\n set(event, false);\n return;\n }\n\n if (typeof event === \"number\") {\n // Some inputs might return number directly\n set(event, false);\n return;\n }\n\n if (t.schema.isBoolean(field)) {\n if (event.target.value === \"true\") {\n set(true, false);\n } else if (event.target.value === \"false\") {\n set(false, false);\n } else if (event.target.value === \"\") {\n set(undefined, false);\n } else {\n set(event.target.checked, false);\n }\n } else {\n set(event.target.value, false);\n }\n },\n };\n\n (attr as any)[\"data-path\"] = path;\n\n if (options.id) {\n attr.id = `${options.id}-${key}`;\n (attr as any)[\"data-testid\"] = attr.id;\n }\n\n if (t.schema.isString(field)) {\n if (field.maxLength != null) {\n attr.maxLength = Number(field.maxLength);\n }\n\n if (field.minLength != null) {\n attr.minLength = Number(field.minLength);\n }\n }\n\n if (options.initialValues?.[name] != null) {\n attr.defaultValue = this.valueToInputEntry(options.initialValues[name]);\n } else if (\"default\" in field && field.default != null) {\n attr.defaultValue = this.valueToInputEntry(field.default);\n }\n\n if (isRequired) {\n attr.required = true;\n }\n\n if (\"description\" in field && typeof field.description === \"string\") {\n attr[\"aria-label\"] = field.description;\n }\n\n if (t.schema.isInteger(field) || t.schema.isNumber(field)) {\n attr.type = \"number\";\n } else if (name === \"password\") {\n attr.type = \"password\";\n } else if (name === \"email\") {\n attr.type = \"email\";\n } else if (name === \"url\") {\n attr.type = \"url\";\n } else if (t.schema.isString(field)) {\n if (field.format === \"binary\") {\n attr.type = \"file\";\n } else if (field.format === \"date\") {\n attr.type = \"date\";\n } else if (field.format === \"time\") {\n attr.type = \"time\";\n } else if (field.format === \"date-time\") {\n attr.type = \"datetime-local\";\n } else {\n attr.type = \"text\";\n }\n } else if (t.schema.isBoolean(field)) {\n attr.type = \"checkbox\";\n }\n\n if (options.onCreateField) {\n const customAttr = options.onCreateField(name, field);\n Object.assign(attr, customAttr);\n }\n\n // if type = object, add items: { [key: string]: InputField }\n if (t.schema.isObject(field)) {\n return {\n path,\n props: attr,\n schema: field,\n set,\n form: this,\n required,\n items: this.createProxyFromSchema(\n options,\n field,\n {\n parent: key,\n store: context.store,\n },\n )\n } as ObjectInputField<any>;\n }\n\n // if type = array, add items: InputField[]\n if (t.schema.isArray(field)) {\n return {\n path,\n props: attr,\n schema: field,\n set,\n form: this,\n required,\n items: [], // <- will be populated dynamically in the UI\n } as ArrayInputField<any>;\n }\n\n return {\n path,\n props: attr,\n schema: field,\n set,\n form: this,\n required,\n };\n }\n\n /**\n * Convert an input value to the correct type based on the schema.\n * Handles raw DOM values (strings, booleans from checkboxes, Files, etc.)\n */\n protected getValueFromInput(input: any, schema: TSchema): any {\n if (input instanceof File) {\n // for file inputs, return the File object directly\n if (t.schema.isString(schema) && schema.format === \"binary\") {\n return input;\n }\n // for now, ignore other formats\n return null;\n }\n\n if (t.schema.isBoolean(schema)) {\n return !!input;\n }\n\n if (t.schema.isNumber(schema)) {\n const num = Number(input);\n return Number.isNaN(num) ? null : num;\n }\n\n if (t.schema.isString(schema)) {\n if (schema.format === \"date\") {\n return new Date(input).toISOString().slice(0, 10); // For date input\n }\n if (schema.format === \"time\") {\n return new Date(`1970-01-01T${input}`).toISOString().slice(11, 16); // For time input\n }\n if (schema.format === \"date-time\") {\n return new Date(input).toISOString(); // For datetime-local input\n }\n return String(input);\n }\n\n return input; // fallback for other types\n }\n\n protected valueToInputEntry(value: any): string | number | boolean {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n if (typeof value === \"boolean\") {\n return value;\n }\n\n if (typeof value === \"number\") {\n return value;\n }\n\n if (typeof value === \"string\") {\n return value;\n }\n\n if (value instanceof Date) {\n return value.toISOString().slice(0, 16); // For datetime-local input\n }\n\n return value;\n }\n}\n\nexport type SchemaToInput<T extends TObject> = {\n [K in keyof T[\"properties\"]]: InputField<T[\"properties\"][K]>;\n};\n\nexport interface FormEventLike {\n preventDefault?: () => void;\n stopPropagation?: () => void;\n}\n\nexport type InputField<T extends TSchema> =\n T extends TObject\n ? ObjectInputField<T>\n : T extends TArray<infer U>\n ? ArrayInputField<U>\n : BaseInputField;\n\nexport interface BaseInputField {\n path: string;\n required: boolean;\n props: InputHTMLAttributesLike;\n schema: TSchema;\n set: (value: any) => void;\n form: FormModel<any>;\n items?: any;\n}\n\nexport interface ObjectInputField<T extends TObject> extends BaseInputField {\n items: SchemaToInput<T>;\n}\n\nexport interface ArrayInputField<T extends TSchema> extends BaseInputField {\n items: Array<InputField<T>>\n}\n\nexport type InputHTMLAttributesLike = Pick<\n InputHTMLAttributes<unknown>,\n | \"id\"\n | \"name\"\n | \"type\"\n | \"value\"\n | \"defaultValue\"\n | \"required\"\n | \"maxLength\"\n | \"minLength\"\n | \"aria-label\"\n | \"autoComplete\"\n> & {\n value?: any;\n defaultValue?: any;\n onChange?: (event: any) => void;\n};\n\nexport type FormCtrlOptions<T extends TObject> = {\n /**\n * The schema defining the structure and validation rules for the form.\n * This should be a TypeBox schema object.\n */\n schema: T;\n\n /**\n * Callback function to handle form submission.\n * This function will receive the parsed and validated form values.\n */\n handler: (values: Static<T>, args: { form: HTMLFormElement }) => unknown;\n\n /**\n * Optional initial values for the form fields.\n * This can be used to pre-populate the form with existing data.\n */\n initialValues?: Partial<Static<T>>;\n\n /**\n * Optional function to create custom field attributes.\n * This can be used to add custom validation, styles, or other attributes.\n */\n onCreateField?: (\n name: keyof Static<T> & string,\n schema: TSchema,\n ) => InputHTMLAttributes<unknown>;\n\n /**\n * If defined, this will generate a unique ID for each field, prefixed with this string.\n *\n * > \"username\" with id=\"form-123\" will become \"form-123-username\".\n *\n * If omitted, IDs will not be generated.\n */\n id?: string;\n\n onError?: (error: Error, args: { form: HTMLFormElement }) => void;\n\n onChange?: (key: string, value: any, store: Record<string, any>) => void;\n\n onReset?: () => void;\n};\n","import { useAlepha } from \"@alepha/react\";\nimport type { TObject } from \"alepha\";\nimport { useId, useMemo } from \"react\";\nimport { type FormCtrlOptions, FormModel } from \"../services/FormModel.ts\";\n\n/**\n * Custom hook to create a form with validation and field management.\n * This hook uses TypeBox schemas to define the structure and validation rules for the form.\n * It provides a way to handle form submission, field creation, and value management.\n *\n * @example\n * ```tsx\n * import { t } from \"alepha\";\n *\n * const form = useForm({\n * schema: t.object({\n * username: t.text(),\n * password: t.text(),\n * }),\n * handler: (values) => {\n * console.log(\"Form submitted with values:\", values);\n * },\n * });\n *\n * return (\n * <form {...form.props}>\n * <input {...form.input.username.props} />\n * <input {...form.input.password.props} />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * ```\n */\nexport const useForm = <T extends TObject>(\n options: FormCtrlOptions<T>,\n deps: any[] = [],\n): FormModel<T> => {\n const alepha = useAlepha();\n const formId = useId();\n\n return useMemo(() => {\n return alepha.inject(FormModel<T>, {\n lifetime: \"transient\",\n args: [options.id || formId, options],\n });\n }, deps);\n};\n","import { TypeBoxError } from \"alepha\";\n\nexport class FormValidationError extends TypeBoxError {\n readonly name = \"ValidationError\";\n\n constructor(\n options: {\n message: string;\n path: string;\n }\n ) {\n super({\n message: options.message,\n instancePath: options.path,\n schemaPath: \"\",\n keyword: \"not\",\n params: {},\n });\n }\n}\n","import { $module } from \"alepha\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport { default as FormState } from \"./components/FormState.tsx\";\nexport * from \"./hooks/useForm.ts\";\nexport * from \"./hooks/useFormState.ts\";\nexport * from \"./services/FormModel.ts\";\nexport * from \"./errors/FormValidationError.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\ndeclare module \"alepha\" {\n interface Hooks {\n \"form:change\": { id: string; path: string; value: any };\n \"form:reset\": { id: string; values: Record<string, any> };\n \"form:submit:begin\": { id: string };\n \"form:submit:success\": { id: string; values: Record<string, any> };\n \"form:submit:error\": { id: string; error: Error };\n \"form:submit:end\": { id: string };\n }\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * React hooks for managing forms in Alepha applications.\n *\n * This module provides a set of hooks to simplify form handling, validation, and submission in React applications built with Alepha.\n *\n * It includes:\n * - `useForm`: A hook for managing form state, validation, and submission.\n *\n * @see {@link useForm}\n * @module alepha.react.form\n */\nexport const AlephaReactForm = $module({\n name: \"alepha.react.form\",\n});\n"],"mappings":"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"}
@@ -95,7 +95,7 @@ declare const useHead: (options?: UseHeadOptions) => UseHeadReturn;
95
95
  type UseHeadOptions = Head | ((previous?: Head) => Head);
96
96
  type UseHeadReturn = [Head, (head?: Head | ((previous?: Head) => Head)) => void];
97
97
  //#endregion
98
- //#region ../../../alepha/src/server/schemas/errorSchema.d.ts
98
+ //#region ../../../alepha/src/server/core/schemas/errorSchema.d.ts
99
99
  declare const errorSchema: alepha22.TObject<{
100
100
  error: alepha22.TString;
101
101
  status: alepha22.TInteger;
@@ -109,7 +109,7 @@ declare const errorSchema: alepha22.TObject<{
109
109
  }>;
110
110
  type ErrorSchema = Static<typeof errorSchema>;
111
111
  //#endregion
112
- //#region ../../../alepha/src/server/errors/HttpError.d.ts
112
+ //#region ../../../alepha/src/server/core/errors/HttpError.d.ts
113
113
  declare class HttpError extends AlephaError {
114
114
  name: string;
115
115
  static is: (error: unknown, status?: number) => error is HttpErrorLike;
@@ -172,11 +172,11 @@ interface Tree<T extends Route> {
172
172
  };
173
173
  }
174
174
  //#endregion
175
- //#region ../../../alepha/src/server/constants/routeMethods.d.ts
175
+ //#region ../../../alepha/src/server/core/constants/routeMethods.d.ts
176
176
  declare const routeMethods: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "CONNECT", "TRACE"];
177
177
  type RouteMethod = (typeof routeMethods)[number];
178
178
  //#endregion
179
- //#region ../../../alepha/src/server/helpers/ServerReply.d.ts
179
+ //#region ../../../alepha/src/server/core/helpers/ServerReply.d.ts
180
180
  /**
181
181
  * Helper for building server replies.
182
182
  */
@@ -204,7 +204,7 @@ declare class ServerReply {
204
204
  setBody(body: any): this;
205
205
  }
206
206
  //#endregion
207
- //#region ../../../alepha/src/server/services/UserAgentParser.d.ts
207
+ //#region ../../../alepha/src/server/core/services/UserAgentParser.d.ts
208
208
  interface UserAgentInfo {
209
209
  os: "Windows" | "Android" | "Ubuntu" | "MacOS" | "iOS" | "Linux" | "FreeBSD" | "OpenBSD" | "ChromeOS" | "BlackBerry" | "Symbian" | "Windows Phone";
210
210
  browser: "Chrome" | "Firefox" | "Safari" | "Edge" | "Opera" | "Internet Explorer" | "Brave" | "Vivaldi" | "Samsung Browser" | "UC Browser" | "Yandex";
@@ -220,7 +220,7 @@ declare class UserAgentParser {
220
220
  parse(userAgent?: string): UserAgentInfo;
221
221
  }
222
222
  //#endregion
223
- //#region ../../../alepha/src/server/interfaces/ServerRequest.d.ts
223
+ //#region ../../../alepha/src/server/core/interfaces/ServerRequest.d.ts
224
224
  type TRequestBody = TObject | TString | TArray | TRecord | TStream;
225
225
  type TResponseBody = TObject | TString | TRecord | TFile | TArray | TStream | TVoid;
226
226
  interface RequestConfigSchema {
@@ -520,7 +520,7 @@ declare const envSchema$2: alepha22.TObject<{
520
520
  /**
521
521
  * Built-in log formats.
522
522
  * - "json" - JSON format, useful for structured logging and log aggregation. {@link JsonFormatterProvider}
523
- * - "pretty" - Simple text format, human-readable, with colors. {@link SimpleFormatterProvider}
523
+ * - "pretty" - Simple text format, human-readable, with colors. {@link PrettyFormatterProvider}
524
524
  * - "raw" - Raw format, no formatting, just the message. {@link RawFormatterProvider}
525
525
  */
526
526
  LOG_FORMAT: alepha22.TOptional<alepha22.TUnsafe<"json" | "pretty" | "raw">>;
@@ -541,7 +541,7 @@ declare module "alepha" {
541
541
  }
542
542
  }
543
543
  //#endregion
544
- //#region ../../../alepha/src/server/services/ServerRequestParser.d.ts
544
+ //#region ../../../alepha/src/server/core/services/ServerRequestParser.d.ts
545
545
  declare class ServerRequestParser {
546
546
  protected readonly alepha: Alepha;
547
547
  protected readonly userAgentParser: UserAgentParser;
@@ -551,7 +551,7 @@ declare class ServerRequestParser {
551
551
  getRequestIp(request: ServerRequestData): string | undefined;
552
552
  }
553
553
  //#endregion
554
- //#region ../../../alepha/src/server/providers/ServerTimingProvider.d.ts
554
+ //#region ../../../alepha/src/server/core/providers/ServerTimingProvider.d.ts
555
555
  type TimingMap = Record<string, [number, number]>;
556
556
  declare class ServerTimingProvider {
557
557
  protected readonly log: Logger;
@@ -568,7 +568,7 @@ declare class ServerTimingProvider {
568
568
  protected setDuration(name: string, timing: TimingMap): void;
569
569
  }
570
570
  //#endregion
571
- //#region ../../../alepha/src/server/providers/ServerRouterProvider.d.ts
571
+ //#region ../../../alepha/src/server/core/providers/ServerRouterProvider.d.ts
572
572
  /**
573
573
  * Main router for all routes on the server side.
574
574
  *
@@ -607,7 +607,7 @@ declare class ServerRouterProvider extends RouterProvider<ServerRouteMatcher> {
607
607
  }, request: ServerRequestConfig): void;
608
608
  }
609
609
  //#endregion
610
- //#region ../../../alepha/src/server/providers/ServerProvider.d.ts
610
+ //#region ../../../alepha/src/server/core/providers/ServerProvider.d.ts
611
611
  /**
612
612
  * Base server provider to handle incoming requests and route them.
613
613
  *
@@ -646,7 +646,7 @@ declare class ServerProvider {
646
646
  protected isViteNotFound(url?: string, route?: Route, params?: Record<string, string>): boolean;
647
647
  }
648
648
  //#endregion
649
- //#region ../../../alepha/src/cache/providers/CacheProvider.d.ts
649
+ //#region ../../../alepha/src/cache/core/providers/CacheProvider.d.ts
650
650
  /**
651
651
  * Cache provider interface.
652
652
  *
@@ -689,7 +689,7 @@ declare abstract class CacheProvider {
689
689
  abstract clear(): Promise<void>;
690
690
  }
691
691
  //#endregion
692
- //#region ../../../alepha/src/cache/primitives/$cache.d.ts
692
+ //#region ../../../alepha/src/cache/core/primitives/$cache.d.ts
693
693
  interface CachePrimitiveOptions<TReturn = any, TParameter extends any[] = any[]> {
694
694
  /**
695
695
  * The cache name. This is useful for invalidating multiple caches at once.
@@ -756,7 +756,7 @@ interface CachePrimitiveFn<TReturn = any, TParameter extends any[] = any[]> exte
756
756
  (...args: TParameter): Promise<TReturn>;
757
757
  }
758
758
  //#endregion
759
- //#region ../../../alepha/src/server/services/HttpClient.d.ts
759
+ //#region ../../../alepha/src/server/core/services/HttpClient.d.ts
760
760
  declare class HttpClient {
761
761
  protected readonly log: Logger;
762
762
  protected readonly alepha: Alepha;
@@ -828,7 +828,7 @@ interface HttpAction {
828
828
  };
829
829
  }
830
830
  //#endregion
831
- //#region ../../../alepha/src/server/primitives/$action.d.ts
831
+ //#region ../../../alepha/src/server/core/primitives/$action.d.ts
832
832
  interface ActionPrimitiveOptions<TConfig extends RequestConfigSchema> extends Omit<ServerRoute, "handler" | "path" | "schema" | "mapParams"> {
833
833
  /**
834
834
  * Name of the action.
@@ -963,7 +963,7 @@ type ServerActionHandler<TConfig extends RequestConfigSchema = RequestConfigSche
963
963
  */
964
964
  interface ServerActionRequest<TConfig extends RequestConfigSchema> extends ServerRequest<TConfig> {}
965
965
  //#endregion
966
- //#region ../../../alepha/src/server/providers/BunHttpServerProvider.d.ts
966
+ //#region ../../../alepha/src/server/core/providers/BunHttpServerProvider.d.ts
967
967
  declare const envSchema$1: alepha22.TObject<{
968
968
  SERVER_PORT: alepha22.TInteger;
969
969
  SERVER_HOST: alepha22.TString;
@@ -972,7 +972,7 @@ declare module "alepha" {
972
972
  interface Env extends Partial<Static<typeof envSchema$1>> {}
973
973
  }
974
974
  //#endregion
975
- //#region ../../../alepha/src/server/providers/NodeHttpServerProvider.d.ts
975
+ //#region ../../../alepha/src/server/core/providers/NodeHttpServerProvider.d.ts
976
976
  declare const envSchema: alepha22.TObject<{
977
977
  SERVER_PORT: alepha22.TInteger;
978
978
  SERVER_HOST: alepha22.TString;
@@ -981,7 +981,7 @@ declare module "alepha" {
981
981
  interface Env extends Partial<Static<typeof envSchema>> {}
982
982
  }
983
983
  //#endregion
984
- //#region ../../../alepha/src/server/index.d.ts
984
+ //#region ../../../alepha/src/server/core/index.d.ts
985
985
  declare module "alepha" {
986
986
  interface State {
987
987
  "alepha.node.server"?: Server;