JSPM

  • Created
  • Published
  • Downloads 4267
  • Score
    100M100P100Q120277F
  • License MIT

Full type-safe RPC library for service worker -- move things off of the UI thread with ease!

Package Exports

  • swarpc
  • swarpc/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (swarpc) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

sw&rpc

RPC for Service Workers -- move that heavy computation off of your UI thread!


Installation

npm add swarpc arktype

Usage

1. Declare your procedures in a shared file

import type { ProceduresMap } from "swarpc"
import { type } from "arktype"

export const procedures = {
  searchIMDb: {
    // Input for the procedure
    input: type({ query: "string", "pageSize?": "number" }),
    // Function to be called whenever you can update progress while the procedure is running -- long computations are a first-class concern here. Examples include using the fetch-progress NPM package.
    progress: type({ transferred: "number", total: "number" }),
    // Output of a successful procedure call
    success: type({
      id: "string",
      primary_title: "string",
      genres: "string[]",
    }).array(),
  },
} as const satisfies ProceduresMap

2. Register your procedures in the service worker

In your service worker file:

import fetchProgress from "fetch-progress"
import { Server } from "swarpc"
import { procedures } from "./procedures.js"

// 1. Give yourself a server instance
const swarpc = Server(procedures)

// 2. Implement your procedures
swarpc.searchIMDb(async ({ query, pageSize = 10 }, onProgress) => {
  const queryParams = new URLSearchParams({
    page_size: pageSize.toString(),
    query,
  })

  return fetch(`https://rest.imdbapi.dev/v2/search/titles?${queryParams}`)
    .then(fetchProgress({ onProgress }))
    .then((response) => response.json())
    .then(({ titles } => titles)
})

// ...

// 3. Start the event listener
swarpc.start(self)

3. Call your procedures from the client

Here's a Svelte example!

<script>
    import { Client } from "swarpc"
    import { procedures } from "./procedures.js"

    const swarpc = Client(procedures)

    let query = $state("")
    let results = $state([])
    let progress = $state(0)
</script>

<search>
    <input type="text" bind:value={query} placeholder="Search IMDb" />
    <button onclick={async () => {
        results = await swarpc.searchIMDb({ query }, (p) => {
            progress = p.transferred / p.total
        })
    }}>
        Search
    </button>
</search>

{#if progress > 0 && progress < 1}
    <progress value={progress} max="1" />
{/if}

<ul>
    {#each results as { id, primary_title, genres } (id)}
        <li>{primary_title} - {genres.join(", ")}</li>
    {/each}
</ul>

Make cancelable requests

Implementation

To make your procedures meaningfully cancelable, you have to make use of the AbortSignal API. This is passed as a third argument when implementing your procedures:

server.searchIMDb(async ({ query }, onProgress, abort) => {
  // If you're doing heavy computation without fetch:
  let aborted = false
  abort?.addEventListener("abort", () => {
    aborted = true
  })

  // Use `aborted` to check if the request was canceled within your hot loop
  for (...) {
    /* here */ if (aborted) return
    ...
  }

  // When using fetch:
  await fetch(..., { signal: abort })
})

Call sites

Instead of calling await client.myProcedure() directly, call client.myProcedure.cancelable(). You'll get back an object with

  • async cancel(reason): a function to cancel the request
  • request: a Promise that resolves to the result of the procedure call. await it to wait for the request to finish.

Example:

// Normal call:
const result = await swarpc.searchIMDb({ query })

// Cancelable call:
const { request, cancel } = swarpc.searchIMDb.cancelable({ query })
setTimeout(() => cancel().then(() => console.warn("Took too long!!")), 5_000)
await request