@haddowg/json-api-query 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/dist/index.cjs +836 -0
- package/dist/index.d.cts +438 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +438 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +810 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["resourceKey"],"sources":["../src/keys.ts","../src/normalize.ts","../src/install.ts","../src/mutate.ts","../src/read.ts"],"sourcesContent":["/**\n * The deterministic query-key factory (CONTEXT.md \"Cache & normalization (TanStack layer)\").\n *\n * A key is a stable, hierarchical readonly tuple `[type, operation, ...id?, ...rel?, params?]`.\n * The hierarchy is the contract the normalization layer + targeted invalidation lean on: a\n * prefix of a key matches every key beneath it (TanStack's `queryKey` matching is prefix-based),\n * so `[type]` invalidates a whole type's subtree, `[type, operation]` an operation's subtree, and\n * `[type, operation, id]` a single resource's reads. Params ride the LAST segment as a normalised\n * object so two semantically-equal queries (keys reordered, equal values) share one key.\n */\n\n/** The read operations a key can address (mirrors the client's per-operation path templates). */\nexport type ReadOperation = 'fetchMany' | 'fetchOne' | 'fetchRelated' | 'fetchRelationship'\n\n/**\n * A normalised query-parameter value: JSON-ish, with object keys sorted recursively so equal\n * params produce an identical (deeply-equal, stably-ordered) value regardless of authoring order.\n */\nexport type NormalizedParams =\n | string\n | number\n | boolean\n | null\n | readonly NormalizedParams[]\n | { readonly [key: string]: NormalizedParams }\n\n/** One segment of a query key: the type/operation/id/rel strings, or the trailing params object. */\nexport type QueryKeyPart = string | { readonly [key: string]: NormalizedParams }\n\n/** A query key: the stable readonly tuple TanStack stores results under. */\nexport type QueryKey = readonly QueryKeyPart[]\n\n/**\n * Recursively normalise a params value so semantically-equal params serialise identically:\n * object keys are sorted; arrays keep order (order is meaningful in a list — `sort`, `include`);\n * `undefined` members are dropped (an absent param == an unset one). Primitives pass through.\n * Not JSON.stringify — the key stays a structured tuple so TanStack's structural matching and\n * the devtools see real values, not an opaque string.\n */\nexport function normalizeParams(value: unknown): NormalizedParams {\n if (value === null || value === undefined) {\n return null\n }\n if (Array.isArray(value)) {\n return value.map((v) => normalizeParams(v))\n }\n if (typeof value === 'object') {\n const out: Record<string, NormalizedParams> = {}\n // `Object.keys` returns a fresh array; sorting it in place is safe (no shared array is\n // mutated) — and `Array#toSorted` is ES2023, outside the ES2022 lib this package targets.\n // oxlint-disable-next-line no-array-sort\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n const member = (value as Record<string, unknown>)[key]\n if (member !== undefined) {\n out[key] = normalizeParams(member)\n }\n }\n return out\n }\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return value\n }\n // Anything else (function, symbol, bigint) is not a legitimate query param; coerce to string so\n // the key stays serialisable rather than throwing at the cache boundary.\n return String(value)\n}\n\n/** The addressing of a key, before params: type + operation, plus the id/rel an op needs. */\nexport interface KeyTarget {\n type: string\n operation: ReadOperation\n /** Present for resource-scoped ops (`fetchOne`/`fetchRelated`/`fetchRelationship`). */\n id?: string\n /** Present for relation ops (`fetchRelated`/`fetchRelationship`). */\n rel?: string\n}\n\n/**\n * Build a deterministic, hierarchical query key from `(type, operation, id?, rel?, params?)`.\n * The fixed-position prefix (`type`, `operation`, then `id`, then `rel`) makes any ancestor a\n * valid invalidation target; params (when present and non-empty) ride a single trailing\n * normalised object so reorderings collapse to one key. An absent/empty params object adds no\n * trailing segment, so a bare `list()` and `list({})` share a key.\n */\nexport function keyFor(target: KeyTarget, params?: unknown): QueryKey {\n const parts: QueryKeyPart[] = [target.type, target.operation]\n if (target.id !== undefined) {\n parts.push(target.id)\n }\n if (target.rel !== undefined) {\n parts.push(target.rel)\n }\n if (params !== undefined) {\n const normalized = normalizeParams(params)\n // Only append a params segment when it carries something — an empty object/array adds no\n // distinguishing information, so dropping it unifies `list()` with `list({})`.\n if (!isEmptyNormalized(normalized)) {\n parts.push(normalized as { readonly [key: string]: NormalizedParams })\n }\n }\n return parts\n}\n\n/** True when a normalised params value carries no information (an empty object or array, or null). */\nfunction isEmptyNormalized(value: NormalizedParams): boolean {\n if (value === null) {\n return true\n }\n if (Array.isArray(value)) {\n return value.length === 0\n }\n if (typeof value === 'object') {\n return Object.keys(value).length === 0\n }\n return false\n}\n\n// ── Hierarchical prefixes (targeted invalidation) ─────────────────────────────────────────\n\n/** The whole-type subtree prefix (`[type]`) — invalidates every operation/read of a type. */\nexport function typeKey(type: string): readonly [string] {\n return [type]\n}\n\n/** An operation subtree prefix (`[type, operation]`) — e.g. every `fetchMany` (list) of a type. */\nexport function operationKey(type: string, operation: ReadOperation): readonly [string, string] {\n return [type, operation]\n}\n\n/**\n * A single-resource subtree prefix (`[type, 'fetchOne', id]`) — matches that resource's reads.\n * Pairs with {@link keyFor} (which places `id` at the same position), so it is a true prefix of\n * every `fetchOne` key for the id regardless of its query params.\n */\nexport function resourceKey(type: string, id: string): readonly [string, string, string] {\n return [type, 'fetchOne', id]\n}\n\n/**\n * A single-relation read prefix (`[type, operation, id, rel]`) for `fetchRelated` /\n * `fetchRelationship` — matches one parent's reads of one relation, regardless of query params.\n * Pairs with {@link keyFor} (which places `id` then `rel` at fixed positions), so it is a true\n * prefix of every related/relationship key for that `(id, rel)`. Used for TARGETED invalidation of\n * a parent's relation after a relationship mutation (never the type's collection lists — ADR 0003).\n */\nexport function relationKey(\n type: string,\n operation: 'fetchRelated' | 'fetchRelationship',\n id: string,\n rel: string,\n): readonly [string, string, string, string] {\n return [type, operation, id, rel]\n}\n\n/**\n * BOTH read prefixes for one parent's one relation — its related-collection reads\n * (`[type, 'fetchRelated', id, rel]`) AND its relationship-linkage reads\n * (`[type, 'fetchRelationship', id, rel]`). A relation is read over two surfaces, so a single\n * prefix can't cover both; this returns the pair so an app targets every cached page of a\n * `(parent, relation)` — for invalidation or an optimistic membership patch — WITHOUT\n * reconstructing keys (or coupling to the page params a read used). PUBLIC: it is the key helper\n * the relationship-mutation optimistic path and apps share (D35b).\n */\nexport function relationReadKeys(\n type: string,\n id: string,\n rel: string,\n): readonly [readonly [string, string, string, string], readonly [string, string, string, string]] {\n return [\n relationKey(type, 'fetchRelated', id, rel),\n relationKey(type, 'fetchRelationship', id, rel),\n ]\n}\n\n/**\n * True when `key` is prefixed by `prefix` (segment-by-segment string equality over the prefix's\n * length). The cache-walking optimistic/invalidation paths use it to find every page variant of a\n * `(parent, relation)` read (whose params ride the key's trailing segment). Mirrors TanStack's own\n * prefix-based `queryKey` matching so a targeted patch and TanStack's `invalidateQueries` agree.\n */\nexport function keyHasPrefix(key: QueryKey, prefix: QueryKey): boolean {\n if (key.length < prefix.length) {\n return false\n }\n for (let i = 0; i < prefix.length; i++) {\n if (key[i] !== prefix[i]) {\n return false\n }\n }\n return true\n}\n","/**\n * Strategy-2 normalization: bespoke, dep-free `type:id` write-through patching (ADR 0003).\n *\n * TanStack Query is not a normalized cache — it keeps each result denormalized under its\n * query key. JSON:API's guarantee that every resource carries `type`+`id` lets us normalize\n * with zero configuration: on every successful response we walk the materialised graph\n * (primary `data` + every hydrated/included nested resource), collect each resource's fresh\n * attributes by `type:id`, and PATCH every cached query that contains that `type:id` IN PLACE\n * — \"edit once, updates everywhere\".\n *\n * A patch replaces a node's ATTRIBUTES (the descriptor distinguishes attributes from\n * relations) while PRESERVING the materialised object's edge-local `$pivot`/`$edge`, its\n * `$`-accessors, and its identity-by-`type:id`. The same Track in two playlists carries\n * different per-edge pivot data, so we never merge one edge's data onto another — only the\n * shared node's attributes change, and those live as own enumerable props that we overwrite\n * key-by-key, leaving the non-enumerable per-edge getters untouched.\n *\n * This only covers UPDATES to existing resources (a node patch). Creates/deletes change\n * collection membership, which is not a node patch — those invalidate the relevant list/\n * relationship queries instead (see ./mutate.ts).\n */\nimport type { ApiDescriptor } from '@haddowg/json-api-client'\nimport type { QueryClient } from '@tanstack/query-core'\nimport { keyHasPrefix, type QueryKey, relationReadKeys } from './keys'\n\n/** A `type:id` cache key for a resource. */\ntype ResourceKey = string\n\nconst resourceKey = (type: string, id: string): ResourceKey => `${type}:${id}`\n\nconst isObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v)\n\n/**\n * The raw JSON:API resource object a materialised resource exposes via its non-enumerable\n * `$raw` accessor — the authoritative split of attributes (which a patch replaces) from\n * relationships (which a patch leaves alone).\n */\ninterface RawResource {\n type?: unknown\n id?: unknown\n attributes?: Record<string, unknown>\n}\n\n/**\n * A materialised resource object: own enumerable `type`/`id`/attributes/relations plus a\n * non-enumerable `$raw` accessor. We treat both the primary data and every nested hydrated\n * member as one of these (a bare identifier view — `{type,id}` with no `$raw.attributes` —\n * carries no attributes to patch, so it is harmlessly skipped).\n */\ninterface MaterialisedResource {\n type: unknown\n id: unknown\n readonly $raw?: RawResource\n}\n\n/** A materialised resource's `type`+`id`, when it is a real resource (string type + id). */\nfunction identity(node: MaterialisedResource): { type: string; id: string } | undefined {\n const { type, id } = node\n if (typeof type === 'string' && typeof id === 'string') {\n return { type, id }\n }\n return undefined\n}\n\n/**\n * The freshest attributes for a node, read from its `$raw.attributes` (the wire split of\n * attributes from relations). Returns `undefined` when the node carries no `$raw` or no\n * attributes block (a bare identifier view, or an attribute-less resource) — nothing to\n * propagate. Never falls back to enumerating own props, which would conflate relation slots\n * with attributes.\n */\nfunction attributesOf(node: MaterialisedResource): Record<string, unknown> | undefined {\n const attrs = node.$raw?.attributes\n return isObject(attrs) ? attrs : undefined\n}\n\n/**\n * Walk a materialised value (a single resource, an augmented array, a to-one/to-many relation\n * value, or `null`/`undefined`) and visit every reachable resource exactly once. Recurses into\n * each resource's own enumerable relation slots so a deeply-included graph is fully indexed.\n * `seen` guards relation cycles (a node memoised by reference) and keeps the walk linear.\n */\nfunction walkResources(\n value: unknown,\n descriptor: ApiDescriptor,\n visit: (node: MaterialisedResource) => void,\n seen: Set<object> = new Set(),\n): void {\n if (value === null || value === undefined) {\n return\n }\n if (Array.isArray(value)) {\n for (const member of value) {\n walkResources(member, descriptor, visit, seen)\n }\n return\n }\n if (!isObject(value)) {\n return\n }\n if (seen.has(value)) {\n return\n }\n seen.add(value)\n\n const node = value as unknown as MaterialisedResource\n const ident = identity(node)\n if (ident === undefined) {\n return\n }\n visit(node)\n\n // Recurse into declared relation slots only — every other own enumerable prop is an\n // attribute (a scalar/array/plain object), never a materialised resource to index.\n const relations = descriptor[ident.type]?.relations\n if (relations === undefined) {\n return\n }\n for (const name of Object.keys(relations)) {\n const slot = (value as Record<string, unknown>)[name]\n if (slot !== undefined) {\n walkResources(slot, descriptor, visit, seen)\n }\n }\n}\n\n/**\n * Patch one materialised node IN PLACE from `attributes`: overwrite each attribute key on the\n * target's own enumerable props (relations and `type`/`id` are left untouched), and shallow-set\n * any new keys. The non-enumerable `$`-accessors and the per-edge `$pivot`/`$edge` getters are\n * defined separately (closing over the original wire member), so they survive unchanged — a\n * per-edge view keeps its own pivot while its shared attributes update.\n *\n * `$raw` is intentionally NOT mutated: it is the immutable original wire object (the escape\n * hatch). Only the flattened, own-enumerable attribute props — the values consumers read —\n * are patched. Returns whether any value actually changed, so an idempotent re-application\n * (e.g. installNormalization re-running after a factory's own `onSuccess`) reports no change and\n * is not written back / re-notified.\n */\nfunction patchNode(target: Record<string, unknown>, attributes: Record<string, unknown>): boolean {\n let changed = false\n for (const [k, v] of Object.entries(attributes)) {\n if (!(k in target) || target[k] !== v) {\n target[k] = v\n changed = true\n }\n }\n return changed\n}\n\n/**\n * Index every resource in `result` by `type:id` → its freshest attributes, then return the\n * set of `type:id`s the result carries. The caller patches every cached query holding any of\n * those keys (see {@link normalize}).\n */\nfunction collectAttributes(\n result: unknown,\n descriptor: ApiDescriptor,\n): Map<ResourceKey, Record<string, unknown>> {\n const fresh = new Map<ResourceKey, Record<string, unknown>>()\n walkResources(result, descriptor, (node) => {\n const ident = identity(node)\n if (ident === undefined) {\n return\n }\n const attributes = attributesOf(node)\n if (attributes !== undefined) {\n // Last write wins: a later occurrence (e.g. the primary resource vs an included copy)\n // overwrites an earlier one. For one response they are the same wire object anyway.\n fresh.set(resourceKey(ident.type, ident.id), attributes)\n }\n })\n return fresh\n}\n\n/**\n * Patch every materialised node in `cached` whose `type:id` appears in `fresh`, in place,\n * with the freshest attributes — returning whether any VALUE actually changed. We mutate the\n * cached objects directly (preserving their identity/accessors) rather than rebuilding them, so\n * writing the same reference back triggers TanStack's subscribers without a structural-sharing diff\n * fighting the per-edge views. A node whose attributes already equal `fresh` is a no-op (reports\n * unchanged), so a redundant re-application doesn't write back or re-notify.\n */\nfunction patchCached(\n cached: unknown,\n fresh: Map<ResourceKey, Record<string, unknown>>,\n descriptor: ApiDescriptor,\n): boolean {\n let changed = false\n walkResources(cached, descriptor, (node) => {\n const ident = identity(node)\n if (ident === undefined) {\n return\n }\n const attributes = fresh.get(resourceKey(ident.type, ident.id))\n if (\n attributes !== undefined &&\n patchNode(node as unknown as Record<string, unknown>, attributes)\n ) {\n changed = true\n }\n })\n return changed\n}\n\n/**\n * Write-through patch (the heart of Strategy 2). Given a freshly-resolved materialised\n * `result`, index its resources by `type:id` and patch every OTHER cached query that holds any\n * of those `type:id`s in place, so a single response updates the same resource everywhere it\n * is cached (\"edit once, updates everywhere\").\n *\n * Edge-local `$pivot`/`$edge` and a node's identity are preserved (only own-enumerable\n * attribute props are overwritten). Pass `result` from an option factory's `onSuccess` (or use\n * {@link installNormalization} to subscribe to every query/mutation success automatically).\n *\n * `result` is the materialised value the client produced for this response (a resource, a\n * `Collection`, a related collection, or linkage). The same reference may also be the cached\n * entry for the query that produced it — we mutate it too (idempotently), which is harmless and\n * keeps every surface holding that resource consistent.\n *\n * CONTRACT: a single QueryClient must hold caches for ONE descriptor only. The patch matches a\n * cached node purely by `type:id` and rewrites it with `descriptor`'s attribute split, so two\n * descriptors that share a QueryClient and a `type:id` (e.g. a `default` + an `admin` server whose\n * `users` differ) would cross-contaminate. `installNormalization` enforces this for the auto path;\n * when calling `normalize` standalone, give each typed client/server its own QueryClient.\n */\nexport function normalize(\n queryClient: QueryClient,\n result: unknown,\n descriptor: ApiDescriptor,\n): void {\n const fresh = collectAttributes(result, descriptor)\n if (fresh.size === 0) {\n return\n }\n patchAll(queryClient, fresh, descriptor)\n}\n\n/**\n * Patch every cached query with a `type:id -> fresh attributes` map. We walk the query cache\n * explicitly and only `setQueryData` back the queries that {@link patchCached} actually mutated —\n * NOT a blanket `setQueriesData({}, ...)`. The blanket form writes data back to every cached\n * query, and TanStack's `setQueryData` unconditionally clears `isInvalidated` on the written\n * query: a normalize pass over one type would silently un-invalidate untouched queries of other\n * types, undoing the create/delete membership invalidation (ADR 0003). Gating the write on the\n * `changed` flag keeps touched queries' in-place identity (so their observers notify) while\n * leaving untouched queries — and their `isInvalidated` flags — completely alone.\n */\nfunction patchAll(\n queryClient: QueryClient,\n fresh: Map<ResourceKey, Record<string, unknown>>,\n descriptor: ApiDescriptor,\n): void {\n for (const query of queryClient.getQueryCache().getAll()) {\n const data = query.state.data\n if (data === undefined || data === null) {\n continue\n }\n if (patchCached(data, fresh, descriptor)) {\n // Write the same (mutated-in-place) reference back so this query's observers see the change;\n // only touched queries are written, so untouched queries' isInvalidated flags survive.\n queryClient.setQueryData(query.queryKey, data)\n }\n }\n}\n\n// ── Optimistic patching (write-through, with rollback) — ./mutate.ts ─────────────────────────\n\n/**\n * A captured snapshot of the prior state of every node an optimistic patch touched, with a\n * `restore()` that puts each one back. The snapshot records, per affected cached node, the exact\n * prior value of every attribute key the patch overwrote (and which keys were newly added), so a\n * rollback is precise — it never disturbs keys the patch didn't touch, and it restores in place\n * (preserving identity + the per-edge `$pivot`/`$edge`, exactly as the forward patch does).\n */\nexport interface Snapshot {\n /** Undo the optimistic patch — restore every touched node to its pre-patch attributes. */\n restore(): void\n}\n\n/** One recorded node edit: the target object + the prior values to put back (undefined = was absent). */\ninterface NodeEdit {\n target: Record<string, unknown>\n /** Prior value per overwritten key; a key absent here that the patch added is recorded in `added`. */\n prior: Map<string, unknown>\n /** Keys the patch newly added (absent before) — restored by deletion. */\n added: Set<string>\n /**\n * The value THIS patch wrote, per key. Restore is compare-and-swap against it: a key is reverted\n * only if its current value is still the one we wrote, so an earlier patch rolling back can't stomp\n * a later concurrent optimistic patch's value on the same key (the later patch's own restore owns it).\n */\n wrote: Record<string, unknown>\n}\n\n/**\n * Apply an OPTIMISTIC attribute patch to a single `type:id` across every cached query and return a\n * {@link Snapshot} to roll it back. Mirrors the success path ({@link normalize}) — same in-place\n * attribute overwrite, same edge preservation — but for one known resource whose fresh attributes\n * the caller predicts (an update patch's attribute keys), and it captures the prior state first so\n * an error can restore it. Used by the update / relationship-set mutation factories' `onMutate`.\n */\nexport function applyOptimisticPatch(\n queryClient: QueryClient,\n type: string,\n id: string,\n attributes: Record<string, unknown>,\n descriptor: ApiDescriptor,\n): Snapshot {\n const key = resourceKey(type, id)\n const edits: NodeEdit[] = []\n\n // Walk the cache explicitly (not `setQueriesData({}, ...)`) so we only write back queries that\n // actually hold this `type:id` — preserving untouched queries' identity and `isInvalidated`\n // flags, exactly as the success path ({@link patchAll}) does.\n for (const query of queryClient.getQueryCache().getAll()) {\n const data = query.state.data\n if (data === undefined || data === null) {\n continue\n }\n let touched = false\n walkResources(data, descriptor, (node) => {\n const ident = identity(node)\n if (ident === undefined || resourceKey(ident.type, ident.id) !== key) {\n return\n }\n const target = node as unknown as Record<string, unknown>\n // Capture prior values BEFORE overwriting, so the snapshot is precise.\n const prior = new Map<string, unknown>()\n const added = new Set<string>()\n for (const k of Object.keys(attributes)) {\n if (k in target) {\n prior.set(k, target[k])\n } else {\n added.add(k)\n }\n }\n edits.push({ target, prior, added, wrote: { ...attributes } })\n patchNode(target, attributes)\n touched = true\n })\n if (touched) {\n queryClient.setQueryData(query.queryKey, data)\n }\n }\n\n return {\n restore() {\n for (const edit of edits) {\n for (const [k, v] of edit.prior) {\n // Compare-and-swap: only revert a key still holding the value WE wrote. If a later\n // concurrent optimistic patch overwrote it, leave that newer value alone — its own\n // restore (or the success patch) owns the key now.\n if (edit.target[k] === edit.wrote[k]) {\n edit.target[k] = v\n }\n }\n for (const k of edit.added) {\n // Likewise: only delete a key we added that no later patch has since re-set.\n if (edit.target[k] === edit.wrote[k]) {\n delete edit.target[k]\n }\n }\n }\n },\n }\n}\n\n// ── Optimistic relationship membership (by key prefix, with rollback) — ./mutate.ts ──────────\n\n/**\n * A to-many array member for the optimistic patch: at minimum a linkage identifier (`type`+`id`,\n * optional `$pivot`), but any extra own props are preserved — so a caller who passes a fully\n * hydrated member (a resource view carrying attributes) gets a rich optimistic row, while a bare\n * `{type,id}` identifier is equally valid. Membership identity is by `type:id` alone (`sameRef`).\n */\nexport interface MembershipRef {\n type: string\n id: string\n $pivot?: Record<string, unknown>\n [key: string]: unknown\n}\n\n/**\n * A membership transform over the CURRENT members of a cached related/relationship read: given the\n * present members (each an identifier `{type,id}` or a hydrated resource view — both expose\n * `type`/`id`), return the optimistic next members. The relationship-mutation factories build one\n * per verb (append the added refs, drop the removed ones, or swap in the replacement set).\n */\nexport type MembershipTransform = (members: readonly MembershipRef[]) => readonly MembershipRef[]\n\n/**\n * Apply an OPTIMISTIC membership change to EVERY cached read of one parent's one relation — both its\n * related-collection (`fetchRelated`) and its relationship-linkage (`fetchRelationship`) reads,\n * matched BY KEY PREFIX so every page/param variant is covered at once (no coupling to the page\n * params a read used — the pain point D35b removes). The to-many read caches an array of members, so\n * the transform runs over that array; the snapshot restores each touched query's PRIOR data\n * wholesale on error. On settle the factory invalidates the relation subtree, so the server\n * reconciles regardless (a degraded optimistic member — an identifier without attributes — is\n * replaced by the real one). A to-one `set` is handled separately (it caches a single value, not an\n * array).\n */\nexport function applyOptimisticMembership(\n queryClient: QueryClient,\n type: string,\n id: string,\n rel: string,\n transform: MembershipTransform,\n): Snapshot {\n const prefixes = relationReadKeys(type, id, rel)\n const edits: { key: QueryKey; previous: unknown }[] = []\n\n for (const query of queryClient.getQueryCache().getAll()) {\n const key = query.queryKey as QueryKey\n if (!prefixes.some((prefix) => keyHasPrefix(key, prefix))) {\n continue\n }\n const data = query.state.data\n if (!Array.isArray(data)) {\n // Only a to-many read caches an array; skip anything else (e.g. an in-flight/empty entry).\n continue\n }\n edits.push({ key, previous: data })\n // Set a fresh array (the augmented `$page`/`$links` accessors are dropped until the settle\n // refetch reconciles — the same trade-off a hand-rolled `setQueryData(next)` makes; the UI\n // renders the members immediately and the real page envelope returns on reconcile).\n queryClient.setQueryData(key, transform(data as readonly MembershipRef[]))\n }\n\n return {\n restore() {\n for (const edit of edits) {\n queryClient.setQueryData(edit.key, edit.previous)\n }\n },\n }\n}\n\n/** True when two refs address the same resource (`type:id`). Ignores `$pivot`/attributes. */\nexport function sameRef(a: MembershipRef, b: MembershipRef): boolean {\n return a.type === b.type && a.id === b.id\n}\n\n/**\n * Apply an OPTIMISTIC to-one `set` to every cached relationship/related read of one parent's one\n * relation (by key prefix): swap each cached value to `next` (a bare identifier ref, or `null` to\n * clear), snapshotting the prior value for rollback. A to-one read caches a single value (not an\n * array), so a plain replacement is correct; the settle refetch reconciles a bare identifier into\n * the fully-hydrated resource. `undefined` next means the ref carried no cache identity (an\n * lid-only atomic handle) — the cache is left untouched (the wire write still happens).\n */\nexport function applyOptimisticToOne(\n queryClient: QueryClient,\n type: string,\n id: string,\n rel: string,\n next: MembershipRef | null | undefined,\n): Snapshot {\n const prefixes = relationReadKeys(type, id, rel)\n const edits: { key: QueryKey; previous: unknown }[] = []\n if (next === undefined) {\n return { restore() {} }\n }\n\n for (const query of queryClient.getQueryCache().getAll()) {\n const key = query.queryKey as QueryKey\n if (!prefixes.some((prefix) => keyHasPrefix(key, prefix))) {\n continue\n }\n const data = query.state.data\n // A to-one read caches a single value or null (an array would be a to-many — skip it).\n if (Array.isArray(data)) {\n continue\n }\n edits.push({ key, previous: data })\n queryClient.setQueryData(key, next)\n }\n\n return {\n restore() {\n for (const edit of edits) {\n queryClient.setQueryData(edit.key, edit.previous)\n }\n },\n }\n}\n","/**\n * Auto-subscribe the Strategy-2 normalizer to a QueryClient (ADR 0003).\n *\n * {@link installNormalization} wires {@link normalize} to fire on every successful query AND\n * mutation, so a fresh response patches the same `type:id` everywhere it is cached with no\n * per-call `onSuccess` plumbing. The returned function tears the subscription down again.\n *\n * Callers who prefer explicit control can skip this and call {@link normalize} from an option\n * factory's `onSuccess` instead — both routes do the same write-through patch. This module adds\n * no runtime dependency (it uses only the QueryClient's own cache-subscription seam).\n */\nimport type { ApiDescriptor } from '@haddowg/json-api-client'\nimport type { QueryClient } from '@tanstack/query-core'\nimport { normalize } from './normalize'\n\n/** Tear down an installed normalization subscription. Idempotent. */\nexport type Teardown = () => void\n\n/**\n * Which descriptor is currently installed on a given QueryClient. The normalizer patches a cached\n * query by walking it with the *patch's* descriptor and matching on `type:id`, so two descriptors\n * sharing one QueryClient (e.g. a `default` + an `admin` server whose `users` carry different\n * attribute sets — CONTEXT.md's multi-client case) would cross-contaminate: a read from one would\n * overwrite the other's cached node with foreign attributes. The supported contract is therefore\n * ONE descriptor per QueryClient; this registry enforces it for the auto-install entry point.\n * A `WeakMap` so a discarded QueryClient (and its install record) is garbage-collected.\n */\nconst installed = new WeakMap<QueryClient, ApiDescriptor>()\n\n/**\n * Subscribe the normalizer to a QueryClient's query + mutation caches: each time a query or\n * mutation settles successfully, the freshly-resolved data is indexed by `type:id` and patched\n * across every cached query in place (preserving edge-local `$pivot`/`$edge`).\n *\n * A QueryClient may host only ONE descriptor (see {@link installed}): installing a *second*,\n * different descriptor on the same QueryClient throws, because the normalizer cannot safely tell\n * which descriptor a given `type:id` belongs to once two descriptors overlap. Use one QueryClient\n * per typed client/server. Re-installing the *same* descriptor is allowed (idempotent-friendly):\n * it just adds another subscription pair with its own teardown.\n *\n * Re-entrancy is guarded: the patch pass itself sets data on other queries (firing further\n * `success` events), so while a pass runs we ignore the events it generates — otherwise a\n * single response would cascade into a loop. Returns a {@link Teardown} that removes both\n * subscriptions (and clears the install record when this was the active install).\n */\nexport function installNormalization(\n queryClient: QueryClient,\n descriptor: ApiDescriptor,\n): Teardown {\n const existing = installed.get(queryClient)\n if (existing !== undefined && existing !== descriptor) {\n throw new Error(\n 'installNormalization: this QueryClient already has a different descriptor installed. ' +\n 'A QueryClient may host only one descriptor — use a separate QueryClient per client/server ' +\n 'so a normalize pass cannot leak one descriptor’s attributes into another’s cached resources.',\n )\n }\n installed.set(queryClient, descriptor)\n\n let normalizing = false\n\n const run = (data: unknown): void => {\n if (normalizing || data === undefined || data === null) {\n return\n }\n normalizing = true\n try {\n normalize(queryClient, data, descriptor)\n } finally {\n normalizing = false\n }\n }\n\n const unsubscribeQueries = queryClient.getQueryCache().subscribe((event) => {\n // Patch on a settled success — the query's data is the freshest copy of its resources.\n if (event.type === 'updated' && event.action.type === 'success') {\n run(event.query.state.data)\n }\n })\n\n const unsubscribeMutations = queryClient.getMutationCache().subscribe((event) => {\n // A write that returns a resource (create/update) carries the freshest attributes; index +\n // patch them. A mutation that resolves nothing (a `204` delete) has no data — `run` no-ops.\n if (event.type === 'updated' && event.action.type === 'success') {\n run(event.mutation.state.data)\n }\n })\n\n let torn = false\n return () => {\n if (torn) {\n return\n }\n torn = true\n unsubscribeQueries()\n unsubscribeMutations()\n // Release the install record so a different descriptor can be installed afterwards (only when\n // ours is still the active record — a same-descriptor re-install left it pointing at the same\n // descriptor, so clearing it is correct either way).\n if (installed.get(queryClient) === descriptor) {\n installed.delete(queryClient)\n }\n }\n}\n","/**\n * Mutation OPTION FACTORIES over the client write surface (CONTEXT.md \"Mutations: option\n * factories with optimistic updates routed through the normalized patch\"; ADR 0003).\n *\n * Each factory returns a plain `{ mutationFn, onMutate?, onError?, onSuccess?, onSettled? }`\n * object structurally compatible with `@tanstack/query-core` — the user passes it to their\n * framework's `useMutation` or to a `MutationObserver`/`mutationCache.build(...)`. The binding\n * depends only on `query-core` (framework-agnostic) and preserves the client's full static\n * narrowing on the variables (create/update input) and the resolved result.\n *\n * The PATCH-vs-INVALIDATE split (ADR 0003):\n * - an UPDATE (or a relationship set/replace on an existing resource) only changes a node's\n * attributes/linkage, never collection membership -> on success run the Build-2 write-through\n * {@link normalize} so the fresh resource patches everywhere it is cached, silently (no\n * refetch);\n * - a CREATE or DELETE (or a relationship add/remove — membership change) changes which\n * resources a list/relationship holds, which is not a node patch -> on settle invalidate the\n * relevant query subtrees by key PREFIX (the Build-1 hierarchy) so they refetch.\n *\n * OPTIMISTIC updates (update/relationship-set/replace only — the membership-changing verbs\n * have no node to pre-patch): `onMutate` applies the expected attribute change THROUGH the\n * same normalized patch and snapshots the touched queries; `onError` restores the snapshot;\n * `onSettled` runs the post-write reconciliation (the success patch having already landed).\n */\nimport type {\n ApiDescriptor,\n AtomicHandle,\n AtomicRecorder,\n AtomicResult,\n AtomicResults,\n CreateInput,\n FieldsMap,\n IncludePath,\n LinkageRef,\n ReadResult,\n RelationName,\n TypeName,\n UpdateInput,\n WriteOptions,\n} from '@haddowg/json-api-client'\nimport type { QueryClient } from '@tanstack/query-core'\nimport { operationKey, relationKey, resourceKey } from './keys'\nimport {\n applyOptimisticMembership,\n applyOptimisticPatch,\n applyOptimisticToOne,\n type MembershipRef,\n normalize,\n sameRef,\n type Snapshot,\n} from './normalize'\n\n/**\n * A TanStack-compatible mutation-options object: the async `mutationFn` plus the lifecycle\n * callbacks the binding wires for normalization / invalidation / optimism. Structurally\n * assignable to `query-core`'s `MutationOptions`/`UseMutationOptions` (the user merges in\n * `retry`, `onMutate` of their own, etc.), so the binding needn't import or re-export\n * query-core's option type — it stays a peer.\n *\n * `TData` is the resolved result, `TVars` the input the caller passes to `mutate(vars)`, and\n * `TContext` the value `onMutate` threads to `onError`/`onSettled` (here a {@link MutationContext}\n * carrying the optimistic snapshot).\n */\nexport interface MutationOptions<TData, TVars, TContext = unknown> {\n mutationFn: (variables: TVars) => Promise<TData>\n onMutate?: (variables: TVars) => Promise<TContext> | TContext\n onError?: (error: unknown, variables: TVars, context: TContext | undefined) => void\n onSuccess?: (data: TData, variables: TVars, context: TContext | undefined) => void\n onSettled?: (\n data: TData | undefined,\n error: unknown,\n variables: TVars,\n context: TContext | undefined,\n ) => void\n}\n\n/** The context an optimistic `onMutate` threads forward: the snapshot to roll back to on error. */\nexport interface MutationContext {\n snapshot: Snapshot\n}\n\n/** A client narrowed to the per-type write methods the mutation factories drive. */\ntype WriteClient<D extends ApiDescriptor, A, W> = {\n [T in TypeName<D>]: {\n create<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}>(\n input: CreateInput<D, W, T>,\n opts?: WriteOptions<D, T, Inc, F>,\n ): Promise<ReadResult<D, A, T, Inc, F>>\n id(id: string): {\n update<\n const Inc extends readonly IncludePath<D, T>[] = [],\n const F extends FieldsMap<D> = {},\n >(\n patch: UpdateInput<D, W, T>,\n opts?: WriteOptions<D, T, Inc, F>,\n ): Promise<ReadResult<D, A, T, Inc, F>>\n delete(): Promise<void>\n rel(name: RelationName<D, T>): {\n add(refs: readonly LinkageRef<string>[]): Promise<unknown>\n remove(refs: readonly LinkageRef<string>[]): Promise<unknown>\n replace(refs: readonly LinkageRef<string>[]): Promise<unknown>\n set(ref: LinkageRef<string> | null): Promise<unknown>\n }\n }\n }\n}\n\n/**\n * Invalidate the list/collection subtree of a type (`[type, 'fetchMany']`) — refetched after a\n * create/delete changes which resources the collection holds. A prefix match, so every cached\n * list of the type (any filter/sort/page) is marked stale at once.\n */\nfunction invalidateLists(queryClient: QueryClient, type: string): Promise<void> {\n return queryClient.invalidateQueries({ queryKey: operationKey(type, 'fetchMany') })\n}\n\n/**\n * Invalidate every read OF a single resource on delete: its `fetchOne` reads (so a stale `get`\n * refetches and 404s/clears) AND its own `fetchRelated`/`fetchRelationship` reads (orphaned once\n * the resource is gone — `[type, 'fetchOne', id]` does not prefix those). Prefix matches across\n * include/fields/rel. NOTE: this covers the deleted resource's OWN reads; OTHER resources whose\n * relationship/related queries listed this one as a member are not auto-invalidated here (the\n * binding can't know which parents hold it without a cache scan — invalidate those explicitly, or\n * model the parent's own relationship mutation which DOES target it).\n */\nfunction invalidateResource(queryClient: QueryClient, type: string, id: string): Promise<void> {\n return Promise.all([\n queryClient.invalidateQueries({ queryKey: resourceKey(type, id) }),\n queryClient.invalidateQueries({ queryKey: [type, 'fetchRelated', id] }),\n queryClient.invalidateQueries({ queryKey: [type, 'fetchRelationship', id] }),\n ]).then(() => undefined)\n}\n\n/**\n * Invalidate one parent resource's reads of one relation after a set/replace/add/remove changes\n * that relation's linkage/membership (ADR 0003's TARGETED invalidation). We invalidate exactly:\n * - the relation's related-collection reads (`[type, 'fetchRelated', id, rel]`),\n * - the relation's linkage reads (`[type, 'fetchRelationship', id, rel]`),\n * - the parent resource's own reads (`[type, 'fetchOne', id]`) — whose `?include` of the relation\n * embeds the now-stale members.\n * It deliberately does NOT touch `[type, 'fetchMany']` (the type's collection lists): a relationship\n * change never alters which resources a collection holds, so scoping to the type would needlessly\n * invalidate every list of the type.\n */\nfunction invalidateParentRelations(\n queryClient: QueryClient,\n type: string,\n id: string,\n rel: string,\n): Promise<void> {\n return Promise.all([\n queryClient.invalidateQueries({ queryKey: relationKey(type, 'fetchRelated', id, rel) }),\n queryClient.invalidateQueries({ queryKey: relationKey(type, 'fetchRelationship', id, rel) }),\n queryClient.invalidateQueries({ queryKey: resourceKey(type, id) }),\n ]).then(() => undefined)\n}\n\n// ── Create / update / delete ──────────────────────────────────────────────────────────────\n\n/**\n * The create mutation (`POST /{type}`): `mutationFn` runs `client.<type>.create(input, opts)`;\n * on settle the type's list subtree is invalidated (a new member changes collection membership —\n * a patch can't insert it, ADR 0003). The fresh resource is also normalized on success so any\n * query already holding the (server-assigned-id) resource reflects it.\n *\n * No optimistic insert by default: inserting into the right cached lists requires knowing each\n * list's sort/filter/page, which is list-specific — we invalidate instead (the documented,\n * always-correct default). A caller wanting optimism supplies their own `onMutate`.\n */\nexport function createMutationOptions<\n D extends ApiDescriptor,\n A,\n W,\n T extends TypeName<D>,\n const Inc extends readonly IncludePath<D, T>[] = [],\n const F extends FieldsMap<D> = {},\n>(\n queryClient: QueryClient,\n client: WriteClient<D, A, W>,\n descriptor: D,\n type: T,\n opts?: WriteOptions<D, T, Inc, F>,\n): MutationOptions<ReadResult<D, A, T, Inc, F>, CreateInput<D, W, T>> {\n const accessor = client[type]\n return {\n mutationFn: (input) => accessor.create<Inc, F>(input, opts),\n onSuccess: (data) => {\n normalize(queryClient, data, descriptor)\n },\n onSettled: () => invalidateLists(queryClient, type),\n }\n}\n\n/**\n * The update mutation (`PATCH /{type}/{id}`): `mutationFn` runs\n * `client.<type>.id(id).update(patch, opts)`. An update only changes a node's attributes/linkage,\n * so on success it PATCHES across the cache via {@link normalize} (no refetch). When optimistic,\n * `onMutate` pre-applies the patch's attributes (those keys that are declared attributes of the\n * type — relation slots are skipped) through the normalized patch and snapshots the touched\n * queries; `onError` restores the snapshot.\n *\n * @param optimistic apply the patch's attributes immediately (rolled back on error). Default off.\n */\nexport function updateMutationOptions<\n D extends ApiDescriptor,\n A,\n W,\n T extends TypeName<D>,\n const Inc extends readonly IncludePath<D, T>[] = [],\n const F extends FieldsMap<D> = {},\n>(\n queryClient: QueryClient,\n client: WriteClient<D, A, W>,\n descriptor: D,\n type: T,\n id: string,\n opts?: WriteOptions<D, T, Inc, F> & { optimistic?: boolean },\n): MutationOptions<ReadResult<D, A, T, Inc, F>, UpdateInput<D, W, T>, MutationContext> {\n const accessor = client[type]\n const optimistic = opts?.optimistic === true\n const base: MutationOptions<\n ReadResult<D, A, T, Inc, F>,\n UpdateInput<D, W, T>,\n MutationContext\n > = {\n mutationFn: (patch) => accessor.id(id).update<Inc, F>(patch, opts),\n onSuccess: (data) => {\n normalize(queryClient, data, descriptor)\n },\n }\n if (!optimistic) {\n return base\n }\n // Optimistic: pre-apply the patch's attributes through the normalized patch (snapshotting the\n // touched queries first) on `onMutate`, and restore that snapshot on `onError`.\n base.onMutate = (patch) => ({\n snapshot: applyOptimisticPatch(\n queryClient,\n type,\n id,\n attributesFromPatch(descriptor, type, patch),\n descriptor,\n ),\n })\n base.onError = (_error, _patch, context) => {\n context?.snapshot.restore()\n }\n return base\n}\n\n/**\n * The delete mutation (`DELETE /{type}/{id}`): `mutationFn` runs `client.<type>.id(id).delete()`\n * (resolves `void`). On settle the type's list subtree AND that resource's reads are invalidated\n * (a delete changes collection membership and orphans the resource's `get`, ADR 0003).\n */\nexport function deleteMutationOptions<D extends ApiDescriptor, A, W, T extends TypeName<D>>(\n queryClient: QueryClient,\n client: WriteClient<D, A, W>,\n type: T,\n id: string,\n): MutationOptions<void, void> {\n const accessor = client[type]\n return {\n mutationFn: () => accessor.id(id).delete(),\n onSettled: async () => {\n await invalidateLists(queryClient, type)\n await invalidateResource(queryClient, type, id)\n },\n }\n}\n\n// ── Relationship mutations ──────────────────────────────────────────────────────────────────\n\n/** Options for a relationship mutation: opt into an optimistic membership/linkage patch (default off). */\nexport interface RelationshipMutationOpts {\n /**\n * Apply the membership change to the parent's cached related/relationship reads immediately\n * (by key prefix — every page variant), snapshotting for automatic rollback on error. The factory\n * still invalidates the relation subtree on settle so the server reconciles. Default off (a plain\n * invalidate-on-settle). An optimistic `add` appends bare identifier members (`{type,id}` + any\n * `$pivot` on the ref) — the settle refetch replaces them with the fully-hydrated resources.\n */\n optimistic?: boolean\n}\n\n/** True when `opts` requests the optimistic membership patch. */\nconst isOptimistic = (opts?: RelationshipMutationOpts): boolean => opts?.optimistic === true\n\n/**\n * Coerce a linkage ref (`{type,id}` / `{type,lid}` / a resource view, optionally `$pivot`) to a\n * membership ref for the optimistic array patch. The whole ref is preserved (so a hydrated member\n * carrying attributes yields a rich optimistic row), requiring only a string `type`+`id`. A ref\n * addressed only by `lid` (an atomic handle) has no cache identity, so it is dropped from the\n * optimistic view (it still writes on the wire).\n */\nfunction toMembershipRef(ref: LinkageRef<string>): MembershipRef | undefined {\n const r = ref as { type?: unknown; id?: unknown }\n if (typeof r.type !== 'string' || typeof r.id !== 'string') {\n return undefined\n }\n return { ...(ref as Record<string, unknown>), type: r.type, id: r.id }\n}\n\n/** The membership refs (cache-addressable) of a to-many mutation's variables. */\nconst membershipRefs = (refs: readonly LinkageRef<string>[]): MembershipRef[] =>\n refs.map(toMembershipRef).filter((r): r is MembershipRef => r !== undefined)\n\n/**\n * Wire the optimistic membership lifecycle onto a to-many relationship mutation: `onMutate` applies\n * `transform` to the parent's cached related/relationship arrays (snapshotting), `onError` rolls\n * back, `onSettled` invalidates the relation subtree (kept from the base). When not optimistic, the\n * base (invalidate-on-settle only) is returned untouched.\n */\nfunction withMembershipOptimism<TVars extends readonly LinkageRef<string>[]>(\n queryClient: QueryClient,\n type: string,\n id: string,\n rel: string,\n base: MutationOptions<unknown, TVars, MutationContext>,\n transform: (current: readonly MembershipRef[], vars: TVars) => readonly MembershipRef[],\n optimistic: boolean,\n): MutationOptions<unknown, TVars, MutationContext> {\n if (!optimistic) {\n return base\n }\n return {\n ...base,\n onMutate: (vars) => ({\n snapshot: applyOptimisticMembership(queryClient, type, id, rel, (current) =>\n transform(current, vars),\n ),\n }),\n onError: (_error, _vars, context) => {\n context?.snapshot.restore()\n },\n }\n}\n\n/**\n * A to-one relationship `set` (`PATCH …/relationships/{rel}` with a single ref or `null`):\n * `mutationFn` runs `client.<type>.id(id).rel(rel).set(ref)`. A `set` changes which resource the\n * to-one points at — linkage, not collection membership and not a node attribute — so on settle\n * we invalidate the PARENT's relations subtree (its `fetchRelationship`/`fetchRelated`/included\n * reads refetch). The mutation response (linkage) is also normalized on success. When optimistic,\n * `onMutate` swaps the parent's cached relationship/related reads to the new single value (or `null`)\n * and rolls back on error.\n */\nexport function setRelationshipMutationOptions<\n D extends ApiDescriptor,\n A,\n W,\n T extends TypeName<D>,\n>(\n queryClient: QueryClient,\n client: WriteClient<D, A, W>,\n descriptor: D,\n type: T,\n id: string,\n rel: RelationName<D, T>,\n opts?: RelationshipMutationOpts,\n): MutationOptions<unknown, LinkageRef<string> | null, MutationContext> {\n const accessor = client[type]\n const base: MutationOptions<unknown, LinkageRef<string> | null, MutationContext> = {\n mutationFn: (ref) => accessor.id(id).rel(rel).set(ref),\n onSuccess: (data) => {\n normalize(queryClient, data, descriptor)\n },\n onSettled: () => invalidateParentRelations(queryClient, type, id, rel),\n }\n if (!isOptimistic(opts)) {\n return base\n }\n return {\n ...base,\n onMutate: (ref) => {\n const next = ref === null ? undefined : toMembershipRef(ref)\n return {\n snapshot: applyOptimisticToOne(queryClient, type, id, rel, ref === null ? null : next),\n }\n },\n onError: (_error, _ref, context) => {\n context?.snapshot.restore()\n },\n }\n}\n\n/**\n * A to-many relationship `replace` (`PATCH …/relationships/{rel}` with the full new set):\n * `mutationFn` runs `client.<type>.id(id).rel(rel).replace(refs)`. A full replacement resets the\n * relation's membership, so on settle the parent's relations subtree is invalidated. The\n * mutation response (the new linkage) is normalized on success. When optimistic, the cached\n * membership is swapped to `refs` and rolled back on error.\n */\nexport function replaceRelationshipMutationOptions<\n D extends ApiDescriptor,\n A,\n W,\n T extends TypeName<D>,\n>(\n queryClient: QueryClient,\n client: WriteClient<D, A, W>,\n descriptor: D,\n type: T,\n id: string,\n rel: RelationName<D, T>,\n opts?: RelationshipMutationOpts,\n): MutationOptions<unknown, readonly LinkageRef<string>[], MutationContext> {\n const accessor = client[type]\n const base: MutationOptions<unknown, readonly LinkageRef<string>[], MutationContext> = {\n mutationFn: (refs) => accessor.id(id).rel(rel).replace(refs),\n onSuccess: (data) => {\n normalize(queryClient, data, descriptor)\n },\n onSettled: () => invalidateParentRelations(queryClient, type, id, rel),\n }\n // Replace: the optimistic set IS the new refs (order preserved), regardless of the current members.\n return withMembershipOptimism(\n queryClient,\n type,\n id,\n rel,\n base,\n (_current, refs) => membershipRefs(refs),\n isOptimistic(opts),\n )\n}\n\n/**\n * A to-many relationship `add` (`POST …/relationships/{rel}`): `mutationFn` runs\n * `client.<type>.id(id).rel(rel).add(refs)`. Adding changes the relation's membership, so on\n * settle the parent's relations subtree is invalidated (a patch can't insert linkage members).\n * When optimistic, the added refs are appended to the cached membership (deduplicated by `type:id`)\n * and rolled back on error.\n */\nexport function addRelationshipMutationOptions<\n D extends ApiDescriptor,\n A,\n W,\n T extends TypeName<D>,\n>(\n queryClient: QueryClient,\n client: WriteClient<D, A, W>,\n type: T,\n id: string,\n rel: RelationName<D, T>,\n opts?: RelationshipMutationOpts,\n): MutationOptions<unknown, readonly LinkageRef<string>[], MutationContext> {\n const accessor = client[type]\n const base: MutationOptions<unknown, readonly LinkageRef<string>[], MutationContext> = {\n mutationFn: (refs) => accessor.id(id).rel(rel).add(refs),\n onSettled: () => invalidateParentRelations(queryClient, type, id, rel),\n }\n return withMembershipOptimism(\n queryClient,\n type,\n id,\n rel,\n base,\n (current, refs) => {\n const added = membershipRefs(refs)\n // Append the added refs, skipping any already present (an add is idempotent on membership).\n const kept = current.filter((m) => !added.some((a) => sameRef(a, m)))\n return [...kept, ...added]\n },\n isOptimistic(opts),\n )\n}\n\n/**\n * A to-many relationship `remove` (`DELETE …/relationships/{rel}`): `mutationFn` runs\n * `client.<type>.id(id).rel(rel).remove(refs)`. Removing changes the relation's membership, so on\n * settle the parent's relations subtree is invalidated. When optimistic, the removed refs are\n * dropped from the cached membership and rolled back on error.\n */\nexport function removeRelationshipMutationOptions<\n D extends ApiDescriptor,\n A,\n W,\n T extends TypeName<D>,\n>(\n queryClient: QueryClient,\n client: WriteClient<D, A, W>,\n type: T,\n id: string,\n rel: RelationName<D, T>,\n opts?: RelationshipMutationOpts,\n): MutationOptions<unknown, readonly LinkageRef<string>[], MutationContext> {\n const accessor = client[type]\n const base: MutationOptions<unknown, readonly LinkageRef<string>[], MutationContext> = {\n mutationFn: (refs) => accessor.id(id).rel(rel).remove(refs),\n onSettled: () => invalidateParentRelations(queryClient, type, id, rel),\n }\n return withMembershipOptimism(\n queryClient,\n type,\n id,\n rel,\n base,\n (current, refs) => {\n const removed = membershipRefs(refs)\n return current.filter((m) => !removed.some((r) => sameRef(r, m)))\n },\n isOptimistic(opts),\n )\n}\n\n// ── Atomic (a thin passthrough) ─────────────────────────────────────────────────────────────\n\n/**\n * A thin atomic-batch mutation (`POST /operations`): `mutationFn` forwards the `build` callback to\n * `client.atomic(build)`, preserving the client's per-op `const`-tuple result inference (the\n * variables ARE the build callback). On success the materialised results are run through\n * {@link normalize} so any updated resource patches across the cache.\n *\n * INVALIDATION is the caller's responsibility for atomic: a batch can mix creates/updates/deletes\n * across many types, and the recorded ops aren't enumerable after the build runs, so the binding\n * can't know which list subtrees changed membership. Updates self-heal via the success\n * normalization; for a batch that creates/deletes, add an `onSettled` that invalidates the\n * affected `operationKey(type, 'fetchMany')` subtrees (or `queryClient.invalidateQueries()` for a\n * blanket refetch). Kept deliberately minimal — atomic is an escape hatch, not a normalized path.\n */\nexport function atomicMutationOptions<D extends ApiDescriptor, A, W>(\n queryClient: QueryClient,\n client: WriteClient<D, A, W>,\n descriptor: D,\n): MutationOptions<\n AtomicResult[],\n <const Ops>(\n tx: AtomicRecorder<D, W>,\n ) => Ops extends readonly AtomicHandle[] ? AtomicResults<D, A, Ops> : Ops\n> {\n return {\n mutationFn: (build) =>\n // The client's `atomic` carries the precise per-op return type; the binding's loose\n // `AtomicResult[]` result is the common supertype (a caller wanting the positional tuple\n // calls `client.atomic` directly — the binding's value is the cache normalization).\n (client as unknown as { atomic: (b: unknown) => Promise<AtomicResult[]> }).atomic(build),\n onSuccess: (data) => {\n normalize(queryClient, data, descriptor)\n },\n }\n}\n\n/**\n * Split an update patch into just its ATTRIBUTE keys (for an optimistic node patch): a key is an\n * attribute iff the descriptor declares it under `attributes` for the type. Relation slots and\n * any unknown key are dropped — an optimistic patch only touches a node's shared attributes (the\n * same split {@link normalize}'s real patch honours), never linkage.\n */\nfunction attributesFromPatch(\n descriptor: ApiDescriptor,\n type: string,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n const attributes = descriptor[type]?.attributes ?? {}\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(patch)) {\n if (key in attributes) {\n out[key] = value\n }\n }\n return out\n}\n\n// ── Bound per-type mutation API (sugar over the standalone factories) ────────────────────────\n\n/** Extra options for an `update(...)` mutation: the write `include`/`fields` plus the optimistic toggle. */\nexport type UpdateMutationOpts<\n D extends ApiDescriptor,\n T extends TypeName<D>,\n Inc extends readonly IncludePath<D, T>[] = [],\n F extends FieldsMap<D> = FieldsMap<D>,\n> = WriteOptions<D, T, Inc, F> & { optimistic?: boolean }\n\n/**\n * The relationship-mutation option factories for one relation (`api.<type>.id(id).rel(rel).add(...)`).\n * Refs are typed loosely (`LinkageRef<string>`) at the `.rel(name)` boundary — parity with the\n * read API, whose per-relation value is also loose there (the related type isn't recoverable from\n * the relation name alone in the bound surface). Each accepts an optional\n * {@link RelationshipMutationOpts} to opt into the optimistic membership/linkage patch.\n */\nexport interface RelationMutationApi {\n add(\n opts?: RelationshipMutationOpts,\n ): MutationOptions<unknown, readonly LinkageRef<string>[], MutationContext>\n remove(\n opts?: RelationshipMutationOpts,\n ): MutationOptions<unknown, readonly LinkageRef<string>[], MutationContext>\n replace(\n opts?: RelationshipMutationOpts,\n ): MutationOptions<unknown, readonly LinkageRef<string>[], MutationContext>\n set(\n opts?: RelationshipMutationOpts,\n ): MutationOptions<unknown, LinkageRef<string> | null, MutationContext>\n}\n\n/** The id-scoped mutation option factories (`api.<type>.id(id).update(...)` etc.). */\nexport interface HandleMutationApi<D extends ApiDescriptor, A, W, T extends TypeName<D>> {\n update<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}>(\n opts?: UpdateMutationOpts<D, T, Inc, F>,\n ): MutationOptions<ReadResult<D, A, T, Inc, F>, UpdateInput<D, W, T>, MutationContext>\n delete(): MutationOptions<void, void>\n rel(name: RelationName<D, T>): RelationMutationApi\n}\n\n/** The per-type mutation option factories, with `queryClient`/`client`/`descriptor`/`type` pre-applied. */\nexport interface TypeMutationApi<D extends ApiDescriptor, A, W, T extends TypeName<D>> {\n create<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}>(\n opts?: WriteOptions<D, T, Inc, F>,\n ): MutationOptions<ReadResult<D, A, T, Inc, F>, CreateInput<D, W, T>>\n id(id: string): HandleMutationApi<D, A, W, T>\n}\n\n/** The bound mutation API: one {@link TypeMutationApi} per wire type (`api.albums.create()`). */\nexport type MutationApi<D extends ApiDescriptor, A, W> = {\n [T in TypeName<D>]: TypeMutationApi<D, A, W, T>\n}\n\n/**\n * Bind a client + QueryClient to the per-type mutation option factories:\n * `createMutationApi(queryClient, client, descriptor).albums.id('1').update({ optimistic: true })`\n * returns the same `MutationOptions` as the standalone {@link updateMutationOptions}, with the\n * common arguments pre-applied. A Proxy (descriptor-keyed type validity is enforced by the static\n * `MutationApi` shape) — no per-type allocation until first access.\n */\nexport function createMutationApi<D extends ApiDescriptor, A, W>(\n queryClient: QueryClient,\n client: WriteClient<D, A, W>,\n descriptor: D,\n): MutationApi<D, A, W> {\n const cache = new Map<string, TypeMutationApi<D, A, W, TypeName<D>>>()\n return new Proxy(Object.create(null) as object, {\n get(_target, prop) {\n if (typeof prop !== 'string') {\n return undefined\n }\n let api = cache.get(prop)\n if (api === undefined) {\n api = typeMutationApi(queryClient, client, descriptor, prop as TypeName<D>)\n cache.set(prop, api)\n }\n return api\n },\n }) as MutationApi<D, A, W>\n}\n\n/** Build one type's bound mutation option factories (delegates to the standalone factories). */\nfunction typeMutationApi<D extends ApiDescriptor, A, W, T extends TypeName<D>>(\n queryClient: QueryClient,\n client: WriteClient<D, A, W>,\n descriptor: D,\n type: T,\n): TypeMutationApi<D, A, W, T> {\n return {\n create: (opts) => createMutationOptions(queryClient, client, descriptor, type, opts),\n id: (id) => ({\n update: (opts) => updateMutationOptions(queryClient, client, descriptor, type, id, opts),\n delete: () => deleteMutationOptions(queryClient, client, type, id),\n rel: (name) => ({\n add: (opts) => addRelationshipMutationOptions(queryClient, client, type, id, name, opts),\n remove: (opts) =>\n removeRelationshipMutationOptions(queryClient, client, type, id, name, opts),\n replace: (opts) =>\n replaceRelationshipMutationOptions(queryClient, client, descriptor, type, id, name, opts),\n set: (opts) =>\n setRelationshipMutationOptions(queryClient, client, descriptor, type, id, name, opts),\n }),\n }),\n }\n}\n","/**\n * Read OPTION FACTORIES over the typed client (CONTEXT.md: \"option factories, not hooks\").\n *\n * Each factory returns a plain `{ queryKey, queryFn }` object compatible with\n * `@tanstack/query-core` — the user passes it to their framework's `useQuery` or to a\n * `QueryClient.fetchQuery`. The binding depends ONLY on `query-core` (framework-agnostic) and\n * preserves the client's full static narrowing: `include` widens relations to hydrated, `fields`\n * narrows to the sparse fieldset, the result type is the client's own materialised view.\n *\n * Surface, exposed two equivalent ways:\n * - standalone factories — `listQueryOptions(client, type, query)`, `getQueryOptions(...)`,\n * `relationshipQueryOptions(...)`, `relatedQueryOptions(...)`;\n * - a bound API — `createQueryApi(client).<type>.list(query)` etc. — sugar over the same\n * factories with `client`/`type` pre-applied (the cleanest per-type call shape).\n */\nimport type {\n ApiDescriptor,\n Collection,\n FieldsMap,\n IncludePath,\n ReadResult,\n RelationName,\n RelationReadQuery,\n RelationshipAccessor,\n SingleReadQuery,\n TypedReadQuery,\n TypeName,\n} from '@haddowg/json-api-client'\nimport { keyFor, type QueryKey } from './keys'\n\n/**\n * The precise materialised result of `.rel(R).related()` / `.get()` — extracted (via `infer`)\n * from the client's own {@link RelationshipAccessor} so the read factories carry the client's\n * full per-relation narrowing (a `Collection` of hydrated members / identifier members, typed\n * `$pivot`, polymorphic unions) instead of a loose `unknown` (D34). A suppressed endpoint (whose\n * method the client types `never`) falls back to `unknown`.\n */\ntype RelatedResult<\n D extends ApiDescriptor,\n A,\n T extends TypeName<D>,\n R extends RelationName<D, T>,\n> = RelationshipAccessor<D, A, T, R>['related'] extends (...args: never[]) => Promise<infer V>\n ? V\n : unknown\n\ntype LinkageResult<\n D extends ApiDescriptor,\n A,\n T extends TypeName<D>,\n R extends RelationName<D, T>,\n> = RelationshipAccessor<D, A, T, R>['get'] extends (...args: never[]) => Promise<infer V>\n ? V\n : unknown\n\n/**\n * A TanStack-compatible query-options object: the deterministic {@link QueryKey} plus a\n * zero-arg `queryFn` resolving the materialised result `R`. Structurally assignable to\n * `query-core`'s `QueryOptions`/`UseQueryOptions` (the user merges in `staleTime` etc.), so the\n * binding needn't import or re-export query-core's option type — it stays a peer.\n */\nexport interface QueryOptions<R> {\n queryKey: QueryKey\n queryFn: () => Promise<R>\n}\n\n/** A client narrowed to its per-type accessors (drops the `atomic` member, which reads don't use). */\ntype TypedClient<D extends ApiDescriptor, A> = {\n [T in TypeName<D>]: {\n list<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}>(\n query?: TypedReadQuery<D, T, Inc, F>,\n ): Promise<Collection<ReadResult<D, A, T, Inc, F>>>\n get<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}>(\n id: string,\n query?: SingleReadQuery<D, T, Inc, F>,\n ): Promise<ReadResult<D, A, T, Inc, F>>\n id(id: string): {\n rel(name: RelationName<D, T>): {\n get(query?: RelationReadQuery): Promise<unknown>\n related(query?: RelationReadQuery): Promise<unknown>\n }\n }\n }\n}\n\n/**\n * The collection-list read (`GET /{type}`): options resolving the typed `Collection`, narrowed by\n * `include`/`fields` exactly as `client.<type>.list(query)`. The key is `[type, 'fetchMany',\n * <params>]` — the normalised query rides the trailing segment, so two equal queries share a key.\n */\nexport function listQueryOptions<\n D extends ApiDescriptor,\n A,\n T extends TypeName<D>,\n const Inc extends readonly IncludePath<D, T>[] = [],\n const F extends FieldsMap<D> = {},\n>(\n client: TypedClient<D, A>,\n type: T,\n query?: TypedReadQuery<D, T, Inc, F>,\n): QueryOptions<Collection<ReadResult<D, A, T, Inc, F>>> {\n const accessor = client[type]\n return {\n queryKey: keyFor({ type, operation: 'fetchMany' }, query),\n queryFn: () => accessor.list<Inc, F>(query),\n }\n}\n\n/**\n * The single-resource read (`GET /{type}/{id}`): options resolving the typed resource view,\n * narrowed by `include`/`fields` as `client.<type>.get(id, query)`. Key: `[type, 'fetchOne', id,\n * <params>]` — the `id` sits at a fixed position so {@link keyFor}'s prefix matches a resource's\n * reads.\n */\nexport function getQueryOptions<\n D extends ApiDescriptor,\n A,\n T extends TypeName<D>,\n const Inc extends readonly IncludePath<D, T>[] = [],\n const F extends FieldsMap<D> = {},\n>(\n client: TypedClient<D, A>,\n type: T,\n id: string,\n query?: SingleReadQuery<D, T, Inc, F>,\n): QueryOptions<ReadResult<D, A, T, Inc, F>> {\n const accessor = client[type]\n return {\n queryKey: keyFor({ type, operation: 'fetchOne', id }, query),\n queryFn: () => accessor.get<Inc, F>(id, query),\n }\n}\n\n/**\n * The relationship-linkage read (`GET /{type}/{id}/relationships/{rel}`): options resolving the\n * materialised linkage (identifier members), via `client.<type>.id(id).rel(rel).get(query)`. Key:\n * `[type, 'fetchRelationship', id, rel, <params>]`. The result is the client's runtime linkage\n * shape; the relationship endpoint's per-relation value is loose at the `.rel(name)` boundary, so\n * the option resolves `unknown` unless the caller annotates it (parity with the client's runtime).\n */\nexport function relationshipQueryOptions<\n D extends ApiDescriptor,\n A,\n T extends TypeName<D>,\n R extends RelationName<D, T>,\n>(\n client: TypedClient<D, A>,\n type: T,\n id: string,\n rel: R,\n query?: RelationReadQuery,\n): QueryOptions<LinkageResult<D, A, T, R>> {\n const accessor = client[type]\n return {\n queryKey: keyFor({ type, operation: 'fetchRelationship', id, rel }, query),\n queryFn: () => accessor.id(id).rel(rel).get(query) as Promise<LinkageResult<D, A, T, R>>,\n }\n}\n\n/**\n * The related-collection read (`GET /{type}/{id}/{rel}`): options resolving the materialised\n * related resource(s), via `client.<type>.id(id).rel(rel).related(query)`. Key: `[type,\n * 'fetchRelated', id, rel, <params>]`. As with linkage, the related value is loose at the\n * `.rel(name)` boundary, so this resolves `unknown` (parity with the client runtime).\n */\nexport function relatedQueryOptions<\n D extends ApiDescriptor,\n A,\n T extends TypeName<D>,\n R extends RelationName<D, T>,\n>(\n client: TypedClient<D, A>,\n type: T,\n id: string,\n rel: R,\n query?: RelationReadQuery,\n): QueryOptions<RelatedResult<D, A, T, R>> {\n const accessor = client[type]\n return {\n queryKey: keyFor({ type, operation: 'fetchRelated', id, rel }, query),\n queryFn: () => accessor.id(id).rel(rel).related(query) as Promise<RelatedResult<D, A, T, R>>,\n }\n}\n\n// ── Bound per-type API (sugar over the standalone factories) ──────────────────────────────\n\n/** The per-type read-option factories, with `client`/`type` pre-applied (`api.<type>.list(...)`). */\nexport interface TypeQueryApi<D extends ApiDescriptor, A, T extends TypeName<D>> {\n list<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}>(\n query?: TypedReadQuery<D, T, Inc, F>,\n ): QueryOptions<Collection<ReadResult<D, A, T, Inc, F>>>\n get<const Inc extends readonly IncludePath<D, T>[] = [], const F extends FieldsMap<D> = {}>(\n id: string,\n query?: SingleReadQuery<D, T, Inc, F>,\n ): QueryOptions<ReadResult<D, A, T, Inc, F>>\n relationship<R extends RelationName<D, T>>(\n id: string,\n rel: R,\n query?: RelationReadQuery,\n ): QueryOptions<LinkageResult<D, A, T, R>>\n related<R extends RelationName<D, T>>(\n id: string,\n rel: R,\n query?: RelationReadQuery,\n ): QueryOptions<RelatedResult<D, A, T, R>>\n}\n\n/** The bound query API: one {@link TypeQueryApi} per wire type (`api.albums.list(...)`). */\nexport type QueryApi<D extends ApiDescriptor, A> = {\n [T in TypeName<D>]: TypeQueryApi<D, A, T>\n}\n\n/**\n * Bind a client to its per-type read-option factories: `createQueryApi(client).albums.list(query)`\n * returns the same `{ queryKey, queryFn }` as `listQueryOptions(client, 'albums', query)`, with\n * `client`/`type` pre-applied. A Proxy (descriptor-free — type validity is enforced by the static\n * `QueryApi` shape) so no per-type allocation until first access.\n */\nexport function createQueryApi<D extends ApiDescriptor, A>(\n client: TypedClient<D, A>,\n): QueryApi<D, A> {\n const cache = new Map<string, TypeQueryApi<D, A, TypeName<D>>>()\n return new Proxy(Object.create(null) as object, {\n get(_target, prop) {\n if (typeof prop !== 'string') {\n return undefined\n }\n let api = cache.get(prop)\n if (api === undefined) {\n api = typeQueryApi(client, prop as TypeName<D>)\n cache.set(prop, api)\n }\n return api\n },\n }) as QueryApi<D, A>\n}\n\n/** Build one type's bound read-option factories (delegates to the standalone factories). */\nfunction typeQueryApi<D extends ApiDescriptor, A, T extends TypeName<D>>(\n client: TypedClient<D, A>,\n type: T,\n): TypeQueryApi<D, A, T> {\n return {\n list: (query) => listQueryOptions(client, type, query),\n get: (id, query) => getQueryOptions(client, type, id, query),\n relationship<R extends RelationName<D, T>>(id: string, rel: R, query?: RelationReadQuery) {\n return relationshipQueryOptions(client, type, id, rel, query)\n },\n related<R extends RelationName<D, T>>(id: string, rel: R, query?: RelationReadQuery) {\n return relatedQueryOptions(client, type, id, rel, query)\n },\n }\n}\n"],"mappings":";;;;;;;;AAuCA,SAAgB,gBAAgB,OAAkC;CAChE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;CAET,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,MAAM,gBAAgB,CAAC,CAAC;CAE5C,IAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,MAAwC,CAAC;EAI/C,KAAK,MAAM,OAAO,OAAO,KAAK,KAAgC,CAAC,CAAC,KAAK,GAAG;GACtE,MAAM,SAAU,MAAkC;GAClD,IAAI,WAAW,KAAA,GACb,IAAI,OAAO,gBAAgB,MAAM;EAErC;EACA,OAAO;CACT;CACA,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,WAC7E,OAAO;CAIT,OAAO,OAAO,KAAK;AACrB;;;;;;;;AAmBA,SAAgB,OAAO,QAAmB,QAA4B;CACpE,MAAM,QAAwB,CAAC,OAAO,MAAM,OAAO,SAAS;CAC5D,IAAI,OAAO,OAAO,KAAA,GAChB,MAAM,KAAK,OAAO,EAAE;CAEtB,IAAI,OAAO,QAAQ,KAAA,GACjB,MAAM,KAAK,OAAO,GAAG;CAEvB,IAAI,WAAW,KAAA,GAAW;EACxB,MAAM,aAAa,gBAAgB,MAAM;EAGzC,IAAI,CAAC,kBAAkB,UAAU,GAC/B,MAAM,KAAK,UAA0D;CAEzE;CACA,OAAO;AACT;;AAGA,SAAS,kBAAkB,OAAkC;CAC3D,IAAI,UAAU,MACZ,OAAO;CAET,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,WAAW;CAE1B,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW;CAEvC,OAAO;AACT;;AAKA,SAAgB,QAAQ,MAAiC;CACvD,OAAO,CAAC,IAAI;AACd;;AAGA,SAAgB,aAAa,MAAc,WAAqD;CAC9F,OAAO,CAAC,MAAM,SAAS;AACzB;;;;;;AAOA,SAAgB,YAAY,MAAc,IAA+C;CACvF,OAAO;EAAC;EAAM;EAAY;CAAE;AAC9B;;;;;;;;AASA,SAAgB,YACd,MACA,WACA,IACA,KAC2C;CAC3C,OAAO;EAAC;EAAM;EAAW;EAAI;CAAG;AAClC;;;;;;;;;;AAWA,SAAgB,iBACd,MACA,IACA,KACiG;CACjG,OAAO,CACL,YAAY,MAAM,gBAAgB,IAAI,GAAG,GACzC,YAAY,MAAM,qBAAqB,IAAI,GAAG,CAChD;AACF;;;;;;;AAQA,SAAgB,aAAa,KAAe,QAA2B;CACrE,IAAI,IAAI,SAAS,OAAO,QACtB,OAAO;CAET,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KACjC,IAAI,IAAI,OAAO,OAAO,IACpB,OAAO;CAGX,OAAO;AACT;;;AClKA,MAAMA,iBAAe,MAAc,OAA4B,GAAG,KAAK,GAAG;AAE1E,MAAM,YAAY,MAChB,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;;AA0BzD,SAAS,SAAS,MAAsE;CACtF,MAAM,EAAE,MAAM,OAAO;CACrB,IAAI,OAAO,SAAS,YAAY,OAAO,OAAO,UAC5C,OAAO;EAAE;EAAM;CAAG;AAGtB;;;;;;;;AASA,SAAS,aAAa,MAAiE;CACrF,MAAM,QAAQ,KAAK,MAAM;CACzB,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AACnC;;;;;;;AAQA,SAAS,cACP,OACA,YACA,OACA,uBAAoB,IAAI,IAAI,GACtB;CACN,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B;CAEF,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,KAAK,MAAM,UAAU,OACnB,cAAc,QAAQ,YAAY,OAAO,IAAI;EAE/C;CACF;CACA,IAAI,CAAC,SAAS,KAAK,GACjB;CAEF,IAAI,KAAK,IAAI,KAAK,GAChB;CAEF,KAAK,IAAI,KAAK;CAEd,MAAM,OAAO;CACb,MAAM,QAAQ,SAAS,IAAI;CAC3B,IAAI,UAAU,KAAA,GACZ;CAEF,MAAM,IAAI;CAIV,MAAM,YAAY,WAAW,MAAM,KAAK,EAAE;CAC1C,IAAI,cAAc,KAAA,GAChB;CAEF,KAAK,MAAM,QAAQ,OAAO,KAAK,SAAS,GAAG;EACzC,MAAM,OAAQ,MAAkC;EAChD,IAAI,SAAS,KAAA,GACX,cAAc,MAAM,YAAY,OAAO,IAAI;CAE/C;AACF;;;;;;;;;;;;;;AAeA,SAAS,UAAU,QAAiC,YAA8C;CAChG,IAAI,UAAU;CACd,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,UAAU,GAC5C,IAAI,EAAE,KAAK,WAAW,OAAO,OAAO,GAAG;EACrC,OAAO,KAAK;EACZ,UAAU;CACZ;CAEF,OAAO;AACT;;;;;;AAOA,SAAS,kBACP,QACA,YAC2C;CAC3C,MAAM,wBAAQ,IAAI,IAA0C;CAC5D,cAAc,QAAQ,aAAa,SAAS;EAC1C,MAAM,QAAQ,SAAS,IAAI;EAC3B,IAAI,UAAU,KAAA,GACZ;EAEF,MAAM,aAAa,aAAa,IAAI;EACpC,IAAI,eAAe,KAAA,GAGjB,MAAM,IAAIA,cAAY,MAAM,MAAM,MAAM,EAAE,GAAG,UAAU;CAE3D,CAAC;CACD,OAAO;AACT;;;;;;;;;AAUA,SAAS,YACP,QACA,OACA,YACS;CACT,IAAI,UAAU;CACd,cAAc,QAAQ,aAAa,SAAS;EAC1C,MAAM,QAAQ,SAAS,IAAI;EAC3B,IAAI,UAAU,KAAA,GACZ;EAEF,MAAM,aAAa,MAAM,IAAIA,cAAY,MAAM,MAAM,MAAM,EAAE,CAAC;EAC9D,IACE,eAAe,KAAA,KACf,UAAU,MAA4C,UAAU,GAEhE,UAAU;CAEd,CAAC;CACD,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,UACd,aACA,QACA,YACM;CACN,MAAM,QAAQ,kBAAkB,QAAQ,UAAU;CAClD,IAAI,MAAM,SAAS,GACjB;CAEF,SAAS,aAAa,OAAO,UAAU;AACzC;;;;;;;;;;;AAYA,SAAS,SACP,aACA,OACA,YACM;CACN,KAAK,MAAM,SAAS,YAAY,cAAc,CAAC,CAAC,OAAO,GAAG;EACxD,MAAM,OAAO,MAAM,MAAM;EACzB,IAAI,SAAS,KAAA,KAAa,SAAS,MACjC;EAEF,IAAI,YAAY,MAAM,OAAO,UAAU,GAGrC,YAAY,aAAa,MAAM,UAAU,IAAI;CAEjD;AACF;;;;;;;;AAsCA,SAAgB,qBACd,aACA,MACA,IACA,YACA,YACU;CACV,MAAM,MAAMA,cAAY,MAAM,EAAE;CAChC,MAAM,QAAoB,CAAC;CAK3B,KAAK,MAAM,SAAS,YAAY,cAAc,CAAC,CAAC,OAAO,GAAG;EACxD,MAAM,OAAO,MAAM,MAAM;EACzB,IAAI,SAAS,KAAA,KAAa,SAAS,MACjC;EAEF,IAAI,UAAU;EACd,cAAc,MAAM,aAAa,SAAS;GACxC,MAAM,QAAQ,SAAS,IAAI;GAC3B,IAAI,UAAU,KAAA,KAAaA,cAAY,MAAM,MAAM,MAAM,EAAE,MAAM,KAC/D;GAEF,MAAM,SAAS;GAEf,MAAM,wBAAQ,IAAI,IAAqB;GACvC,MAAM,wBAAQ,IAAI,IAAY;GAC9B,KAAK,MAAM,KAAK,OAAO,KAAK,UAAU,GACpC,IAAI,KAAK,QACP,MAAM,IAAI,GAAG,OAAO,EAAE;QAEtB,MAAM,IAAI,CAAC;GAGf,MAAM,KAAK;IAAE;IAAQ;IAAO;IAAO,OAAO,EAAE,GAAG,WAAW;GAAE,CAAC;GAC7D,UAAU,QAAQ,UAAU;GAC5B,UAAU;EACZ,CAAC;EACD,IAAI,SACF,YAAY,aAAa,MAAM,UAAU,IAAI;CAEjD;CAEA,OAAO,EACL,UAAU;EACR,KAAK,MAAM,QAAQ,OAAO;GACxB,KAAK,MAAM,CAAC,GAAG,MAAM,KAAK,OAIxB,IAAI,KAAK,OAAO,OAAO,KAAK,MAAM,IAChC,KAAK,OAAO,KAAK;GAGrB,KAAK,MAAM,KAAK,KAAK,OAEnB,IAAI,KAAK,OAAO,OAAO,KAAK,MAAM,IAChC,OAAO,KAAK,OAAO;EAGzB;CACF,EACF;AACF;;;;;;;;;;;;AAoCA,SAAgB,0BACd,aACA,MACA,IACA,KACA,WACU;CACV,MAAM,WAAW,iBAAiB,MAAM,IAAI,GAAG;CAC/C,MAAM,QAAgD,CAAC;CAEvD,KAAK,MAAM,SAAS,YAAY,cAAc,CAAC,CAAC,OAAO,GAAG;EACxD,MAAM,MAAM,MAAM;EAClB,IAAI,CAAC,SAAS,MAAM,WAAW,aAAa,KAAK,MAAM,CAAC,GACtD;EAEF,MAAM,OAAO,MAAM,MAAM;EACzB,IAAI,CAAC,MAAM,QAAQ,IAAI,GAErB;EAEF,MAAM,KAAK;GAAE;GAAK,UAAU;EAAK,CAAC;EAIlC,YAAY,aAAa,KAAK,UAAU,IAAgC,CAAC;CAC3E;CAEA,OAAO,EACL,UAAU;EACR,KAAK,MAAM,QAAQ,OACjB,YAAY,aAAa,KAAK,KAAK,KAAK,QAAQ;CAEpD,EACF;AACF;;AAGA,SAAgB,QAAQ,GAAkB,GAA2B;CACnE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE;AACzC;;;;;;;;;AAUA,SAAgB,qBACd,aACA,MACA,IACA,KACA,MACU;CACV,MAAM,WAAW,iBAAiB,MAAM,IAAI,GAAG;CAC/C,MAAM,QAAgD,CAAC;CACvD,IAAI,SAAS,KAAA,GACX,OAAO,EAAE,UAAU,CAAC,EAAE;CAGxB,KAAK,MAAM,SAAS,YAAY,cAAc,CAAC,CAAC,OAAO,GAAG;EACxD,MAAM,MAAM,MAAM;EAClB,IAAI,CAAC,SAAS,MAAM,WAAW,aAAa,KAAK,MAAM,CAAC,GACtD;EAEF,MAAM,OAAO,MAAM,MAAM;EAEzB,IAAI,MAAM,QAAQ,IAAI,GACpB;EAEF,MAAM,KAAK;GAAE;GAAK,UAAU;EAAK,CAAC;EAClC,YAAY,aAAa,KAAK,IAAI;CACpC;CAEA,OAAO,EACL,UAAU;EACR,KAAK,MAAM,QAAQ,OACjB,YAAY,aAAa,KAAK,KAAK,KAAK,QAAQ;CAEpD,EACF;AACF;;;;;;;;;;;;AC3cA,MAAM,4BAAY,IAAI,QAAoC;;;;;;;;;;;;;;;;;AAkB1D,SAAgB,qBACd,aACA,YACU;CACV,MAAM,WAAW,UAAU,IAAI,WAAW;CAC1C,IAAI,aAAa,KAAA,KAAa,aAAa,YACzC,MAAM,IAAI,MACR,6QAGF;CAEF,UAAU,IAAI,aAAa,UAAU;CAErC,IAAI,cAAc;CAElB,MAAM,OAAO,SAAwB;EACnC,IAAI,eAAe,SAAS,KAAA,KAAa,SAAS,MAChD;EAEF,cAAc;EACd,IAAI;GACF,UAAU,aAAa,MAAM,UAAU;EACzC,UAAU;GACR,cAAc;EAChB;CACF;CAEA,MAAM,qBAAqB,YAAY,cAAc,CAAC,CAAC,WAAW,UAAU;EAE1E,IAAI,MAAM,SAAS,aAAa,MAAM,OAAO,SAAS,WACpD,IAAI,MAAM,MAAM,MAAM,IAAI;CAE9B,CAAC;CAED,MAAM,uBAAuB,YAAY,iBAAiB,CAAC,CAAC,WAAW,UAAU;EAG/E,IAAI,MAAM,SAAS,aAAa,MAAM,OAAO,SAAS,WACpD,IAAI,MAAM,SAAS,MAAM,IAAI;CAEjC,CAAC;CAED,IAAI,OAAO;CACX,aAAa;EACX,IAAI,MACF;EAEF,OAAO;EACP,mBAAmB;EACnB,qBAAqB;EAIrB,IAAI,UAAU,IAAI,WAAW,MAAM,YACjC,UAAU,OAAO,WAAW;CAEhC;AACF;;;;;;;;ACSA,SAAS,gBAAgB,aAA0B,MAA6B;CAC9E,OAAO,YAAY,kBAAkB,EAAE,UAAU,aAAa,MAAM,WAAW,EAAE,CAAC;AACpF;;;;;;;;;;AAWA,SAAS,mBAAmB,aAA0B,MAAc,IAA2B;CAC7F,OAAO,QAAQ,IAAI;EACjB,YAAY,kBAAkB,EAAE,UAAU,YAAY,MAAM,EAAE,EAAE,CAAC;EACjE,YAAY,kBAAkB,EAAE,UAAU;GAAC;GAAM;GAAgB;EAAE,EAAE,CAAC;EACtE,YAAY,kBAAkB,EAAE,UAAU;GAAC;GAAM;GAAqB;EAAE,EAAE,CAAC;CAC7E,CAAC,CAAC,CAAC,WAAW,KAAA,CAAS;AACzB;;;;;;;;;;;;AAaA,SAAS,0BACP,aACA,MACA,IACA,KACe;CACf,OAAO,QAAQ,IAAI;EACjB,YAAY,kBAAkB,EAAE,UAAU,YAAY,MAAM,gBAAgB,IAAI,GAAG,EAAE,CAAC;EACtF,YAAY,kBAAkB,EAAE,UAAU,YAAY,MAAM,qBAAqB,IAAI,GAAG,EAAE,CAAC;EAC3F,YAAY,kBAAkB,EAAE,UAAU,YAAY,MAAM,EAAE,EAAE,CAAC;CACnE,CAAC,CAAC,CAAC,WAAW,KAAA,CAAS;AACzB;;;;;;;;;;;AAcA,SAAgB,sBAQd,aACA,QACA,YACA,MACA,MACoE;CACpE,MAAM,WAAW,OAAO;CACxB,OAAO;EACL,aAAa,UAAU,SAAS,OAAe,OAAO,IAAI;EAC1D,YAAY,SAAS;GACnB,UAAU,aAAa,MAAM,UAAU;EACzC;EACA,iBAAiB,gBAAgB,aAAa,IAAI;CACpD;AACF;;;;;;;;;;;AAYA,SAAgB,sBAQd,aACA,QACA,YACA,MACA,IACA,MACqF;CACrF,MAAM,WAAW,OAAO;CACxB,MAAM,aAAa,MAAM,eAAe;CACxC,MAAM,OAIF;EACF,aAAa,UAAU,SAAS,GAAG,EAAE,CAAC,CAAC,OAAe,OAAO,IAAI;EACjE,YAAY,SAAS;GACnB,UAAU,aAAa,MAAM,UAAU;EACzC;CACF;CACA,IAAI,CAAC,YACH,OAAO;CAIT,KAAK,YAAY,WAAW,EAC1B,UAAU,qBACR,aACA,MACA,IACA,oBAAoB,YAAY,MAAM,KAAK,GAC3C,UACF,EACF;CACA,KAAK,WAAW,QAAQ,QAAQ,YAAY;EAC1C,SAAS,SAAS,QAAQ;CAC5B;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,sBACd,aACA,QACA,MACA,IAC6B;CAC7B,MAAM,WAAW,OAAO;CACxB,OAAO;EACL,kBAAkB,SAAS,GAAG,EAAE,CAAC,CAAC,OAAO;EACzC,WAAW,YAAY;GACrB,MAAM,gBAAgB,aAAa,IAAI;GACvC,MAAM,mBAAmB,aAAa,MAAM,EAAE;EAChD;CACF;AACF;;AAiBA,MAAM,gBAAgB,SAA6C,MAAM,eAAe;;;;;;;;AASxF,SAAS,gBAAgB,KAAoD;CAC3E,MAAM,IAAI;CACV,IAAI,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,OAAO,UAChD;CAEF,OAAO;EAAE,GAAI;EAAiC,MAAM,EAAE;EAAM,IAAI,EAAE;CAAG;AACvE;;AAGA,MAAM,kBAAkB,SACtB,KAAK,IAAI,eAAe,CAAC,CAAC,QAAQ,MAA0B,MAAM,KAAA,CAAS;;;;;;;AAQ7E,SAAS,uBACP,aACA,MACA,IACA,KACA,MACA,WACA,YACkD;CAClD,IAAI,CAAC,YACH,OAAO;CAET,OAAO;EACL,GAAG;EACH,WAAW,UAAU,EACnB,UAAU,0BAA0B,aAAa,MAAM,IAAI,MAAM,YAC/D,UAAU,SAAS,IAAI,CACzB,EACF;EACA,UAAU,QAAQ,OAAO,YAAY;GACnC,SAAS,SAAS,QAAQ;EAC5B;CACF;AACF;;;;;;;;;;AAWA,SAAgB,+BAMd,aACA,QACA,YACA,MACA,IACA,KACA,MACsE;CACtE,MAAM,WAAW,OAAO;CACxB,MAAM,OAA6E;EACjF,aAAa,QAAQ,SAAS,GAAG,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG;EACrD,YAAY,SAAS;GACnB,UAAU,aAAa,MAAM,UAAU;EACzC;EACA,iBAAiB,0BAA0B,aAAa,MAAM,IAAI,GAAG;CACvE;CACA,IAAI,CAAC,aAAa,IAAI,GACpB,OAAO;CAET,OAAO;EACL,GAAG;EACH,WAAW,QAAQ;GACjB,MAAM,OAAO,QAAQ,OAAO,KAAA,IAAY,gBAAgB,GAAG;GAC3D,OAAO,EACL,UAAU,qBAAqB,aAAa,MAAM,IAAI,KAAK,QAAQ,OAAO,OAAO,IAAI,EACvF;EACF;EACA,UAAU,QAAQ,MAAM,YAAY;GAClC,SAAS,SAAS,QAAQ;EAC5B;CACF;AACF;;;;;;;;AASA,SAAgB,mCAMd,aACA,QACA,YACA,MACA,IACA,KACA,MAC0E;CAC1E,MAAM,WAAW,OAAO;CASxB,OAAO,uBACL,aACA,MACA,IACA,KACA;EAZA,aAAa,SAAS,SAAS,GAAG,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,IAAI;EAC3D,YAAY,SAAS;GACnB,UAAU,aAAa,MAAM,UAAU;EACzC;EACA,iBAAiB,0BAA0B,aAAa,MAAM,IAAI,GAAG;CAQlE,IACF,UAAU,SAAS,eAAe,IAAI,GACvC,aAAa,IAAI,CACnB;AACF;;;;;;;;AASA,SAAgB,+BAMd,aACA,QACA,MACA,IACA,KACA,MAC0E;CAC1E,MAAM,WAAW,OAAO;CAKxB,OAAO,uBACL,aACA,MACA,IACA,KACA;EARA,aAAa,SAAS,SAAS,GAAG,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI;EACvD,iBAAiB,0BAA0B,aAAa,MAAM,IAAI,GAAG;CAOlE,IACF,SAAS,SAAS;EACjB,MAAM,QAAQ,eAAe,IAAI;EAGjC,OAAO,CAAC,GADK,QAAQ,QAAQ,MAAM,CAAC,MAAM,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,CACrD,GAAG,GAAG,KAAK;CAC3B,GACA,aAAa,IAAI,CACnB;AACF;;;;;;;AAQA,SAAgB,kCAMd,aACA,QACA,MACA,IACA,KACA,MAC0E;CAC1E,MAAM,WAAW,OAAO;CAKxB,OAAO,uBACL,aACA,MACA,IACA,KACA;EARA,aAAa,SAAS,SAAS,GAAG,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,OAAO,IAAI;EAC1D,iBAAiB,0BAA0B,aAAa,MAAM,IAAI,GAAG;CAOlE,IACF,SAAS,SAAS;EACjB,MAAM,UAAU,eAAe,IAAI;EACnC,OAAO,QAAQ,QAAQ,MAAM,CAAC,QAAQ,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC;CAClE,GACA,aAAa,IAAI,CACnB;AACF;;;;;;;;;;;;;;AAiBA,SAAgB,sBACd,aACA,QACA,YAMA;CACA,OAAO;EACL,aAAa,UAIV,OAA0E,OAAO,KAAK;EACzF,YAAY,SAAS;GACnB,UAAU,aAAa,MAAM,UAAU;EACzC;CACF;AACF;;;;;;;AAQA,SAAS,oBACP,YACA,MACA,OACyB;CACzB,MAAM,aAAa,WAAW,KAAK,EAAE,cAAc,CAAC;CACpD,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAC7C,IAAI,OAAO,YACT,IAAI,OAAO;CAGf,OAAO;AACT;;;;;;;;AA+DA,SAAgB,kBACd,aACA,QACA,YACsB;CACtB,MAAM,wBAAQ,IAAI,IAAmD;CACrE,OAAO,IAAI,MAAM,OAAO,OAAO,IAAI,GAAa,EAC9C,IAAI,SAAS,MAAM;EACjB,IAAI,OAAO,SAAS,UAClB;EAEF,IAAI,MAAM,MAAM,IAAI,IAAI;EACxB,IAAI,QAAQ,KAAA,GAAW;GACrB,MAAM,gBAAgB,aAAa,QAAQ,YAAY,IAAmB;GAC1E,MAAM,IAAI,MAAM,GAAG;EACrB;EACA,OAAO;CACT,EACF,CAAC;AACH;;AAGA,SAAS,gBACP,aACA,QACA,YACA,MAC6B;CAC7B,OAAO;EACL,SAAS,SAAS,sBAAsB,aAAa,QAAQ,YAAY,MAAM,IAAI;EACnF,KAAK,QAAQ;GACX,SAAS,SAAS,sBAAsB,aAAa,QAAQ,YAAY,MAAM,IAAI,IAAI;GACvF,cAAc,sBAAsB,aAAa,QAAQ,MAAM,EAAE;GACjE,MAAM,UAAU;IACd,MAAM,SAAS,+BAA+B,aAAa,QAAQ,MAAM,IAAI,MAAM,IAAI;IACvF,SAAS,SACP,kCAAkC,aAAa,QAAQ,MAAM,IAAI,MAAM,IAAI;IAC7E,UAAU,SACR,mCAAmC,aAAa,QAAQ,YAAY,MAAM,IAAI,MAAM,IAAI;IAC1F,MAAM,SACJ,+BAA+B,aAAa,QAAQ,YAAY,MAAM,IAAI,MAAM,IAAI;GACxF;EACF;CACF;AACF;;;;;;;;ACnkBA,SAAgB,iBAOd,QACA,MACA,OACuD;CACvD,MAAM,WAAW,OAAO;CACxB,OAAO;EACL,UAAU,OAAO;GAAE;GAAM,WAAW;EAAY,GAAG,KAAK;EACxD,eAAe,SAAS,KAAa,KAAK;CAC5C;AACF;;;;;;;AAQA,SAAgB,gBAOd,QACA,MACA,IACA,OAC2C;CAC3C,MAAM,WAAW,OAAO;CACxB,OAAO;EACL,UAAU,OAAO;GAAE;GAAM,WAAW;GAAY;EAAG,GAAG,KAAK;EAC3D,eAAe,SAAS,IAAY,IAAI,KAAK;CAC/C;AACF;;;;;;;;AASA,SAAgB,yBAMd,QACA,MACA,IACA,KACA,OACyC;CACzC,MAAM,WAAW,OAAO;CACxB,OAAO;EACL,UAAU,OAAO;GAAE;GAAM,WAAW;GAAqB;GAAI;EAAI,GAAG,KAAK;EACzE,eAAe,SAAS,GAAG,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,KAAK;CACnD;AACF;;;;;;;AAQA,SAAgB,oBAMd,QACA,MACA,IACA,KACA,OACyC;CACzC,MAAM,WAAW,OAAO;CACxB,OAAO;EACL,UAAU,OAAO;GAAE;GAAM,WAAW;GAAgB;GAAI;EAAI,GAAG,KAAK;EACpE,eAAe,SAAS,GAAG,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,QAAQ,KAAK;CACvD;AACF;;;;;;;AAoCA,SAAgB,eACd,QACgB;CAChB,MAAM,wBAAQ,IAAI,IAA6C;CAC/D,OAAO,IAAI,MAAM,OAAO,OAAO,IAAI,GAAa,EAC9C,IAAI,SAAS,MAAM;EACjB,IAAI,OAAO,SAAS,UAClB;EAEF,IAAI,MAAM,MAAM,IAAI,IAAI;EACxB,IAAI,QAAQ,KAAA,GAAW;GACrB,MAAM,aAAa,QAAQ,IAAmB;GAC9C,MAAM,IAAI,MAAM,GAAG;EACrB;EACA,OAAO;CACT,EACF,CAAC;AACH;;AAGA,SAAS,aACP,QACA,MACuB;CACvB,OAAO;EACL,OAAO,UAAU,iBAAiB,QAAQ,MAAM,KAAK;EACrD,MAAM,IAAI,UAAU,gBAAgB,QAAQ,MAAM,IAAI,KAAK;EAC3D,aAA2C,IAAY,KAAQ,OAA2B;GACxF,OAAO,yBAAyB,QAAQ,MAAM,IAAI,KAAK,KAAK;EAC9D;EACA,QAAsC,IAAY,KAAQ,OAA2B;GACnF,OAAO,oBAAoB,QAAQ,MAAM,IAAI,KAAK,KAAK;EACzD;CACF;AACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haddowg/json-api-query",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TanStack Query bindings for the JSON:API client: option/key factories + type:id cache normalization.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Gregory Haddow (https://github.com/haddowg)",
|
|
7
|
+
"homepage": "https://haddowg.github.io/json-api-ts/",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/haddowg/json-api-ts.git",
|
|
11
|
+
"directory": "packages/query"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/haddowg/json-api-ts/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"json-api",
|
|
16
|
+
"jsonapi",
|
|
17
|
+
"typescript",
|
|
18
|
+
"tanstack-query",
|
|
19
|
+
"react-query",
|
|
20
|
+
"cache",
|
|
21
|
+
"normalization",
|
|
22
|
+
"openapi"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"default": "./dist/index.mjs"
|
|
31
|
+
},
|
|
32
|
+
"require": {
|
|
33
|
+
"types": "./dist/index.d.cts",
|
|
34
|
+
"default": "./dist/index.cjs"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"main": "./dist/index.cjs",
|
|
39
|
+
"module": "./dist/index.mjs",
|
|
40
|
+
"types": "./dist/index.d.cts",
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"provenance": true
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@haddowg/json-api-client": "^1.0.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@tanstack/query-core": "^5.0.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@tanstack/query-core": "^5.0.0"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsdown",
|
|
62
|
+
"typecheck": "tsc --noEmit",
|
|
63
|
+
"lint:publish": "publint --strict && attw --pack ."
|
|
64
|
+
}
|
|
65
|
+
}
|