### Install Dependencies and Run Dev Server Source: https://github.com/codesan-git/sanify/blob/main/packages/create-sanify/README.md After creating the project, navigate into the project directory, install the necessary dependencies, and start the development server with Hot Module Replacement (HMR). ```bash cd my-app bun install bun dev ``` -------------------------------- ### Typical Setup with createClient, resource, mutation Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Demonstrates the typical setup for creating an API client and defining resources and mutations using the @sanify/core library. ```typescript import { createClient, resource, mutation, invalidate } from "@sanify/core"; const api = createClient({ baseUrl: "/api", headers: () => ({ Authorization: `Bearer ${token()}` }), }); const users = resource((signal) => api.get("/users", { signal }), { key: "users:list" }); const create = mutation((data: NewUser) => api.post("/users", data), { invalidates: ["users:list"] }); ``` -------------------------------- ### Basic API Client Setup Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Configure and use the API client for basic GET, POST, and DELETE requests. Ensure to import `createClient` from '@sanify/core'. ```typescript import { createClient } from "@sanify/core"; const api = createClient({ baseUrl: "https://api.example.com", headers: { "X-App-Version": "1.0" }, }); const user = await api.get("/me"); const created = await api.post("/users", { name: "Sat" }); await api.delete(`/users/${id}`); ``` -------------------------------- ### Install Dependencies Source: https://github.com/codesan-git/sanify/blob/main/packages/create-sanify/templates/default/README.md Installs project dependencies using Bun. Ensure Bun is installed and meets the version requirement. ```bash bun install ``` -------------------------------- ### Minimal Router Setup Source: https://github.com/codesan-git/sanify/blob/main/docs/router.md This snippet demonstrates the basic setup for the Sanify router, defining root, about, and fallback routes. It renders the router's output into the document body. ```typescript import { router, html, render } from "@sanify/core"; const view = router({ "/": () => html``, "/about": () => html``, "*": () => html``, // optional fallback }); render(html`
${view}
`, document.body); ``` -------------------------------- ### Install @sanify/core Source: https://github.com/codesan-git/sanify/blob/main/packages/core/README.md Use this command to add the @sanify/core package to your project. ```bash bun add @sanify/core ``` -------------------------------- ### Run Development Server and Tests in Sanify Repo Source: https://github.com/codesan-git/sanify/blob/main/README.md Commands to run the example app, execute all workspace tests, perform type checking, and build the project within the Sanify repository. Useful for local development and testing. ```bash bun install bun dev # example app at http://localhost:54712 bun test # all workspace tests bun run typecheck bun run build ``` -------------------------------- ### Install Sanify Dependencies Source: https://github.com/codesan-git/sanify/blob/main/README.md Installs all necessary project dependencies using the Bun package manager. This is a prerequisite for running Sanify projects. ```bash bun install ``` -------------------------------- ### CreateStore: Initial State Mutation Example Source: https://github.com/codesan-git/sanify/blob/main/docs/store.md Illustrates how to mutate properties within a `createStore` proxy, including nested objects and array pushes. These mutations trigger reactivity only for the affected leaf nodes. ```typescript ok tate.user.age++; // logs nothing — different leaf state.todos.push({ id: 1, text: "ship", done: false }); ``` -------------------------------- ### Navigation API Examples Source: https://github.com/codesan-git/sanify/blob/main/docs/router.md Utilize the navigation API for programmatic routing. `navigate` adds a history entry, while `redirect` replaces it. ```typescript navigate("/users/7"); // pushState — new history entry redirect("/login"); // replaceState — replaces current entry, used by guards back(); // history.back() forward(); // history.forward() ``` -------------------------------- ### Sanify Component Setup: Reactive vs. Static Signal Reading Source: https://github.com/codesan-git/sanify/blob/main/AGENTS.md Demonstrates the correct way to read signals within a Sanify component. Signals read directly in the setup function are static and not reactive, while signals read within the view function are reactive. This distinction is crucial for ensuring dynamic updates. ```typescript // ❌ Salah — signal dibaca di setup, nilainya cuma kebaca sekali component("x-page", () => { const id = params().id; // dibaca di setup, gak reaktif return () => html`

${id}

`; }); // ✅ Benar — signal dibaca di view function, reaktif component("x-page", () => { return () => { const id = params().id; // dibaca di view, reaktif return html`

${id}

`; }; }); ``` -------------------------------- ### Using the Custom Counter Component Source: https://github.com/codesan-git/sanify/blob/main/docs/rendering.md Example of how to use the custom 'my-counter' component in HTML, setting the 'count' attribute to '5'. ```html ``` -------------------------------- ### Reactive Binding Example Source: https://github.com/codesan-git/sanify/blob/main/docs/rendering.md Illustrates the correct way to create reactive bindings within `html` templates. Bindings are reactive only when the value is a function, ensuring updates when the underlying state changes. Incorrect usage involves direct reads that evaluate only once. ```typescript // WRONG: evaluated once during render, never updates html`${count()}` ``` ```typescript // RIGHT: wrapped in a function, re-runs whenever `count` changes html`${() => count()}` ``` -------------------------------- ### Use a Custom Web Component Source: https://github.com/codesan-git/sanify/blob/main/packages/core/README.md Shows how to render a custom web component, 'hello-counter', after it has been defined. ```html ``` -------------------------------- ### Router Initialization Source: https://github.com/codesan-git/sanify/blob/main/docs/router.md Compiles a route table and returns a reactive view. It accepts an array of routes and optional configuration options. ```APIDOC ## `router` ### Description Compile a route table; returns the reactive view. ### Signature `router(routes, options?) => Getter` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **routes**: `RouteConfig[]` - An array of route configurations. - **options?**: `object` - Optional configuration options for the router. ``` -------------------------------- ### Create a Basic Reactive Component Source: https://github.com/codesan-git/sanify/blob/main/packages/core/README.md Demonstrates how to create a simple reactive component with a click counter using signals and the html template helper. Ensure you import necessary functions from '@sanify/core'. ```typescript import { component, html, signal } from "@sanify/core"; component("hello-counter", () => { const [count, setCount] = signal(0); return () => html` `; }); ``` -------------------------------- ### Create and Render a Basic Form Source: https://github.com/codesan-git/sanify/blob/main/docs/form.md Demonstrates how to create a form with initial values, validation, and an onSubmit handler. It then renders the form using HTML templates and reactive components, including input fields, error display, and a submit button. ```typescript import { createForm, html, render, Show } from "@sanify/core"; const form = createForm({ initialValues: { email: "", password: "" }, validate: (v) => { const e: Record = {}; if (!v.email.includes("@")) e.email = "invalid email"; if (v.password.length < 6) e.password = "password too short"; return e; }, onSubmit: async (values) => { await api.login(values); }, }); render( html`
${Show( () => form.errors.email, () => html`

${() => form.errors.email}

`, )} ${Show( () => form.errors.password, () => html`

${() => form.errors.password}

`, )}
`, document.body, ); ``` -------------------------------- ### Sanify Form Handling Source: https://github.com/codesan-git/sanify/blob/main/README.md Manages form state, validation, and submission. `register` installs reactive value and event handlers into inputs. Validation runs on submit by default, with options for blur or input validation. ```typescript import { createForm, html, render, Show } from "@sanify/core"; const form = createForm({ initialValues: { email: "", password: "" }, validate: (v) => (v.email.includes("@") ? {} : { email: "invalid" }), onSubmit: async (values) => api.login(values), }); render(html`
${Show(() => form.errors.email, () => html`

${() => form.errors.email}

`)}
`, document.body); ``` -------------------------------- ### Create a New Sanify Project Source: https://github.com/codesan-git/sanify/blob/main/packages/create-sanify/README.md Use this command to scaffold a new Sanify Frontend project. You can specify the project name directly or omit it to be prompted interactively. ```bash bun create sanify my-app # or npm create sanify my-app ``` -------------------------------- ### Use a Sanify Component Source: https://github.com/codesan-git/sanify/blob/main/README.md Demonstrates how to render a custom Sanify component ('hello-world') within an HTML document. This shows the integration of custom elements. ```html ``` -------------------------------- ### Run Typecheck, Test, and Build Source: https://github.com/codesan-git/sanify/blob/main/README.md Before publishing, ensure all checks pass by running typecheck, tests, and the build process. The build command also emits .d.ts files using tsc. ```bash bun run typecheck bun test bun run build # also emits .d.ts via tsc ``` -------------------------------- ### Resource with Cache and Deduplication using Key Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Demonstrates how to enable caching and request deduplication for a resource by providing a reactive 'key' option. The fetcher re-runs automatically when the key changes. ```typescript const user = resource( () => fetch(`/api/users/${id()}`).then((r) => r.json()), { key: () => `user:${id()}` }, ); ``` -------------------------------- ### Create a New Sanify Project Source: https://github.com/codesan-git/sanify/blob/main/README.md Scaffolds a new Sanify project using the 'bun create' command. This command sets up a minimal project structure with HMR support. ```bash bun create sanify my-app cd my-app && bun install && bun dev ``` -------------------------------- ### Reactive Data Fetching with Resource Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Demonstrates how to fetch data reactively based on a signal. The fetcher automatically tracks signal dependencies. Use the `key` option for caching and instant navigation back to previous states. ```typescript const [page, setPage] = signal(1); const items = resource( () => fetch(`/api/items?page=${page()}`).then((r) => r.json()), { key: () => `items:${page()}` }, ); setPage(2); // items.loading() → true on next microtask; data updates after fetch ``` -------------------------------- ### createClient Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Builds a configured fetch wrapper for making API requests. It allows for setting base URLs, headers, and interceptors for request and response handling. ```APIDOC ## `createClient` ### Description Builds a configured fetch wrapper for making API requests. It allows for setting base URLs, headers, and interceptors for request and response handling. ### Signature `(options?: ClientOptions) => Client` ### Parameters #### `options` (ClientOptions) - Optional An object to configure the client. It can include: - `baseUrl` (string) - Optional - The base URL for all requests. - `headers` (object | function) - Optional - Static headers or a function that returns headers per request. - `before` (RequestInterceptor) - Optional - A function to intercept and modify outgoing requests. - `after` (ResponseInterceptor) - Optional - A function to intercept and process incoming responses. ### Returns `Client` - An object with methods for making HTTP requests (`request`, `get`, `post`, `put`, `patch`, `delete`). ### Example ```ts import { createClient } from "@sanify/core"; const api = createClient({ baseUrl: "https://api.example.com", headers: { "X-App-Version": "1.0" }, }); const user = await api.get("/me"); ``` ``` -------------------------------- ### Sanify Resource Management with Fetch Wrappers Source: https://github.com/codesan-git/sanify/blob/main/README.md Illustrates creating a reusable API client with `createClient` for base URLs and headers, then using `resource` for reactive data fetching with caching and `mutation` for data writes with automatic cache invalidation. ```typescript import { createClient, resource, mutation, invalidate } from "@sanify/core"; // Reusable fetch wrapper with base URL + auth header + error normalisation const api = createClient({ baseUrl: "/api", headers: () => (token() ? { Authorization: `Bearer ${token()}` } : {}), }); // Read: reactive fetch, cached, abortable const user = resource( (signal) => api.get(`/users/${id()}`, { signal }), { key: () => `user:${id()}`, staleTime: 30_000 }, ); // Write: trigger + auto-invalidate const updateUser = mutation( (patch: Partial) => api.patch(`/users/${id()}`, patch), { invalidates: () => [`user:${id()}`] }, ); await updateUser.mutate({ name: "New Name" }); // updateUser.loading() / .error() / .data() — reactive signals ``` -------------------------------- ### Basic HTML Template Rendering Source: https://github.com/codesan-git/sanify/blob/main/docs/rendering.md Demonstrates how to use the `html` tagged template literal to define a view and the `render` function to attach it to the DOM. Ensure `html` and `render` are imported from '@sanify/core'. ```typescript import { html, render } from "@sanify/core"; const view = html`

${() => title()}

`; render(view, document.body); ``` -------------------------------- ### Publish Packages to npm Source: https://github.com/codesan-git/sanify/blob/main/README.md After ensuring all checks are green, navigate to the respective package directories and publish them to npm with public access. Ensure '@sanify/core' is published first. ```bash cd packages/core && npm publish --access public cd ../create-sanify && npm publish --access public ``` -------------------------------- ### Importing Sanify Core Utilities Source: https://github.com/codesan-git/sanify/blob/main/README.md Demonstrates a comprehensive import of various utilities and components available from the `@sanify/core` package. ```typescript import { signal, effect, computed, batch, untrack, on, createContext, useContext, createRoot, createOwner, runWithOwner, html, render, For, TransitionGroup, Show, Switch, Match, Index, Portal, ErrorBoundary, Suspense, Dynamic, Transition, provide, component, resource, mutation, invalidate, setResourceData, getResourceData, createClient, HttpError, router, lazy, navigate, redirect, back, forward, current, params, query, createStore, produce, persisted, createForm, schema, validators, createSelector, debounced, throttled, __debug, } from "@sanify/core"; ``` -------------------------------- ### Project Structure Overview Source: https://github.com/codesan-git/sanify/blob/main/packages/create-sanify/templates/default/README.md Illustrates the directory and file organization for the Sanify project. Key directories include 'src' for source code and 'dist' for production builds. ```text src/ main.ts entry: loads app-root app.ts root + router (including nested routes + outlet) state/ todos (persisted), settings (nested createStore) data/ sample data + simulated async fetch components/ nav-bar, users-sidebar, todo-item, live-clock pages/ home, todos, settings, user-list, user-detail, about index.html host page, loads the bundled main.js dev-server.ts Bun dev server with HMR + automatic bundling ``` -------------------------------- ### Create and Use a Signal Source: https://github.com/codesan-git/sanify/blob/main/docs/reactivity.md Demonstrates creating a signal with an initial value and updating it. The getter subscribes the current observer to the signal. The setter accepts a value or a `(prev) => next` updater. Updates are skipped if `Object.is(prev, next)`. ```typescript import { signal, effect } from "@sanify/core"; const [count, setCount] = signal(0); effect(() => console.log("count:", count())); // logs "count: 0" setCount(1); // logs "count: 1" on next microtask setCount((prev) => prev + 1); // logs "count: 2" ``` -------------------------------- ### Create a Basic Sanify Component Source: https://github.com/codesan-git/sanify/blob/main/README.md Defines a 'hello-world' component that displays a click counter using signals and HTML template literals. This is the core pattern for creating components in Sanify. ```typescript import { component, signal, html } from "@sanify/core"; component("hello-world", () => { const [count, setCount] = signal(0); return () => html` `; }); ``` -------------------------------- ### Basic Resource Usage Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Shows how to use the 'resource' function to create a reactive data signal for fetching user data and displaying it in a component with loading and error states. ```typescript import { resource } from "@sanify/core"; const user = resource(() => fetch("/api/me").then((r) => r.json())); // in a component: return () => html` ${Show( () => user.loading(), () => html``, () => html`

${() => user.data()?.name ?? "anon"}

`, )} `; ``` -------------------------------- ### Sanify Rendering with Components and Control Flow Source: https://github.com/codesan-git/sanify/blob/main/README.md Shows how to define a component using `component` and render dynamic lists and conditional content using `html`, `For`, `Show`, and `component`. Template rules dictate reactivity for expressions. ```typescript import { html, For, Show, component } from "@sanify/core"; component("todo-list", () => { const [todos, setTodos] = signal<{ id: number; text: string; done: boolean }[]>([]); return () => html` ${Show( () => todos().length === 0, () => html`

no todos yet

`, () => html`
    ${For( () => todos(), (todo) => html`
  • (todo().done ? "done" : "")}>${() => todo().text}
  • `, { key: (t) => t.id }, )}
`, )} `; }); // Template rules: // `${() => value()}` - Reactive // `name=${value}` - Attribute // `.name=${value}` - Property // `@event=${handler}` - Event listener ``` -------------------------------- ### Nested Routes with Layouts Source: https://github.com/codesan-git/sanify/blob/main/docs/router.md Shows how to implement nested routes and layouts. A layout component wraps child routes, and the `ctx.outlet` provides the rendering target for the child view. Layouts are reference-shared for performance. ```typescript router({ "/dashboard": { layout: (ctx) => html`
${ctx.outlet}
`, children: { "/": () => html``, "/users": () => html``, "/users/:id": (ctx) => html` ctx.params().id}>`, }, }, }); ``` -------------------------------- ### Providing Context with provide Source: https://github.com/codesan-git/sanify/blob/main/docs/rendering.md Use `provide` to supply a context value to a subtree. Consumers can access this value by walking up the owner chain. ```typescript const Theme = createContext<"light" | "dark">("light"); provide(Theme, "dark", () => html``); ``` -------------------------------- ### Resource Fetching with AbortController Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Shows how to integrate an AbortSignal into a resource fetcher to cancel stale requests. The framework passes the signal, which should be forwarded to the fetch API. This prevents race conditions and allows cancellation on key changes or component unmount. ```typescript const items = resource( (signal) => fetch(`/api/items?q=${q()}`, { signal }).then((r) => r.json()), { key: () => q() }, ); ``` -------------------------------- ### Navigation Methods Source: https://github.com/codesan-git/sanify/blob/main/docs/router.md Provides functions to programmatically control navigation within the application. ```APIDOC ## Navigation Functions ### Description Provides functions to programmatically control navigation within the application. ### Functions - **`navigate(to) => void`**: Push a new entry to history. - **`redirect(to) => void`**: Replace the current entry in history. - **`back() => void`**: Move backward through the history stack. - **`forward() => void`**: Move forward through the history stack. ### Parameters - **`to`**: `string` - The target path for navigation. ``` -------------------------------- ### Applying a Fade Transition Source: https://github.com/codesan-git/sanify/blob/main/docs/rendering.md Wraps reactive content with a fade transition. The 'fade' class is applied during enter and leave animations. Duration is set to 200ms. ```ts Transition( "fade", () => visible() ? html`
${() => content()}
` : null, { duration: 200 }, ); ``` -------------------------------- ### Create and Manage an Effect with Cleanup Source: https://github.com/codesan-git/sanify/blob/main/docs/reactivity.md An effect runs eagerly when created and re-runs when its dependencies change. Returning a function from the body registers it as cleanup, which runs before the next re-execution and on disposal. Dependencies are dynamic, and branches are automatically unsubscribed. ```typescript effect(() => { const value = source(); console.log(value); return () => console.log("cleanup", value); // optional cleanup }); ``` -------------------------------- ### Client Methods Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md The `Client` object provides methods for performing various HTTP operations. ```APIDOC ## Client Methods ### Description The `Client` object, returned by `createClient`, provides methods for performing various HTTP operations. ### Methods - `request(url: string, init?: RequestInit): Promise`: Makes a generic fetch request. - `get(url: string, init?: RequestInit): Promise`: Sends a GET request and parses the JSON response. - `post(url: string, body?: any, init?: RequestInit): Promise`: Sends a POST request with a JSON body and parses the JSON response. - `put(url: string, body?: any, init?: RequestInit): Promise`: Sends a PUT request with a JSON body and parses the JSON response. - `patch(url: string, body?: any, init?: RequestInit): Promise`: Sends a PATCH request with a JSON body and parses the JSON response. - `delete(url: string, init?: RequestInit): Promise`: Sends a DELETE request. ### Example ```ts const user = await api.get("/me"); const created = await api.post("/users", { name: "Sat" }); await api.delete(`/users/${id}`); ``` ``` -------------------------------- ### Solid-Style Context with Owner Chain Source: https://github.com/codesan-git/sanify/blob/main/docs/reactivity.md Provides and consumes context data by walking the owner's parent chain. Context survives re-renders due to persistent owners within boundaries like Suspense or ErrorBoundary. ```ts const ThemeCtx = createContext<"light" | "dark">("light"); // provider — see rendering docs for the `provide` directive provide(ThemeCtx, "dark", () => html``); // consumer (must run inside the provider's subtree) const theme = useContext(ThemeCtx); ``` -------------------------------- ### Enabling Debugging Features Source: https://github.com/codesan-git/sanify/blob/main/docs/reactivity.md Import and enable `__debug` from `@sanify/core` to activate global debugging tools. This provides access to runtime statistics and an owner tree snapshot for introspection. ```ts import { __debug } from "@sanify/core"; __debug.enable(); // installs globalThis.__sanify_debug too __debug.stats(); // { signals, effects, pendingEffects, rootOwners } __debug.ownerTree(); // OwnerNode[] tree snapshot ``` -------------------------------- ### Integration with Resource and Mutation Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Combine the API client with `resource` for data fetching and `mutation` for data modification. The client passes `AbortController.signal` for end-to-end network cancellation. ```typescript const api = createClient({ baseUrl: "/api", headers: () => authHeaders() }); const user = resource((signal) => api.get("/me", { signal }), { key: "me", staleTime: 60_000 }); const updateProfile = mutation( (input: ProfileEdit) => api.patch("/me", input), { invalidates: ["me"] }, ); ``` -------------------------------- ### CreateStore: Batching Writes with setState and produce Source: https://github.com/codesan-git/sanify/blob/main/docs/store.md Use `setState` with `produce` to batch multiple mutations into a single flush, ensuring downstream effects run only once. This is useful for complex updates or when dependent on the previous state. ```typescript setState(produce((draft) => { draft.user.name = "Satria"; draft.user.age = 27; draft.todos.push({ id: 2, text: "ship", done: false }); })); // one flush, one effect run ``` -------------------------------- ### Animated List Rendering with TransitionGroup Source: https://github.com/codesan-git/sanify/blob/main/docs/rendering.md Combine keyed list reconciliation with CSS animations for enter/leave transitions. Reordered items are moved without animation. ```typescript TransitionGroup( "list", () => todos(), (todo) => html`
  • ${() => todo().text}
  • `, { key: (t) => t.id, duration: 300 }, ); ``` -------------------------------- ### Sanify Store Creation and Usage Source: https://github.com/codesan-git/sanify/blob/main/README.md Creates a reactive store using Proxies for state management. Leaf properties become signals, updating only when accessed. Persisted store mirrors state to localStorage with optional sync. ```typescript import { createStore, produce, persisted } from "@sanify/core"; const [state, setState] = createStore({ user: { name: "Sat", age: 26 } }); // Idiomatic: assign directly to the proxy. state.user.name = "Satria"; state.user.age++; // `setState` is for batched bursts, updater functions, or programmatic paths. setState(produce((d) => { d.user.name = "X"; d.user.age = 30; })); const [theme, setTheme] = persisted("theme", "light", { sync: true }); ``` -------------------------------- ### Sanify Reactivity Primitives Source: https://github.com/codesan-git/sanify/blob/main/README.md Demonstrates the basic usage of signals, computed values, effects, and batching for reactive state management. Effects are automatically cleaned up when their owner scope is disposed. ```typescript import { signal, effect, computed, batch, untrack } from "@sanify/core"; const [count, setCount] = signal(0); const doubled = computed(() => count() * 2); effect(() => console.log(doubled())); // logs 0, then 2 after setCount(1) batch(() => { setCount(1); setCount(2); // effect only sees the final value, runs once }); ``` -------------------------------- ### Navigation Guards Source: https://github.com/codesan-git/sanify/blob/main/docs/router.md Demonstrates how to use navigation guards to protect routes. A guard function can return a redirect path or `undefined` to allow navigation. Guards are reactive and run in order. ```typescript router({ "/admin": { guard: (params) => (authed() ? undefined : "/login"), component: () => html``, }, }); ``` -------------------------------- ### CreateStore: Direct Property Mutation Source: https://github.com/codesan-git/sanify/blob/main/docs/store.md Demonstrates direct assignment for updating nested properties, array elements, and replacing entire subtrees within a createStore proxy. ```typescript state.user.name = "Satria"; // single leaf state.todos[0].done = true; // array index state.todos.push({ id: 2, text: "docs", done: false }); // array mutation state.user = { name: "Replaced", age: 0 }; // replace subtree ``` -------------------------------- ### Compose createStore with persisted for Reload Survival Source: https://github.com/codesan-git/sanify/blob/main/docs/store.md Use this pattern when you need a state tree that should survive page reloads. It mirrors the state from createStore into a persisted signal. Ensure to mirror only necessary leaves for large trees. ```typescript const [persistedState, setPersistedState] = persisted("app", { theme: "light" }); const [state, setState] = createStore(persistedState()); effect(() => setPersistedState(() => ({ ...state }))); ``` -------------------------------- ### Handling Asynchronous Loading with Suspense Source: https://github.com/codesan-git/sanify/blob/main/docs/rendering.md Use `Suspense` to display a fallback UI while asynchronous operations (resources) are loading. Children remain mounted and their effects continue to run. ```typescript Suspense( () => html`loading...`, () => html``, ); ``` -------------------------------- ### CSS Animations for TransitionGroup Source: https://github.com/codesan-git/sanify/blob/main/docs/rendering.md Define enter and leave animations using CSS classes and `@keyframes`. Animations are skipped if `prefers-reduced-motion` is enabled. ```css .list-enter { animation: fade-in 300ms ease; } .list-leave { animation: fade-out 300ms ease; } @keyframes fade-in { from { opacity: 0; transform: translateY(-8px); } } @keyframes fade-out { to { opacity: 0; transform: translateY(8px); } } ``` -------------------------------- ### Component Definition with Attribute Converters Source: https://github.com/codesan-git/sanify/blob/main/docs/rendering.md Defines a component 'x-foo' with attributes 'count' (number), 'active' (boolean), and 'data' (json). It demonstrates using built-in attribute shortcut converters. ```ts component({ "x-foo", setup, { attrs: { count: "number", active: "boolean", data: "json", // mix with custom converters when needed: // tags: (raw) => (raw ?? "").split(","), }, }, }); ``` -------------------------------- ### Route with Loader Source: https://github.com/codesan-git/sanify/blob/main/docs/router.md Define a route with a loader function to fetch data before rendering. The loader's result is available via `ctx.data()`. ```typescript router({ "/users/:id": { loader: async ({ id }) => fetch(`/api/users/${id}`).then((r) => r.json()), component: (ctx) => html` ctx.data()}> `, }, }); ``` -------------------------------- ### Resource with Staleness using staleTime Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Shows how to implement stale-while-revalidate behavior using the 'staleTime' option. The resource returns stale data synchronously while a background refresh occurs. ```typescript const user = resource( () => fetch(`/api/users/${id()}`).then((r) => r.json()), { key: () => `user:${id()}`, staleTime: 30000 }, // 30s ); ``` -------------------------------- ### Route Configuration Types Source: https://github.com/codesan-git/sanify/blob/main/docs/router.md Defines the types used for configuring routes, including layouts, components, children, guards, and loaders. ```APIDOC ## Route Configuration Types ### Description Defines the types used for configuring routes, including layouts, components, children, guards, and loaders. ### Types - **`RouteConfig`**: `{ layout?, component?, children?, guard?, loader? }` - **`layout`**: `(ctx: RouteContext) => TemplateResult` - Optional layout component. - **`component`**: `() => TemplateResult` - The route's component. - **`children`**: `Record TemplateResult)>` - Nested routes. - **`guard`**: `(params: RouteParams) => string | undefined | void` - Navigation guard. - **`loader`**: `(ctx: RouteContext) => Promise` - Data loader function. ### `RouteContext` Interface ```ts interface RouteContext { params: () => RouteParams; outlet: () => TemplateResult; data: () => unknown; } ``` #### `RouteContext` Fields - **`params()`**: Path params of the active route. - **`outlet()`**: The child route's rendered output. - **`data()`**: Resolved value from the route's `loader`. ``` -------------------------------- ### Sanify Router Configuration Source: https://github.com/codesan-git/sanify/blob/main/README.md Defines routes for client-side navigation, including dynamic parameters, nested routes with layouts, and a catch-all for not found pages. Layouts persist across child navigation and loader results are cached. ```typescript import { router, html, render } from "@sanify/core"; const view = router({ "/": () => html``, "/users/:id": { loader: ({ id }) => fetch(`/api/users/${id}`).then((r) => r.json()), component: (ctx) => html` ctx.data()}>`, }, "/dashboard": { layout: (ctx) => html`
    ${ctx.outlet}
    `, children: { "/": () => html``, "/reports": () => html``, }, }, "*": () => html``, }); render(html`
    ${view}
    `, document.body); ``` -------------------------------- ### Integrate Resources with Suspense Fallbacks Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Wrap resources within a Suspense boundary to display fallback UI during data fetching. Multiple resources within a boundary share a single pending counter. ```typescript Suspense( () => html``, () => html``, // contains a resource() inside setup ); ``` -------------------------------- ### Apply Dark Mode from Local Storage Source: https://github.com/codesan-git/sanify/blob/main/example/index.html This snippet attempts to retrieve a theme preference from local storage. If found and parsed, it toggles the 'dark' class on the document element accordingly. If an error occurs during retrieval or parsing, it defaults to applying the 'dark' class. ```javascript try { var t = localStorage.getItem("sanify-theme"); if (t) t = JSON.parse(t); document.documentElement.classList.toggle("dark", t !== "light"); } catch (e) { document.documentElement.classList.add("dark"); } ``` -------------------------------- ### Define Effect with Explicit Dependencies Source: https://github.com/codesan-git/sanify/blob/main/docs/reactivity.md The `on` function allows defining an effect with an explicit dependency list. The callback function runs untracked, and an optional cleanup function can be returned. The `defer: true` option skips the first run. ```typescript effect(on( () => userId(), (id, prev) => { fetchUser(id); // runs untracked — fetchUser internals don't add deps return () => abort(); }, { defer: true }, // optional: skip the first run, wait for the first change )); ``` -------------------------------- ### Lazy Loading Routes Source: https://github.com/codesan-git/sanify/blob/main/docs/router.md Enables code-splitting for routes using dynamic imports, providing a fallback component while the route loads. ```APIDOC ## `lazy` ### Description Code-split a route via dynamic import. ### Signature `lazy(loader, tag, fallback?)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters - **loader**: `() => Promise` - A function that returns a dynamic import. - **tag**: `string` - A tag or identifier for the lazy-loaded route. - **fallback?**: `TemplateResult` - An optional fallback template to render while loading. ``` -------------------------------- ### Enabling Sanify Debugging Source: https://github.com/codesan-git/sanify/blob/main/README.md Enables the debugging utilities for Sanify Core, providing access to runtime statistics and the owner tree. Debugging is opt-in to maintain zero runtime cost when disabled. ```typescript import { __debug } from "@sanify/core"; __debug.enable(); __debug.stats(); // { signals, effects, pendingEffects, rootOwners } __debug.ownerTree(); // OwnerNode[] tree snapshot ``` -------------------------------- ### Optimizing List Rendering with createSelector Source: https://github.com/codesan-git/sanify/blob/main/docs/reactivity.md Use `createSelector` to memoize computations based on reactive sources. It ensures that only consumers directly affected by a change re-run, significantly improving performance for large lists. ```ts const isSelected = createSelector(() => selectedId()); For(items, (item) => html`
  • (isSelected(item().id) ? "active" : "")}>...
  • `, { key: (it) => it.id }); ``` -------------------------------- ### Mutation for Data Writing and Cache Invalidation Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Illustrates how to use the `mutation` function to wrap write operations. It provides loading, error, and data states, and can invalidate cache keys upon successful completion. Use `mutate` to trigger the operation and `reset` to clear its state. ```typescript import { createForm, mutation, invalidate } from "@sanify/core"; const create = mutation( (input: NewUser) => api.post("/users", input), { invalidates: ["users:list"], onSuccess: (data) => navigate(`/users/${data.id}`), onError: (err) => console.error("create failed", err), }, ); const form = createForm({ initialValues: { name: "" }, onSubmit: async (values) => { try { await create.mutate(values); } catch { form.errors._submit = (create.error() as Error).message; } }, }); html``; ``` ```typescript create.reset(); // data → undefined, error → undefined, loading → false ``` -------------------------------- ### Managing Effect Lifetimes with Owners Source: https://github.com/codesan-git/sanify/blob/main/docs/reactivity.md Use `createOwner` to create a lifetime container for effects. Effects created within an active owner are attached to it and run their cleanup when the owner is disposed. `createRoot` is a convenience for setting up reactive scopes. ```ts const owner = createOwner(); runWithOwner(owner, () => { effect(() => /* ... */); }); // ... later owner.dispose(); // tears down the effect above ``` -------------------------------- ### Set Garbage Collection Time for Cache Entries Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Configure cache entries to automatically evict after a specified duration when no longer subscribed. Useful for long-running applications with many ephemeral keys. ```typescript const search = resource( (signal) => api.get(`/search?q=${q()}`, { signal }), { key: () => `search:${q()}`, gcTime: 5 * 60_000 }, // 5 minutes ); ``` -------------------------------- ### Resource with Disabled State using Undefined Key Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Illustrates how to disable a resource fetcher by returning 'undefined' from the 'key' getter. The resource resets to its initial state and does not fetch when the key is undefined. ```typescript const profile = resource( () => fetch(`/api/users/${userId()}`).then((r) => r.json()), { key: () => (loggedIn() ? `user:${userId()}` : undefined), initial: null, }, ); ``` -------------------------------- ### Reactive Headers with Signals Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Dynamically set headers, such as authorization tokens, by providing a function to the `headers` option. This function is executed per request, ensuring reactive values are always up-to-date. ```typescript const [token, setToken] = persisted("auth_token", null); const api = createClient({ baseUrl: "/api", headers: () => (token() ? { Authorization: `Bearer ${token()}` } : {}), }); ``` -------------------------------- ### Form Registration Object Structure Source: https://github.com/codesan-git/sanify/blob/main/docs/form.md Illustrates the structure of the object returned by `form.register(name)`. This object contains properties for the input's name, its reactive value, and event listeners for input and blur events. ```typescript form.register("email") // → FieldProps // { // name: "email", // ".value": () => form.values.email, // "@input": (e) => { form.values.email = e.target.value (or .checked / .valueAsNumber) }, // "@blur": () => { form.touched.email = true; (maybe validate) }, // } ``` -------------------------------- ### Interceptors (`before` and `after`) Source: https://github.com/codesan-git/sanify/blob/main/docs/resource.md Allows intercepting outgoing requests before they are sent and incoming responses before they are processed. ```APIDOC ## Interceptors (`before` / `after`) ### Description Interceptors allow you to hook into the request and response lifecycle. The `before` interceptor modifies outgoing requests, while the `after` interceptor processes incoming responses. ### `before` Interceptor - Type: `RequestInterceptor` (`(init: RequestInit, url: string) => RequestInit | Promise`) - Purpose: Modify request `init` options (headers, body, etc.) before the request is sent. ### `after` Interceptor - Type: `ResponseInterceptor` (`(res: Response, req: { url, init }) => unknown | Promise`) - Purpose: Process the `Response` object. Can transform the response, handle errors, or return custom data. ### Default `after` Behavior - If `res.ok` is false, throws an `HttpError`. - If `Content-Type` is `application/json`, parses the response as JSON. - If `status` is 204, returns `undefined`. - Otherwise, returns the response as text. ### Example ```ts const api = createClient({ before: async (init) => { // Attach correlation id, mutate body, sign request, etc. return { ...init, headers: { ...init.headers, "X-Trace-Id": uuid() } }; }, after: async (res, req) => { if (res.status === 401) { setToken(null); navigate("/login"); throw new HttpError(401, null, "session expired"); } if (!res.ok) throw new HttpError(res.status, await res.json().catch(() => null)); if (res.status === 204) return undefined; return res.json(); }, }); ``` ```