{"_id":"@api-platform/ld","name":"@api-platform/ld","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@api-platform/ld","version":"1.0.0","description":"Fetch Edge Side APIs","main":"ld.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","lint":"eslint ld.ts","tsc":"tsc"},"author":{"name":"soyuka"},"license":"MIT","devDependencies":{"@eslint/js":"^9.3.0","eslint":"^8.57.0","globals":"^15.3.0","typescript":"^5.4.5","typescript-eslint":"^7.10.0"},"dependencies":{"urlpattern-polyfill":"^10.0.0"},"homepage":"https://edge-side-api.rocks/linked-data","_id":"@api-platform/ld@1.0.0","gitHead":"8a37076fa58f0c0ed41b9ea03903d39f33ab3b8b","_nodeVersion":"22.6.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-ygWQfwg82HQKPx69hfvdI3vqFPoHnP45ZWEzlSV/N//vCNoB/Oh9HVNCM9IUpBnpllKyv3227C46wM30ihPZHQ==","shasum":"be2be4e9fbaba04a5ee08e91898e9cc75377ea28","tarball":"https://registry.npmjs.org/@api-platform/ld/-/ld-1.0.0.tgz","fileCount":5,"unpackedSize":10237,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDNHP72ncO19Kq3Jbxq86mt1FBx9m13l+OFfR7CyeHlMwIgLc/D8PI9SsPy2/abHtE+SzQcEYKJSvu7M55lnzFH9Ps="}]},"_npmUser":{"name":"dunglas","email":"dunglas@gmail.com"},"directories":{},"maintainers":[{"name":"dunglas","email":"dunglas@gmail.com"},{"name":"simperfit","email":"hamza.simperfit@gmail.com"},{"name":"mysiar","email":"psynowiec@gmail.com"},{"name":"meyerbaptiste","email":"baptiste.meyer@gmail.com"},{"name":"teohhanhui","email":"teohhanhui@gmail.com"},{"name":"soyuka","email":"soyuka@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/ld_1.0.0_1724155061114_0.2965727349987952"},"_hasShrinkwrap":false}},"time":{"created":"2024-08-20T11:57:41.026Z","1.0.0":"2024-08-20T11:57:41.278Z","modified":"2024-08-20T11:57:41.612Z"},"maintainers":[{"name":"dunglas","email":"dunglas@gmail.com"},{"name":"simperfit","email":"hamza.simperfit@gmail.com"},{"name":"mysiar","email":"psynowiec@gmail.com"},{"name":"meyerbaptiste","email":"baptiste.meyer@gmail.com"},{"name":"teohhanhui","email":"teohhanhui@gmail.com"},{"name":"soyuka","email":"soyuka@gmail.com"}],"description":"Fetch Edge Side APIs","homepage":"https://edge-side-api.rocks/linked-data","author":{"name":"soyuka"},"license":"MIT","readme":"# @api-platform/ld\n\nRich JSON formats such as JSON-LD use IRIs to reference embeded data. This library fetches the wanted *Linked Data* automatically.\n\nI have an API referencing books and their authors, `GET /books/1` returns:\n\n```json\n{\n      \"@id\": \"/books/1\",\n      \"@type\": [\"https://schema.org/Book\"],\n      \"title\": \"Hyperion\",\n      \"author\": \"https://localhost/authors/1\"\n}\n```\n\nThanks to `@api-platform/ld` you can load authors automatically when you need them:\n\n```javascript\nimport ld from '@api-platform/ld'\n\nconst pattern = new URLPattern(\"/authors/:id\", \"https://localhost\");\nconst books = await ld('/books', {\n    urlPattern: pattern,\n    onUpdate: (newBooks) => {\n        log()\n    }\n})\n\nfunction log() {\n    console.log(books.author?.name)\n}\n\nlog()\n```\n\n## Installation\n\n```shell\nnpm install @api-platform/ld\n```\n\n## Usage\n\nUse `ld` like [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and specify the [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) to match IRIs that are going to be fetched automatically:\n\n```javascript\nimport ld from '@api-platform/ld'\n\nawait ld('/books', {urlPattern: new URLPattern(\"/authors/:id\", \"https://localhost\")})\n```\n\nAvailable options:\n\n- `fetchFn` fetch function, defaults to `fetch().then((res) => res.json())`\n- `urlPattern` the url pattern filter \n- `relativeURIs` supports relative URIs (defaults to `true`)\n- `onUpdate: (root, options: { iri: string, data: any })` callback on data update\n- `onError` error callback on fetch errors\n\nURLPattern is available as a polyfil at https://www.npmjs.com/package/urlpattern-polyfill\n\n## Examples\n\n### Tanstack Query\n\n```javascript\nimport ld from \"@api-platform/ld\";\nimport {useEffect} from \"react\"\nimport {createRoot} from \"react-dom/client\"\nimport {\n  QueryClient,\n  useQuery,\n  QueryClientProvider,\n} from '@tanstack/react-query'\n\nconst queryClient = new QueryClient();\nconst pattern = new URLPattern(\"/(books|authors)/:id\", window.origin);\n\nfunction Books() {\n  const {isPending, error, data: books} = useQuery({\n    queryKey: ['/books'],\n    notifyOnChangeProps: 'all',\n    queryFn: ({queryKey}) => ld(queryKey, {\n      urlPattern: pattern,\n      onUpdate: (root, {iri, data}) => {\n        queryClient.setQueryData(queryKey, root)\n      }\n    })\n  })\n\n  if (isPending) return 'Loading...'\n  if (error) return 'An error has occurred: ' + error.message\n  return (\n    <ul>\n      {books.member.map(b => (<li data-testid=\"book\">{b?.title} - {b?.author?.name}</li>))}\n    </ul>\n  );\n}\n\nfunction App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <Books />\n    </QueryClientProvider>\n  )\n}\ncreateRoot(root).render(<App />)\n```\n\n### React\n\n```javascript\nimport { useState, useEffect } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport ld from \"@api-platform/ld\";\n\nfunction App() {\nconst pattern = new URLPattern(\"/(books|authors)/:id\", window.origin);\nconst [books, setBooks] = useState({});\n\nuseEffect(() => {\n  let ignore = false;\n  setBooks({});\n  ld('/books', {onUpdate: (books) => setBooks(books), urlPattern: pattern})\n  .then(books => {\n    if (!ignore) {\n      setBooks(books);\n    }\n  });\n  return () => {\n    ignore = true;\n  };\n}, []);\n\nreturn (\n  <ul>\n    {books.member?.map(b => (<li data-testid=\"book\">{b.title} - {b.author?.name}</li>))}\n  </ul>\n);\n}\n\nconst root = createRoot(document.getElementById(\"root\"));\nroot.render(<App />);\n```\n\n### Axios\n\n```javascript\nimport ld from \"@api-platform/ld\";\nconst pattern = new URLPattern(\"/(books|authors)/:id\", window.origin);\nconst list = document.getElementById('list')\n\nfunction onUpdate(books) {\n  const l = []\n  books.member.forEach((book) => {\n    const li = document.createElement('li')\n    li.dataset.testid = 'book'\n    li.innerText = `${book.title} - ${book.author?.name}`\n    l.push(li)\n  });\n  list.replaceChildren(...l)\n}\n\nld('/books', {urlPattern: pattern, onUpdate, fetchfn: (url, options) => axios.get(url)})\n  .then((books) => {\n    books.member.forEach((book) => {\n      const li = document.createElement('li')\n      li.dataset.testid = 'book'\n      li.innerText = `${book.title} - ${book.author?.name}`\n      list.appendChild(li)\n    });\n  })\n```\n\n### VanillaJS\n\n```javascript\nimport ld from \"@api-platform/ld\";\nconst pattern = new URLPattern(\"/(books|authors)/:id\", window.origin);\nconst list = document.getElementById('list')\n\nfunction onUpdate(books) {\n  const l = []\n  books.member.forEach((book) => {\n    const li = document.createElement('li')\n    li.dataset.testid = 'book'\n    li.innerText = `${book.title} - ${book.author?.name}`\n    l.push(li)\n  });\n  list.replaceChildren(...l)\n}\n\nld('/books', {urlPattern: pattern, onUpdate})\n  .then((books) => {\n    books.member.forEach((book) => {\n      const li = document.createElement('li')\n      li.dataset.testid = 'book'\n      li.innerText = `${book.title} - ${book.author?.name}`\n      list.appendChild(li)\n    });\n  })\n```\n\n### SWR\n\nExample of a SWR hook:\n\n```typescript\nimport ld, { LdOptions } from '@api-platform/ld'\nimport useSWR from 'swr'\nimport {useState} from 'react'\nimport type { SWRConfiguration, KeyedMutator } from 'swr'\n\nexport type fetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\nexport type Fetcher = (...args: any[]) => Promise<any>\nexport type onUpdateCallback<T> = (root: T, options: { iri: string, data: object }) => void;\n\nexport default function useSWRLd<T extends object>(url: string, fetcher: Fetcher, config: Partial<LdOptions<T>> & SWRConfiguration = {}) {\n  let cb: undefined | KeyedMutator<T> = undefined\n\n  // You may need to force re-rendering as the comparison algorithm of SWR does not work well when object keys are added\n  // another solution is to improve the compare function\n  const [, setRender] = useState(false)\n  const res = useSWR(\n    url,\n    (url: RequestInfo | URL, opts: RequestInit) =>\n      ld(url, {\n        ...opts,\n        fetchFn: fetcher,\n        urlPattern: config.urlPattern,\n        onUpdate: (root) => {\n          if (cb) {\n            cb(root, { optimisticData: root, revalidate: false })\n            setRender((s: boolean) => !s)\n          }\n        },\n        relativeURIs: config.relativeURIs,\n        onError: config.onError,\n      }),\n    config\n  );\n\n  cb = res.mutate\n  return res\n}\n```\n","readmeFilename":"README.md"}