feat(fe2): greatly improved DX for apollo cache modification (#2831)

* proof of concept - types work

* WIP

* wipp

* new modifyObjectFieldf

* updatePathIfExists

* wipp

* working?

* projects dashboard test

* more improvements

* more improvements

* fixx

* bugfix

* minor fixes and cleanup

* moar cleanup

* autoEvictFiltered
This commit is contained in:
Kristaps Fabians Geikins
2024-09-03 10:59:16 +03:00
committed by GitHub
parent 2e272b321e
commit 596ccf8ee3
9 changed files with 1816 additions and 152 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ const config: CodegenConfig = {
fragmentMasking: false,
dedupeFragments: true
},
plugins: []
plugins: ['./tools/gqlCacheHelpersCodegenPlugin.js']
}
}
}
@@ -68,12 +68,7 @@ import {
projectsDashboardWorkspaceQuery
} from '~~/lib/projects/graphql/queries'
import { graphql } from '~~/lib/common/generated/gql'
import {
getCacheId,
evictObjectFields,
modifyObjectFields
} from '~~/lib/common/helpers/graphql'
import type { User, UserProjectsArgs } from '~~/lib/common/generated/gql/graphql'
import { getCacheId, modifyObjectField } from '~~/lib/common/helpers/graphql'
import { UserProjectsUpdatedMessageType } from '~~/lib/common/generated/gql/graphql'
import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables/toast'
import { projectRoute } from '~~/lib/common/helpers/route'
@@ -171,35 +166,19 @@ onUserProjectsUpdate((res) => {
if (isNewProject && incomingProject) {
// Add to User.projects where possible
modifyObjectFields<UserProjectsArgs, User['projects']>(
modifyObjectField(
cache,
getCacheId('User', activeUserId),
(fieldName, variables, value, { ref }) => {
if (fieldName !== 'projects') return
if (variables.filter?.search?.length) return
if (variables.filter?.onlyWithRoles?.length) {
const roles = variables.filter.onlyWithRoles
if (!roles.includes(incomingProject.role || '')) return
}
return {
...value,
items: [ref('Project', incomingProject.id), ...(value.items || [])],
totalCount: (value.totalCount || 0) + 1
}
}
)
// Elsewhere - just evict fields directly
evictObjectFields<UserProjectsArgs, User['projects']>(
cache,
getCacheId('User', activeUserId),
(fieldName, variables) => {
if (fieldName !== 'projects') return false
if (variables.filter?.search?.length) return true
return false
}
'projects',
({ helpers: { ref, createUpdatedValue } }) =>
createUpdatedValue(({ update }) => {
update('items', (items) => [
ref('Project', incomingProject.id),
...(items || [])
])
update('totalCount', (count) => count + 1)
}),
{ autoEvictFiltered: true }
)
}
@@ -12,7 +12,7 @@ import { useQuery } from '@vue/apollo-composable'
import { convertThrowIntoFetchResult } from '~/lib/common/helpers/graphql'
import type { InfiniteLoaderState } from '@speckle/ui-components'
import { isUndefined } from 'lodash-es'
import type { MaybeNullOrUndefined, Optional } from '@speckle/shared'
import { type MaybeNullOrUndefined, type Optional } from '@speckle/shared'
import { useScopedState } from '~/lib/common/composables/scopedState'
export const useApolloClientIfAvailable = () => {
File diff suppressed because one or more lines are too long
+282 -83
View File
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { isUndefinedOrVoid } from '@speckle/shared'
import type { Optional } from '@speckle/shared'
import { isNullOrUndefined, isUndefinedOrVoid } from '@speckle/shared'
import type { MaybeNullOrUndefined, Optional } from '@speckle/shared'
import { ApolloError, defaultDataIdFromObject } from '@apollo/client/core'
import type {
FetchResult,
@@ -15,30 +15,54 @@ import { GraphQLError } from 'graphql'
import type { DocumentNode } from 'graphql'
import {
flatten,
isUndefined,
has,
isFunction,
isString,
isArray,
intersection
intersection,
get,
set,
cloneDeep,
isObjectLike
} from 'lodash-es'
import type { Modifier, Reference } from '@apollo/client/cache'
import type { PartialDeep } from 'type-fest'
import type { Modifier, ModifierDetails, Reference } from '@apollo/client/cache'
import type { Get, PartialDeep, Paths, ReadonlyDeep, Tagged } from 'type-fest'
import type { GraphQLErrors, NetworkError } from '@apollo/client/errors'
import { nanoid } from 'nanoid'
import { StackTrace } from '~~/lib/common/helpers/debugging'
import dayjs from 'dayjs'
import { base64Encode } from '~/lib/common/helpers/encodeDecode'
import type { ErrorResponse } from '@apollo/client/link/error'
import type {
AllObjectFieldArgTypes,
AllObjectTypes
} from '~/lib/common/generated/gql/graphql'
/**
* Cache key of a specific cached GQL object. Tends to look like `Type:id`.
*/
export type ApolloCacheObjectKey<Type extends keyof AllObjectTypes> = Tagged<
string,
Type
>
export const isServerError = (err: Error): err is ServerError =>
has(err, 'response') && has(err, 'result') && has(err, 'statusCode')
export const isServerParseError = (err: Error): err is ServerParseError =>
has(err, 'response') && has(err, 'bodyText') && has(err, 'statusCode')
export const ROOT_QUERY = 'ROOT_QUERY'
export const ROOT_MUTATION = 'ROOT_MUTATION'
export const ROOT_SUBSCRIPTION = 'ROOT_SUBSCRIPTION'
export const ROOT_QUERY = 'ROOT_QUERY' as ApolloCacheObjectKey<'Query'>
export const ROOT_MUTATION = 'ROOT_MUTATION' as ApolloCacheObjectKey<'Mutation'>
export const ROOT_SUBSCRIPTION =
'ROOT_SUBSCRIPTION' as ApolloCacheObjectKey<'Subscription'>
type ModifyFnCacheDataSingle<Data> = Data extends Record<string, unknown>
? Data extends { id: string; __typename?: infer TypeName }
? CacheObjectReference<TypeName extends keyof AllObjectTypes ? TypeName : string>
: PartialDeep<{
[key in keyof Data]: ModifyFnCacheData<Data[key]>
}>
: Data
/**
* Utility type for typing cached data in Apollo modify functions.
@@ -46,33 +70,24 @@ export const ROOT_SUBSCRIPTION = 'ROOT_SUBSCRIPTION'
* to CacheObjectReference objects and additionally all properties are optional as you can never know what exactly has been requested & cached
* and what hasn't
*/
export type ModifyFnCacheData<Data> = Data extends
| Record<string, unknown>
| Record<string, unknown>[]
? Data extends { id: string }
? CacheObjectReference
: Data extends { id: string }[]
? CacheObjectReference[]
: PartialDeep<{
[key in keyof Data]: Data[key] extends { id: string }
? CacheObjectReference
: Data[key] extends { id: string }[]
? CacheObjectReference[]
: ModifyFnCacheData<Data[key]>
}>
: Data
export type ModifyFnCacheData<Data> = Data extends Array<infer ArrayItem>
? ModifyFnCacheDataSingle<ArrayItem>[]
: ModifyFnCacheDataSingle<Data>
/**
* Get a cached object's identifier
*/
export function getCacheId(typeName: string, id: string) {
export function getCacheId<Type extends keyof AllObjectTypes>(
typeName: Type,
id: string
): ApolloCacheObjectKey<Type> {
const cachedId = defaultDataIdFromObject({
__typename: typeName,
id
})
if (!cachedId) throw new Error('Unable to build Apollo cache ID')
return cachedId
return cachedId as ApolloCacheObjectKey<Type>
}
export function isInvalidAuth(error: ApolloError | NetworkError) {
@@ -277,16 +292,23 @@ export function getStoreFieldName(
* Inside cache.modify calls you'll get these instead of full objects when reading fields that hold
* identifiable objects or object arrays
*/
export type CacheObjectReference = Reference
export type CacheObjectReference<Type extends string = string> = {
readonly __ref: Type extends keyof AllObjectTypes
? ApolloCacheObjectKey<Type>
: string
}
/**
* Objects & object arrays in `cache.modify` calls are represented through reference objects, so
* if you want to add new ones you shouldn't add the entire object, but only its reference
*/
export function getObjectReference(typeName: string, id: string): CacheObjectReference {
export function getObjectReference<Type extends keyof AllObjectTypes>(
typeName: Type,
id: string
): CacheObjectReference<Type> {
return {
__ref: getCacheId(typeName, id)
}
} as CacheObjectReference<Type>
}
export function isReference(obj: unknown): obj is CacheObjectReference {
@@ -332,6 +354,7 @@ export const revolveFieldNameAndVariables = <
* better filtering capabilities to filter filters to update (e.g. you can actually get each field's variables)
* Note: This uses cache.modify underneath which means that `data` will only hold object references (CacheObjectReference) not
* full objects. Read more: https://www.apollographql.com/docs/react/caching/cache-interaction/#values-vs-references
* @deprecated Use modifyObjectField instead
*/
export function modifyObjectFields<
Variables extends Optional<Record<string, unknown>> = undefined,
@@ -350,7 +373,8 @@ export function modifyObjectFields<
) =>
| Optional<ModifyFnCacheData<FieldData>>
| Parameters<Modifier<ModifyFnCacheData<FieldData>>>[1]['DELETE']
| Parameters<Modifier<ModifyFnCacheData<FieldData>>>[1]['INVALIDATE'],
| Parameters<Modifier<ModifyFnCacheData<FieldData>>>[1]['INVALIDATE']
| void,
options?: Partial<{
fieldNameWhitelist: string[]
debug: boolean
@@ -391,22 +415,27 @@ export function modifyObjectFields<
)
log('invoking updater', { fieldName, variables, fieldValue })
const res = updater(
fieldName,
(variables || {}) as Variables,
fieldValue as ModifyFnCacheData<FieldData>,
{
...details,
ref: getObjectReference,
revolveFieldNameAndVariables
}
)
try {
const res = updater(
fieldName,
(variables || {}) as Variables,
fieldValue as ModifyFnCacheData<FieldData>,
{
...details,
ref: getObjectReference,
revolveFieldNameAndVariables
}
)
if (isUndefined(res)) {
return fieldValue as unknown
} else {
log('updater returned', { res })
return res
if (isUndefinedOrVoid(res)) {
return fieldValue as unknown
} else {
log('updater returned', { res })
return res
}
} catch (e) {
log('updater threw an error', e)
throw e
}
}
})
@@ -416,6 +445,7 @@ export function modifyObjectFields<
* Iterate over a cached object's fields and evict/delete the ones that the predicate returns true for
* Note: This uses cache.modify underneath which means that `data` will only hold object references (CacheObjectReference) not
* full objects. Read more: https://www.apollographql.com/docs/react/caching/cache-interaction/#values-vs-references
* @deprecated Use modifyObjectField instead, just return the evict() call from the updater
*/
export function evictObjectFields<
V extends Optional<Record<string, unknown>> = undefined,
@@ -498,44 +528,6 @@ export const getDateCursorFromReference = (params: {
return base64Encode(iso)
}
/**
* Simplified version of modifyObjectFields, just targetting a single field
* @see modifyObjectFields
*/
export const modifyObjectField = <
FieldData = unknown,
Variables extends Optional<Record<string, unknown>> = undefined
>(
cache: ApolloCache<unknown>,
id: string,
fieldName: string,
updater: (params: {
fieldName: string
variables: Variables
value: ModifyFnCacheData<FieldData>
details: Parameters<Modifier<ModifyFnCacheData<FieldData>>>[1] & {
ref: typeof getObjectReference
revolveFieldNameAndVariables: typeof revolveFieldNameAndVariables
}
}) =>
| Optional<ModifyFnCacheData<FieldData>>
| Parameters<Modifier<ModifyFnCacheData<FieldData>>>[1]['DELETE']
| Parameters<Modifier<ModifyFnCacheData<FieldData>>>[1]['INVALIDATE'],
options?: Partial<{
debug: boolean
}>
) => {
modifyObjectFields<Variables, FieldData>(
cache,
id,
(field, variables, value, details) => {
if (field !== fieldName) return
return updater({ fieldName: field, variables, value, details })
},
options
)
}
/**
* Build skipLoggingErrors function that skips logging errors if there's only one error and it's related to a specific field
*/
@@ -548,3 +540,210 @@ export const skipLoggingErrorsIfOneFieldError =
err.graphQLErrors.some((e) => intersection(e.path || [], fieldNames).length > 0)
)
}
type NonUndefined<T> = T extends undefined ? never : T
/**
* Update field at specific path in object, only if it exists. Useful for cache modification
* when fields should only be updated if they exist.
*/
export const updatePathIfExists = <Value, Path extends Paths<Value> & string>(
val: Value,
path: Path,
updater: (val: NonUndefined<Get<Value, Path>>) => NonUndefined<Get<Value, Path>>
) => {
if (!val) return val
if (has(val, path)) {
const pathVal = get(val, path) as NonUndefined<Get<Value, Path>>
const newVal = updater(pathVal)
set(val, path, newVal)
}
return val
}
/**
* Get value from specific path in object, only if it exists.
*/
export const getFromPathIfExists = <Value, Path extends Paths<Value> & string>(
val: MaybeNullOrUndefined<Value>,
path: Path
): Optional<Get<Value, Path>> => {
if (!val) return undefined
if (!has(val, path)) return undefined
return get(val, path) as Get<Value, Path>
}
type ModifyObjectFieldValue<
Type extends keyof AllObjectTypes,
Field extends keyof AllObjectTypes[Type]
> = ModifyFnCacheData<AllObjectTypes[Type][Field]>
/**
* Simplified & improved version of modifyObjectFields, just targetting a single field for a cache modification
* @see modifyObjectFields
*/
export const modifyObjectField = <
Type extends keyof AllObjectTypes,
Field extends keyof AllObjectTypes[Type]
>(
cache: ApolloCache<unknown>,
key: ApolloCacheObjectKey<Type>,
fieldName: Field,
updater: (params: {
fieldName: string
variables: Field extends keyof AllObjectFieldArgTypes[Type]
? AllObjectFieldArgTypes[Type][Field]
: never
/**
* Value found in the cache. Read-only and should not be mutated directly. Use the
* createUpdatedValue() helper to build a new value with updated fields.
*/
value: ReadonlyDeep<ModifyObjectFieldValue<Type, Field>>
helpers: {
/**
* Build new value with the values at specific paths updated with the provided updater functions,
* ONLY if the paths exist in the cache.
*
* This function operates on a deeply cloned value that is safe to mutate
*/
createUpdatedValue: (
updateHandler: (params: {
/**
* Invoke this function to update one specific path in the object
*/
update: <Path extends Paths<ModifyObjectFieldValue<Type, Field>> & string>(
path: Path,
pathUpdate: (
val: NonUndefined<Get<ModifyObjectFieldValue<Type, Field>, Path>>
) => NonUndefined<Get<ModifyObjectFieldValue<Type, Field>, Path>>
) => MaybeNullOrUndefined<ModifyObjectFieldValue<Type, Field>>
}) => void
) => ModifyObjectFieldValue<Type, Field>
/**
* Get value from specific path, only if it exists in the cache value
*/
get: <Path extends Paths<ModifyObjectFieldValue<Type, Field>> & string>(
path: Path
) => Optional<Get<ModifyObjectFieldValue<Type, Field>, Path>>
/**
* Invoke and return this out from the modify call to evict the field from the cache
*/
evict: () => ModifierDetails['DELETE']
/**
* Read field data from a Reference object
*/
readField: <
ReadFieldType extends keyof AllObjectTypes,
ReadFieldName extends keyof AllObjectTypes[ReadFieldType] & string
>(
ref: CacheObjectReference<ReadFieldType>,
fieldName: ReadFieldName
) => Optional<AllObjectTypes[ReadFieldType][ReadFieldName]>
/**
* Build a reference object for a specific object in the cache
*/
ref: typeof getObjectReference
}
}) =>
| Optional<ModifyObjectFieldValue<Type, Field>>
| ReadonlyDeep<ModifyObjectFieldValue<Type, Field>>
| ModifierDetails['DELETE']
| ModifierDetails['INVALIDATE']
| void,
options?: Partial<{
debug: boolean
/**
* Whether to auto evict values that have variables with common filters in them (e.g. a 'filter' or
* 'search' prop). Often its better to evict filtered values, because we can't tell if the newly
* added item should be included in the filtered list or not.
*/
autoEvictFiltered: boolean
}>
) => {
const { autoEvictFiltered } = options || {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
modifyObjectFields<any, any>(
cache,
key,
(field, variables, value, details) => {
if (field !== fieldName) return
// Auto evict filtered values?
if (autoEvictFiltered && isObjectLike(variables)) {
const checkFilter = (filter: string) => {
if (!has(variables, filter)) return false
const val = get(variables, filter)
// True, if any primitive value (e.g. string, number) && arrays
if (isNullOrUndefined(val)) return false
if (isArray(val)) return true
if (!isObjectLike(val)) return true
return false
}
const commonFilters = ['query', 'filter', 'search', 'filter.search']
const hasFilter = commonFilters.some(checkFilter)
if (hasFilter) {
return details.DELETE
}
}
// Build helpers & clone value to allow for direct mutation
const createUpdatedValue = (
updateHandler: (params: {
update: <Path extends Paths<ModifyObjectFieldValue<Type, Field>> & string>(
path: Path,
pathUpdate: (
val: NonUndefined<Get<ModifyObjectFieldValue<Type, Field>, Path>>
) => NonUndefined<Get<ModifyObjectFieldValue<Type, Field>, Path>>
) => MaybeNullOrUndefined<ModifyObjectFieldValue<Type, Field>>
}) => void
) => {
let clonedValue = cloneDeep(value) as ModifyObjectFieldValue<Type, Field>
updateHandler({
update: (path, pathUpdate) => {
clonedValue = updatePathIfExists(clonedValue, path, pathUpdate)
return clonedValue
}
})
return clonedValue
}
const getIfExists = <
Path extends Paths<ModifyObjectFieldValue<Type, Field>> & string
>(
path: Path
) => getFromPathIfExists<ModifyObjectFieldValue<Type, Field>, Path>(value, path)
const evict = () => details.DELETE
const readField = <
ReadFieldType extends keyof AllObjectTypes,
ReadFieldName extends keyof AllObjectTypes[ReadFieldType] & string
>(
ref: CacheObjectReference<ReadFieldType>,
fieldName: ReadFieldName
) =>
details.readField(
fieldName,
ref
) as AllObjectTypes[ReadFieldType][ReadFieldName]
return updater({
fieldName: field,
variables,
value,
helpers: {
createUpdatedValue,
get: getIfExists,
evict,
readField,
ref: getObjectReference
}
})
},
options
)
}
@@ -3,11 +3,6 @@ import { waitForever, type MaybeAsync, type Optional } from '@speckle/shared'
import { useApolloClient, useMutation } from '@vue/apollo-composable'
import { graphql } from '~/lib/common/generated/gql'
import type {
Query,
QueryWorkspaceArgs,
QueryWorkspaceInviteArgs,
User,
UserWorkspacesArgs,
UseWorkspaceInviteManager_PendingWorkspaceCollaboratorFragment,
Workspace,
WorkspaceCreateInput,
@@ -33,7 +28,7 @@ import {
processWorkspaceInviteMutation,
workspaceUpdateRoleMutation
} from '~/lib/workspaces/graphql/mutations'
import { isFunction, isUndefined } from 'lodash-es'
import { isFunction } from 'lodash-es'
import type { GraphQLError } from 'graphql'
import { useClipboard } from '~~/composables/browser'
@@ -143,33 +138,33 @@ export const useProcessWorkspaceInvite = () => {
if (accepted) {
// Evict Query.workspace
modifyObjectField<Query['workspace'], QueryWorkspaceArgs>(
modifyObjectField(
cache,
ROOT_QUERY,
'workspace',
({ variables, details: { DELETE } }) => {
if (variables.id === workspaceId) return DELETE
({ variables, helpers: { evict } }) => {
if (variables.id === workspaceId) return evict()
}
)
// Evict all User.workspaces
modifyObjectField<User['workspaces'], UserWorkspacesArgs>(
modifyObjectField(
cache,
getCacheId('User', userId),
'workspaces',
({ details: { DELETE } }) => DELETE
({ helpers: { evict } }) => evict()
)
}
// Set Query.workspaceInvite(id) = null (no invite)
modifyObjectField<Query['workspaceInvite'], QueryWorkspaceInviteArgs>(
modifyObjectField(
cache,
ROOT_QUERY,
'workspaceInvite',
({ value, variables, details: { readField } }) => {
({ value, variables, helpers: { readField } }) => {
if (value) {
const workspaceId = readField('workspaceId', value)
if (workspaceId === workspaceId) return null
const inviteWorkspaceId = readField(value, 'workspaceId')
if (inviteWorkspaceId === workspaceId) return null
} else {
if (variables.workspaceId === workspaceId) return null
}
@@ -367,25 +362,18 @@ export function useCreateWorkspace() {
if (!workspaceId) return
// Navigation to workspace is gonna fetch everything needed for the page, so we only
// really need to update workspace fields used in sidebar & settings: User.workspaces
modifyObjectField<User['workspaces'], UserWorkspacesArgs>(
modifyObjectField(
cache,
getCacheId('User', userId),
'workspaces',
({ variables, value, details: { DELETE } }) => {
if (variables.filter?.search?.length) return DELETE // evict if filtered search
const totalCount = isUndefined(value?.totalCount)
? undefined
: value.totalCount + 1
const items = isUndefined(value?.items)
? undefined
: [...value.items, getObjectReference('Workspace', workspaceId)]
return {
...value,
totalCount,
items
}
({ helpers: { createUpdatedValue, ref } }) => {
return createUpdatedValue(({ update }) => {
update('totalCount', (totalCount) => totalCount + 1)
update('items', (items) => [...items, ref('Workspace', workspaceId)])
})
},
{
autoEvictFiltered: true
}
)
}
+4 -1
View File
@@ -91,6 +91,9 @@
"@eslint/config-inspector": "^0.4.10",
"@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/client-preset": "^4.3.0",
"@graphql-codegen/plugin-helpers": "^5.0.4",
"@graphql-codegen/typescript": "^4.0.9",
"@graphql-codegen/visitor-plugin-common": "5.3.1",
"@nuxt/devtools": "^1.3.9",
"@nuxt/eslint": "^0.3.13",
"@nuxtjs/tailwindcss": "^6.3.0",
@@ -137,7 +140,7 @@
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^26.0.0",
"tailwindcss": "^3.4.1",
"type-fest": "^3.5.1",
"type-fest": "^4.24.0",
"typescript": "^4.8.3",
"vue-tsc": "2.0.10",
"wait-on": "^6.0.1",
@@ -0,0 +1,59 @@
const { reduce } = require('lodash')
const capitalize = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1)
}
const formatTsTypeName = (gqlTypeName) => {
// Not sure why it gets converted this way in parent types
return gqlTypeName.replace('AI', 'Ai')
}
/**
* Plugin that adds some extra generated types and type mappings to support better Apollo Cache modification utilities
* @type {import('@graphql-codegen/plugin-helpers').PluginFunction}
*/
const plugin = (schema) => {
/** @type {Record<string, import('graphql').GraphQLNamedType>} */
const objectTypeMap = reduce(
schema.getTypeMap(),
(acc, type, typeName) => {
if (type.astNode?.kind === 'ObjectTypeDefinition') {
acc[typeName] = type
}
return acc
},
{}
)
let output = `export type AllObjectTypes = {\n`
for (const [typeName] of Object.entries(objectTypeMap)) {
output += ` ${typeName}: ${formatTsTypeName(typeName)},\n`
}
output += `}\n`
for (const [typeName, type] of Object.entries(objectTypeMap)) {
const finalTypeName = formatTsTypeName(typeName)
output += `export type ${finalTypeName}FieldArgs = {\n`
for (const [fieldName, fieldDef] of Object.entries(type.getFields())) {
const argCount = fieldDef.args.length
const argsName = formatTsTypeName(`${finalTypeName}${capitalize(fieldName)}Args`)
output += ` ${fieldName}: ${argCount ? argsName : '{}'},\n`
}
output += `}\n`
}
output += `export type AllObjectFieldArgTypes = {\n`
for (const [typeName] of Object.entries(objectTypeMap)) {
const finalTypeName = formatTsTypeName(typeName)
output += ` ${typeName}: ${finalTypeName}FieldArgs,\n`
}
output += `}\n`
return `${output}\n`
}
module.exports = {
plugin
}
+46 -1
View File
@@ -10205,6 +10205,21 @@ __metadata:
languageName: node
linkType: hard
"@graphql-codegen/typescript@npm:^4.0.9":
version: 4.0.9
resolution: "@graphql-codegen/typescript@npm:4.0.9"
dependencies:
"@graphql-codegen/plugin-helpers": "npm:^5.0.4"
"@graphql-codegen/schema-ast": "npm:^4.0.2"
"@graphql-codegen/visitor-plugin-common": "npm:5.3.1"
auto-bind: "npm:~4.0.0"
tslib: "npm:~2.6.0"
peerDependencies:
graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
checksum: 10/304026adfe622530b8a2827569dd5bbd390177051be8c214fb79873ec64ef21793635c91657703bfd229a3d06f1a8a6f1addd8ae7eab20d1eff2efe6fb044df7
languageName: node
linkType: hard
"@graphql-codegen/visitor-plugin-common@npm:5.2.0, @graphql-codegen/visitor-plugin-common@npm:^5.0.0, @graphql-codegen/visitor-plugin-common@npm:^5.2.0":
version: 5.2.0
resolution: "@graphql-codegen/visitor-plugin-common@npm:5.2.0"
@@ -10225,6 +10240,26 @@ __metadata:
languageName: node
linkType: hard
"@graphql-codegen/visitor-plugin-common@npm:5.3.1":
version: 5.3.1
resolution: "@graphql-codegen/visitor-plugin-common@npm:5.3.1"
dependencies:
"@graphql-codegen/plugin-helpers": "npm:^5.0.4"
"@graphql-tools/optimize": "npm:^2.0.0"
"@graphql-tools/relay-operation-optimizer": "npm:^7.0.0"
"@graphql-tools/utils": "npm:^10.0.0"
auto-bind: "npm:~4.0.0"
change-case-all: "npm:1.0.15"
dependency-graph: "npm:^0.11.0"
graphql-tag: "npm:^2.11.0"
parse-filepath: "npm:^1.0.2"
tslib: "npm:~2.6.0"
peerDependencies:
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
checksum: 10/6dd0464d9099d5aeabeb766515fc8dd2fc84bcae4cb0e3653d7f38aea716d6622d35d7cbb57a1954e6bc1cde10f4dd8c4a75ceb4e8bb8cdbba16219615666a5f
languageName: node
linkType: hard
"@graphql-tools/apollo-engine-loader@npm:^8.0.0":
version: 8.0.1
resolution: "@graphql-tools/apollo-engine-loader@npm:8.0.1"
@@ -14991,6 +15026,9 @@ __metadata:
"@eslint/config-inspector": "npm:^0.4.10"
"@graphql-codegen/cli": "npm:^5.0.2"
"@graphql-codegen/client-preset": "npm:^4.3.0"
"@graphql-codegen/plugin-helpers": "npm:^5.0.4"
"@graphql-codegen/typescript": "npm:^4.0.9"
"@graphql-codegen/visitor-plugin-common": "npm:5.3.1"
"@headlessui/vue": "npm:^1.7.13"
"@heroicons/vue": "npm:^2.0.12"
"@jsonforms/core": "npm:^3.3.0"
@@ -15087,7 +15125,7 @@ __metadata:
tailwindcss: "npm:^3.4.1"
tweetnacl-sealedbox-js: "npm:^1.2.0"
tweetnacl-util: "npm:^0.15.1"
type-fest: "npm:^3.5.1"
type-fest: "npm:^4.24.0"
typescript: "npm:^4.8.3"
ua-parser-js: "npm:^1.0.38"
vee-validate: "npm:^4.7.0"
@@ -48527,6 +48565,13 @@ __metadata:
languageName: node
linkType: hard
"type-fest@npm:^4.24.0":
version: 4.24.0
resolution: "type-fest@npm:4.24.0"
checksum: 10/60efd6ec71f5113ef0a0fcabe61fc722bb2520ea082bc23e4b4dfb44204234dc691560a5e837f939160d7c18b410ed8fae32ddb752d57bed009248e0f61dce6b
languageName: node
linkType: hard
"type-is@npm:^1.6.16, type-is@npm:^1.6.18, type-is@npm:~1.6.18":
version: 1.6.18
resolution: "type-is@npm:1.6.18"