Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logging and request retry logic, timeouts, hooks with ky backend #394

Merged
merged 5 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-countries-worry.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions .changeset/cyan-spoons-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@spear-ai/relay-environment": minor
---

Added debug logging and the ability to pass in a custom logger.
5 changes: 5 additions & 0 deletions .changeset/short-eagles-heal.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions .changeset/short-fans-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@spear-ai/relay-environment": minor
---

Added request retries and timeouts.
7 changes: 6 additions & 1 deletion packages/relay-environment/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
123 changes: 82 additions & 41 deletions packages/relay-environment/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import abslog, { AbstractLoggerOptions, ValidLogger } from "abslog";
import ky, { Hooks, Options as KyOptions } from "ky";
import {
CacheConfig,
Environment,
Expand All @@ -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<GraphQLResponse> => {
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<KyOptions, "hooks" | "retry" | "timeout">;

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,
Expand All @@ -59,7 +80,15 @@ const createFetchResponse = (options: {
}

const fetchStart = performance.now();
const fetchResponse = await networkFetch(parameters, variables, apiUrl);
const fetchResponse = await ky.post<GraphQLResponse>(apiUrl, {
...kyOptions,
hooks: mergedHooks,
json: {
query: parameters.text,
variables,
},
});
const response = await fetchResponse.json();
const fetchEnd = performance.now();

recentFetchLatencyList.push(fetchEnd - fetchStart);
Expand All @@ -68,7 +97,7 @@ const createFetchResponse = (options: {
recentFetchLatencyList.shift();
}

return fetchResponse;
return response;
};
};

Expand All @@ -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 },
Expand Down
37 changes: 36 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down