diff --git a/__fixtures__/example-todo-main/web/src/graphql/graphql.ts b/__fixtures__/example-todo-main/web/src/graphql/graphql.ts index 6973b81d64ef..aac6283994fd 100644 --- a/__fixtures__/example-todo-main/web/src/graphql/graphql.ts +++ b/__fixtures__/example-todo-main/web/src/graphql/graphql.ts @@ -15,6 +15,7 @@ export type Scalars = { Int: { input: number; output: number; } Float: { input: number; output: number; } BigInt: { input: any; output: any; } + Byte: { input: any; output: any; } Date: { input: any; output: any; } DateTime: { input: any; output: any; } JSON: { input: any; output: any; } diff --git a/packages/cli/src/commands/generate/__tests__/helpers.test.js b/packages/cli/src/commands/generate/__tests__/helpers.test.js index 4ab7310e733f..5ba670cb4772 100644 --- a/packages/cli/src/commands/generate/__tests__/helpers.test.js +++ b/packages/cli/src/commands/generate/__tests__/helpers.test.js @@ -541,6 +541,10 @@ describe('mapPrismaScalarToPagePropTsType', () => { expect(helpers.mapPrismaScalarToPagePropTsType('DateTime')).toBe('string') }) + it('maps scalar type Bytes to TS type Buffer', () => { + expect(helpers.mapPrismaScalarToPagePropTsType('Bytes')).toBe('Buffer') + }) + it('maps all other type not-known to TS to unknown', () => { expect(helpers.mapPrismaScalarToPagePropTsType('Json')).toBe('unknown') }) diff --git a/packages/cli/src/commands/generate/helpers.js b/packages/cli/src/commands/generate/helpers.js index e3d48ff99c7d..c17bb6eb2985 100644 --- a/packages/cli/src/commands/generate/helpers.js +++ b/packages/cli/src/commands/generate/helpers.js @@ -298,7 +298,7 @@ export const mapRouteParamTypeToTsType = (paramType) => { return routeParamToTsType[paramType] || 'unknown' } -/** @type {(scalarType: 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' ) => string } **/ +/** @type {(scalarType: 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' | 'Bytes' ) => string } **/ export const mapPrismaScalarToPagePropTsType = (scalarType) => { const prismaScalarToTsType = { String: 'string', @@ -308,6 +308,7 @@ export const mapPrismaScalarToPagePropTsType = (scalarType) => { Float: 'number', Decimal: 'number', DateTime: 'string', + Bytes: 'Buffer', } return prismaScalarToTsType[scalarType] || 'unknown' } diff --git a/packages/cli/src/commands/generate/sdl/__tests__/__snapshots__/sdl.test.js.snap b/packages/cli/src/commands/generate/sdl/__tests__/__snapshots__/sdl.test.js.snap index 4121cc48665d..bbe1df8bc373 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/__snapshots__/sdl.test.js.snap +++ b/packages/cli/src/commands/generate/sdl/__tests__/__snapshots__/sdl.test.js.snap @@ -728,6 +728,63 @@ exports[`with graphql documentations in javascript mode creates a multi word sdl " `; +exports[`with graphql documentations in javascript mode creates a sdl file with Byte definitions 1`] = ` +"export const schema = gql\` + """ + Representation of Key. + """ + type Key { + "Description for id." + id: Int! + + "Description for publicKey." + publicKey: Byte! + } + + """ + About queries + """ + type Query { + "Fetch Keys." + keys: [Key!]! @requireAuth + + "Fetch a Key by id." + key(id: Int!): Key @requireAuth + } + + """ + Autogenerated input type of InputKey. + """ + input CreateKeyInput { + "Description for publicKey." + publicKey: Byte! + } + + """ + Autogenerated input type of UpdateKey. + """ + input UpdateKeyInput { + "Description for publicKey." + publicKey: Byte + } + + """ + About mutations + """ + type Mutation { + "Creates a new Key." + createKey(input: CreateKeyInput!): Key! @requireAuth + + "Updates an existing Key." + updateKey(id: Int!, input: UpdateKeyInput!): Key! @requireAuth + + "Deletes an existing Key." + deleteKey(id: Int!): Key! @requireAuth + } +\` +" +`; + exports[`with graphql documentations in javascript mode creates a sdl file with enum definitions 1`] = ` "export const schema = gql\` """ @@ -1163,6 +1220,63 @@ exports[`with graphql documentations in typescript mode creates a multi word sdl " `; +exports[`with graphql documentations in typescript mode creates a sdl file with Byte definitions 1`] = ` +"export const schema = gql\` + """ + Representation of Key. + """ + type Key { + "Description for id." + id: Int! + + "Description for publicKey." + publicKey: Byte! + } + + """ + About queries + """ + type Query { + "Fetch Keys." + keys: [Key!]! @requireAuth + + "Fetch a Key by id." + key(id: Int!): Key @requireAuth + } + + """ + Autogenerated input type of InputKey. + """ + input CreateKeyInput { + "Description for publicKey." + publicKey: Byte! + } + + """ + Autogenerated input type of UpdateKey. + """ + input UpdateKeyInput { + "Description for publicKey." + publicKey: Byte + } + + """ + About mutations + """ + type Mutation { + "Creates a new Key." + createKey(input: CreateKeyInput!): Key! @requireAuth + + "Updates an existing Key." + updateKey(id: Int!, input: UpdateKeyInput!): Key! @requireAuth + + "Deletes an existing Key." + deleteKey(id: Int!): Key! @requireAuth + } +\` +" +`; + exports[`with graphql documentations in typescript mode creates a sdl file with enum definitions 1`] = ` "export const schema = gql\` """ @@ -1526,6 +1640,35 @@ exports[`without graphql documentations in javascript mode creates a multi word " `; +exports[`without graphql documentations in javascript mode creates a sdl file with Byte definitions 1`] = ` +"export const schema = gql\` + type Key { + id: Int! + publicKey: Byte! + } + + type Query { + keys: [Key!]! @requireAuth + key(id: Int!): Key @requireAuth + } + + input CreateKeyInput { + publicKey: Byte! + } + + input UpdateKeyInput { + publicKey: Byte + } + + type Mutation { + createKey(input: CreateKeyInput!): Key! @requireAuth + updateKey(id: Int!, input: UpdateKeyInput!): Key! @requireAuth + deleteKey(id: Int!): Key! @requireAuth + } +\` +" +`; + exports[`without graphql documentations in javascript mode creates a sdl file with enum definitions 1`] = ` "export const schema = gql\` type Shoe { @@ -1734,6 +1877,35 @@ exports[`without graphql documentations in typescript mode creates a multi word " `; +exports[`without graphql documentations in typescript mode creates a sdl file with Byte definitions 1`] = ` +"export const schema = gql\` + type Key { + id: Int! + publicKey: Byte! + } + + type Query { + keys: [Key!]! @requireAuth + key(id: Int!): Key @requireAuth + } + + input CreateKeyInput { + publicKey: Byte! + } + + input UpdateKeyInput { + publicKey: Byte + } + + type Mutation { + createKey(input: CreateKeyInput!): Key! @requireAuth + updateKey(id: Int!, input: UpdateKeyInput!): Key! @requireAuth + deleteKey(id: Int!): Key! @requireAuth + } +\` +" +`; + exports[`without graphql documentations in typescript mode creates a sdl file with enum definitions 1`] = ` "export const schema = gql\` type Shoe { diff --git a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/schema.prisma b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/schema.prisma index 86d854f9895d..d31245c497a1 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/fixtures/schema.prisma +++ b/packages/cli/src/commands/generate/sdl/__tests__/fixtures/schema.prisma @@ -53,6 +53,11 @@ model Photo { metadata Json } +model Key { + id Int @id @default(autoincrement()) + publicKey Bytes +} + /// A list of allowed colors. enum Color { RED diff --git a/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js b/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js index 081aebe7ba19..77ec6be07efc 100644 --- a/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js +++ b/packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js @@ -203,6 +203,21 @@ const itCreatesAnSDLFileWithJsonDefinitions = (baseArgs = {}) => { }) } +const itCreatesAnSDLFileWithByteDefinitions = (baseArgs = {}) => { + test('creates a sdl file with Byte definitions', async () => { + const files = await sdl.files({ + ...baseArgs, + name: 'Key', + crud: true, + }) + const ext = extensionForBaseArgs(baseArgs) + + expect( + files[path.normalize(`/path/to/project/api/src/graphql/keys.sdl.${ext}`)] + ).toMatchSnapshot() + }) +} + describe('without graphql documentations', () => { describe('in javascript mode', () => { const baseArgs = { ...getDefaultArgs(sdl.defaults), tests: true } @@ -215,6 +230,7 @@ describe('without graphql documentations', () => { itCreateAMultiWordSDLFileWithCRUD(baseArgs) itCreatesAnSDLFileWithEnumDefinitions(baseArgs) itCreatesAnSDLFileWithJsonDefinitions(baseArgs) + itCreatesAnSDLFileWithByteDefinitions(baseArgs) }) describe('in typescript mode', () => { @@ -232,6 +248,7 @@ describe('without graphql documentations', () => { itCreateAMultiWordSDLFileWithCRUD(baseArgs) itCreatesAnSDLFileWithEnumDefinitions(baseArgs) itCreatesAnSDLFileWithJsonDefinitions(baseArgs) + itCreatesAnSDLFileWithByteDefinitions(baseArgs) }) }) @@ -251,6 +268,7 @@ describe('with graphql documentations', () => { itCreateAMultiWordSDLFileWithCRUD(baseArgs) itCreatesAnSDLFileWithEnumDefinitions(baseArgs) itCreatesAnSDLFileWithJsonDefinitions(baseArgs) + itCreatesAnSDLFileWithByteDefinitions(baseArgs) }) describe('in typescript mode', () => { @@ -269,20 +287,11 @@ describe('with graphql documentations', () => { itCreateAMultiWordSDLFileWithCRUD(baseArgs) itCreatesAnSDLFileWithEnumDefinitions(baseArgs) itCreatesAnSDLFileWithJsonDefinitions(baseArgs) + itCreatesAnSDLFileWithByteDefinitions(baseArgs) }) }) describe('handler', () => { - beforeEach(() => { - jest.spyOn(console, 'info').mockImplementation(() => {}) - jest.spyOn(console, 'log').mockImplementation(() => {}) - }) - - afterEach(() => { - console.info.mockRestore() - console.log.mockRestore() - }) - const canBeCalledWithGivenModelName = (letterCase, model) => { test(`can be called with ${letterCase} model name`, async () => { const spy = jest.spyOn(fs, 'writeFileSync') diff --git a/packages/cli/src/commands/generate/sdl/sdl.js b/packages/cli/src/commands/generate/sdl/sdl.js index 60344bba965a..53625a5fdaf1 100644 --- a/packages/cli/src/commands/generate/sdl/sdl.js +++ b/packages/cli/src/commands/generate/sdl/sdl.js @@ -69,13 +69,14 @@ const modelFieldToSDL = ({ field.kind === 'object' ? idType(types[field.type]) : field.type } - const dictionary = { + const prismaTypeToGraphqlType = { Json: 'JSON', Decimal: 'Float', + Bytes: 'Byte', } const fieldContent = `${field.name}: ${field.isList ? '[' : ''}${ - dictionary[field.type] || field.type + prismaTypeToGraphqlType[field.type] || field.type }${field.isList ? ']' : ''}${ (field.isRequired && required) | field.isList ? '!' : '' }` @@ -344,7 +345,11 @@ export const handler = async ({ }, }, ].filter(Boolean), - { rendererOptions: { collapseSubtasks: false }, exitOnError: true } + { + rendererOptions: { collapseSubtasks: false }, + exitOnError: true, + silentRendererCondition: process.env.NODE_ENV === 'test', + } ) if (rollback && !force) { diff --git a/packages/cli/src/commands/generate/service/service.js b/packages/cli/src/commands/generate/service/service.js index c2ad7974e413..b3df5afac1cd 100644 --- a/packages/cli/src/commands/generate/service/service.js +++ b/packages/cli/src/commands/generate/service/service.js @@ -44,6 +44,11 @@ export const parseSchema = async (model) => { export const scenarioFieldValue = (field) => { const randFloat = Math.random() * 10000000 const randInt = parseInt(Math.random() * 10000000) + const randIntArray = [ + parseInt(Math.random() * 300), + parseInt(Math.random() * 300), + parseInt(Math.random() * 300), + ] switch (field.type) { case 'BigInt': @@ -61,6 +66,8 @@ export const scenarioFieldValue = (field) => { return { foo: 'bar' } case 'String': return field.isUnique ? `String${randInt}` : 'String' + case 'Bytes': + return `Buffer.from([${randIntArray}])` default: { if (field.kind === 'enum' && field.enumValues[0]) { return field.enumValues[0].dbName || field.enumValues[0].name @@ -125,6 +132,7 @@ export const buildScenario = async (model) => { Object.keys(scenarioData).forEach((key) => { const value = scenarioData[key] + // Support BigInt if (value && typeof value === 'string' && value.match(/^\d+n$/)) { scenarioData[key] = `${value.slice(0, value.length - 1)}n` } @@ -141,7 +149,7 @@ export const buildScenario = async (model) => { export const buildStringifiedScenario = async (model) => { const scenario = await buildScenario(model) - return JSON.stringify(scenario, (_key, value) => { + const jsonString = JSON.stringify(scenario, (_key, value) => { if (typeof value === 'bigint') { return value.toString() } @@ -152,6 +160,9 @@ export const buildStringifiedScenario = async (model) => { return value }) + + // Not all values can be represented as JSON, like function invocations + return jsonString.replace(/"Buffer\.from\(([^)]+)\)"/g, 'Buffer.from($1)') } export const fieldTypes = async (model) => { diff --git a/packages/cli/src/commands/generate/service/templates/test.ts.template b/packages/cli/src/commands/generate/service/templates/test.ts.template index fe7403925624..e073d8952be7 100644 --- a/packages/cli/src/commands/generate/service/templates/test.ts.template +++ b/packages/cli/src/commands/generate/service/templates/test.ts.template @@ -15,7 +15,7 @@ return `new Prisma.Decimal(${obj})` } - return JSON.stringify(obj).replace(/['"].*?['"]/g, (string) => { + const jsonString = JSON.stringify(obj).replace(/['"].*?['"]/g, (string) => { if (string.match(/scenario\./)) { return string.replace(/['"]/g, '') } @@ -27,6 +27,9 @@ return string }) + + // Not all values can be represented as JSON, like function invocations + return jsonString.replace(/"Buffer\.from\(([^)]+)\)"/g, 'Buffer.from($1)') } %> <% if (prismaImport) { %>import { Prisma, ${prismaModel} } from '@prisma/client'<% } else { %>import type { ${prismaModel} } from '@prisma/client'<% } %> diff --git a/packages/graphql-server/src/rootSchema.ts b/packages/graphql-server/src/rootSchema.ts index 7332d49b94ed..4010e7d87d99 100644 --- a/packages/graphql-server/src/rootSchema.ts +++ b/packages/graphql-server/src/rootSchema.ts @@ -6,6 +6,7 @@ import { DateTimeResolver, JSONResolver, JSONObjectResolver, + ByteResolver, } from 'graphql-scalars' import gql from 'graphql-tag' @@ -29,6 +30,7 @@ export const schema = gql` scalar DateTime scalar JSON scalar JSONObject + scalar Byte """ The RedwoodJS Root Schema @@ -61,6 +63,7 @@ export interface Resolvers { JSON: typeof JSONResolver JSONObject: typeof JSONObjectResolver Query: Record + Byte: typeof ByteResolver } export const resolvers: Resolvers = { @@ -79,4 +82,5 @@ export const resolvers: Resolvers = { }, }), }, + Byte: ByteResolver, } diff --git a/packages/internal/src/__tests__/__snapshots__/graphqlCodeGen.test.ts.snap b/packages/internal/src/__tests__/__snapshots__/graphqlCodeGen.test.ts.snap index 7faa93c8b756..844786338004 100644 --- a/packages/internal/src/__tests__/__snapshots__/graphqlCodeGen.test.ts.snap +++ b/packages/internal/src/__tests__/__snapshots__/graphqlCodeGen.test.ts.snap @@ -33,6 +33,7 @@ export type Scalars = { Int: number; Float: number; BigInt: number; + Byte: Buffer; Date: Date | string; DateTime: Date | string; JSON: Prisma.JsonValue; @@ -160,6 +161,7 @@ export type DirectiveResolverFn; Boolean: ResolverTypeWrapper; + Byte: ResolverTypeWrapper; Date: ResolverTypeWrapper; DateTime: ResolverTypeWrapper; Int: ResolverTypeWrapper; @@ -177,6 +179,7 @@ export type ResolversTypes = { export type ResolversParentTypes = { BigInt: Scalars['BigInt']; Boolean: Scalars['Boolean']; + Byte: Scalars['Byte']; Date: Scalars['Date']; DateTime: Scalars['DateTime']; Int: Scalars['Int']; @@ -204,6 +207,10 @@ export interface BigIntScalarConfig extends GraphQLScalarTypeConfig { + name: 'Byte'; +} + export interface DateScalarConfig extends GraphQLScalarTypeConfig { name: 'Date'; } @@ -280,6 +287,7 @@ export type TodoRelationResolvers = { BigInt: GraphQLScalarType; + Byte: GraphQLScalarType; Date: GraphQLScalarType; DateTime: GraphQLScalarType; JSON: GraphQLScalarType; @@ -313,6 +321,7 @@ export type Scalars = { Int: number; Float: number; BigInt: number; + Byte: Buffer; Date: string; DateTime: string; JSON: Prisma.JsonValue; diff --git a/packages/internal/src/__tests__/__snapshots__/graphqlSchema.test.ts.snap b/packages/internal/src/__tests__/__snapshots__/graphqlSchema.test.ts.snap index 316f6ec48c7d..4bcc745101a9 100644 --- a/packages/internal/src/__tests__/__snapshots__/graphqlSchema.test.ts.snap +++ b/packages/internal/src/__tests__/__snapshots__/graphqlSchema.test.ts.snap @@ -7,6 +7,8 @@ directive @skipAuth on FIELD_DEFINITION scalar BigInt +scalar Byte + scalar Date scalar DateTime @@ -76,6 +78,8 @@ directive @skipAuth on FIELD_DEFINITION scalar BigInt +scalar Byte + scalar Date scalar DateTime diff --git a/packages/internal/src/__tests__/graphqlCodeGen.test.ts b/packages/internal/src/__tests__/graphqlCodeGen.test.ts index e968b14e1abd..d87613c518fc 100644 --- a/packages/internal/src/__tests__/graphqlCodeGen.test.ts +++ b/packages/internal/src/__tests__/graphqlCodeGen.test.ts @@ -83,6 +83,7 @@ test('Generate gql typedefs api', async () => { // Check that JSON types are imported from prisma expect(data).toContain('JSON: Prisma.JsonValue;') expect(data).toContain('JSONObject: Prisma.JsonObject;') + expect(data).toContain('Byte: Buffer;') // Check that prisma model imports are added to the top of the file expect(data).toContain( diff --git a/packages/internal/src/__tests__/graphqlSchema.test.ts b/packages/internal/src/__tests__/graphqlSchema.test.ts index 0dd639575c7f..7a1cece65722 100644 --- a/packages/internal/src/__tests__/graphqlSchema.test.ts +++ b/packages/internal/src/__tests__/graphqlSchema.test.ts @@ -73,10 +73,6 @@ test('Returns error message when schema loading fails', async () => { const [schemaLoadingError] = errors - console.log({ - errors, - }) - expect(schemaLoadingError.message).toEqual( [ 'Schema loading failed. Unknown type: "Shelf".', diff --git a/packages/internal/src/generate/generate.ts b/packages/internal/src/generate/generate.ts index 51d3020a4d8a..847e02dec82b 100644 --- a/packages/internal/src/generate/generate.ts +++ b/packages/internal/src/generate/generate.ts @@ -8,6 +8,7 @@ import { generatePossibleTypes } from './possibleTypes' import { generateTypeDefs } from './typeDefinitions' export const generate = async () => { + console.log('internal generate') const config = getConfig() const { schemaPath, errors: generateGraphQLSchemaErrors } = await generateGraphQLSchema() diff --git a/packages/internal/src/generate/graphqlCodeGen.ts b/packages/internal/src/generate/graphqlCodeGen.ts index a5e44b7d4fa9..90dafd158133 100644 --- a/packages/internal/src/generate/graphqlCodeGen.ts +++ b/packages/internal/src/generate/graphqlCodeGen.ts @@ -80,7 +80,7 @@ export const generateTypeDefGraphQLApi = async (): Promise => { codegenPlugin: addPlugin, }, { - name: 'print-mapped-moddels', + name: 'print-mapped-models', options: {}, codegenPlugin: printMappedModelsPlugin, }, @@ -285,6 +285,7 @@ function getPluginConfig(side: CodegenSide) { JSON: 'Prisma.JsonValue', JSONObject: 'Prisma.JsonObject', Time: side === CodegenSide.WEB ? 'string' : 'Date | string', + Byte: 'Buffer', }, // prevent type names being PetQueryQuery, RW generators already append // Query/Mutation/etc