{"id":19895415,"url":"https://github.com/razshare/sveltekit-sse","last_synced_at":"2025-05-16T07:06:27.872Z","repository":{"id":183924262,"uuid":"657108423","full_name":"razshare/sveltekit-sse","owner":"razshare","description":"Server Sent Events with SvelteKit","archived":false,"fork":false,"pushed_at":"2025-04-19T21:58:26.000Z","size":568,"stargazers_count":380,"open_issues_count":1,"forks_count":10,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-10T22:39:07.499Z","etag":null,"topics":["server-sent-events","sse","svelte","sveltekit"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/sveltekit-sse","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/razshare.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"razshare"}},"created_at":"2023-06-22T10:36:59.000Z","updated_at":"2025-05-05T07:01:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"f38efd79-4118-47fd-9a78-d6a63626effc","html_url":"https://github.com/razshare/sveltekit-sse","commit_stats":null,"previous_names":["tncrazvan/sveltekit-sse","razshare/sveltekit-sse"],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razshare%2Fsveltekit-sse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razshare%2Fsveltekit-sse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razshare%2Fsveltekit-sse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/razshare%2Fsveltekit-sse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/razshare","download_url":"https://codeload.github.com/razshare/sveltekit-sse/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254485063,"owners_count":22078767,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["server-sent-events","sse","svelte","sveltekit"],"created_at":"2024-11-12T18:36:37.226Z","updated_at":"2025-05-16T07:06:27.768Z","avatar_url":"https://github.com/razshare.png","language":"JavaScript","funding_links":["https://github.com/sponsors/razshare"],"categories":[],"sub_categories":[],"readme":"# SvelteKit SSE\n\nThis library provides an easy way to produce and consume server sent events.\n\nInstall with:\n\n```sh\nnpm i -D sveltekit-sse\n```\n\nProduce your server sent events with\n\n```js\n// src/routes/custom-event/+server.js\nimport { produce } from 'sveltekit-sse'\n\n/**\n * @param {number} milliseconds\n * @returns\n */\nfunction delay(milliseconds) {\n  return new Promise(function run(resolve) {\n    setTimeout(resolve, milliseconds)\n  })\n}\n\nexport function POST() {\n  return produce(async function start({ emit }) {\n      while (true) {\n        const {error} = emit('message', `the time is ${Date.now()}`)\n        if(error) {\n          return\n        }\n        await delay(1000)\n      }\n  })\n}\n```\n\nConsume them on your client with\n\n```svelte\n\u003cscript\u003e\n  // src/routes/+page.svelte\n  import { source } from 'sveltekit-sse'\n  const value = source('/custom-event').select('message')\n\u003c/script\u003e\n\n{$value}\n```\nYour application will render something like this\n\n![Peek 2024-05-28 03-03](https://github.com/razshare/sveltekit-sse/assets/6891346/215b0b26-fa21-4841-9b69-b28a4c1ec731)\n\n\n## Locking\n\nAll streams are locked server side by default, meaning the server will keep the connection alive indefinitely.\n\nThe locking mechanism is achieved through a `Writable\u003cbool\u003e`, which you can access from the `start` function.\n\n```js\nimport { produce } from 'sveltekit-sse'\nexport function POST() {\n  return produce(function start({ emit, lock }) {\n    emit('message', 'hello world')\n    setTimeout(function unlock() {\n      lock.set(false)\n    }, 2000)\n  })\n}\n```\n\nThe above code `emit`s the `hello world` string with the `message` event name and closes the stream after 2 seconds.\n\nYou should not send any more messages after invoking `lock.set(false)` otherwise your `emit` function will return an error.\\\nThe resulting error is wrapped in `Unsafe\u003cvoid\u003e`, which you can manage using conditionals\n\n```js\nlock.set(false)\nconst { error } = emit('message', 'I have a bad feeling about this...')\nif (error) {\n  console.error(error)\n  return\n}\n```\n\n## Stop\n\nYou can stop a connection and run code when a connection is stopped\n- by returning a function from `start()`\n  ```js\n  import { produce } from 'sveltekit-sse'\n  export function POST() {\n    return produce(function start({ emit, lock }) {\n      emit('message', 'hello')\n      return function cancel() {\n        console.log('Connection canceled.')\n      }\n    })\n  }\n  ```\n\n- or by setting `options::stop()` and calling `lock.set(false)`\n\n  ```js\n  import { produce } from 'sveltekit-sse'\n  export function POST() {\n    return produce(\n      function start({ emit, lock }) {\n        emit('message', 'hello')\n        lock.set(false)\n      },\n      {\n        stop() {\n          console.log('Connection stopped.')\n        },\n      },\n    )\n  }\n  ```\n\nBoth ways are valid.\n\n\u003e [!NOTE]\n\u003e In the second case, using `options::stop()`, your code will also \n\u003e run if the client itself cancels the connections.\n\n## Cleanup\n\nWhenever the client disconnects from the stream, the server will detect that event and trigger your [stop function](#stop).\\\nThis behavior has a delay of 30 seconds by default.\\\nThis is achieved through a ping mechanism, by periodically (_every 30 seconds by default_) sending a ping event to the client.\n\nYou can customize that ping event's time interval \n\n```js\nimport { produce } from 'sveltekit-sse'\nexport function POST() {\n  return produce(\n    function start() {\n      // Your emitters go here\n    },\n    { \n      ping: 4000, // Custom ping interval\n      stop(){\n        console.log(\"Client disconnected.\")\n      }\n    },\n  )\n}\n```\n\nYou can also forcefully detect disconnected clients by simply emitting any event to that client, you will get back an error if the client has disconnected.\n\nWhen that happens, you should stop your producer\n\n```js\nimport { produce } from 'sveltekit-sse'\nexport function POST() {\n  return produce(async function start({ emit }) {\n    await new Promise(function start(resolve){\n      someRemoteEvent(\"incoming-data\", function run(data){\n        const {error} = emit(\"data\", data)\n        if(error){\n          resolve(error)\n        }\n      })\n    })\n    return function stop(){\n      // Do your cleanup here\n      console.log(\"Client has disconnected.\")\n    }\n  })\n}\n```\n\n## Reconnect\n\nYou can reconnect to the stream whenever the stream closes\n\n```html\n\u003cscript\u003e\n  import { source } from 'sveltekit-sse'\n\n  const connection = source('/custom-event', {\n    close({ connect }) {\n      console.log('reconnecting...')\n      connect()\n    },\n  })\n\n  const data = connection.select('message')\n\n  setTimeout(function run() {\n    connection.close()\n  }, 3000)\n\u003c/script\u003e\n\n{$data}\n```\n\n## Custom Headers\n\nYou can apply custom headers to the request\n\n```html\n\u003cscript\u003e\n  import { source } from 'sveltekit-sse'\n\n  const connection = source('/event', {\n    options: {\n      headers: {\n        Authorization: 'Bearer ...',\n      },\n    },\n  })\n\n  const data = connection.select('message')\n\u003c/script\u003e\n\n{$data}\n```\n\n## Transform\n\nYou can transform the stream into any type of object you want by using `source::select::transform`.\n\nThe `transform` method receives a `string`, which is the value of the store.\n\nHere's an example how to use it.\n\n```html\n\u003cscript\u003e\n  import { source } from 'sveltekit-sse'\n\n  const connection = source('/custom-event')\n  const channel = connection.select('message')\n\n  const transformed = channel.transform(function run(data) {\n    return `transformed: ${data}`\n  })\n\n  $: console.log({ $transformed })\n\u003c/script\u003e\n```\n\n## Json\n\nYou can parse incoming messages from the source as json using `source::select::json`.\n\n```svelte\n\u003cscript\u003e\n  import { source } from 'sveltekit-sse'\n\n  const connection = source('/custom-event')\n  const json = connection.select('message').json(\n    function or({error, raw, previous}){\n      console.error(`Could not parse \"${raw}\" as json.`, error)\n      return previous  // This will be the new value of the store\n    }\n  )\n  $: console.log({$json})\n\u003c/svelte\u003e\n```\n\nWhen a parsing error occurs, `or` is invoked.\\\nWhatever this function returns will become the new value of the store, in the example above `previous`, which is the previous (valid) value of the store.\n\n## FAQ\n\n- How can I produce data from third party sources?\\\n  This library doesn't provide an direct answer to that question, but the road to an answer is short.\\\n  You will have to identify users using sessions (or some equivalent mechanism, for example actual database-backed accounts) and map them to references of their respective emitters.\\\n  For example using a global map\n  ```js\n  // src/lib/clients.js\n  /** @type {Map\u003cstring,(eventName:string,data:string)=\u003eimport('sveltekit-sse').Unsafe\u003cvoid,Error\u003e\u003e} */\n  export const clients = new Map()\n  ```\n  ```js\n  // src/routes/events/+server.js\n  import { clients } from '$lib/clients'\n\n  export function POST({ request }) {\n    return produce(\n      function start({ emit }) {\n        const sessionId = request.headers.get('session-id') ?? ''\n        if (!sessionId) {\n          return function stop() {\n            console.error('Client session id not found.')\n          }\n        }\n        // Map the session id to an emitter.\n        // This will also indicate to you that the client is \"online\".\n        clients.set(sessionId, emit)\n      },\n      {\n        // Client goes \"offline\", so remove the entry.\n        stop() {\n          const sessionId = request.headers.get('session-id') ?? ''\n          if (!sessionId) {\n            return\n          }\n          clients.delete(sessionId)\n        },\n      },\n    )\n  }\n  ```\n  Then you can access any of the clients' emitters from your third party data source, external to the original _POST_ method, as long as you have their session id.\n  ```js\n  // some-file.js\n  import { clients } from '$lib/clients'\n  clients.get('some-session-id').emit('message', 'hello')\n  ```\n  In a distributed system this mapping would have to happen externally, see next question.\n\n- Is this distributed systems friendly?\\\n  Yes - see https://github.com/razshare/sveltekit-sse/issues/50, https://github.com/razshare/sveltekit-sse/issues/49 , https://github.com/razshare/sveltekit-sse/issues/34 and https://github.com/razshare/sveltekit-sse/issues/16 .\n  \n  There is no special setup required for distributed systems for this library specifically, but you should always remember to avoid saving state directly on nodes as that memory is [transient](https://en.wikipedia.org/wiki/Transient_(computer_programming)).\n  \n  \n  To solve the transient memory issue you will need to abstract away the mapping between sessions and emitters described above.\\\n  For example you could use a [DBMS](https://en.wikipedia.org/wiki/Database), such as Postgre, that can emit events, then you just forward those events to your clients.\n\n  Here's an example using [pg-listen](https://github.com/andywer/pg-listen)\n\n  ```js\n  import { clients } from '$lib/clients'\n  import createSubscriber from \"pg-listen\"\n  import { databaseURL } from \"./config\"\n\n  // Accepts the same connection config object that the \"pg\" package would take\n  const subscriber = createSubscriber({ connectionString: databaseURL })\n\n  subscriber.notifications.on(\"my-channel\", (payload) =\u003e {\n    clients.get('some-session-id').emit('my-channel', payload.message)\n  })\n  ```\n\n  There's also this [guide on how to use Postgre Events](https://edernegrete.medium.com/psql-event-triggers-in-node-js-ec27a0ba9baa), which may be useful to you.\n\n## Other notes\n\n\u003e [!NOTE]\n\u003e\n\u003e 1. Multiple sources connecting to the same path will use the same cached connection.\n\u003e 2. When the readable store becomes inactive, meaning when the last subscriber unsubscribes from the store, the background connection is closed.\n\u003e 3. (Then) When the first subscription is issued to the store, the store will attempt to connect (again) to the server.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frazshare%2Fsveltekit-sse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frazshare%2Fsveltekit-sse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frazshare%2Fsveltekit-sse/lists"}