{"_id":"@alvarolm/resilient","_rev":"3-f758fb547072abc090d463b09c85e4ee","name":"@alvarolm/resilient","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.0":{"name":"@alvarolm/resilient","version":"0.1.0","keywords":["datastar","sse","reconnection","resilience","fetch"],"author":{"name":"Alvaro Leiva M."},"license":"MIT","_id":"@alvarolm/resilient@0.1.0","maintainers":[{"name":"alvarolm","email":"alvaroflmiranda@gmail.com"}],"homepage":"https://github.com/alvarolm/datastar-resilient#readme","bugs":{"url":"https://github.com/alvarolm/datastar-resilient/issues"},"dist":{"shasum":"352f5fb2a665a18af9442d4f5238b002a9f97b59","tarball":"https://registry.npmjs.org/@alvarolm/resilient/-/resilient-0.1.0.tgz","fileCount":30,"integrity":"sha512-i4aIVh8MlvnZSH8oRa6NM568e993Wu0FEtXKO/MhjgVcwMwXXaQT9/YM7NpguHDOWsp03BRw7/bH2QW9SrwpXA==","signatures":[{"sig":"MEQCIENWD2/di/51RGvzkPHfu0oA1F8r1dDb0vANcdd4kaIwAiAS2OZgXPJnB3M7/dgW1tMlrZAbMEpz78QRKCUnGEaSUw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":467768},"main":"dist/resilient.js","type":"module","module":"dist/resilient.js","exports":{".":{"import":"./dist/resilient.js"}},"gitHead":"771ca6009eb77a9284991d89b0cebbbe2967e580","scripts":{"build":"./build.sh","clean":"rm -rf dist","build:watch":"./build.sh --watch"},"_npmUser":{"name":"alvarolm","email":"alvaroflmiranda@gmail.com"},"repository":{"url":"git+https://github.com/alvarolm/datastar-resilient.git","type":"git"},"_npmVersion":"11.3.0","description":"Automatic reconnection and resilience for Datastar SSE connections","directories":{"test":"test"},"_nodeVersion":"24.2.0","_hasShrinkwrap":false,"devDependencies":{"esbuild":"^0.24.2"},"_npmOperationalInternal":{"tmp":"tmp/resilient_0.1.0_1760069499407_0.5815438514999207","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@alvarolm/resilient","version":"0.1.2","keywords":["datastar","sse","reconnection","resilience","fetch"],"author":{"name":"Alvaro Leiva M."},"license":"MIT","_id":"@alvarolm/resilient@0.1.2","maintainers":[{"name":"alvarolm","email":"alvaroflmiranda@gmail.com"}],"homepage":"https://github.com/alvarolm/datastar-resilient#readme","bugs":{"url":"https://github.com/alvarolm/datastar-resilient/issues"},"dist":{"shasum":"f5f49013217cebeccd850fd2d425c5cde7ff1424","tarball":"https://registry.npmjs.org/@alvarolm/resilient/-/resilient-0.1.2.tgz","fileCount":31,"integrity":"sha512-VaSXQ8OMu4GY4SmvQxinrSfx0sBUXkHkYQm2Wzod59vGp2Q3dLyd6fFC/HM/mRJYPe6J+9BZki7XJA6UUzgMIA==","signatures":[{"sig":"MEQCIHdW3B9oc3Lue8sdd4NewY53WF338Y/W5u+4bVIuGreHAiBqNvIXu9LxjYp6nN56ajQLjD402zrUoH6gxhJBIKlDsg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":478993},"main":"dist/resilient.js","type":"module","module":"dist/resilient.js","exports":{".":{"import":"./dist/resilient.js"}},"gitHead":"884358269f9c1ab89bb2c5094bb05cada411e707","scripts":{"build":"./build.sh","clean":"rm -rf dist","build:watch":"./build.sh --watch"},"_npmUser":{"name":"alvarolm","email":"alvaroflmiranda@gmail.com"},"repository":{"url":"git+https://github.com/alvarolm/datastar-resilient.git","type":"git"},"_npmVersion":"11.3.0","description":"Automatic reconnection and resilience for Datastar SSE connections","directories":{"test":"test"},"_nodeVersion":"24.2.0","_hasShrinkwrap":false,"devDependencies":{"esbuild":"^0.24.2"},"_npmOperationalInternal":{"tmp":"tmp/resilient_0.1.2_1761237090306_0.7510777926913339","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@alvarolm/resilient","version":"0.1.3","description":"Automatic reconnection and resilience for Datastar SSE connections","keywords":["datastar","sse","reconnection","resilience","fetch"],"homepage":"https://github.com/alvarolm/datastar-resilient#readme","bugs":{"url":"https://github.com/alvarolm/datastar-resilient/issues"},"repository":{"type":"git","url":"git+https://github.com/alvarolm/datastar-resilient.git"},"license":"MIT","author":{"name":"Alvaro Leiva M."},"type":"module","exports":{".":{"import":"./dist/resilient.js"}},"main":"dist/resilient.js","directories":{"test":"test"},"scripts":{"build":"./build.sh","build:watch":"./build.sh --watch","clean":"rm -rf dist"},"devDependencies":{"esbuild":"^0.24.2"},"module":"dist/resilient.js","_id":"@alvarolm/resilient@0.1.3","gitHead":"33868d1913d5b7333135946b7aafd0457e8a415e","_nodeVersion":"24.2.0","_npmVersion":"11.3.0","dist":{"integrity":"sha512-e3IVvR+uUqzYQTaqo1Hp50ZcgXFJXtyBm63CVw9NlSvz7jZNjMfnB2Qn9+ZjjYkxUfdUAf8Vx8ArPgg1OcC+fg==","shasum":"64a731739ce3f7e5b78ed1a9233b4115e091695c","tarball":"https://registry.npmjs.org/@alvarolm/resilient/-/resilient-0.1.3.tgz","fileCount":31,"unpackedSize":479005,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHSy3uCZNAVm3+KqmnFopTY4I3K4MnAffAuWc3EsIoU8AiBxs0q6hYTdcI3DwmxREQVIrCIh0kGifPaSL5vQeUO5sg=="}]},"_npmUser":{"name":"alvarolm","email":"alvaroflmiranda@gmail.com"},"maintainers":[{"name":"alvarolm","email":"alvaroflmiranda@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/resilient_0.1.3_1761830018662_0.9882557441945954"},"_hasShrinkwrap":false}},"time":{"created":"2025-10-10T04:11:39.308Z","modified":"2025-10-30T13:13:39.090Z","0.1.0":"2025-10-10T04:11:39.658Z","0.1.2":"2025-10-23T16:31:30.557Z","0.1.3":"2025-10-30T13:13:38.898Z"},"bugs":{"url":"https://github.com/alvarolm/datastar-resilient/issues"},"author":{"name":"Alvaro Leiva M."},"license":"MIT","homepage":"https://github.com/alvarolm/datastar-resilient#readme","keywords":["datastar","sse","reconnection","resilience","fetch"],"repository":{"type":"git","url":"git+https://github.com/alvarolm/datastar-resilient.git"},"description":"Automatic reconnection and resilience for Datastar SSE connections","maintainers":[{"name":"alvarolm","email":"alvaroflmiranda@gmail.com"}],"readme":"[![npm version](https://img.shields.io/npm/v/@alvarolm/resilient.svg)](https://www.npmjs.com/package/@alvarolm/resilient)\n[![license](https://img.shields.io/npm/l/@alvarolm/resilient.svg)](https://github.com/alvarolm/datastar-resilient/blob/main/LICENSE)\n\n![thinkaboutit](thinkaboutit.png)\n\nI believe it's not the (web) app user's responsibility to take action if something breaks (It could be the connection or something else).\nAs developers, we should aim to provide resilient solutions that don't rely on the users or third parties.\nFor me, this is a must for every decent product or service that respects the end user.\n\n> [!CAUTION]\n> #### This is a new library looking for improvements. It may contain unexpected behaviors Contributions, opinions or feedback of any kind are more than welcome and greatly appreciated!\n>\n> #### This code was implemented with the aid of Claude Code and has not been actively tested.\n>\n> #### The original author of datastar, has stated this solution doesn't aligns with datastar, and it can be implemented solely with the datastar client.\n> #### So, I recommend you, to first attempt to overcome any need for this library.\n\n## Why \"Resilient\"?\n\n**\"Resilient\"** serves a vital function for those using Datastar:\n\n**Essential for:**\n- Environments with unstable connections\n- Applications running through a proxy or third-party managed infrastructure\n\n**Or even if you just need to:**\n- Keep connections active without requiring user intervention\n- Provide reliability guarantees\n\n**Improve your web application users' quality of life** by implementing mechanisms that allow you to:\n- Apply flexible reconnection policies\n- Monitor and manipulate server responses\n\n## Important Considerations\n\nYour server or intermediary servers (proxy, etc.) must consider the necessary resources to handle the persistent requests attempts and keep themselves healthy.\nCommercial providers of proxy services and \"cloud workers\" (like cloudflare) already implement rate limiting and other protections.\n\nBy default, with no custom **dataInterceptor** (see Configuration Options below), there is negligible performance overhead with low to medium volumes of Server-Sent Events (SSE). If your web application has high SSE throughput and custom data modification, there could be significant performance overhead.\n\n## Technical Overview\n\n**Resilient** works by intercepting `window.fetch` and coordinating with Datastar's action system to provide automatic reconnection for SSE connections, while also allowing you to transform and monitor SSE events and responses through a customizable stream transformation system.\n\n### Architecture\n\nThe library is modular and organized into separate concerns:\n\n1. **Datastar Integration** (`datastar.js`) - Datastar plugin, signal system for reactive connection state updates, and stream transformation utilities\n2. **Fetch Interceptor** (`interceptor.js`) - Overrides `window.fetch` to track request lifecycle, apply stream transformations, and coordinate with Retryer instances\n3. **Retryer** (`retryer.js`) - Manages reconnection logic with configurable backoff, tracks connection state, and provides request/response/data interceptor configuration\n4. **Shared Utilities** (`shared.js`) - Common utilities, data structures, and constants used across modules\n5. **Entry Point** (`index.js`) - Public API exports\n\n## Installation\n\n### NPM\n\n```bash\nnpm install @alvarolm/resilient\n```\n\n### CDN\n\nYou can also use the library directly from jsDelivr CDN:\n\n```html\n<script type=\"module\">\n  import { LoadDatastarPlugin } from \"https://cdn.jsdelivr.net/gh/alvarolm/datastar-resilient@0.1.2/dist/resilient.min.js\";\n  ...\n</script>\n```\n\n## Example Usage\n\n### Basic Setup\n\n**Note:** The examples below use `\"./dist/resilient.min.js\"` as the import path. Adjust the path based on your file structure, or use the CDN URL above.\n\n**Important:** This library aims to be compatible with the latest version of Datastar, currently **v1.0.0-RC.6**. If you're looking for support for v1.0.0-RC.5, please read [MIGRATION.md](MIGRATION.md).\n\n```html\n<script type=\"module\">\n  // IMPORTANT: Resilient must be imported first, to ensure the fetch interceptor\n  // is set up before Datastar initializes\n  import { LoadDatastarPlugin } from \"./dist/resilient.min.js\";\n  import { action, actions } from \"https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-RC.6/bundles/datastar.js\";\n\n  LoadDatastarPlugin({ action, actions });\n</script>\n\n<!-- Create Retryer inline using data-init with 'el' reference -->\n<!-- Note: Using Datastar signals for connection state (enableDatastarSignals option) -->\n<div data-signals=\"{isConnected: false}\"\n     data-init=\"new Resilient.Retryer(el, {debug:true, enableDatastarSignals: 'isConnected'})\"\n     data-on:connect=\"@get('/api/feed')\">\n\n  <!-- Connection status indicator -->\n  <div data-show=\"$isConnected !== 'connected'\"\n       style=\"padding: 10px; background: #fef3c7; border: 1px solid #f59e0b;\">\n    <span data-text=\"$isConnected === 'connecting' ? 'Connecting...' : 'Reconnecting to server...'\"></span>\n  </div>\n\n  <!-- Your content here -->\n</div>\n```\n\n### Using with Script Tags\n\nAlternatively, you can create the Retryer in a script block and use traditional events:\n\n```html\n<div id=\"my-feed\"\n     data-on:connect=\"@get('/api/feed')\">\n  <!-- SSE content -->\n</div>\n\n<script>\n  const element = document.getElementById('my-feed');\n  const retryer = new window.Resilient.Retryer(element, {\n    debug: true,\n    enableConnectionEvents: true,  // Enable event dispatching\n    backoffCalculator: (retryCount, lastStartTime, reconnections) => {\n      return Math.min(30000, 1000 * Math.pow(2, retryCount));\n    },\n    inactivityTimeoutMs: 30000\n  });\n\n  // Listen to events\n  element.addEventListener('connected', () => {\n    console.log('Connected to feed');\n  });\n</script>\n```\n\n### Configuration Options\n\n```javascript\nnew window.Resilient.Retryer(element, {\n  // Enable console logging (default: false)\n  debug: true,\n\n  // Custom backoff strategy\n  // Default: exponential backoff with 2 multiplier, capped at 30s\n  // For initial connection (reconnections === -1): max 3 attempts with 20ms delay\n  // Return false to stop retrying entirely\n  backoffCalculator: (retryCount, lastStartTime, reconnections) => {\n    // retryCount: consecutive retry attempts (starts at 0)\n    // lastStartTime: timestamp when the last request started\n    // reconnections: number of successful connections (-1 = initial connection, 0+ = reconnections)\n\n    // Example: limit initial connection to 5 attempts\n    if (reconnections === -1 && retryCount > 5) {\n      return false;  // Stop retrying\n    }\n    return Math.min(10000, 1000 * Math.pow(2, retryCount));\n  },\n\n  // Define failed requests (default: status >= 400, per Datastar convention)\n  // See: https://data-star.dev/essays/im_a_teapot/\n  isFailedRequest: (response) => {\n    return response.status >= 500;  // Only retry on server errors\n  },\n\n  // Inactivity timeout in ms (default: 0 = disabled)\n  // Reconnects if no SSE chunks have been received within this time\n  inactivityTimeoutMs: 30000,\n\n  // Enable connection lifecycle events (default: false)\n  // When true, dispatches 'connected' and 'disconnected' events\n  // Note: 'connect' event is always dispatched regardless of this setting\n  enableConnectionEvents: true,\n\n  // Enable Datastar signals for connection state (default: \"\" = disabled)\n  // Provide a signal key name to receive state updates\n  // Values: \"connecting\", \"connected\", \"disconnected\"\n  enableDatastarSignals: \"connectionState\",\n\n  // Request interceptor (default: null)\n  // Modify fetch requests before they execute\n  // Takes ({ resource, init }) and returns { resource, init }\n  requestInterceptor: ({ resource, init }) => {\n    // resource can be string, URL, or Request object\n    // init is the optional RequestInit\n    console.log('Request:', resource);\n    return { resource, init };\n  },\n\n  // Response interceptor (default: null)\n  // Modify Response object before it's returned to Datastar\n  // Takes ({ url, response }) and returns modified Response\n  responseInterceptor: ({ url, response }) => {\n    console.log('Response from:', url, response.status);\n    return response;\n  },\n\n  // Data interceptor (default: null)\n  // Modify streaming response data chunks\n  // Takes ({ url, response, chunk }) and returns modified chunk\n  // Chunk is a Uint8Array containing binary data\n  dataInterceptor: ({ url, response, chunk }) => {\n    // Example: log chunk size\n    console.log('Chunk size:', chunk.length);\n    return chunk;  // Return the chunk (optionally modified)\n  }\n});\n```\n\n### Stream Transformation\n\n```javascript\n// Intercept and log all SSE chunks using dataInterceptor option\n\nconst element = document.getElementById('my-feed');\nconst decoder = new TextDecoder();\n\nconst retryer = new window.Resilient.Retryer(element, {\n  dataInterceptor: ({ url, response, chunk }) => {\n    const text = decoder.decode(chunk, { stream: true });\n    console.log(`[${url}] Received:`, text);\n\n    // You have access to:\n    // - url: The fetch URL\n    // - response: The Response object\n    // - chunk: The current Uint8Array chunk\n\n    // Return the modified chunk (or original)\n    return chunk;\n  }\n});\n```\n\n### Event Handling\n\n**Important:** The `connected` and `disconnected` events are opt-in. You must enable them with `enableConnectionEvents: true`. The `connect` event is always dispatched.\n\n```javascript\nconst element = document.getElementById('my-feed');\n\n// Listen for connection attempts (always dispatched)\nelement.addEventListener('connect', () => {\n  console.log('Attempting to connect...');\n});\n\n// Create retryer with connected/disconnected events enabled\nconst retryer = new window.Resilient.Retryer(element, {\n  enableConnectionEvents: true  // Required for 'connected' and 'disconnected' events\n});\n\n// Listen for connection established (requires enableConnectionEvents: true)\nelement.addEventListener('connected', () => {\n  console.log('Connected to server');\n  // Update UI, hide loading indicators, etc.\n});\n\n// Listen for disconnection (requires enableConnectionEvents: true)\nelement.addEventListener('disconnected', () => {\n  console.log('Disconnected from server');\n  // Show reconnecting indicator, etc.\n});\n\n// Check connection status\nif (retryer.connected) {\n  console.log('Connected!');\n}\n```\n\n**Alternative: Using Datastar Signals**\n\nIf you prefer Datastar's reactive signals over events:\n\n```html\n<div data-signals=\"{connectionState: 'disconnected'}\"\n     data-init=\"new Resilient.Retryer(el, {enableDatastarSignals: 'connectionState'})\"\n     data-on:connect=\"@get('/api/feed')\">\n\n  <!-- Signal automatically updates with: \"connecting\", \"connected\", \"disconnected\" -->\n  <div data-show=\"$connectionState !== 'connected'\">\n    Connecting...\n  </div>\n</div>\n```\n\n### Cleanup\n\n```javascript\n// Destroy retryer when element is removed\nconst retryer = window.Resilient.GetRetryer(element);\nretryer?.destroy();\n```\n\n## API Reference\n\n### Window API\n\n**`window.Resilient.Retryer`**\n- Constructor class for creating Retryer instances\n- See Configuration Options section above\n\n**`window.Resilient.GetRetryer(element)`**\n- Retrieves the Retryer instance associated with a DOM element\n- Returns `Retryer | undefined`\n\n**`window.Resilient.SimpleBackoffCalculator`**\n- Factory function for creating configurable backoff calculators\n- See Module Exports section above for detailed documentation and examples\n\n### Module Exports\n\nThe following are available as named imports from `dist/resilient.min.js`:\n\n**`LoadDatastarPlugin({ action, actions })`**\n- Function to load the Datastar plugin\n- Must be called before Datastar initializes\n- Requires `action` and `actions` from Datastar v1.0.0-RC.6\n\n**`ToggleInterceptorLogging(enabled)`**\n- Function to enable/disable fetch interceptor logging\n- Pass `true` to enable, `false` to disable\n\n**`CONNECT_EVENT`**\n- Constant: `\"connect\"` - event name dispatched when reconnection is initiated\n- Always dispatched regardless of `enableConnectionEvents` setting\n\n**`CONNECTED_EVENT`**\n- Constant: `\"connected\"` - event name dispatched when connection is established\n- Requires `enableConnectionEvents: true` in Retryer options\n\n**`DISCONNECTED_EVENT`**\n- Constant: `\"disconnected\"` - event name dispatched when connection is lost\n- Requires `enableConnectionEvents: true` in Retryer options\n\n**`SIGNALS_CONNECTION_STATES`**\n- Object containing connection state values for Datastar signals\n- Values: `{ CONNECTING: \"connecting\", CONNECTED: \"connected\", DISCONNECTED: \"disconnected\" }`\n- Used with `enableDatastarSignals` option\n\n**`ContentType`**\n- Utility class for parsing Content-Type headers\n- Example:\n  ```javascript\n  import { ContentType } from \"./dist/resilient.min.js\";\n\n  const ct = new ContentType(\"text/html; charset=utf-8\");\n  console.log(ct.type);        // \"text\"\n  console.log(ct.subtype);     // \"html\"\n  console.log(ct.charset);     // \"utf-8\"\n  console.log(ct.isHTML);      // true\n  console.log(ct.isSSE);       // false\n  ```\n\n**`SimpleBackoffCalculator`**\n- Factory function that creates configurable exponential backoff calculator\n- Returns a backoff calculator function compatible with Retryer's `backoffCalculator` option\n- Configuration options:\n  - `maxInitialAttempts` (default: 3) - Maximum number of quick retries for initial connection\n  - `initialDelayMs` (default: 20) - Initial retry delay in milliseconds\n  - `maxDelayMs` (default: 30000) - Maximum delay cap in milliseconds\n  - `baseDelayMs` (default: 1000) - Base delay multiplier in milliseconds\n  - `baseMultiplier` (default: 2) - Base for exponential calculation\n- Example:\n  ```javascript\n  import { SimpleBackoffCalculator } from \"./dist/resilient.min.js\";\n\n  const customBackoff = SimpleBackoffCalculator({\n    maxInitialAttempts: 5,\n    initialDelayMs: 50,\n    maxDelayMs: 60000,\n    baseDelayMs: 2000,\n    baseMultiplier: 2\n  });\n\n  const retryer = new window.Resilient.Retryer(element, {\n    backoffCalculator: customBackoff\n  });\n  ```\n\n## Implementation Details\n\n### Initial Connection Logic\n\nWhen a Retryer is created, it immediately attempts to establish an initial connection:\n\n1. On initialization, calls `notifyRequestStopped()` which triggers reconnection logic\n2. Dispatches a `connect` event to trigger the Datastar action (e.g., `data-on:connect`)\n3. The default `backoffCalculator` handles initial connection attempts specially:\n   - When `reconnections === -1` (first connection), uses a short 20ms delay\n   - Limits initial attempts to 3 by default (configurable via custom backoffCalculator)\n   - Returns `false` to stop retrying if max attempts exceeded\n4. Once connected, `reconnections` counter increments to 0 and normal backoff applies\n\n**Stopping Initial Connection Attempts:**\n\nThe backoffCalculator can return `false` to stop all retry attempts:\n\n```javascript\nnew window.Resilient.Retryer(element, {\n  backoffCalculator: (retryCount, lastStartTime, reconnections) => {\n    // Stop after 5 initial connection attempts\n    if (reconnections === -1 && retryCount > 5) {\n      return false;  // Stops retrying entirely\n    }\n    return Math.min(30000, 1000 * Math.pow(2, retryCount));\n  }\n});\n```\n\n### Reconnection Logic\n\nAfter an established connection is lost:\n\n1. `notifyRequestStopped()` is called by the interceptor\n2. Retryer schedules a reconnect using `backoffCalculator`\n3. For reconnections (`reconnections > 0`), backoffCalculator typically uses exponential backoff\n4. After delay, dispatches `connect` event to retry\n5. Retry count increments on each consecutive failure\n6. Retry count resets to 0 once connection is re-established\n\n### Inactivity Detection\n\nWhen `inactivityTimeoutMs` is configured:\n\n1. Each SSE chunk updates `lastSSETime` timestamp\n2. On each new chunk, checks if time since last chunk exceeds timeout\n3. If timeout exceeded, aborts the request and schedules reconnect\n4. Uses the same reconnection logic as normal failures\n\n### AbortController Chain\n\nThe library properly handles abort signals:\n\n1. Creates a new AbortController for each request\n2. If the original request had a signal, forwards abort events to new controller\n3. Retryer can abort via its own controller (for failures or inactivity)\n4. Prevents double-abort by clearing controller reference after use\n\n### Datastar Integration\n\n**Important:** The Datastar plugin makes the following automatic modifications:\n\n1. **Disables Datastar's Retry**: Sets `retryMaxCount: 0` on all Datastar actions to disable Datastar's built-in retry mechanism. This ensures Resilient has complete control over reconnection logic, preventing conflicts between two retry systems.\n\n2. **Injects Fetch IDs**: Adds `X-Fetch-Id` headers to all fetch requests from elements with Retryer instances, enabling the fetch interceptor to associate requests with their originating elements.\n\n3. **Suppresses Errors**: Catches and suppresses Datastar's \"FetchFailed\" errors since Resilient handles reconnection automatically.\n\nThis happens automatically when the plugin is loaded - you don't need to configure anything. All Datastar actions (`$get`, `$post`, etc.) from elements with Retryers will be managed by Resilient.\n\n## Best Practices\n\n### Server-Side Considerations\n\n1. **Rate Limiting**: Implement rate limiting to prevent abuse from aggressive retry policies\n2. **Timeout Configuration**: Set appropriate server timeouts that align with `inactivityTimeoutMs`\n3. **Resource Management**: Monitor SSE connection counts and implement connection limits per client\n4. **Health Checks**: Use the inactivity timeout feature to detect and close stale connections\n\n### Client-Side Recommendations\n\n1. **Backoff Strategy**: Use exponential backoff with a reasonable cap (5-30 seconds) to avoid overwhelming the server. Optionally return `false` from backoffCalculator to stop retrying if needed.\n2. **Connection State UI**: Choose between events (`enableConnectionEvents`) or Datastar signals (`enableDatastarSignals`) based on your needs. Signals integrate seamlessly with Datastar's reactivity, while events provide more control for non-Datastar code.\n3. **Debug Mode**: Enable debug mode during development to understand connection behavior. Use `ToggleInterceptorLogging(true)` for detailed fetch interceptor logs.\n4. **Cleanup**: Always call `retryer.destroy()` when removing elements from the DOM to prevent memory leaks.\n5. **Data Interceptor**: Keep dataInterceptor logic lightweight to minimize performance impact on high-throughput SSE streams.\n\n### Common Patterns\n\n**Auto-reconnecting feed with connection state UI:**\n```javascript\nconst retryer = new window.Resilient.Retryer(element, {\n  enableDatastarSignals: 'feedStatus',  // Use Datastar signals for UI\n  backoffCalculator: (retryCount, lastStartTime, reconnections) => {\n    // Stop initial connection after 3 attempts\n    if (reconnections === -1 && retryCount > 3) {\n      return false;\n    }\n    // Exponential backoff for reconnections, capped at 30s\n    return Math.min(30000, 1000 * Math.pow(2, retryCount));\n  },\n  inactivityTimeoutMs: 60000  // 1 minute timeout\n});\n```\n\n```html\n<!-- In your HTML -->\n<div data-signals=\"{feedStatus: 'disconnected'}\"\n     data-init=\"/* create retryer with enableDatastarSignals: 'feedStatus' */\">\n\n  <div data-show=\"$feedStatus === 'connecting'\">\n    Connecting to feed...\n  </div>\n\n  <div data-show=\"$feedStatus === 'disconnected'\">\n    Unable to connect. Please refresh the page.\n  </div>\n\n  <div data-show=\"$feedStatus === 'connected'\">\n    <!-- Your feed content -->\n  </div>\n</div>\n```\n\n**Using traditional events for fine-grained control:**\n```javascript\nconst retryer = new window.Resilient.Retryer(element, {\n  debug: true,\n  enableConnectionEvents: true  // Enable events\n});\n\nelement.addEventListener('connected', () => {\n  console.log('Feed connected');\n  // Update external UI, analytics, etc.\n});\n\nelement.addEventListener('disconnected', () => {\n  console.log('Feed disconnected');\n});\n```\n\n**Development debugging:**\n```javascript\nimport { ToggleInterceptorLogging } from \"./dist/resilient.min.js\";\n\n// Enable all logging in development\nToggleInterceptorLogging(true);\n\nconst retryer = new window.Resilient.Retryer(element, {\n  debug: true\n});\n```\n\n## Running the Test Server\n\n### Quick Start\n\nFrom the resilient directory:\n\n```bash\n./start-test-server.sh\n```\n\nThe script will:\n- ✅ Start the Go test server on http://localhost:8080\n- ✅ Serve source files directly from `/src` (no bundling required!)\n- ✅ Changes to source files take effect immediately - just refresh your browser!\n\n\nSee [test/README.md](test/README.md) for detailed test scenarios and documentation.\n","readmeFilename":"README.md"}