mirror of
https://github.com/BradNut/gQuery
synced 2025-09-08 17:40:18 +00:00
adds some actual code. not ready yet
This commit is contained in:
parent
dae849234d
commit
5d40d6a60e
7 changed files with 545 additions and 12 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
node_modules/*
|
||||||
|
.DS_Store
|
||||||
33
README.md
33
README.md
|
|
@ -34,9 +34,11 @@ docs coming soon
|
||||||
|
|
||||||
```
|
```
|
||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
|
// The generated function that fetches and caches
|
||||||
import { getSeriesList } from '../whatever'
|
import { getSeriesList } from '../whatever'
|
||||||
|
|
||||||
export async function load() {
|
export async function load() {
|
||||||
|
// Runs the cache/fetch function populating $gCache before use.
|
||||||
await getSeriesList({
|
await getSeriesList({
|
||||||
limit: 0
|
limit: 0
|
||||||
})
|
})
|
||||||
|
|
@ -45,6 +47,7 @@ docs coming soon
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
// Cache becomes populated with data available for SSR
|
||||||
import { gCache } from '@leveluptuts/gQuery'
|
import { gCache } from '@leveluptuts/gQuery'
|
||||||
|
|
||||||
// $: console.log($gCache.seriesList)
|
// $: console.log($gCache.seriesList)
|
||||||
|
|
@ -56,10 +59,6 @@ docs coming soon
|
||||||
|
|
||||||
I guess if you want to do it this way you can.
|
I guess if you want to do it this way you can.
|
||||||
|
|
||||||
## gFetch
|
|
||||||
|
|
||||||
The graphql fetcher client.
|
|
||||||
|
|
||||||
### 1. Initialize
|
### 1. Initialize
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -68,25 +67,31 @@ export const g = new GFetch({
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
Fetch
|
### 2. Create Fetch Function
|
||||||
|
|
||||||
|
This is a function that run a gFetch. A gFetch is a simple graphql fetcher that accepts an array of queries and variables.
|
||||||
|
The result of this function is your data. If you don't want the caching, you can just use this data directly.
|
||||||
|
|
||||||
```
|
```
|
||||||
const seriesList = ({ variables}) =>
|
const seriesList = ({ variables}) =>
|
||||||
g.fetch<SeriesListQuery>({
|
g.fetch({
|
||||||
queries: [{ query: SeriesListDoc, variables }],
|
queries: [{ query: SeriesListDoc, variables }],
|
||||||
})
|
})
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Cache and Fetch
|
### 3. Cache and Fetch (optional)
|
||||||
|
|
||||||
|
If you want the caching into `$gCache`, you can pass your fetch func into the cacher and send the data to $gcache instead of using it directly.
|
||||||
|
Pass gQuery the query name, the fetch function and any variables you might have.
|
||||||
|
|
||||||
```
|
```
|
||||||
export async function getSeriesList(variables) {
|
async function getSeriesList(variables) {
|
||||||
await gQuery('seriesList', { query: seriesList, variables })
|
await gQuery('seriesList', { query: seriesList, variables })
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Use
|
### 4. Use in Svelte Kit App
|
||||||
|
|
||||||
```
|
```
|
||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
|
|
@ -114,6 +119,10 @@ Use
|
||||||
|
|
||||||
It's a Svelte Writable Store. So after a mutation you can quickly and easily manually update the cache.
|
It's a Svelte Writable Store. So after a mutation you can quickly and easily manually update the cache.
|
||||||
|
|
||||||
### Q? Can't you update the cache magically for me?
|
### Q? Can't you update the cache magically for me after a mutation?
|
||||||
|
|
||||||
Maybe? If you want to be in charge of writing that bit, the door is open 😼
|
Maybe? If you want to be in charge of writing that bit, the door is open 😼
|
||||||
|
|
||||||
|
### Q? Why can't I use this yet?
|
||||||
|
|
||||||
|
It's changing too much rn, but will be available asap. Trust me, the sooner I get this done the better.
|
||||||
|
|
|
||||||
127
package-lock.json
generated
Normal file
127
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
{
|
||||||
|
"name": "gquery",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "gquery",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"camel-case": "^4.1.2",
|
||||||
|
"graphql": "^15.6.0",
|
||||||
|
"pascal-case": "^3.1.2",
|
||||||
|
"svelte": "^3.43.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/camel-case": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
|
||||||
|
"dependencies": {
|
||||||
|
"pascal-case": "^3.1.2",
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/graphql": {
|
||||||
|
"version": "15.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/graphql/-/graphql-15.6.0.tgz",
|
||||||
|
"integrity": "sha512-WJR872Zlc9hckiEPhXgyUftXH48jp2EjO5tgBBOyNMRJZ9fviL2mJBD6CAysk6N5S0r9BTs09Qk39nnJBkvOXQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lower-case": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/no-case": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
|
||||||
|
"dependencies": {
|
||||||
|
"lower-case": "^2.0.2",
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pascal-case": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"no-case": "^3.0.4",
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/svelte": {
|
||||||
|
"version": "3.43.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.43.0.tgz",
|
||||||
|
"integrity": "sha512-T2pMPHrxXp+SM8pLLUXLQgkdo+JhTls7aqj9cD7z8wT2ccP+OrCAmtQS7h6pvMjitaZhXFNnCK582NxDpy8HSw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"camel-case": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
|
||||||
|
"requires": {
|
||||||
|
"pascal-case": "^3.1.2",
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"graphql": {
|
||||||
|
"version": "15.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/graphql/-/graphql-15.6.0.tgz",
|
||||||
|
"integrity": "sha512-WJR872Zlc9hckiEPhXgyUftXH48jp2EjO5tgBBOyNMRJZ9fviL2mJBD6CAysk6N5S0r9BTs09Qk39nnJBkvOXQ=="
|
||||||
|
},
|
||||||
|
"lower-case": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"no-case": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
|
||||||
|
"requires": {
|
||||||
|
"lower-case": "^2.0.2",
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pascal-case": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
|
||||||
|
"requires": {
|
||||||
|
"no-case": "^3.0.4",
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"svelte": {
|
||||||
|
"version": "3.43.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.43.0.tgz",
|
||||||
|
"integrity": "sha512-T2pMPHrxXp+SM8pLLUXLQgkdo+JhTls7aqj9cD7z8wT2ccP+OrCAmtQS7h6pvMjitaZhXFNnCK582NxDpy8HSw=="
|
||||||
|
},
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
package.json
Normal file
32
package.json
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"author": "Scott Tolinski",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/leveluptuts/gQuery/issues"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"camel-case": "^4.1.2",
|
||||||
|
"graphql": "^15.6.0",
|
||||||
|
"pascal-case": "^3.1.2",
|
||||||
|
"svelte": "^3.43.0"
|
||||||
|
},
|
||||||
|
"description": "Not like jQuery. A GraphQL Fetcher & Cache for Svelte Kit",
|
||||||
|
"exports": {
|
||||||
|
"./gfetch": "src/gfetch.js"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/leveluptuts/gQuery#readme",
|
||||||
|
"keywords": [
|
||||||
|
"graphql"
|
||||||
|
],
|
||||||
|
"license": "ISC",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"name": "gquery",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/leveluptuts/gQuery.git"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.1"
|
||||||
|
}
|
||||||
138
src/codegen.ts
Normal file
138
src/codegen.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
import { CodegenPlugin } from '@graphql-codegen/plugin-helpers'
|
||||||
|
import { LoadedFragment } from '@graphql-codegen/visitor-plugin-common'
|
||||||
|
import { concatAST, FragmentDefinitionNode, Kind, OperationDefinitionNode, visit } from 'graphql'
|
||||||
|
import { pascalCase } from 'pascal-case'
|
||||||
|
|
||||||
|
const visitorPluginCommon = require('@graphql-codegen/visitor-plugin-common')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugin: (schema, documents, config, info) => {
|
||||||
|
const allAst = concatAST(documents.map((d) => d.document))
|
||||||
|
|
||||||
|
const allFragments: LoadedFragment[] = [
|
||||||
|
...(allAst.definitions.filter((d) => d.kind === Kind.FRAGMENT_DEFINITION) as FragmentDefinitionNode[]).map(
|
||||||
|
(fragmentDef) => ({
|
||||||
|
node: fragmentDef,
|
||||||
|
name: fragmentDef.name.value,
|
||||||
|
onType: fragmentDef.typeCondition.name.value,
|
||||||
|
isExternal: false,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
...(config.externalFragments || []),
|
||||||
|
]
|
||||||
|
|
||||||
|
const visitor = new visitorPluginCommon.ClientSideBaseVisitor(
|
||||||
|
schema,
|
||||||
|
allFragments,
|
||||||
|
{},
|
||||||
|
{ documentVariableSuffix: 'Doc' },
|
||||||
|
documents
|
||||||
|
)
|
||||||
|
const visitorResult = visit(allAst, { leave: visitor })
|
||||||
|
|
||||||
|
const operations = allAst.definitions.filter(
|
||||||
|
(d) => d.kind === Kind.OPERATION_DEFINITION
|
||||||
|
) as OperationDefinitionNode[]
|
||||||
|
const defaultTypes = `
|
||||||
|
|
||||||
|
type FetchWrapperArgs<T> = {
|
||||||
|
fetch: typeof fetch,
|
||||||
|
variables?: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubscribeWrapperArgs<T> = {
|
||||||
|
variables?: T,
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ops = operations
|
||||||
|
.map((o) => {
|
||||||
|
if (o) {
|
||||||
|
let name = o?.name?.value || ''
|
||||||
|
// const dsl = `export const ${pascalCase(op.name.value)}Doc = gql\`
|
||||||
|
// ${documents.find((d) => d.rawSDL.includes(`${op.operation} ${op.name.value}`)).rawSDL}\``
|
||||||
|
const op = `${pascalCase(name)}${pascalCase(o.operation)}`
|
||||||
|
const opv = `${op}Variables`
|
||||||
|
let operations = ''
|
||||||
|
|
||||||
|
if (o.operation === 'query') {
|
||||||
|
operations += `
|
||||||
|
export const ${name} = ({ variables, fetch}: FetchWrapperArgs<${opv}>):
|
||||||
|
Promise<GFetchReturnWithErrors<${op}>> =>
|
||||||
|
g.fetch<${op}>({
|
||||||
|
queries: [{ query: ${pascalCase(name)}Doc, variables }],
|
||||||
|
fetch
|
||||||
|
})
|
||||||
|
`
|
||||||
|
// If config is set to have subscription query, also write the
|
||||||
|
if (config.subscriptionQuery) {
|
||||||
|
operations += `
|
||||||
|
export const ${name}Subscribe = ({ variables }: SubscribeWrapperArgs<${opv}>):
|
||||||
|
Readable<GFetchReturnWithErrors<${op}>> =>
|
||||||
|
g.oFetch<${op}>({
|
||||||
|
queries: [{ query: ${pascalCase(name)}Doc, variables }]
|
||||||
|
})
|
||||||
|
`
|
||||||
|
}
|
||||||
|
} else if (o.operation === 'mutation') {
|
||||||
|
operations += `
|
||||||
|
export const ${name} = ({ variables }: SubscribeWrapperArgs<${opv}>):
|
||||||
|
Promise<GFetchReturnWithErrors<${op}>> =>
|
||||||
|
g.fetch<${op}>({
|
||||||
|
queries: [{ query: ${pascalCase(name)}Doc, variables }],
|
||||||
|
fetch,
|
||||||
|
})
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
return operations
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
let imports = [
|
||||||
|
`import type { Readable } from "svelte/store"`,
|
||||||
|
`import { g } from '${config.gFetchPath}'`,
|
||||||
|
`import type { GFetchReturnWithErrors } from '$graphql/gfetchLib'`,
|
||||||
|
`import gql from "graphql-tag"`,
|
||||||
|
]
|
||||||
|
|
||||||
|
// let schemaInputs = getCachedDocumentNodeFromSchema(schema).definitions.filter((d) => {
|
||||||
|
// return d.kind === 'InputObjectTypeDefinition'
|
||||||
|
// })
|
||||||
|
// let inputs = schemaInputs
|
||||||
|
// .map((d) => {
|
||||||
|
// console.log('/* START */')
|
||||||
|
// // @ts-ignore
|
||||||
|
// console.log('NAME: ', d.fields[0].name.value)
|
||||||
|
// // @ts-ignore
|
||||||
|
// let isReq = d.fields[0]?.type?.kind === 'NonNullType'
|
||||||
|
// console.log('REQUIRED: ', isReq ? '✅' : '❌')
|
||||||
|
// // @ts-ignore
|
||||||
|
// console.log('TYPE: ', isReq ? d.fields[0]?.type?.type?.name?.value : d.fields[0]?.type?.name?.value)
|
||||||
|
// // @ts-ignore
|
||||||
|
// // @ts-ignore
|
||||||
|
// console.log('d.fields[0]', d.fields[0]?.type)
|
||||||
|
// console.log('/* END */')
|
||||||
|
// console.log('')
|
||||||
|
|
||||||
|
// return `
|
||||||
|
// const inputName = {
|
||||||
|
// ${d.fields[0].name.value}: ${isReq ? d.fields[0]?.type?.type?.name?.value : d.fields[0]?.type?.name?.value}
|
||||||
|
// }
|
||||||
|
// `
|
||||||
|
// })
|
||||||
|
// .join('\n')
|
||||||
|
// console.log('inputs', inputs)
|
||||||
|
|
||||||
|
return {
|
||||||
|
prepend: imports,
|
||||||
|
content: [
|
||||||
|
defaultTypes,
|
||||||
|
visitor.fragments,
|
||||||
|
...visitorResult.definitions.filter((t) => typeof t == 'string'),
|
||||||
|
ops,
|
||||||
|
].join('\n'),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} as CodegenPlugin
|
||||||
185
src/gFetch.ts
Normal file
185
src/gFetch.ts
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
import type { DocumentNode } from "graphql/language/ast";
|
||||||
|
import { readable, writable } from "svelte/store";
|
||||||
|
import type { Readable } from "svelte/store";
|
||||||
|
|
||||||
|
// What's the deal with *gFetch*?
|
||||||
|
// gFetch is a 0 dependency fetcher for graphql that accepts a custom fetch function
|
||||||
|
// This is useful if your platform provides you with a customer fetch. ie SvelteKit
|
||||||
|
// It also uses batched queries to batch several queries into one request.
|
||||||
|
|
||||||
|
// Two key exports from this file
|
||||||
|
// gFetch -> a fetcher that returns async
|
||||||
|
// ogFetch -> a fetcher that returns a subscription
|
||||||
|
|
||||||
|
// * Main Features *
|
||||||
|
// 1. 0 deps outside of graphql and svelte kit
|
||||||
|
// 2. Allows for batched queries
|
||||||
|
// 3. Plays nice with custom fetch functions
|
||||||
|
// 4. Doesn't use it's own cache, ie, would rely on Svelte's stores
|
||||||
|
export declare type GFetchQueryDefault = {
|
||||||
|
errors?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type OptionalPropertyNames<T> = {
|
||||||
|
[K in keyof T]-?: {} extends { [P in K]: T[K] } ? K : never;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
type SpreadProperties<L, R, K extends keyof L & keyof R> = {
|
||||||
|
[P in K]: L[P] | Exclude<R[P], undefined>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
|
||||||
|
|
||||||
|
type SpreadTwo<L, R> = Id<
|
||||||
|
Pick<L, Exclude<keyof L, keyof R>> &
|
||||||
|
Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>> &
|
||||||
|
Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>> &
|
||||||
|
SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>
|
||||||
|
>;
|
||||||
|
|
||||||
|
type Spread<A extends readonly [...any]> = A extends [infer L, ...infer R]
|
||||||
|
? SpreadTwo<L, Spread<R>>
|
||||||
|
: unknown;
|
||||||
|
|
||||||
|
export declare type GFetchQueryResult<F> = {
|
||||||
|
[k: string]: F;
|
||||||
|
};
|
||||||
|
|
||||||
|
export declare type GFetchQueries = {
|
||||||
|
query: DocumentNode;
|
||||||
|
variables?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This function accepts a graphql document and returns a string to be used
|
||||||
|
// in fetch calls
|
||||||
|
export function gqlToString(tag: DocumentNode): string {
|
||||||
|
return tag.loc.source.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
type gFetchProperties = {
|
||||||
|
queries: GFetchQueries[];
|
||||||
|
fetch: typeof fetch;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApolloClientOptions = {
|
||||||
|
path?: string;
|
||||||
|
};
|
||||||
|
export type ApolloClient = {
|
||||||
|
path?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GFetchReturn<T> = {
|
||||||
|
data: T;
|
||||||
|
errors?: Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GFetchReturnWithErrors<T> = Spread<[T, GFetchQueryDefault]>;
|
||||||
|
|
||||||
|
export class GFetch extends Object {
|
||||||
|
public path: string;
|
||||||
|
|
||||||
|
constructor(options: ApolloClientOptions) {
|
||||||
|
super();
|
||||||
|
const { path } = options;
|
||||||
|
this.path = path;
|
||||||
|
this.fetch = this.fetch.bind(this);
|
||||||
|
this.oFetch = this.oFetch.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// * gFetch
|
||||||
|
// This is a fetcher that returns a promise that resolves to a graphql response
|
||||||
|
public async fetch<T>({
|
||||||
|
queries,
|
||||||
|
fetch,
|
||||||
|
}: gFetchProperties): Promise<GFetchReturnWithErrors<T>> {
|
||||||
|
// Get all the queries and transform the docs into strings
|
||||||
|
// Fetching gql require them to be strings.
|
||||||
|
if (!fetch && window) {
|
||||||
|
fetch = window.fetch;
|
||||||
|
}
|
||||||
|
const newQueries = {
|
||||||
|
...queries[0],
|
||||||
|
query: gqlToString(queries[0].query),
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is generic fetch, that is polyfilled via svelte kit
|
||||||
|
// graph ql fetches must be POST
|
||||||
|
// credentials include for user ssr data
|
||||||
|
const res = await fetch(this.path, {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(newQueries),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gets the data back from the server
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...data.data,
|
||||||
|
} as GFetchReturnWithErrors<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// * ogFetch
|
||||||
|
// This function is a fetcher that returns a svelte readable subscription
|
||||||
|
// This is to be used for client side fetching of data
|
||||||
|
public oFetch<F>({
|
||||||
|
queries,
|
||||||
|
}: {
|
||||||
|
queries: GFetchQueries[];
|
||||||
|
}): Readable<GFetchReturnWithErrors<F>> {
|
||||||
|
// 1. Build the store and initialize it as empty and error free
|
||||||
|
const initial = new Map();
|
||||||
|
// Creates a store that will be used to subscribe to the data
|
||||||
|
const store = readable(initial, this.makeSubscribe(initial, queries));
|
||||||
|
return store as unknown as Readable<GFetchReturnWithErrors<F>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A dummy function that is used to make subscribe happy.
|
||||||
|
private unsubscribe() {
|
||||||
|
// Nothing to do in this case
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part of ogFetch
|
||||||
|
// Designed this way to work will with Svelte's readable store
|
||||||
|
private makeSubscribe(data, queries) {
|
||||||
|
// Create a closure with access to the
|
||||||
|
// initial data and initialization arguments
|
||||||
|
return (set) => {
|
||||||
|
// 3. This won't get executed until the store has
|
||||||
|
// its first subscriber. Kick off retrieval.
|
||||||
|
this.fetchDataForSubscription(data, set, queries);
|
||||||
|
|
||||||
|
// We're not waiting for the response.
|
||||||
|
// Return the unsubscribe function which doesn't do
|
||||||
|
// do anything here (but is part of the stores protocol).
|
||||||
|
return this.unsubscribe;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Part of ogFetch
|
||||||
|
// Runs gFetch and updates subscription
|
||||||
|
private async fetchDataForSubscription(data, set, queries: GFetchQueries[]) {
|
||||||
|
try {
|
||||||
|
// Dispatch the request for the users
|
||||||
|
// This code is ONLY run client side, so fetch comes globally from the browser
|
||||||
|
const response = await this.fetch({ queries, fetch });
|
||||||
|
set(response);
|
||||||
|
} catch (error) {
|
||||||
|
// 6b. if there is a fetch error - deal with it
|
||||||
|
// and let observers know
|
||||||
|
data.error = error;
|
||||||
|
set(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const data = writable();
|
||||||
|
|
||||||
|
// ! IDEAS
|
||||||
|
// Mutations should take care of updating a generated writeable.
|
||||||
|
// import { tutorial } from '$graphql/state'
|
||||||
|
// import { updateTutorial } from '$graphql/gfetch.generated'
|
||||||
|
// updateTutorial()
|
||||||
|
// $tutorial is auto updated site wide
|
||||||
|
// Devtools based on svelte toy
|
||||||
39
src/index.ts
Normal file
39
src/index.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { writable, get } from "svelte/store";
|
||||||
|
|
||||||
|
const newGCache = () => {
|
||||||
|
const { subscribe, update, set } = writable({});
|
||||||
|
|
||||||
|
async function hydrate(newData) {
|
||||||
|
update((old) => {
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
...newData,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
set,
|
||||||
|
update,
|
||||||
|
hydrate,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const gCache = newGCache();
|
||||||
|
|
||||||
|
export async function gQuery(typename, { query, variables }) {
|
||||||
|
const current = get(gCache);
|
||||||
|
|
||||||
|
// Extremely Naive cache
|
||||||
|
// Just checks to see if the data is there, if it is, don't
|
||||||
|
// Hit the network again
|
||||||
|
if (!current?.[typename]) {
|
||||||
|
const newData = await query({
|
||||||
|
variables,
|
||||||
|
fetch,
|
||||||
|
});
|
||||||
|
|
||||||
|
await gCache.hydrate(newData);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue