{"_id":"@burojs/hasura","_rev":"2-10b50d756c304d0f69ac4bfa95a9c3a0","name":"@burojs/hasura","dist-tags":{"latest":"0.4.0"},"versions":{"0.2.0":{"name":"@burojs/hasura","version":"0.2.0","license":"MIT","_id":"@burojs/hasura@0.2.0","maintainers":[{"name":"maksimkuznetsov","email":"mak.kooz@gmail.com"}],"dist":{"shasum":"ecc445cd9ba6b8291f8c58687f2f4a90ab28d77f","tarball":"https://registry.npmjs.org/@burojs/hasura/-/hasura-0.2.0.tgz","fileCount":9,"integrity":"sha512-FSoZIzNpyi8mjghmUsvYiacf+5NMnBWBVFMnJw+AYKmgf4IlV5gI/uMkSiNyf6TZJSreBQJpqphsC4jZXg1tnQ==","signatures":[{"sig":"MEQCIG3xX3L2W4/jSndI72w9EfcLQM8SkRTS+AGNEqwrGJXxAiAiWw6T20uKAcuxDziMJAS4pzO6M2Q1Ovb1fNQ93Qznug==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":351062},"main":"./dist/index.cjs","type":"module","_from":"file:burojs-hasura-0.2.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit","typecheck:tests":"tsc -p tsconfig.test.json"},"_npmUser":{"name":"maksimkuznetsov","email":"mak.kooz@gmail.com"},"_resolved":"/tmp/b1a65c305c4cf710d82f5a214a85397c/burojs-hasura-0.2.0.tgz","_integrity":"sha512-FSoZIzNpyi8mjghmUsvYiacf+5NMnBWBVFMnJw+AYKmgf4IlV5gI/uMkSiNyf6TZJSreBQJpqphsC4jZXg1tnQ==","_npmVersion":"10.9.8","description":"Buro: Hasura GraphQL data provider and live (subscription) provider","directories":{},"_nodeVersion":"22.23.2","dependencies":{"graphql-ws":"^6.1.0","@burojs/core":"0.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.0","vitest":"^2.1.0","typescript":"^5.5.0","@burojs/mock-kit":"0.2.0","@burojs/tooling-eslint":"0.0.0","@burojs/tooling-tsconfig":"0.0.0"},"_npmOperationalInternal":{"tmp":"tmp/hasura_0.2.0_1786820376209_0.7857798649067156","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"_id":"@burojs/hasura@0.4.0","dist":{"shasum":"6f2790087ac36c1835e5a908ba50b052053f1e91","tarball":"https://registry.npmjs.org/@burojs/hasura/-/hasura-0.4.0.tgz","fileCount":9,"integrity":"sha512-cNRi4pp28nLzfVdawTqWK3Bm72yAEDPz+PNZF3yxv6x9J3j5IbZ+W+ctQsgA1tcW5B1W6sTuH9A4UWEZTukGFg==","signatures":[{"sig":"MEQCIGWd2YU64ZRZtqARz08u1EaQroaSRMoZtiDf/M5MFv04AiAOhPXl9+hWB1nGhhjPG8ObrH5l9m/QB3EjOCrbD1jeLQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIF8zmd4ZuKoahHcuIT32tAszht+YKu0rW5mxX14BKNzVAiA23YhN8aQKr+2i+o+WQjypZ4zbJvSlfOGsptJjegDRlw=="}],"unpackedSize":445032},"main":"./dist/index.cjs","name":"@burojs/hasura","type":"module","_from":"file:burojs-hasura-0.4.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"license":"MIT","scripts":{"dev":"tsdown --watch","build":"tsdown","typecheck":"tsc --noEmit","typecheck:tests":"tsc -p tsconfig.test.json"},"version":"0.4.0","_npmUser":{"name":"maksimkuznetsov","email":"mak.kooz@gmail.com"},"_resolved":"/tmp/43feb281f91f8fe0ce36d06d5ac49166/burojs-hasura-0.4.0.tgz","_integrity":"sha512-cNRi4pp28nLzfVdawTqWK3Bm72yAEDPz+PNZF3yxv6x9J3j5IbZ+W+ctQsgA1tcW5B1W6sTuH9A4UWEZTukGFg==","_npmVersion":"10.9.8","description":"Buro: Hasura GraphQL data provider and live (subscription) provider","directories":{},"maintainers":[{"name":"maksimkuznetsov","email":"mak.kooz@gmail.com"}],"_nodeVersion":"22.23.2","dependencies":{"graphql-ws":"^6.1.0","@burojs/core":"0.4.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.0","typescript":"^5.5.0","@burojs/mock-kit":"0.4.0","@burojs/tooling-eslint":"0.0.0","@burojs/tooling-tsconfig":"0.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/hasura_0.4.0_1790626270169_0.9746310970205672"}}},"time":{"created":"2026-08-15T18:59:35.992Z","modified":"2026-09-28T20:11:10.575Z","0.2.0":"2026-08-15T18:59:36.407Z","0.4.0":"2026-09-28T20:11:10.408Z"},"license":"MIT","description":"Buro: Hasura GraphQL data provider and live (subscription) provider","maintainers":[{"name":"maksimkuznetsov","email":"mak.kooz@gmail.com"}],"readme":"# @burojs/hasura\n\nA [Hasura](https://hasura.io/) GraphQL data provider and live (subscription)\nprovider for [buro](https://github.com/buro/buro).\n\n## Install\n\n```bash\npnpm add @burojs/hasura @burojs/core\n```\n\n## Usage\n\n```ts\nimport { createHasuraDataProvider } from '@burojs/hasura';\n\nconst dataProvider = createHasuraDataProvider({\n  // Where to POST. Same-origin path, path-prefixed path, or absolute URL.\n  // Defaults to '/gql/v1/graphql'.\n  endpoint: '/gql/v1/graphql',\n  getToken: async () => session.jwt,\n  // X-Hasura-Role for resources that don't declare their own. Omit for none.\n  defaultRole: 'company-admin',\n  resources: {\n    orders: {\n      table: 'orders',\n      selectFields: ['id', 'code', 'status', 'created_at'],\n      pkType: 'uuid',\n      live: true,\n    },\n  },\n});\n```\n\n### `createHasuraDataProvider(config)`\n\nA `DataProvider` over Hasura's GraphQL API: `list` / `one` / `many` / `count`\nqueries, `createOne` / `updateOne` / `deleteOne` mutations, an opt-in\ncustom-operation registry, and a bundled live provider. Offset pagination only\n— cursor pagination is refused with a `capability` error.\n\nEverything the provider does is driven by the per-resource `ResourceMapping`\nregistry rather than by branching on resource names:\n\n| Mapping field | What it declares |\n|---|---|\n| `table`, `selectFields` | the Hasura table and the columns to select |\n| `pkField`, `pkType` | primary-key column name and its GraphQL scalar (`bigint` \\| `uuid` \\| `Int` \\| `String`) |\n| `byPkRoot` | the `<table>_by_pk` root name, or `false` when the role exposes only the collection root |\n| `aggregate`, `noAggregate` | whether a count root exists, and whether to pay for a count on every page |\n| `role` | per-resource `X-Hasura-Role` (`null` suppresses the header) |\n| `scopeFields` | which keys from `getScope()` this table actually has columns for |\n| `fixedFilter` | an always-on `where` fragment — several resource names over one table, each a different slice |\n| `int64Fields` | columns Hasura serialises as JSON strings (ClickHouse `int64`), whose filter values must be stringified |\n| `jsonbSpill` | route a flat form record into/out of JSONB `head` / `meta` columns |\n| `dataBody` | route a heavy array payload into/out of a 1:1 sibling table |\n| `live` | opt this resource into subscription-driven invalidation |\n\n#### `recoverAuth` fires on HTTP 401 — which is not how Hasura rejects a credential\n\n`config.recoverAuth` is consulted when a request comes back with HTTP **401**,\nand only then. The one capture in this repository of a real Hasura rejection —\n`packages/mock-kit/fixtures/hasura/error-unauthorized.json`, taken from a live\nbackend with a deliberately malformed bearer token — came back **HTTP 200 with\nan `invalid-jwt` errors envelope**. Replayed through this provider that\nsurfaces as `{ kind: 'protocol', code: 'graphql-error' }`, `recoverAuth` is\nnever called, and app logic keyed on `kind: 'auth'` never fires either.\n\nSo `recoverAuth` is useful for a deployment that really answers 401 (a proxy or\ngateway in front of Hasura); against Hasura's own rejection shape it is inert.\nThe behaviour is pinned by `tests/fixture-fidelity.test.ts`'s\n`error-unauthorized` case, and the package's existing 401-recovery tests use a\nfabricated 401 — they are not evidence about Hasura. Keying recovery on the\nerror envelope is a product decision this package has not taken.\n\nOptimistic concurrency is available per call: pass\n`providerOptions.ifUpdatedAt` on an update and the mutation switches to a\n`where`-guarded form, rejecting with `{ kind: 'conflict', code:\n'stale-record' }` when the row has moved on. Omit it (or pass `null`) and\nnothing changes.\n\n### `createHasuraLiveProvider(config)`\n\nThe subscription half, already wired in by `createHasuraDataProvider`. Exported\nseparately for callers that want it on its own. The socket carries only a\nchange SIGNAL — rows always come back through the regular descriptor query, so\nmapping and pagination stay single-sourced in the data provider.\n`hasuraWsUrl(endpoint)` (also exported) is the http(s) → ws(s) conversion. Two\nthings a caller has to know about it:\n\n- Its argument is the **full endpoint**, the same string you pass\n  `createHasuraDataProvider` — not a path prefix. It appends nothing. (The two\n  applications this package was distilled from had a same-named,\n  same-signature function that took a PREFIX and appended `/gql/v1/graphql`\n  itself; copying one of those call sites here yields a socket URL that\n  addresses nothing, and a socket that never connects does not throw. Pinned by\n  `tests/live-provider.test.ts`.)\n- The conversion needs a `window`. With none (SSR, or a non-browser import) the\n  endpoint is returned **unchanged** — an absolute `https://…` endpoint comes\n  back as `https://…`, not `wss://…`. A relative endpoint has no host to\n  resolve against there, so this is a real boundary, not just a scheme bug.\n\n## Provenance\n\nThis package was distilled from two independently-evolved copies of the same\nprovider that lived in two applications. The copies had drifted in exactly\nseven places; each resolution is documented in a comment next to the code it\naffects, saying which side it came from and whether it was a feature, a bug\nfix, or drift. Two defects that neither copy had fixed at the time were closed\nhere, each with a test that reddens without the fix; both were subsequently\nbackported into both applications, so the in-code notes describe the state this\npackage was distilled FROM, not the state of the repository today.\n\nNeither application is migrated onto this package and neither has been removed\nyet, so at present the repository holds three copies of this provider rather\nthan one. That is deliberate — the applications are leaving, so migrating them\nwould be wasted work — but the de-duplication this package exists for is not\ncomplete until they go.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}