@swan-io/graphql-client
Advanced tools
Comparing version 0.1.0-beta1 to 0.1.0-beta2
import { DocumentNode, OperationDefinitionNode } from "@0no-co/graphql.web"; | ||
import { Option, Result } from "@swan-io/boxed"; | ||
import { Connection, Edge } from "../types"; | ||
type CacheEntry = { | ||
@@ -30,3 +31,11 @@ requestedKeys: Set<symbol>; | ||
}): void; | ||
unsafe__update<A>(cacheKey: symbol, path: (symbol | string)[], updater: (value: A) => A): void; | ||
updateConnection<A>(connection: Connection<A>, config: { | ||
prepend: Edge<A>[]; | ||
} | { | ||
append: Edge<A>[]; | ||
} | { | ||
remove: string[]; | ||
}): void; | ||
} | ||
export {}; |
@@ -5,3 +5,3 @@ import { DocumentNode } from "@0no-co/graphql.web"; | ||
import { ClientError } from "./errors"; | ||
import { TypedDocumentNode } from "./types"; | ||
import { Connection, Edge, TypedDocumentNode } from "./types"; | ||
export type RequestConfig = { | ||
@@ -20,4 +20,22 @@ url: string; | ||
}; | ||
type RequestOptions = { | ||
type ConnectionUpdate<Node> = [ | ||
Connection<Node>, | ||
{ | ||
prepend: Edge<Node>[]; | ||
} | { | ||
append: Edge<Node>[]; | ||
} | { | ||
remove: string[]; | ||
} | ||
]; | ||
export type GetConnectionUpdate<Data, Variables> = (config: { | ||
data: Data; | ||
variables: Variables; | ||
prepend: <A>(connection: Connection<A>, edges: Edge<A>[]) => ConnectionUpdate<A>; | ||
append: <A>(connection: Connection<A>, edges: Edge<A>[]) => ConnectionUpdate<A>; | ||
remove: <A>(connection: Connection<A>, ids: string[]) => ConnectionUpdate<A>; | ||
}) => Option<ConnectionUpdate<unknown>>; | ||
type RequestOptions<Data, Variables> = { | ||
optimize?: boolean; | ||
connectionUpdates?: GetConnectionUpdate<Data, Variables>[] | undefined; | ||
}; | ||
@@ -36,7 +54,7 @@ export declare class Client { | ||
subscribe(func: () => void): () => boolean; | ||
request<Data, Variables>(document: TypedDocumentNode<Data, Variables>, variables: Variables, { optimize }?: RequestOptions): Future<Result<Data, ClientError>>; | ||
request<Data, Variables>(document: TypedDocumentNode<Data, Variables>, variables: Variables, { optimize, connectionUpdates, }?: RequestOptions<Data, Variables>): Future<Result<Data, ClientError>>; | ||
readFromCache<Data, Variables>(document: TypedDocumentNode<Data, Variables>, variables: Variables): Option<Result<unknown, unknown>>; | ||
query<Data, Variables>(document: TypedDocumentNode<Data, Variables>, variables: Variables, requestOptions?: RequestOptions): Future<Result<Data, ClientError>>; | ||
commitMutation<Data, Variables>(document: TypedDocumentNode<Data, Variables>, variables: Variables, requestOptions?: RequestOptions): Future<Result<Data, ClientError>>; | ||
query<Data, Variables>(document: TypedDocumentNode<Data, Variables>, variables: Variables, requestOptions?: RequestOptions<Data, Variables>): Future<Result<Data, ClientError>>; | ||
commitMutation<Data, Variables>(document: TypedDocumentNode<Data, Variables>, variables: Variables, requestOptions?: RequestOptions<Data, Variables>): Future<Result<Data, ClientError>>; | ||
} | ||
export {}; |
@@ -9,1 +9,2 @@ export * from "./client"; | ||
export * from "./react/useQuery"; | ||
export { Connection, Edge } from "./types"; |
@@ -186,2 +186,8 @@ 'use strict'; | ||
if (isRecord(value) && typeof value.__typename === "string" && value.__typename.endsWith("Connection")) { | ||
value.__connectionCacheKey = cacheKey.description; | ||
value.__connectionCachePath = [ | ||
[...writePath, fieldNameWithArguments].map( | ||
(item) => typeof item === "symbol" ? { symbol: item.description } : item | ||
) | ||
]; | ||
value.__connectionArguments = variables; | ||
@@ -213,2 +219,121 @@ } | ||
} | ||
unsafe__update(cacheKey, path, updater) { | ||
this.get(cacheKey).map((cachedAncestor) => { | ||
const value = path.reduce( | ||
(acc, key) => acc.flatMap( | ||
(acc2) => boxed.Option.fromNullable(isRecord(acc2) ? acc2[key] : null) | ||
), | ||
boxed.Option.fromNullable(cachedAncestor.value) | ||
); | ||
value.map((item) => { | ||
const deepUpdate = path.reduce( | ||
(acc, key) => { | ||
return { | ||
[key]: acc | ||
}; | ||
}, | ||
updater(item) | ||
); | ||
this.set( | ||
cacheKey, | ||
mergeCacheEntries(cachedAncestor, { | ||
requestedKeys: /* @__PURE__ */ new Set(), | ||
value: deepUpdate | ||
}) | ||
); | ||
}); | ||
}); | ||
} | ||
updateConnection(connection, config) { | ||
tsPattern.match(connection).with( | ||
{ | ||
__connectionCacheKey: tsPattern.P.string, | ||
__connectionCachePath: tsPattern.P.array( | ||
tsPattern.P.array(tsPattern.P.union({ symbol: tsPattern.P.string }, tsPattern.P.string)) | ||
) | ||
}, | ||
({ __connectionCacheKey, __connectionCachePath }) => { | ||
const cacheKey = Symbol.for(__connectionCacheKey); | ||
const cachePath = __connectionCachePath.map( | ||
(path) => path.map( | ||
(item) => typeof item === "string" ? item : Symbol.for(item.symbol) | ||
) | ||
); | ||
const typenameSymbol = Symbol.for("__typename"); | ||
const edgesSymbol = Symbol.for("edges"); | ||
const nodeSymbol = Symbol.for("node"); | ||
tsPattern.match(config).with({ prepend: tsPattern.P.select(tsPattern.P.nonNullable) }, (edges) => { | ||
const firstPath = cachePath[0]; | ||
if (firstPath != null) { | ||
this.unsafe__update(cacheKey, firstPath, (value) => { | ||
if (!isRecord(value) || !boxed.Array.isArray(value[edgesSymbol])) { | ||
return value; | ||
} | ||
return { | ||
...value, | ||
[edgesSymbol]: [ | ||
...boxed.Array.filterMap( | ||
edges, | ||
({ node, __typename }) => getCacheKeyFromJson(node).flatMap( | ||
(key) => ( | ||
// we can omit the requested fields here because the Connection<A> contrains the fields | ||
this.getFromCacheWithoutKey(key).map(() => ({ | ||
[typenameSymbol]: __typename, | ||
[nodeSymbol]: key | ||
})) | ||
) | ||
) | ||
), | ||
...value[edgesSymbol] | ||
] | ||
}; | ||
}); | ||
} | ||
}).with({ append: tsPattern.P.select(tsPattern.P.nonNullable) }, (edges) => { | ||
const lastPath = cachePath[cachePath.length - 1]; | ||
if (lastPath != null) { | ||
this.unsafe__update(cacheKey, lastPath, (value) => { | ||
if (!isRecord(value) || !boxed.Array.isArray(value[edgesSymbol])) { | ||
return value; | ||
} | ||
return { | ||
...value, | ||
[edgesSymbol]: [ | ||
...value[edgesSymbol], | ||
...boxed.Array.filterMap( | ||
edges, | ||
({ node, __typename }) => getCacheKeyFromJson(node).flatMap( | ||
(key) => ( | ||
// we can omit the requested fields here because the Connection<A> contrains the fields | ||
this.getFromCacheWithoutKey(key).map(() => ({ | ||
[typenameSymbol]: __typename, | ||
[nodeSymbol]: key | ||
})) | ||
) | ||
) | ||
) | ||
] | ||
}; | ||
}); | ||
} | ||
}).with({ remove: tsPattern.P.select(tsPattern.P.array()) }, (nodeIds) => { | ||
cachePath.forEach((path) => { | ||
this.unsafe__update(cacheKey, path, (value) => { | ||
return isRecord(value) && boxed.Array.isArray(value[edgesSymbol]) ? { | ||
...value, | ||
[edgesSymbol]: value[edgesSymbol].filter((edge) => { | ||
const node = edge[nodeSymbol]; | ||
return !nodeIds.some((nodeId) => { | ||
var _a; | ||
return (_a = node.description) == null ? void 0 : _a.includes(`<${nodeId}>`); | ||
}); | ||
}) | ||
} : value; | ||
}); | ||
}); | ||
}).exhaustive(); | ||
} | ||
).otherwise(() => { | ||
}); | ||
} | ||
}; | ||
@@ -970,2 +1095,11 @@ var getSelectedKeys = (fieldNode, variables) => { | ||
}; | ||
var prepend = (connection, edges) => { | ||
return [connection, { prepend: edges }]; | ||
}; | ||
var append = (connection, edges) => { | ||
return [connection, { append: edges }]; | ||
}; | ||
var remove = (connection, ids) => { | ||
return [connection, { remove: ids }]; | ||
}; | ||
var Client = class { | ||
@@ -1004,3 +1138,6 @@ constructor(config) { | ||
} | ||
request(document, variables, { optimize = false } = {}) { | ||
request(document, variables, { | ||
optimize = false, | ||
connectionUpdates | ||
} = {}) { | ||
const transformedDocument = this.getTransformedDocument(document); | ||
@@ -1040,2 +1177,12 @@ const transformedDocumentsForRequest = this.getTransformedDocumentsForRequest(document); | ||
); | ||
}).tapOk((data) => { | ||
if (connectionUpdates !== void 0) { | ||
connectionUpdates.forEach((getUpdate) => { | ||
getUpdate({ data, variables, prepend, append, remove }).map( | ||
([connection, update]) => { | ||
this.cache.updateConnection(connection, update); | ||
} | ||
); | ||
}); | ||
} | ||
}).tap((result) => { | ||
@@ -1134,4 +1281,6 @@ this.cache.setOperationInCache( | ||
}; | ||
var useMutation = (mutation) => { | ||
var useMutation = (mutation, config = {}) => { | ||
const client = react.useContext(ClientContext); | ||
const connectionUpdatesRef = react.useRef(config == null ? void 0 : config.connectionUpdates); | ||
connectionUpdatesRef.current = config == null ? void 0 : config.connectionUpdates; | ||
const [stableMutation] = react.useState(mutation); | ||
@@ -1144,3 +1293,5 @@ const [data, setData] = react.useState( | ||
setData(boxed.AsyncData.Loading()); | ||
return client.commitMutation(stableMutation, variables).tap((result) => setData(boxed.AsyncData.Done(result))); | ||
return client.commitMutation(stableMutation, variables, { | ||
connectionUpdates: connectionUpdatesRef.current | ||
}).tap((result) => setData(boxed.AsyncData.Done(result))); | ||
}, | ||
@@ -1171,2 +1322,9 @@ [client, stableMutation] | ||
...next, | ||
__connectionCachePath: tsPattern.match(mode).with("before", () => [ | ||
..."__connectionCachePath" in next && Array.isArray(next.__connectionCachePath) ? next.__connectionCachePath : [], | ||
..."__connectionCachePath" in previous && Array.isArray(previous.__connectionCachePath) ? previous.__connectionCachePath : [] | ||
]).with("after", () => [ | ||
..."__connectionCachePath" in previous && Array.isArray(previous.__connectionCachePath) ? previous.__connectionCachePath : [], | ||
..."__connectionCachePath" in next && Array.isArray(next.__connectionCachePath) ? next.__connectionCachePath : [] | ||
]).exhaustive(), | ||
edges: tsPattern.match(mode).with("before", () => { | ||
@@ -1173,0 +1331,0 @@ var _a, _b; |
import { AsyncData, Future, Result } from "@swan-io/boxed"; | ||
import { GetConnectionUpdate } from "../client"; | ||
import { ClientError } from "../errors"; | ||
@@ -8,2 +9,5 @@ import { TypedDocumentNode } from "../types"; | ||
]; | ||
export declare const useMutation: <Data, Variables>(mutation: TypedDocumentNode<Data, Variables>) => Mutation<Data, Variables>; | ||
export type MutationConfig<Data, Variables> = { | ||
connectionUpdates?: GetConnectionUpdate<Data, Variables>[] | undefined; | ||
}; | ||
export declare const useMutation: <Data, Variables>(mutation: TypedDocumentNode<Data, Variables>, config?: MutationConfig<Data, Variables>) => Mutation<Data, Variables>; |
@@ -1,15 +0,3 @@ | ||
export type Edge<T> = { | ||
cursor?: string | null; | ||
node?: T | null | undefined; | ||
}; | ||
export type Connection<T> = { | ||
edges?: (Edge<T> | null | undefined)[] | null | undefined; | ||
pageInfo: { | ||
hasPreviousPage?: boolean | null | undefined; | ||
hasNextPage?: boolean | null | undefined; | ||
endCursor?: string | null | undefined; | ||
startCursor?: string | null | undefined; | ||
}; | ||
} | null | undefined; | ||
import { Connection } from "../types"; | ||
export declare const useForwardPagination: <A, T extends Connection<A>>(connection: T) => T; | ||
export declare const useBackwardPagination: <A, T extends Connection<A>>(connection: T) => T; |
@@ -16,1 +16,15 @@ import { DocumentNode } from "@0no-co/graphql.web"; | ||
} | ||
export type Edge<T> = { | ||
__typename?: string | null | undefined; | ||
cursor?: string | null | undefined; | ||
node?: T | null | undefined; | ||
}; | ||
export type Connection<T> = { | ||
edges?: (Edge<T> | null | undefined)[] | null | undefined; | ||
pageInfo: { | ||
hasPreviousPage?: boolean | null | undefined; | ||
hasNextPage?: boolean | null | undefined; | ||
endCursor?: string | null | undefined; | ||
startCursor?: string | null | undefined; | ||
}; | ||
} | null | undefined; |
{ | ||
"name": "@swan-io/graphql-client", | ||
"version": "0.1.0-beta1", | ||
"version": "0.1.0-beta2", | ||
"license": "MIT", | ||
@@ -5,0 +5,0 @@ "description": "A simple, typesafe GraphQL client for React", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
326270
3118