fetchium 0.2.0 → 0.2.2
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/CHANGELOG.md +14 -0
- package/dist/cjs/development/QueryAdapter-DUo338ga.js.map +1 -1
- package/dist/cjs/development/QueryClient-BZpqASy3.js +2 -0
- package/dist/cjs/development/QueryClient-BZpqASy3.js.map +1 -0
- package/dist/cjs/development/index.js +1 -1
- package/dist/cjs/{production/mutation-Dk0gznwX.js → development/mutation-COeBCn7p.js} +2 -2
- package/dist/cjs/development/mutation-COeBCn7p.js.map +1 -0
- package/dist/cjs/development/react/index.js +1 -1
- package/dist/cjs/development/rest/index.js +1 -1
- package/dist/cjs/development/topic/index.js +1 -1
- package/dist/cjs/development/topic/index.js.map +1 -1
- package/dist/cjs/production/QueryAdapter-DUo338ga.js.map +1 -1
- package/dist/cjs/production/QueryClient-BmoHLlvu.js +2 -0
- package/dist/cjs/production/QueryClient-BmoHLlvu.js.map +1 -0
- package/dist/cjs/production/index.js +1 -1
- package/dist/cjs/{development/mutation-wUhcGxKl.js → production/mutation-CfFdNkYV.js} +2 -2
- package/dist/cjs/production/mutation-CfFdNkYV.js.map +1 -0
- package/dist/cjs/production/react/index.js +1 -1
- package/dist/cjs/production/rest/index.js +1 -1
- package/dist/cjs/production/topic/index.js +1 -1
- package/dist/cjs/production/topic/index.js.map +1 -1
- package/dist/esm/QueryAdapter.d.ts +11 -0
- package/dist/esm/QueryAdapter.d.ts.map +1 -1
- package/dist/esm/QueryClient.d.ts +5 -5
- package/dist/esm/QueryClient.d.ts.map +1 -1
- package/dist/esm/QueryResult.d.ts.map +1 -1
- package/dist/esm/development/QueryAdapter-Bu5UJjE4.js.map +1 -1
- package/dist/esm/development/{QueryClient-BajBmpnA.js → QueryClient-4co72n4i.js} +636 -628
- package/dist/esm/development/QueryClient-4co72n4i.js.map +1 -0
- package/dist/esm/development/index.js +2 -2
- package/dist/esm/development/{mutation-DAOZE4Ok.js → mutation-BMAWDUP4.js} +2 -2
- package/dist/esm/development/mutation-BMAWDUP4.js.map +1 -0
- package/dist/esm/development/react/index.js +1 -1
- package/dist/esm/development/rest/index.js +2 -2
- package/dist/esm/development/topic/index.js +1 -1
- package/dist/esm/development/topic/index.js.map +1 -1
- package/dist/esm/mutation.d.ts +3 -3
- package/dist/esm/mutation.d.ts.map +1 -1
- package/dist/esm/production/QueryAdapter-Bu5UJjE4.js.map +1 -1
- package/dist/esm/production/{QueryClient-KH0Ex_8m.js → QueryClient-CS4iUKWj.js} +782 -774
- package/dist/esm/production/QueryClient-CS4iUKWj.js.map +1 -0
- package/dist/esm/production/index.js +2 -2
- package/dist/esm/production/{mutation-C7BOChR2.js → mutation-B1EiA34B.js} +2 -2
- package/dist/esm/production/mutation-B1EiA34B.js.map +1 -0
- package/dist/esm/production/react/index.js +1 -1
- package/dist/esm/production/rest/index.js +2 -2
- package/dist/esm/production/topic/index.js +1 -1
- package/dist/esm/production/topic/index.js.map +1 -1
- package/dist/esm/query.d.ts +3 -3
- package/dist/esm/query.d.ts.map +1 -1
- package/dist/esm/retry.d.ts.map +1 -1
- package/dist/esm/topic/TopicQuery.d.ts +2 -1
- package/dist/esm/topic/TopicQuery.d.ts.map +1 -1
- package/package.json +1 -1
- package/plugin/docs/api/fetchium.md +2 -2
- package/plugin/docs/api/stores-async.md +9 -3
- package/plugin/docs/api/stores-sync.md +9 -3
- package/plugin/docs/core/entities.md +2 -2
- package/plugin/docs/core/queries.md +12 -19
- package/plugin/docs/data/mutations.md +1 -1
- package/plugin/docs/guides/auth.md +65 -42
- package/plugin/docs/guides/error-handling.md +9 -5
- package/plugin/docs/guides/offline.md +11 -8
- package/plugin/docs/guides/testing.md +1 -1
- package/plugin/docs/quickstart.md +1 -1
- package/plugin/docs/reference/rest-queries.md +9 -9
- package/plugin/docs/setup/project-setup.md +5 -5
- package/dist/cjs/development/QueryClient-m7BzCIe9.js +0 -2
- package/dist/cjs/development/QueryClient-m7BzCIe9.js.map +0 -1
- package/dist/cjs/development/mutation-wUhcGxKl.js.map +0 -1
- package/dist/cjs/production/QueryClient-4T90peFN.js +0 -2
- package/dist/cjs/production/QueryClient-4T90peFN.js.map +0 -1
- package/dist/cjs/production/mutation-Dk0gznwX.js.map +0 -1
- package/dist/esm/development/QueryClient-BajBmpnA.js.map +0 -1
- package/dist/esm/development/mutation-DAOZE4Ok.js.map +0 -1
- package/dist/esm/production/QueryClient-KH0Ex_8m.js.map +0 -1
- package/dist/esm/production/mutation-C7BOChR2.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as r, E as s, G as t, L as o, a as n, M as i, N as M, b as g, c as u, d as y, e as f, Q as C, f as N, g as Q, h as d, R as k, i as l, j as p, k as m, q as E, r as R, t as w } from "./QueryClient-
|
|
1
|
+
import { A as r, E as s, G as t, L as o, a as n, M as i, N as M, b as g, c as u, d as y, e as f, Q as C, f as N, g as Q, h as d, R as k, i as l, j as p, k as m, q as E, r as R, t as w } from "./QueryClient-CS4iUKWj.js";
|
|
2
2
|
import { Q as F } from "./QueryAdapter-Bu5UJjE4.js";
|
|
3
|
-
import { M as A, g as K, m as Y } from "./mutation-
|
|
3
|
+
import { M as A, g as K, m as Y } from "./mutation-B1EiA34B.js";
|
|
4
4
|
export {
|
|
5
5
|
r as ARRAY_KEY,
|
|
6
6
|
s as Entity,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getContext as h } from "signalium";
|
|
2
|
-
import { l as y, h as D, m as w, V as u, t as d } from "./QueryClient-
|
|
2
|
+
import { l as y, h as D, m as w, V as u, t as d } from "./QueryClient-CS4iUKWj.js";
|
|
3
3
|
class x {
|
|
4
4
|
static adapter;
|
|
5
5
|
params;
|
|
@@ -55,4 +55,4 @@ export {
|
|
|
55
55
|
S as g,
|
|
56
56
|
C as m
|
|
57
57
|
};
|
|
58
|
-
//# sourceMappingURL=mutation-
|
|
58
|
+
//# sourceMappingURL=mutation-B1EiA34B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation-B1EiA34B.js","sources":["../../../src/mutation.ts"],"sourcesContent":["import { getContext, ReactiveTask } from 'signalium';\nimport { ExtractType, InternalTypeDef, MutationEffects, TypeDef, RetryConfig, TypeDefShape } from './types.js';\nimport { QueryClientContext, type QueryContext } from './QueryClient.js';\nimport { ValidatorDef, t } from './typeDefs.js';\nimport { createDefinitionProxy, extractDefinition, type CapturedDefinition } from './fieldRef.js';\nimport type { QueryAdapter, QueryAdapterClass } from './QueryAdapter.js';\n\n// ================================\n// Mutation Definition Types\n// ================================\n\nexport interface MutationConfigOptions {\n retry?: RetryConfig | number | false;\n}\n\nexport interface MutationDefinition<Request, Response> {\n id: string;\n requestShape: InternalTypeDef;\n responseShape: InternalTypeDef | undefined;\n captured: CapturedDefinition<Mutation>;\n optimisticUpdates: boolean;\n config?: MutationConfigOptions;\n effects?: MutationEffects;\n hasGetEffects: boolean;\n adapterClass: QueryAdapterClass;\n}\n\n// ================================\n// Mutation base class\n// ================================\n\nexport abstract class Mutation {\n static adapter?: QueryAdapterClass;\n\n readonly params?: TypeDefShape;\n readonly result?: TypeDefShape;\n readonly optimisticUpdates?: boolean;\n readonly config?: MutationConfigOptions;\n readonly effects?: Readonly<MutationEffects>;\n\n declare context: QueryContext;\n\n abstract getIdentityKey(): unknown;\n\n getEffects?(): MutationEffects;\n\n constructor() {\n return createDefinitionProxy(this);\n }\n}\n\n// ================================\n// Mutation definition cache and lookup\n// ================================\n\nconst mutationDefCache = new WeakMap<new () => Mutation, () => MutationDefinition<any, any>>();\n\nexport const mutationKeyForClass = (cls: new () => Mutation): string => {\n const getMutationDef = mutationDefCache.get(cls);\n\n if (getMutationDef === undefined) {\n throw new Error('Mutation definition not found');\n }\n\n return getMutationDef().id;\n};\n\n// ================================\n// Internal: build mutation definition from class\n// ================================\n\nfunction buildMutationDefinition(MutationClass: new () => Mutation): () => MutationDefinition<any, any> {\n let cached = mutationDefCache.get(MutationClass);\n\n if (cached !== undefined) {\n return cached;\n }\n\n let mutationDefinition: MutationDefinition<any, any> | undefined;\n\n const getter = (): MutationDefinition<any, any> => {\n if (mutationDefinition !== undefined) {\n return mutationDefinition;\n }\n\n const instance = new MutationClass();\n const captured = extractDefinition(instance);\n const { fields } = captured;\n\n const id = `mutation:${String(captured.methods.getIdentityKey.call(fields))}`;\n\n const requestDef = fields.params ?? {};\n const requestShape = (requestDef instanceof ValidatorDef\n ? requestDef\n : t.object(requestDef)) as unknown as InternalTypeDef;\n const responseDef = fields.result;\n const responseShape =\n responseDef !== undefined\n ? ((responseDef instanceof ValidatorDef ? responseDef : t.object(responseDef)) as unknown as InternalTypeDef)\n : undefined;\n\n const adapterClass = (MutationClass as typeof Mutation).adapter;\n if (!adapterClass) {\n throw new Error(\n `Mutation class \"${MutationClass.name}\" must define a static \\`adapter\\` property. ` +\n `Extend RESTMutation (from fetchium/rest) or set \\`static adapter = MyAdapter\\` on your mutation class.`,\n );\n }\n\n mutationDefinition = {\n id,\n requestShape,\n responseShape,\n captured,\n optimisticUpdates: fields.optimisticUpdates ?? false,\n config: fields.config,\n effects: fields.effects,\n hasGetEffects: typeof captured.methods.getEffects === 'function',\n adapterClass,\n };\n\n return mutationDefinition;\n };\n\n mutationDefCache.set(MutationClass, getter);\n return getter;\n}\n\n// ================================\n// Public API\n// ================================\n\nexport function getMutation<T extends Mutation>(\n MutationClass: new () => T,\n): ReactiveTask<Readonly<ExtractType<T['result']>>, [ExtractType<T['params']>]> {\n const getMutationDef = buildMutationDefinition(MutationClass);\n\n const queryClient = getContext(QueryClientContext);\n\n if (queryClient === undefined) {\n throw new Error('QueryClient not found');\n }\n\n return queryClient.getMutation<any, any>(getMutationDef());\n}\n"],"names":["Mutation","createDefinitionProxy","mutationDefCache","mutationKeyForClass","cls","getMutationDef","buildMutationDefinition","MutationClass","cached","mutationDefinition","getter","instance","captured","extractDefinition","fields","id","requestDef","requestShape","ValidatorDef","t","responseDef","responseShape","adapterClass","getMutation","queryClient","getContext","QueryClientContext"],"mappings":";;AA+BO,MAAeA,EAAS;AAAA,EAC7B,OAAO;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAQT,cAAc;AACZ,WAAOC,EAAsB,IAAI;AAAA,EACnC;AACF;AAMA,MAAMC,wBAAuB,QAAA,GAEhBC,IAAsB,CAACC,MAAoC;AACtE,QAAMC,IAAiBH,EAAiB,IAAIE,CAAG;AAE/C,MAAIC,MAAmB;AACrB,UAAM,IAAI,MAAM,+BAA+B;AAGjD,SAAOA,IAAiB;AAC1B;AAMA,SAASC,EAAwBC,GAAuE;AACtG,MAAIC,IAASN,EAAiB,IAAIK,CAAa;AAE/C,MAAIC,MAAW;AACb,WAAOA;AAGT,MAAIC;AAEJ,QAAMC,IAAS,MAAoC;AACjD,QAAID,MAAuB;AACzB,aAAOA;AAGT,UAAME,IAAW,IAAIJ,EAAA,GACfK,IAAWC,EAAkBF,CAAQ,GACrC,EAAE,QAAAG,MAAWF,GAEbG,IAAK,YAAY,OAAOH,EAAS,QAAQ,eAAe,KAAKE,CAAM,CAAC,CAAC,IAErEE,IAAaF,EAAO,UAAU,CAAA,GAC9BG,IAAgBD,aAAsBE,IACxCF,IACAG,EAAE,OAAOH,CAAU,GACjBI,IAAcN,EAAO,QACrBO,IACJD,MAAgB,SACVA,aAAuBF,IAAeE,IAAcD,EAAE,OAAOC,CAAW,IAC1E,QAEAE,IAAgBf,EAAkC;AACxD,QAAI,CAACe;AACH,YAAM,IAAI;AAAA,QACR,mBAAmBf,EAAc,IAAI;AAAA,MAAA;AAKzC,WAAAE,IAAqB;AAAA,MACnB,IAAAM;AAAA,MACA,cAAAE;AAAA,MACA,eAAAI;AAAA,MACA,UAAAT;AAAA,MACA,mBAAmBE,EAAO,qBAAqB;AAAA,MAC/C,QAAQA,EAAO;AAAA,MACf,SAASA,EAAO;AAAA,MAChB,eAAe,OAAOF,EAAS,QAAQ,cAAe;AAAA,MACtD,cAAAU;AAAA,IAAA,GAGKb;AAAA,EACT;AAEA,SAAAP,EAAiB,IAAIK,GAAeG,CAAM,GACnCA;AACT;AAMO,SAASa,EACdhB,GAC8E;AAC9E,QAAMF,IAAiBC,EAAwBC,CAAa,GAEtDiB,IAAcC,EAAWC,CAAkB;AAEjD,MAAIF,MAAgB;AAClB,UAAM,IAAI,MAAM,uBAAuB;AAGzC,SAAOA,EAAY,YAAsBnB,GAAgB;AAC3D;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as v, o as U, f as O } from "../QueryClient-
|
|
1
|
+
import { n as v, o as U, f as O } from "../QueryClient-CS4iUKWj.js";
|
|
2
2
|
import { Q as T } from "../QueryAdapter-Bu5UJjE4.js";
|
|
3
|
-
import { M as w } from "../mutation-
|
|
3
|
+
import { M as w } from "../mutation-B1EiA34B.js";
|
|
4
4
|
class P extends T {
|
|
5
5
|
_fetch;
|
|
6
6
|
_baseUrl;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/topic/TopicQuery.ts","../../../../src/topic/TopicQueryAdapter.ts"],"sourcesContent":["import { Query } from '../query.js';\nimport type { TopicQueryAdapter } from './TopicQueryAdapter.js';\nimport type { QueryConfigOptions } from '../query-types.js';\n\n// ================================\n// TopicQuery — declarative topic-based query definition\n// ================================\n\nexport abstract class TopicQuery extends Query {\n static override adapter:
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/topic/TopicQuery.ts","../../../../src/topic/TopicQueryAdapter.ts"],"sourcesContent":["import { Query } from '../query.js';\nimport type { TopicQueryAdapter } from './TopicQueryAdapter.js';\nimport type { QueryAdapterClass } from '../QueryAdapter.js';\nimport type { QueryConfigOptions } from '../query-types.js';\n\n// ================================\n// TopicQuery — declarative topic-based query definition\n// ================================\n\nexport abstract class TopicQuery extends Query {\n static override adapter: QueryAdapterClass<TopicQueryAdapter>;\n\n abstract topic: string;\n\n getIdentityKey(): string {\n return `topic:${this.topic}`;\n }\n\n getConfig(): QueryConfigOptions {\n return {\n staleTime: 0,\n subscribe: () => {\n return () => {\n const adapter = (this as Record<string, any>)._topicAdapter as TopicQueryAdapter | undefined;\n adapter?.unsubscribe(this.topic);\n };\n },\n };\n }\n}\n","import { QueryAdapter } from '../QueryAdapter.js';\nimport type { Query } from '../query.js';\nimport type { MutationEvent } from '../types.js';\n\n// ================================\n// TopicQueryAdapter — abstract adapter for topic-based subscriptions\n// ================================\n\ninterface TopicCtx extends Query {\n topic: string;\n _topicAdapter?: TopicQueryAdapter;\n}\n\ninterface TopicState {\n status: 'pending' | 'fulfilled' | 'rejected';\n promise?: Promise<unknown>;\n resolve?: (data: unknown) => void;\n reject?: (error: unknown) => void;\n data?: unknown;\n error?: unknown;\n}\n\nexport abstract class TopicQueryAdapter extends QueryAdapter {\n private _topics = new Map<string, TopicState>();\n\n /**\n * Called when a query activates for a given topic.\n * Implementations should start delivering data for this topic,\n * calling `fulfillTopic()` when initial data is available and\n * `sendMutationEvent()` for ongoing updates.\n */\n abstract subscribe(topic: string): void;\n\n /**\n * Called when the query deactivates. Implementations should\n * tear down any resources for this topic.\n */\n abstract unsubscribe(topic: string): void;\n\n /**\n * Resolve the pending promise for a topic with initial data.\n * Can be called before `send()` — the data will be picked up\n * when the query activates.\n */\n protected fulfillTopic(topic: string, data: unknown): void {\n const state = this._topics.get(topic);\n\n if (state === undefined) {\n this._topics.set(topic, { status: 'fulfilled', data });\n return;\n }\n\n if (state.status === 'pending') {\n state.status = 'fulfilled';\n state.data = data;\n state.resolve!(data);\n }\n }\n\n /**\n * Reject the pending promise for a topic.\n * Can be called before `send()` — the error will be propagated\n * when the query activates.\n */\n protected rejectTopic(topic: string, error: unknown): void {\n const state = this._topics.get(topic);\n\n if (state === undefined) {\n this._topics.set(topic, { status: 'rejected', error });\n return;\n }\n\n if (state.status === 'pending') {\n state.status = 'rejected';\n state.error = error;\n state.reject!(error);\n }\n }\n\n /**\n * Clears internal state for a topic. Called automatically by\n * `unsubscribe` — subclasses generally don't need to call this.\n */\n protected clearTopic(topic: string): void {\n this._topics.delete(topic);\n }\n\n protected clearAll(): void {\n this._topics.clear();\n }\n\n override async send(ctx: Query, _signal: AbortSignal): Promise<unknown> {\n const topicCtx = ctx as TopicCtx;\n topicCtx._topicAdapter = this;\n const topic = topicCtx.topic;\n\n const existing = this._topics.get(topic);\n\n if (existing) {\n switch (existing.status) {\n case 'fulfilled':\n return existing.data;\n case 'rejected':\n throw existing.error;\n case 'pending':\n return existing.promise;\n }\n }\n\n // No state yet — create a deferred and subscribe\n let resolve!: (data: unknown) => void;\n let reject!: (error: unknown) => void;\n const promise = new Promise<unknown>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n\n this._topics.set(topic, { status: 'pending', promise, resolve, reject });\n this.subscribe(topic);\n\n return promise;\n }\n\n /**\n * Convenience wrapper — pushes a mutation event through the QueryClient\n * so that entities and live collections are updated reactively.\n */\n protected sendMutationEvent(event: MutationEvent): void {\n this.queryClient!.applyMutationEvent(event);\n }\n}\n"],"names":["TopicQuery","Query","TopicQueryAdapter","QueryAdapter","topic","data","state","error","ctx","_signal","topicCtx","existing","resolve","reject","promise","res","rej","event"],"mappings":";;AASO,MAAeA,UAAmBC,EAAM;AAAA,EAC7C,OAAgB;AAAA,EAIhB,iBAAyB;AACvB,WAAO,SAAS,KAAK,KAAK;AAAA,EAC5B;AAAA,EAEA,YAAgC;AAC9B,WAAO;AAAA,MACL,WAAW;AAAA,MACX,WAAW,MACF,MAAM;AAEX,QADiB,KAA6B,eACrC,YAAY,KAAK,KAAK;AAAA,MACjC;AAAA,IACF;AAAA,EAEJ;AACF;ACPO,MAAeC,UAA0BC,EAAa;AAAA,EACnD,8BAAc,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBZ,aAAaC,GAAeC,GAAqB;AACzD,UAAMC,IAAQ,KAAK,QAAQ,IAAIF,CAAK;AAEpC,QAAIE,MAAU,QAAW;AACvB,WAAK,QAAQ,IAAIF,GAAO,EAAE,QAAQ,aAAa,MAAAC,GAAM;AACrD;AAAA,IACF;AAEA,IAAIC,EAAM,WAAW,cACnBA,EAAM,SAAS,aACfA,EAAM,OAAOD,GACbC,EAAM,QAASD,CAAI;AAAA,EAEvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,YAAYD,GAAeG,GAAsB;AACzD,UAAMD,IAAQ,KAAK,QAAQ,IAAIF,CAAK;AAEpC,QAAIE,MAAU,QAAW;AACvB,WAAK,QAAQ,IAAIF,GAAO,EAAE,QAAQ,YAAY,OAAAG,GAAO;AACrD;AAAA,IACF;AAEA,IAAID,EAAM,WAAW,cACnBA,EAAM,SAAS,YACfA,EAAM,QAAQC,GACdD,EAAM,OAAQC,CAAK;AAAA,EAEvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,WAAWH,GAAqB;AACxC,SAAK,QAAQ,OAAOA,CAAK;AAAA,EAC3B;AAAA,EAEU,WAAiB;AACzB,SAAK,QAAQ,MAAA;AAAA,EACf;AAAA,EAEA,MAAe,KAAKI,GAAYC,GAAwC;AACtE,UAAMC,IAAWF;AACjB,IAAAE,EAAS,gBAAgB;AACzB,UAAMN,IAAQM,EAAS,OAEjBC,IAAW,KAAK,QAAQ,IAAIP,CAAK;AAEvC,QAAIO;AACF,cAAQA,EAAS,QAAA;AAAA,QACf,KAAK;AACH,iBAAOA,EAAS;AAAA,QAClB,KAAK;AACH,gBAAMA,EAAS;AAAA,QACjB,KAAK;AACH,iBAAOA,EAAS;AAAA,MAAA;AAKtB,QAAIC,GACAC;AACJ,UAAMC,IAAU,IAAI,QAAiB,CAACC,GAAKC,MAAQ;AACjD,MAAAJ,IAAUG,GACVF,IAASG;AAAA,IACX,CAAC;AAED,gBAAK,QAAQ,IAAIZ,GAAO,EAAE,QAAQ,WAAW,SAAAU,GAAS,SAAAF,GAAS,QAAAC,GAAQ,GACvE,KAAK,UAAUT,CAAK,GAEbU;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,kBAAkBG,GAA4B;AACtD,SAAK,YAAa,mBAAmBA,CAAK;AAAA,EAC5C;AACF;"}
|
package/dist/esm/query.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { QueryCacheOptions, QueryConfigOptions, FetchNextConfig, QueryParams } f
|
|
|
3
3
|
import { ValidatorDef } from './typeDefs.js';
|
|
4
4
|
import { HasRequiredKeys, Optionalize, Signalize } from './type-utils.js';
|
|
5
5
|
import { type CapturedDefinition } from './fieldRef.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { QueryAdapterClass } from './QueryAdapter.js';
|
|
7
7
|
export interface ResolvedRetryConfig {
|
|
8
8
|
retries: number;
|
|
9
9
|
retryDelay: (attempt: number) => number;
|
|
@@ -15,7 +15,7 @@ export declare abstract class Query {
|
|
|
15
15
|
* The adapter class responsible for sending requests for this query type.
|
|
16
16
|
* Must be set on each concrete Query subclass (or inherited from a base like RESTQuery).
|
|
17
17
|
*/
|
|
18
|
-
static adapter?:
|
|
18
|
+
static adapter?: QueryAdapterClass;
|
|
19
19
|
params?: Record<string, TypeDef>;
|
|
20
20
|
abstract result: TypeDefShape;
|
|
21
21
|
config?: QueryConfigOptions;
|
|
@@ -45,7 +45,7 @@ export interface QueryDefinitionStatics {
|
|
|
45
45
|
/** Whether the result shape is already an entity (vs synthetic wrapper). */
|
|
46
46
|
readonly isEntityResult: boolean;
|
|
47
47
|
/** The adapter class responsible for sending requests. */
|
|
48
|
-
readonly adapterClass:
|
|
48
|
+
readonly adapterClass: QueryAdapterClass;
|
|
49
49
|
}
|
|
50
50
|
export declare class QueryDefinition<Params extends QueryParams | undefined, Result, StreamType> {
|
|
51
51
|
readonly captured: CapturedDefinition<Query>;
|
package/dist/esm/query.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/query.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAkB,MAAM,YAAY,CAAC;AAC3G,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EAEf,WAAW,EAEZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAK,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/query.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/retry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/retry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/retry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAgBtD,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBrE;AAED,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,MAAM,EAAE,mBAAmB,EAC3B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,CAAC,CAmBZ"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Query } from '../query.js';
|
|
2
2
|
import type { TopicQueryAdapter } from './TopicQueryAdapter.js';
|
|
3
|
+
import type { QueryAdapterClass } from '../QueryAdapter.js';
|
|
3
4
|
import type { QueryConfigOptions } from '../query-types.js';
|
|
4
5
|
export declare abstract class TopicQuery extends Query {
|
|
5
|
-
static adapter:
|
|
6
|
+
static adapter: QueryAdapterClass<TopicQueryAdapter>;
|
|
6
7
|
abstract topic: string;
|
|
7
8
|
getIdentityKey(): string;
|
|
8
9
|
getConfig(): QueryConfigOptions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TopicQuery.d.ts","sourceRoot":"","sources":["../../../src/topic/TopicQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAM5D,8BAAsB,UAAW,SAAQ,KAAK;IAC5C,OAAgB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"TopicQuery.d.ts","sourceRoot":"","sources":["../../../src/topic/TopicQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAM5D,8BAAsB,UAAW,SAAQ,KAAK;IAC5C,OAAgB,OAAO,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IAE9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,cAAc,IAAI,MAAM;IAIxB,SAAS,IAAI,kBAAkB;CAWhC"}
|
package/package.json
CHANGED
|
@@ -226,7 +226,7 @@ new QueryClient(config: QueryClientConfig)
|
|
|
226
226
|
|
|
227
227
|
| Field | Type | Default | Description |
|
|
228
228
|
| -------------------- | ---------------------------- | ---------------------- | ------------------------------------------------------------------------------------ |
|
|
229
|
-
| `store` | `QueryStore` |
|
|
229
|
+
| `store` | `QueryStore` | In-memory | Persistent storage backend. Defaults to `SyncQueryStore(MemoryPersistentStore)`. |
|
|
230
230
|
| `adapters` | `QueryAdapter[]` | `[]` | Transport adapters (e.g. `new RESTQueryAdapter({ fetch, baseUrl })`). |
|
|
231
231
|
| `log` | `LogContext \| undefined` | `console` | Logger with `error`, `warn`, `info`, `debug` methods. |
|
|
232
232
|
| `evictionMultiplier` | `number \| undefined` | `1` | Scales all GC times for testing. Set to `0.001` to make timers fire in milliseconds. |
|
|
@@ -604,7 +604,7 @@ The `t` object provides a declarative type definition DSL for describing query p
|
|
|
604
604
|
| `QueryCacheOptions` | `{ maxCount?: number; cacheTime?: number }` | Persistent storage cache settings. `cacheTime` is in minutes (default: 1440 / 24 hours). `maxCount` is the LRU queue size. |
|
|
605
605
|
| `QueryConfigOptions` | `{ gcTime?, staleTime?, debounce?, networkMode?, retry?, refreshStaleOnReconnect?, subscribe? }` | Instance-level query configuration. See property table below. |
|
|
606
606
|
| `QueryRequestOptions` | `{ baseUrl?, credentials?, mode?, cache?, redirect?, referrer?, referrerPolicy?, integrity?, keepalive?, signal? }` | Extended fetch options for individual queries. |
|
|
607
|
-
| `QueryContext` | `{
|
|
607
|
+
| `QueryContext` | `{ log?, evictionMultiplier? }` | Context object provided to the `QueryClient`. |
|
|
608
608
|
| `QueryParams` | `Record<string, string \| number \| boolean \| undefined \| null \| Signal<...> \| unknown[] \| Record<string, unknown>>` | The shape of query parameters at runtime. |
|
|
609
609
|
| `FetchNextConfig` | `{ url?: unknown; searchParams?: Record<string, unknown> }` | Pagination configuration. Values can be FieldRefs. |
|
|
610
610
|
|
|
@@ -181,6 +181,7 @@ type StoreMessage =
|
|
|
181
181
|
```ts
|
|
182
182
|
import { QueryClient } from 'fetchium';
|
|
183
183
|
import { AsyncQueryStore } from 'fetchium/stores/async';
|
|
184
|
+
import { RESTQueryAdapter } from 'fetchium/rest';
|
|
184
185
|
|
|
185
186
|
const worker = new Worker('./store-worker.js');
|
|
186
187
|
|
|
@@ -194,9 +195,14 @@ const store = new AsyncQueryStore({
|
|
|
194
195
|
},
|
|
195
196
|
});
|
|
196
197
|
|
|
197
|
-
const client = new QueryClient(
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
const client = new QueryClient({
|
|
199
|
+
store,
|
|
200
|
+
adapters: [
|
|
201
|
+
new RESTQueryAdapter({
|
|
202
|
+
fetch: globalThis.fetch,
|
|
203
|
+
baseUrl: 'https://api.example.com',
|
|
204
|
+
}),
|
|
205
|
+
],
|
|
200
206
|
});
|
|
201
207
|
```
|
|
202
208
|
|
|
@@ -121,13 +121,19 @@ Internally, `SyncQueryStore` uses the following key prefixes in the underlying `
|
|
|
121
121
|
```ts
|
|
122
122
|
import { QueryClient } from 'fetchium';
|
|
123
123
|
import { SyncQueryStore, MemoryPersistentStore } from 'fetchium/stores/sync';
|
|
124
|
+
import { RESTQueryAdapter } from 'fetchium/rest';
|
|
124
125
|
|
|
125
126
|
// Create an in-memory store
|
|
126
127
|
const store = new SyncQueryStore(new MemoryPersistentStore());
|
|
127
128
|
|
|
128
129
|
// Create the query client
|
|
129
|
-
const client = new QueryClient(
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
const client = new QueryClient({
|
|
131
|
+
store,
|
|
132
|
+
adapters: [
|
|
133
|
+
new RESTQueryAdapter({
|
|
134
|
+
fetch: globalThis.fetch,
|
|
135
|
+
baseUrl: 'https://api.example.com',
|
|
136
|
+
}),
|
|
137
|
+
],
|
|
132
138
|
});
|
|
133
139
|
```
|
|
@@ -171,7 +171,7 @@ class User extends Entity {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
greet() {
|
|
174
|
-
return `Hello, ${this.
|
|
174
|
+
return `Hello, ${this.fullName}!`;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
isAdult() {
|
|
@@ -185,7 +185,7 @@ Methods work on entity proxies just like regular methods:
|
|
|
185
185
|
```tsx
|
|
186
186
|
const user = result.user;
|
|
187
187
|
user.fullName; // "Alice Smith"
|
|
188
|
-
user.greet(); // "Hello, Alice!"
|
|
188
|
+
user.greet(); // "Hello, Alice Smith!"
|
|
189
189
|
user.isAdult(); // true
|
|
190
190
|
```
|
|
191
191
|
|
|
@@ -163,17 +163,16 @@ interface ReactivePromise<T> {
|
|
|
163
163
|
|
|
164
164
|
```ts
|
|
165
165
|
type QueryResult<Q extends Query> = Q['result'] & {
|
|
166
|
-
__refetch():
|
|
166
|
+
__refetch(): QueryPromise<Q>;
|
|
167
167
|
__fetchNext(): Promise<Q['result']>;
|
|
168
|
+
__hasNext: boolean;
|
|
169
|
+
__isFetchingNext: boolean;
|
|
168
170
|
};
|
|
169
171
|
|
|
170
172
|
declare function useQuery<Q extends Query>(
|
|
171
|
-
|
|
172
|
-
params
|
|
173
|
-
|
|
174
|
-
suspended?: boolean;
|
|
175
|
-
},
|
|
176
|
-
): ReactivePromise<QueryResult<Q>>;
|
|
173
|
+
QueryClass: new () => Q,
|
|
174
|
+
params?: ExtractQueryParams<Q>,
|
|
175
|
+
): QueryPromise<Q>;
|
|
177
176
|
```
|
|
178
177
|
|
|
179
178
|
The reason `__refetch` and `__fetchNext` are defined on the _result_ of the query and not the `ReactivePromise` is about composability, which leads us into usage within Signalium.
|
|
@@ -190,27 +189,21 @@ import { GetCurrentUser, GetUserProfile } from './queries';
|
|
|
190
189
|
|
|
191
190
|
export function UserProfile() {
|
|
192
191
|
const userResult = useQuery(GetCurrentUser);
|
|
193
|
-
const userProfileResult = useQuery(
|
|
194
|
-
|
|
195
|
-
{ user },
|
|
196
|
-
{ suspended: !user },
|
|
192
|
+
const userProfileResult = useQuery(GetUserProfile, {
|
|
193
|
+
user: userResult.value,
|
|
197
194
|
});
|
|
198
195
|
|
|
199
196
|
if (userResult.isRejected || userProfileResult.isRejected) {
|
|
200
197
|
const message =
|
|
201
|
-
userResult.error?.message ||
|
|
202
|
-
userProfileResult.error?.message;
|
|
198
|
+
userResult.error?.message || userProfileResult.error?.message;
|
|
203
199
|
|
|
204
|
-
return <div>
|
|
205
|
-
Error: {message}
|
|
206
|
-
</div>;
|
|
200
|
+
return <div>Error: {message}</div>;
|
|
207
201
|
}
|
|
208
202
|
|
|
209
203
|
if (!userResult.isReady || !userProfileResult.isReady) {
|
|
210
|
-
|
|
204
|
+
return <div>Loading...</div>;
|
|
211
205
|
}
|
|
212
206
|
|
|
213
|
-
|
|
214
207
|
return (
|
|
215
208
|
<div>
|
|
216
209
|
<h1>{userProfileResult.value.name}</h1>
|
|
@@ -344,7 +337,7 @@ class GetUser extends RESTQuery {
|
|
|
344
337
|
}
|
|
345
338
|
```
|
|
346
339
|
|
|
347
|
-
By convention,
|
|
340
|
+
By convention, most fields provided by `RESTQuery` and other query implementations have a corresponding `get*` method. So for `path` there is `getPath`, for `searchParams` there is `getSearchParams`, for `body` there is `getBody`, and so on.
|
|
348
341
|
|
|
349
342
|
{% callout title="API design by TypeScript limitations" type="note" %}
|
|
350
343
|
The original API design for this feature allowed getters in place of fields, so `get path() {}` would work as well. The issue was that TypeScript does not allow this specific combination on abstract classes at the moment. See [this issue](https://github.com/microsoft/TypeScript/issues/40635) for more information.
|
|
@@ -364,7 +364,7 @@ import { Mutation, t } from 'fetchium';
|
|
|
364
364
|
class UploadAvatar extends Mutation {
|
|
365
365
|
static override adapter = MyAdapter;
|
|
366
366
|
|
|
367
|
-
params = { userId: t.id
|
|
367
|
+
params = { userId: t.id };
|
|
368
368
|
result = { url: t.string };
|
|
369
369
|
|
|
370
370
|
getIdentityKey() {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
title: Auth & Headers
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
In most data-fetching libraries, authentication is handled through interceptors, middleware chains, or framework-specific hooks. Fetchium takes a different approach: authentication is handled through the `fetch` function you pass to the `
|
|
5
|
+
In most data-fetching libraries, authentication is handled through interceptors, middleware chains, or framework-specific hooks. Fetchium takes a different approach: authentication is handled through the `fetch` function you pass to the `RESTQueryAdapter`.
|
|
6
6
|
|
|
7
7
|
This is intentional. Rather than adding a framework-specific interceptor system, Fetchium leverages the web platform's standard `fetch` API. Your auth logic is a _plain JavaScript function_ --- testable, portable, and completely decoupled from the library. You can unit test it without importing Fetchium, reuse it across projects, or swap it out without touching a single query definition.
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ This page covers the common patterns for adding authentication and custom header
|
|
|
12
12
|
|
|
13
13
|
## Global Headers via a Fetch Wrapper
|
|
14
14
|
|
|
15
|
-
The simplest and most common pattern is wrapping the native `fetch` with a function that injects your auth token on every request. You pass this wrapper to the `
|
|
15
|
+
The simplest and most common pattern is wrapping the native `fetch` with a function that injects your auth token on every request. You pass this wrapper to the `RESTQueryAdapter` at setup time, and every query uses it automatically.
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
18
|
function createAuthFetch(getToken: () => string | null) {
|
|
@@ -28,9 +28,13 @@ function createAuthFetch(getToken: () => string | null) {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const client = new QueryClient(
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const client = new QueryClient({
|
|
32
|
+
adapters: [
|
|
33
|
+
new RESTQueryAdapter({
|
|
34
|
+
fetch: createAuthFetch(() => localStorage.getItem('auth_token')),
|
|
35
|
+
baseUrl: 'https://api.example.com',
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
34
38
|
});
|
|
35
39
|
```
|
|
36
40
|
|
|
@@ -45,13 +49,17 @@ Notice that `createAuthFetch` accepts a _getter function_ rather than the token
|
|
|
45
49
|
If your API uses a static key rather than a user token, the pattern is even simpler:
|
|
46
50
|
|
|
47
51
|
```ts
|
|
48
|
-
const client = new QueryClient(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
const client = new QueryClient({
|
|
53
|
+
adapters: [
|
|
54
|
+
new RESTQueryAdapter({
|
|
55
|
+
fetch: async (url, init) => {
|
|
56
|
+
const headers = new Headers(init?.headers);
|
|
57
|
+
headers.set('X-API-Key', process.env.API_KEY!);
|
|
58
|
+
return fetch(url, { ...init, headers });
|
|
59
|
+
},
|
|
60
|
+
baseUrl: 'https://api.example.com',
|
|
61
|
+
}),
|
|
62
|
+
],
|
|
55
63
|
});
|
|
56
64
|
```
|
|
57
65
|
|
|
@@ -93,9 +101,13 @@ function createReactiveAuthFetch() {
|
|
|
93
101
|
};
|
|
94
102
|
}
|
|
95
103
|
|
|
96
|
-
const client = new QueryClient(
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
const client = new QueryClient({
|
|
105
|
+
adapters: [
|
|
106
|
+
new RESTQueryAdapter({
|
|
107
|
+
fetch: createReactiveAuthFetch(),
|
|
108
|
+
baseUrl: 'https://api.example.com',
|
|
109
|
+
}),
|
|
110
|
+
],
|
|
99
111
|
});
|
|
100
112
|
```
|
|
101
113
|
|
|
@@ -132,7 +144,7 @@ class UploadAvatar extends RESTQuery {
|
|
|
132
144
|
|
|
133
145
|
The layering is straightforward: your global fetch wrapper handles _auth_ (the concern that applies everywhere), and per-query headers handle _API-specific needs_ (the concerns that vary by endpoint). The two are composed naturally --- `headers` from the query class are passed through `init.headers` to your fetch wrapper, which can merge them with auth headers using `new Headers(init?.headers)`.
|
|
134
146
|
|
|
135
|
-
For dynamic per-query headers that depend on runtime conditions, use
|
|
147
|
+
For dynamic per-query headers that depend on runtime conditions, use `getRequestOptions()` to include headers as part of the fetch options:
|
|
136
148
|
|
|
137
149
|
```ts
|
|
138
150
|
class GetReport extends RESTQuery {
|
|
@@ -143,14 +155,11 @@ class GetReport extends RESTQuery {
|
|
|
143
155
|
|
|
144
156
|
path = `/reports/${this.params.reportId}`;
|
|
145
157
|
|
|
146
|
-
|
|
147
|
-
const headers: Record<string, string> = {};
|
|
148
|
-
|
|
158
|
+
getRequestOptions() {
|
|
149
159
|
if (this.params.format === 'csv') {
|
|
150
|
-
headers
|
|
160
|
+
return { headers: { Accept: 'text/csv' } };
|
|
151
161
|
}
|
|
152
|
-
|
|
153
|
-
return headers;
|
|
162
|
+
return undefined;
|
|
154
163
|
}
|
|
155
164
|
|
|
156
165
|
result = {
|
|
@@ -159,7 +168,7 @@ class GetReport extends RESTQuery {
|
|
|
159
168
|
}
|
|
160
169
|
```
|
|
161
170
|
|
|
162
|
-
As described in the [Queries](/core/queries) guide,
|
|
171
|
+
As described in the [Queries](/core/queries) guide, most fields on `RESTQuery` have a corresponding `get*()` method for when you need logic that goes beyond simple references and interpolations.
|
|
163
172
|
|
|
164
173
|
---
|
|
165
174
|
|
|
@@ -215,13 +224,17 @@ A few things to note in this pattern:
|
|
|
215
224
|
Wire it into your client the same way:
|
|
216
225
|
|
|
217
226
|
```ts
|
|
218
|
-
const client = new QueryClient(
|
|
219
|
-
|
|
220
|
-
(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
227
|
+
const client = new QueryClient({
|
|
228
|
+
adapters: [
|
|
229
|
+
new RESTQueryAdapter({
|
|
230
|
+
fetch: createAuthFetchWithRefresh(
|
|
231
|
+
() => authToken.value,
|
|
232
|
+
() => api.refreshSession(),
|
|
233
|
+
(token) => authToken.set(token),
|
|
234
|
+
),
|
|
235
|
+
baseUrl: 'https://api.example.com',
|
|
236
|
+
}),
|
|
237
|
+
],
|
|
225
238
|
});
|
|
226
239
|
```
|
|
227
240
|
|
|
@@ -236,18 +249,28 @@ If your application talks to multiple APIs with different auth schemes --- for i
|
|
|
236
249
|
The cleanest approach is creating a dedicated `QueryClient` for each backend:
|
|
237
250
|
|
|
238
251
|
```ts
|
|
239
|
-
const appClient = new QueryClient(
|
|
240
|
-
|
|
241
|
-
|
|
252
|
+
const appClient = new QueryClient({
|
|
253
|
+
store: appStore,
|
|
254
|
+
adapters: [
|
|
255
|
+
new RESTQueryAdapter({
|
|
256
|
+
fetch: createAuthFetch(() => authToken.value),
|
|
257
|
+
baseUrl: 'https://api.myapp.com',
|
|
258
|
+
}),
|
|
259
|
+
],
|
|
242
260
|
});
|
|
243
261
|
|
|
244
|
-
const analyticsClient = new QueryClient(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
262
|
+
const analyticsClient = new QueryClient({
|
|
263
|
+
store: analyticsStore,
|
|
264
|
+
adapters: [
|
|
265
|
+
new RESTQueryAdapter({
|
|
266
|
+
fetch: async (url, init) => {
|
|
267
|
+
const headers = new Headers(init?.headers);
|
|
268
|
+
headers.set('X-API-Key', ANALYTICS_API_KEY);
|
|
269
|
+
return fetch(url, { ...init, headers });
|
|
270
|
+
},
|
|
271
|
+
baseUrl: 'https://analytics.example.com',
|
|
272
|
+
}),
|
|
273
|
+
],
|
|
251
274
|
});
|
|
252
275
|
```
|
|
253
276
|
|
|
@@ -294,12 +317,12 @@ This is a deliberate design decision. Fetchium favors _composition over configur
|
|
|
294
317
|
| Global fetch wrapper | Auth that applies to all requests (JWT, session cookies, API keys) |
|
|
295
318
|
| Reactive signal token | SPAs where auth state changes at runtime (login/logout) |
|
|
296
319
|
| Per-query `headers` | Endpoint-specific headers (content types, API versions) |
|
|
297
|
-
| `
|
|
320
|
+
| `getRequestOptions()` method | Dynamic per-query headers based on runtime conditions |
|
|
298
321
|
| 401 catch + refresh + retry | Token expiration with automatic renewal |
|
|
299
322
|
| Multiple `QueryClient` instances | Different APIs with different auth schemes or stores |
|
|
300
323
|
| Per-query `requestOptions.baseUrl` | Same auth, different host |
|
|
301
324
|
|
|
302
|
-
The common thread is that Fetchium does not own your auth logic. It provides the _seam_ --- the `fetch` option on `
|
|
325
|
+
The common thread is that Fetchium does not own your auth logic. It provides the _seam_ --- the `fetch` option on `RESTQueryAdapter` --- and you fill it with whatever your application needs. This keeps the library small, your auth testable, and your options open.
|
|
303
326
|
|
|
304
327
|
---
|
|
305
328
|
|
|
@@ -202,7 +202,7 @@ function UserProfile({ userId }: { userId: number }) {
|
|
|
202
202
|
|
|
203
203
|
Sometimes you need to intercept errors _before_ they reach individual queries --- for instance, redirecting to a login page on a 401, refreshing an auth token, or logging all failures to a telemetry service.
|
|
204
204
|
|
|
205
|
-
The `
|
|
205
|
+
The `RESTQueryAdapter` accepts a `fetch` function, which is the standard place to add global error handling. You can wrap the native `fetch` with your own logic:
|
|
206
206
|
|
|
207
207
|
```ts
|
|
208
208
|
function createFetchWithErrorHandling(baseFetch: typeof fetch) {
|
|
@@ -227,15 +227,19 @@ Then pass it when constructing the client:
|
|
|
227
227
|
```tsx
|
|
228
228
|
import { QueryClient, QueryClientContext } from 'fetchium';
|
|
229
229
|
import { SyncQueryStore, MemoryPersistentStore } from 'fetchium/stores/sync';
|
|
230
|
+
import { RESTQueryAdapter } from 'fetchium/rest';
|
|
230
231
|
import { ContextProvider } from 'signalium/react';
|
|
231
232
|
|
|
232
233
|
const store = new SyncQueryStore(new MemoryPersistentStore());
|
|
233
234
|
const customFetch = createFetchWithErrorHandling(fetch);
|
|
234
|
-
const client = new QueryClient(
|
|
235
|
+
const client = new QueryClient({
|
|
236
|
+
store,
|
|
237
|
+
adapters: [new RESTQueryAdapter({ fetch: customFetch })],
|
|
238
|
+
});
|
|
235
239
|
|
|
236
240
|
function App() {
|
|
237
241
|
return (
|
|
238
|
-
<ContextProvider
|
|
242
|
+
<ContextProvider contexts={[[QueryClientContext, client]]}>
|
|
239
243
|
<YourApp />
|
|
240
244
|
</ContextProvider>
|
|
241
245
|
);
|
|
@@ -253,8 +257,8 @@ Non-fatal parse failures (optional fields falling back to `undefined`, array ite
|
|
|
253
257
|
Fetchium routes these warnings through `QueryContext.log.warn`. You can plug in a custom logger when creating the `QueryClient`:
|
|
254
258
|
|
|
255
259
|
```ts
|
|
256
|
-
const client = new QueryClient(
|
|
257
|
-
|
|
260
|
+
const client = new QueryClient({
|
|
261
|
+
store,
|
|
258
262
|
log: {
|
|
259
263
|
warn: (message: string, ...args: unknown[]) => {
|
|
260
264
|
console.warn(message, ...args);
|
|
@@ -29,7 +29,7 @@ import { SyncQueryStore, MemoryPersistentStore } from 'fetchium/stores/sync';
|
|
|
29
29
|
const store = new SyncQueryStore(new MemoryPersistentStore());
|
|
30
30
|
const networkManager = new NetworkManager();
|
|
31
31
|
|
|
32
|
-
const client = new QueryClient(store,
|
|
32
|
+
const client = new QueryClient({ store, networkManager });
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
If you do not provide a `NetworkManager`, the `QueryClient` creates one automatically.
|
|
@@ -124,7 +124,7 @@ The `SyncQueryStore` wraps a synchronous key-value store. It is the simplest opt
|
|
|
124
124
|
import { SyncQueryStore, MemoryPersistentStore } from 'fetchium/stores/sync';
|
|
125
125
|
|
|
126
126
|
const store = new SyncQueryStore(new MemoryPersistentStore());
|
|
127
|
-
const client = new QueryClient(
|
|
127
|
+
const client = new QueryClient({ store });
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
The `MemoryPersistentStore` keeps everything in memory --- data is lost when the page is refreshed. For persistence across sessions, implement the `SyncPersistentStore` interface with a durable backend like `localStorage`.
|
|
@@ -220,18 +220,21 @@ Here is a complete example that sets up a `QueryClient` with persistence and net
|
|
|
220
220
|
```tsx
|
|
221
221
|
import { QueryClient, NetworkManager } from 'fetchium';
|
|
222
222
|
import { SyncQueryStore } from 'fetchium/stores/sync';
|
|
223
|
+
import { RESTQueryAdapter } from 'fetchium/rest';
|
|
223
224
|
|
|
224
225
|
const store = new SyncQueryStore(new LocalStoragePersistentStore());
|
|
225
226
|
const networkManager = new NetworkManager();
|
|
226
227
|
|
|
227
|
-
const client = new QueryClient(
|
|
228
|
+
const client = new QueryClient({
|
|
228
229
|
store,
|
|
229
|
-
{
|
|
230
|
-
fetch: globalThis.fetch,
|
|
231
|
-
baseUrl: 'https://api.example.com',
|
|
232
|
-
},
|
|
233
230
|
networkManager,
|
|
234
|
-
|
|
231
|
+
adapters: [
|
|
232
|
+
new RESTQueryAdapter({
|
|
233
|
+
fetch: globalThis.fetch,
|
|
234
|
+
baseUrl: 'https://api.example.com',
|
|
235
|
+
}),
|
|
236
|
+
],
|
|
237
|
+
});
|
|
235
238
|
```
|
|
236
239
|
|
|
237
240
|
With this setup:
|