Skip to content

Commit

Permalink
feat: Indicate whether a DataBridge supports GraphQL
Browse files Browse the repository at this point in the history
  • Loading branch information
codinsonn committed Dec 26, 2023
1 parent ab3fb45 commit c25156a
Show file tree
Hide file tree
Showing 16 changed files with 89 additions and 52 deletions.
7 changes: 5 additions & 2 deletions apps/next/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -980,11 +980,14 @@ type UpsertResumeResponse {
upsertedResume: ResumeData!
}

type Mutation {
syncUserBiosFromAirtable(args: SyncUserBiosFromAirtableArgs!): SyncUserBiosFromAirtableResponse
upsertResume(args: UpsertResumeArgs!): UpsertResumeResponse
}

type Query {
getResumeDataByUserSlug(args: GetResumeDataByUserSlugArgs!): ResumeData
getShopifyProducts(args: GetShopifyProductsArgs): GetShopifyProductsResponse
getUserBioBySlug(args: UserBioInput!): UserBio
healthCheck(args: HealthCheckArgs): HealthCheckResponse
syncUserBiosFromAirtable(args: SyncUserBiosFromAirtableArgs!): SyncUserBiosFromAirtableResponse
upsertResume(args: UpsertResumeArgs!): UpsertResumeResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ export type GetResumeDataByUserSlugResponse = z.infer<typeof GetResumeDataByUser
/** -i- API Config for getResumeDataByUserSlug() */
export const GetResumeDataByUserSlugDataBridge = createDataBridge({
resolverName: 'getResumeDataByUserSlug',
resolverType: 'query',
argsSchema: GetResumeDataByUserSlugArgs,
responseSchema: GetResumeDataByUserSlugResponse,
apiPath: '/api/resume/[slug]',
allowedMethods: ['GET', 'POST'],
allowedMethods: ['GRAPHQL', 'GET', 'POST'],
})
3 changes: 2 additions & 1 deletion features/cv-page/schemas/UpsertResumeDataBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ export type UpsertResumeResponse = z.infer<typeof UpsertResumeResponse>
/** -i- Aetherspace API Config for upsertResume() */
export const UpsertResumeDataBridge = createDataBridge({
resolverName: 'upsertResume',
resolverType: 'mutation',
argsSchema: UpsertResumeArgs,
responseSchema: UpsertResumeResponse,
apiPath: '/api/resume/[slug]/upsert',
allowedMethods: ['GET', 'POST'],
allowedMethods: ['GRAPHQL', 'GET', 'POST'],
})
3 changes: 2 additions & 1 deletion features/links-page/schemas/GetUserBioBySlugDataBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { UserBio, UserBioInput } from './UserBio'
/** -i- API Config for getResumeDataByUserSlug() */
export const GetUserBioBySlugDataBridge = createDataBridge({
resolverName: 'getUserBioBySlug',
resolverType: 'query',
argsSchema: UserBioInput,
responseSchema: UserBio,
apiPath: '/api/bio/[slug]',
allowedMethods: ['GET', 'POST'],
allowedMethods: ['GRAPHQL', 'GET', 'POST'],
})
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ export type SyncUserBiosFromAirtableResponse = z.infer<typeof SyncUserBiosFromAi
/** -i- Aetherspace API Config for syncUserBiosFromAirtable() */
export const SyncUserBiosFromAirtableDataBridge = createDataBridge({
resolverName: 'syncUserBiosFromAirtable',
resolverType: 'mutation',
argsSchema: SyncUserBiosFromAirtableArgs,
responseSchema: SyncUserBiosFromAirtableResponse,
apiPath: '/api/links/syncs/airtable',
allowedMethods: ['GET', 'POST'],
allowedMethods: ['GRAPHQL', 'GET', 'POST'],
})
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export type GetShopifyProductsResponse = z.infer<typeof GetShopifyProductsRespon
/** -i- The API config for the getShopifyProduct() resolver */
export const GetShopifyProductsDataBridge = createDataBridge({
resolverName: 'getShopifyProducts',
resolverType: 'query',
argsSchema: GetShopifyProductsArgs,
responseSchema: GetShopifyProductsResponse,
apiPath: '/api/aetherspace/commerce/shopify/products',
allowedMethods: ['GET', 'POST'],
allowedMethods: ['GRAPHQL', 'GET', 'POST'],
})
2 changes: 1 addition & 1 deletion packages/@aetherspace/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ Similar to adding recurring features, removing features or packages from a fresh
## Getting started with Aetherspace

- [Quickstart](/packages/@aetherspace/README.md)
- [Recommended way of working](/packages/@aetherspace/scripts/README.md)
- [Universal Routing](/packages/@aetherspace/navigation/README.md)
- [Free and Premium Licenses](/LICENSE.md)

...

Expand Down
70 changes: 43 additions & 27 deletions packages/@aetherspace/generators/aether-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
validateNonEmptyNoSpaces,
createAutocompleteSource,
getAvailableSchemas,
normalizeName,
replaceMany,
} from '../scripts/helpers/scriptUtils'

/* --- Disclaimer ------------------------------------------------------------------------------ */
Expand Down Expand Up @@ -66,6 +68,7 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>
name: 'resolverName',
message: 'What will you name the resolver function? (e.g. "doSomething")',
validate: validateNonEmptyNoSpaces,
transformer: normalizeName,
},
{
type: 'input',
Expand All @@ -78,7 +81,8 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>
message: 'Will this resolver query or mutate data?',
choices: [GraphqlQueryOption, GraphqlMutationOption],
default: (data) => {
const { resolverName, resolverDescription } = data
const { resolverDescription } = data
const resolverName = normalizeName(data.resolverName)
const mutationTriggerWords = ['update', 'edit', 'delete', 'remove', 'add', 'create']
const checkingString = `${resolverName} ${resolverDescription}`.toLowerCase()
const isMutatable = mutationTriggerWords.some((word) => checkingString.includes(word))
Expand Down Expand Up @@ -118,9 +122,10 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>
type: 'input',
name: 'argsSchemaName',
message: 'What will you call this new args schema?',
default: (data) => `${uppercaseFirstChar(data.resolverName)}Args`,
default: (data) => `${uppercaseFirstChar(normalizeName(data.resolverName))}Args`,
validate: validateNonEmptyNoSpaces,
when: (data) => data.argsSchemaTarget === NewArgsSchemaOption,
transformer: normalizeName,
},
{
type: 'autocomplete',
Expand All @@ -137,39 +142,48 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>
type: 'input',
name: 'resSchemaName',
message: 'What will you call this new response schema?',
default: (data) => `${uppercaseFirstChar(data.resolverName)}Response`,
validate: validateNonEmptyNoSpaces,
default: (data) => `${uppercaseFirstChar(normalizeName(data.resolverName))}Response`,
when: (data) => data.resSchemaTarget === NewResponseSchemaOption,
validate: validateNonEmptyNoSpaces,
transformer: normalizeName,
},
{
type: 'input',
name: 'apiPath',
message: 'What API path would you like to use for REST? (e.g. "/api/some/resolver/[slug]")',
default: (data) => {
const resolverName = normalizeName(data.resolverName)
const workspacePath = workspaceOptions[data.workspaceTarget]
const workspaceName = workspacePath.split('/')[1].replace('-core', '').replace('-page', '') // prettier-ignore
return `/api/${workspaceName}/${camelToDash(data.resolverName)}`
return `/api/${workspaceName}/${camelToDash(resolverName)}`
},
when: (data) => ['api route', 'GraphQL'].some(includesOption(data.generatables)),
validate: validateNonEmptyNoSpaces,
validate: (input) => {
if (!input.startsWith('/api/')) return 'API paths must start with "/api/"'
if (!input.includes('/')) return 'API paths must include at least one "/"'
if (input.includes(' ')) return 'API paths cannot include spaces, use dashes "-" instead'
if (input.includes('//')) return 'API paths cannot include double slashes "//"'
if (input.includes('.')) return 'API paths cannot include periods "." or file extensions'
return validateNonEmptyNoSpaces(input)
},
},
{
type: 'input',
name: 'formHookName',
message: 'What should the form hook be called?',
default: (data) => {
const { resolverName } = data
let formHookName = `use${uppercaseFirstChar(resolverName)}Form`
formHookName = formHookName.replace('Edit', '').replace('Resolver', '').replace('Update', '') // prettier-ignore
return formHookName
const resolverName = normalizeName(data.resolverName)
const formHookName = `use${uppercaseFirstChar(normalizeName(resolverName))}Form`
return replaceMany(formHookName, ['Add', 'Create', 'Edit', 'Update', 'Delete', 'Resolver'], '') // prettier-ignore
},
when: (data) => ['formState hook'].some(includesOption(data.generatables)),
validate: validateNonEmptyNoSpaces,
transformer: normalizeName,
},
],
actions: (data) => {
// Args
const { workspaceTarget, resolverName, resolverTarget, apiPath, resolverDescription } = data || {} // prettier-ignore
const { workspaceTarget, apiPath, resolverTarget, resolverDescription } = data || {} // prettier-ignore
const generatables = data!.generatables.map((option) => RESOLVER_GENERATABLES[option])
const workspacePath = workspaceOptions[workspaceTarget]
const resolverType = resolverTarget === GraphqlQueryOption ? 'query' : 'mutation'
Expand All @@ -179,6 +193,7 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>

// -- Vars --

const resolverName = normalizeName(data!.resolverName)
const ResolverName = uppercaseFirstChar(resolverName)
const resolverBridgeName = `${ResolverName}DataBridge`
const descriptions = [] as string[]
Expand All @@ -190,7 +205,7 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>
const jsDocArgsTitle = `/** --- ${argsSchemaName} ${argsSchemaLines} */`
const jsDocArgsDescription = `/** -i- ${argsSchemaDescription} */`
const jsDocArgsHeader = `${jsDocArgsTitle}\n${jsDocArgsDescription}`
const argsSchemaBody = [`test: z.string().default('Hello World'), // TODO: Add your own fields`] // prettier-ignore
const argsSchemaBody = [`exampleArg: z.string().default('Hello World'), // TODO: Add your own fields`] // prettier-ignore
const argsDescriptionStatement = `.describe(d.${argsSchemaName})`

const resSchemaDescription = `Response for the ${resolverName}() resolver`
Expand All @@ -200,20 +215,21 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>
const jsDocResTitle = `/** --- ${resSchemaName} ${resSchemaLines} */`
const jsDocResDescription = `/** -i- ${resSchemaDescription} */`
const jsDocResHeader = `${jsDocResTitle}\n${jsDocResDescription}`
const resSchemaBody = [`test: z.string().default('Hello World'), // TODO: Add your own fields`] // prettier-ignore
const resSchemaBody = [`exampleField: z.string().default('Hello World'), // TODO: Add your own fields`] // prettier-ignore
const resDescriptionStatement = `.describe(d.${resSchemaName})`

const apiConfigName = `${resolverName}APIConfig`
const jsDocResolverConfigTitle = `/** --- ${apiConfigName} ${'-'.repeat(LINES - apiConfigName.length)} */` // prettier-ignore
const apiBridgeName = `${resolverName}DataBridge`
const jsDocResolverConfigTitle = `/** --- ${apiBridgeName} ${'-'.repeat(LINES - apiBridgeName.length)} */` // prettier-ignore
const jsDocResolverConfigDescription = `/** -i- Aetherspace API Config for ${resolverName}() */` // prettier-ignore
const jsDocResolverConfigHeader = `${jsDocResolverConfigTitle}\n${jsDocResolverConfigDescription}` // prettier-ignore
const hasGraphResolver = generatables.includes('graphResolver')
const apiPathStatements = [''] as string[]
const allowedMethods = generatables.filter(matchMethods(['GET', 'POST', 'PUT']))
const allowedMethods = generatables.filter(matchMethods(['GET', 'POST', 'PUT', 'DELETE'])) as string[] // prettier-ignore
if (hasGraphResolver) allowedMethods.unshift('GRAPHQL')
const allowGET = allowedMethods.includes('GET')
const allowPOST = allowedMethods.includes('POST')
const allowPUT = allowedMethods.includes('PUT')
const allowDELETE = allowedMethods.includes('DELETE')
const hasGraphResolver = generatables.includes('graphResolver')

const jsDocResolverTitle = `/** --- ${resolverName} ${'-'.repeat(LINES - resolverName.length)} */` // prettier-ignore
const jsDocResolverDescription = `/** -i- ${resolverDescription || 'TODO: Add description'} */` // prettier-ignore
Expand Down Expand Up @@ -247,7 +263,7 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>
const resolverImportPath = `${traversalParts.join('/')}/resolvers/${resolverName}`

// Figure out API statements
if (allowGET || allowPOST || allowPUT) {
if (allowGET || allowPOST || allowPUT || allowDELETE) {
serverUtilImports.push('makeNextRouteHandler')
const apiPathTitle = `/** --- ${apiPath} ${'-'.repeat(LINES - apiPath.length)} */\n`
apiStatements.push(apiPathTitle)
Expand Down Expand Up @@ -280,7 +296,7 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>

// Add form hook?
if (requiresFormHook) {
const { formHookName } = data || {}
const formHookName = normalizeName(data!.formHookName)
const formHookDivider = `/* --- ${formHookName}() ${'-'.repeat(LINES - formHookName.length - 1)} */` // prettier-ignore
extraActions.push({
type: 'add',
Expand All @@ -298,7 +314,7 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>

// Add args schema?
if (data!.argsSchemaName) {
const ArgsSchemaName = uppercaseFirstChar(data!.argsSchemaName)
const ArgsSchemaName = uppercaseFirstChar(normalizeName(data!.argsSchemaName))
const jsDocTitle = `/* --- ${ArgsSchemaName} ${'-'.repeat(
LINES - ArgsSchemaName.length
)} */`
Expand All @@ -307,10 +323,10 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>
path: `${workspacePath}/schemas/${ArgsSchemaName}.ts`,
templateFile: '../../packages/@aetherspace/generators/templates/basic-schema.hbs',
data: {
descriptions: descriptions.join('\n '),
descriptions: `id: \`unique identifier\`,`,
jsDocHeader: `${jsDocTitle}\n`,
schemaName: ArgsSchemaName,
schemaBody: ``,
schemaBody: `id: z.string().id().describe(d.id), // TODO: Replace with your own fields`,
describeStatement: ``,
jsDocDescription: ``,
},
Expand All @@ -320,17 +336,17 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>

// Add response schema?
if (data!.resSchemaName) {
const ResSchemaName = uppercaseFirstChar(data!.resSchemaName)
const ResSchemaName = uppercaseFirstChar(normalizeName(data!.resSchemaName))
const jsDocTitle = `/* --- ${ResSchemaName} ${'-'.repeat(LINES - ResSchemaName.length)} */`
extraActions.push({
type: 'add',
path: `${workspacePath}/schemas/${ResSchemaName}.ts`,
templateFile: '../../packages/@aetherspace/generators/templates/basic-schema.hbs',
data: {
descriptions: descriptions.join('\n '),
descriptions: `id: \`unique identifier\`,`,
jsDocHeader: `${jsDocTitle}\n`,
schemaName: ResSchemaName,
schemaBody: ``,
schemaBody: `id: z.string().id().nullish().describe(d.id), // TODO: Replace with your own fields`,
describeStatement: ``,
jsDocDescription: ``,
},
Expand Down Expand Up @@ -367,8 +383,8 @@ export const registerAetherResolverGenerator = (plop: PlopTypes.NodePlopAPI) =>
} as PlopTypes.ActionType

if (isLinkedToSchemas) {
const ArgsSchemaName = uppercaseFirstChar(argsSchemaConfig?.schemaName || data!.argsSchemaName) // prettier-ignore
const ResSchemaName = uppercaseFirstChar(resSchemaConfig?.schemaName || data!.resSchemaName)
const ArgsSchemaName = uppercaseFirstChar(normalizeName(argsSchemaConfig?.schemaName || data!.argsSchemaName)) // prettier-ignore
const ResSchemaName = uppercaseFirstChar(normalizeName(resSchemaConfig?.schemaName || data!.resSchemaName)) // prettier-ignore
const argsSchemaWorkspace = argsSchemaConfig?.workspaceName || '..'
const resSchemaWorkspace = resSchemaConfig?.workspaceName || '..'
const argsSchemaImportStatement = `import { ${ArgsSchemaName} } from '${argsSchemaWorkspace}/schemas/${ArgsSchemaName}'`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@ import { z, aetherSchema, createDataBridge } from 'aetherspace/schemas'
{{{argsSchemaImportStatement}}}
{{{resSchemaImportStatement}}}

/* --- Descriptions ---------------------------------------------------------------------------- */

const d = {
{{{descriptions}}}
}

{{jsDocResolverConfigHeader}}
export const {{ResolverName}}DataBridge = createDataBridge({
resolverName: '{{resolverName}}',
resolverType: '{{resolverType}}',
argsSchema: {{ArgsSchemaName}}{{{inputSchemaTransforms}}},
responseSchema: {{ResSchemaName}},
responseSchema: {{ResSchemaName}},{{{apiPathStatements}}}
})
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ export const {{ResolverName}}DataBridge = createDataBridge({
resolverName: '{{resolverName}}',
resolverType: '{{resolverType}}',
argsSchema: {{ResolverName}}Args,
responseSchema: {{ResolverName}}Response,
responseSchema: {{ResolverName}}Response,{{{apiPathStatements}}}
})
2 changes: 1 addition & 1 deletion packages/@aetherspace/navigation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,4 @@ If you're wondering what the `screenConfig` should contain, how you should feed
- [Single Sources of Truth for your Web & Mobile apps](/packages/@aetherspace/schemas/README.md)
- [Getting data from GraphQL into your Screens](/packages/@aetherspace/navigation/AetherPage/README.md)
- [Aetherspace Core Concepts for cross-platform success](/packages/@aetherspace/core/README.md)
- [Automation based on Single Sources of Truth and the File System](/packages/@aetherspace/scripts/README.md)
- [Recommended way of working](/packages/@aetherspace/scripts/README.md)
4 changes: 2 additions & 2 deletions packages/@aetherspace/schemas/aetherGraphSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ const aetherGraphSchema = (
resolverName,
argSchema: resolver?.argSchema,
resSchema: resolver?.resSchema,
isMutation: !!resolver?.isMutation,
isMutation: resolver?.isMutation,
resolver,
}))

Expand Down Expand Up @@ -257,7 +257,7 @@ const aetherGraphSchema = (
JSONObject: GraphQLJSONObject,
...customScalars,
...(hasQueries ? { Query: queryResolvers } : {}),
...(hasMutations ? { Mutations: mutationResolvers } : {}),
...(hasMutations ? { Mutation: mutationResolvers } : {}),
},
}
}
Expand Down
7 changes: 5 additions & 2 deletions packages/@aetherspace/schemas/createDataBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const createDataBridge = <
refetchOnMount,
backgroundColor,
dynamic = 'auto',
...restOptions
}: {
resolverName: RN
resolverType?: 'query' | 'mutation'
Expand All @@ -92,16 +93,18 @@ export const createDataBridge = <
paramsToArgs?: (navParams: any) => AT
responseToProps?: (response: RT) => PT
apiPath?: string
allowedMethods?: ('GET' | 'POST' | 'PUT' | 'DELETE')[]
allowedMethods?: ('GRAPHQL' | 'GET' | 'POST' | 'PUT' | 'DELETE')[]
graphqlQuery?: string
refetchOnMount?: boolean
backgroundColor?: string
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
isMutation?: boolean
}) => {
// Vars & Flags
const constainsMutationKeyword = customGraphqlQuery?.includes?.('mutation')
const resolverType = customResolverType || (constainsMutationKeyword ? 'mutation' : 'query')
const isMutation = resolverType === 'mutation' || constainsMutationKeyword
const isMutation =
restOptions.isMutation || resolverType === 'mutation' || constainsMutationKeyword

// -- Error Checks --

Expand Down
Loading

0 comments on commit c25156a

Please sign in to comment.