diff --git a/.changeset/calm-countries-worry.md b/.changeset/calm-countries-worry.md new file mode 100644 index 00000000..02f32877 --- /dev/null +++ b/.changeset/calm-countries-worry.md @@ -0,0 +1,5 @@ +--- +"@spear-ai/relay-environment": major +--- + +Requests now throw an `HTTPError` when they timeout or when they return a non-200 status code. diff --git a/.changeset/cyan-spoons-boil.md b/.changeset/cyan-spoons-boil.md new file mode 100644 index 00000000..cb73744b --- /dev/null +++ b/.changeset/cyan-spoons-boil.md @@ -0,0 +1,5 @@ +--- +"@spear-ai/relay-environment": minor +--- + +Added debug logging and the ability to pass in a custom logger. diff --git a/.changeset/short-eagles-heal.md b/.changeset/short-eagles-heal.md new file mode 100644 index 00000000..42e826bd --- /dev/null +++ b/.changeset/short-eagles-heal.md @@ -0,0 +1,5 @@ +--- +"@spear-ai/relay-environment": minor +--- + +Backed requests with the [ky](https://github.com/sindresorhus/ky) library which adds support for custom hooks. This makes it easier to integrate authentication workflows. diff --git a/.changeset/short-fans-hide.md b/.changeset/short-fans-hide.md new file mode 100644 index 00000000..fcf567b7 --- /dev/null +++ b/.changeset/short-fans-hide.md @@ -0,0 +1,5 @@ +--- +"@spear-ai/relay-environment": minor +--- + +Added request retries and timeouts. diff --git a/packages/relay-environment/package.json b/packages/relay-environment/package.json index ff97e030..e4bcc541 100644 --- a/packages/relay-environment/package.json +++ b/packages/relay-environment/package.json @@ -8,6 +8,11 @@ "url": "https://spear.ai" }, "type": "module", + "dependencies": { + "abslog": "^2.4.4", + "ky": "^1.7.4", + "uuid": "^11.0.5" + }, "devDependencies": { "@spear-ai/eslint-config": "20.0.1", "@spear-ai/npm-package-json-lint-config": "3.1.1", @@ -30,7 +35,7 @@ "main": "./dist/index.cjs", "module": "./dist/index.js", "peerDependencies": { - "relay-runtime": "^16.2.0" + "relay-runtime": "^18.2.0" }, "repository": { "type": "git", diff --git a/packages/relay-environment/src/index.ts b/packages/relay-environment/src/index.ts index a22c7fdb..85119f0f 100644 --- a/packages/relay-environment/src/index.ts +++ b/packages/relay-environment/src/index.ts @@ -1,3 +1,5 @@ +import abslog, { AbstractLoggerOptions, ValidLogger } from "abslog"; +import ky, { Hooks, Options as KyOptions } from "ky"; import { CacheConfig, Environment, @@ -9,35 +11,54 @@ import { Store, Variables, } from "relay-runtime"; +import { v4 as uuidv4 } from "uuid"; -const networkFetch = async ( - request: RequestParameters, - variables: Variables, - apiUrl: string, -): Promise => { - const response = await fetch(apiUrl, { - body: JSON.stringify({ - query: request.text, - variables, - }), - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - method: "POST", - }); +type PartialKyOptions = Pick; - return (await response.json()) as unknown as GraphQLResponse; -}; +const makeFetchResponse = ( + options: PartialKyOptions & { + apiUrl: string; + cacheSize: number; + cacheTtl: number; + latencyRetentionCount: number; + logger: ValidLogger; + recentFetchLatencyList: number[]; + }, +) => { + const { + apiUrl, + cacheSize, + cacheTtl, + hooks, + latencyRetentionCount, + logger, + recentFetchLatencyList, + ...kyOptions + } = options; -const createFetchResponse = (options: { - apiUrl: string; - cacheSize: number; - cacheTtl: number; - latencyRetentionCount: number; - recentFetchLatencyList: number[]; -}) => { - const { apiUrl, cacheSize, cacheTtl, latencyRetentionCount, recentFetchLatencyList } = options; + const requestId = uuidv4(); + + const mergedHooks: Hooks = { + ...hooks, + afterResponse: [ + ...(hooks?.afterResponse ?? []), + (request, requestOptions, response) => { + logger.debug({ request, requestId, requestOptions, response }, "afterResponse"); + }, + ], + beforeRequest: [ + ...(hooks?.beforeRequest ?? []), + (request, requestOptions) => { + logger.debug({ request, requestId, requestOptions }, "beforeRequest"); + }, + ], + beforeRetry: [ + ...(hooks?.beforeRetry ?? []), + (retryOptions) => { + logger.debug({ requestId, retryOptions }, "beforeRetry"); + }, + ], + }; const responseCache: QueryResponseCache = new QueryResponseCache({ size: cacheSize, @@ -59,7 +80,15 @@ const createFetchResponse = (options: { } const fetchStart = performance.now(); - const fetchResponse = await networkFetch(parameters, variables, apiUrl); + const fetchResponse = await ky.post(apiUrl, { + ...kyOptions, + hooks: mergedHooks, + json: { + query: parameters.text, + variables, + }, + }); + const response = await fetchResponse.json(); const fetchEnd = performance.now(); recentFetchLatencyList.push(fetchEnd - fetchStart); @@ -68,7 +97,7 @@ const createFetchResponse = (options: { recentFetchLatencyList.shift(); } - return fetchResponse; + return response; }; }; @@ -79,28 +108,40 @@ export type RelayEnvironment = Environment & { }; }; -export const createEnvironment = (options: { - /** The GraphQL API URL. */ - apiUrl: string; - /** The maximum number of entities to keep inside Relay’s cache. */ - cacheSize?: number | undefined; - /** The maximum time to keep entities inside Relay’s cache. */ - cacheTtl?: number | undefined; - /** The maximum number of latency metrics to retain. */ - latencyRetentionCount?: number | undefined; -}): RelayEnvironment => { - const { apiUrl, cacheSize = 100, cacheTtl = 5000, latencyRetentionCount = 100 } = options; +export const createEnvironment = ( + options: PartialKyOptions & { + /** The GraphQL API endpoint URL. */ + apiUrl: string; + /** The maximum number of entities to keep inside Relay's cache. */ + cacheSize?: number | undefined; + /** The maximum time to keep entities inside Relay's cache. */ + cacheTtl?: number | undefined; + /** The maximum number of latency metrics to retain. */ + latencyRetentionCount?: number | undefined; + /** The logger to use when handling requests. */ + logger?: AbstractLoggerOptions | undefined; + }, +): RelayEnvironment => { + const { + cacheSize = 100, + cacheTtl = 5000, + latencyRetentionCount = 100, + logger: customLogger, + ...rest + } = options; const recentFetchLatencyList: number[] = []; + const logger = abslog(customLogger); return new Environment({ isServer: false, network: Network.create( - createFetchResponse({ - apiUrl, + makeFetchResponse({ cacheSize, cacheTtl, latencyRetentionCount, + logger, recentFetchLatencyList, + ...rest, }), ), options: { recentFetchLatencyList }, diff --git a/yarn.lock b/yarn.lock index fd4275d0..8e21d373 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6210,9 +6210,11 @@ __metadata: "@spear-ai/prettier-config": "npm:2.2.0" "@spear-ai/tsconfig": "npm:4.0.0" "@types/relay-runtime": "npm:18.2.3" + abslog: "npm:^2.4.4" autoprefixer: "npm:10.4.20" eslint: "npm:8.57.1" graphql: "npm:16.10.0" + ky: "npm:^1.7.4" npm-package-json-lint: "npm:8.0.0" prettier: "npm:3.4.2" react: "npm:19.0.0" @@ -6221,8 +6223,9 @@ __metadata: tailwindcss: "npm:3.4.17" tsup: "npm:8.3.5" typescript: "npm:5.7.2" + uuid: "npm:^11.0.5" peerDependencies: - relay-runtime: ^16.2.0 + relay-runtime: ^18.2.0 languageName: unknown linkType: soft @@ -8155,6 +8158,15 @@ __metadata: languageName: node linkType: hard +"abslog@npm:^2.4.4": + version: 2.4.4 + resolution: "abslog@npm:2.4.4" + dependencies: + nooplog: "npm:1.0.2" + checksum: 10/c1596bd07d1bbbbd6ba18d257c5dc6d88cc8c2b339e1b2a3d203a67de4480ff22fed18511b7927c6fc171becafc09fa1b0c2a904bd18af17d7b5d5e37fcbc2b3 + languageName: node + linkType: hard + "accepts@npm:~1.3.5": version: 1.3.8 resolution: "accepts@npm:1.3.8" @@ -13499,6 +13511,13 @@ __metadata: languageName: node linkType: hard +"ky@npm:^1.7.4": + version: 1.7.4 + resolution: "ky@npm:1.7.4" + checksum: 10/a5e98434866e4e8e691f76aaa4b4d5bd3abe6527dbb0e853ee37c4475852af6e0cbeef442be31b6824ca7fd3998740492419a45be60352836b9a21a54458f56c + languageName: node + linkType: hard + "language-subtag-registry@npm:^0.3.20": version: 0.3.23 resolution: "language-subtag-registry@npm:0.3.23" @@ -14493,6 +14512,13 @@ __metadata: languageName: node linkType: hard +"nooplog@npm:1.0.2": + version: 1.0.2 + resolution: "nooplog@npm:1.0.2" + checksum: 10/ef22d1555d8ec15cea24b223cb3ede48543a9cc90ab73884d413e89c47b842a4e042172478ead252e2f8873fe3cb683694cd546474522b744ae525e5c62ad1eb + languageName: node + linkType: hard + "nopt@npm:^7.0.0": version: 7.2.1 resolution: "nopt@npm:7.2.1" @@ -18599,6 +18625,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^11.0.5": + version: 11.0.5 + resolution: "uuid@npm:11.0.5" + bin: + uuid: dist/esm/bin/uuid + checksum: 10/0594ecdff3051e15d4a2c614b4c72e73af373bde0a5d156512353c01156975295d024ae8d7151846d7bd4d22ccd251b16ed51b4318fa71505fb20ad984102dc1 + languageName: node + linkType: hard + "uuid@npm:^9.0.0": version: 9.0.1 resolution: "uuid@npm:9.0.1"