Skip to content

Commit

Permalink
Add support for Prisma Bytes and GraphQL scalar Byte (#9847)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobbe committed Jan 18, 2024
1 parent f16b136 commit a9705c1
Show file tree
Hide file tree
Showing 16 changed files with 248 additions and 21 deletions.
1 change: 1 addition & 0 deletions __fixtures__/example-todo-main/web/src/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/commands/generate/__tests__/helpers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/commands/generate/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -308,6 +308,7 @@ export const mapPrismaScalarToPagePropTsType = (scalarType) => {
Float: 'number',
Decimal: 'number',
DateTime: 'string',
Bytes: 'Buffer',
}
return prismaScalarToTsType[scalarType] || 'unknown'
}
Original file line number Diff line number Diff line change
Expand Up @@ -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\`
"""
Expand Down Expand Up @@ -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\`
"""
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 19 additions & 10 deletions packages/cli/src/commands/generate/sdl/__tests__/sdl.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -215,6 +230,7 @@ describe('without graphql documentations', () => {
itCreateAMultiWordSDLFileWithCRUD(baseArgs)
itCreatesAnSDLFileWithEnumDefinitions(baseArgs)
itCreatesAnSDLFileWithJsonDefinitions(baseArgs)
itCreatesAnSDLFileWithByteDefinitions(baseArgs)
})

describe('in typescript mode', () => {
Expand All @@ -232,6 +248,7 @@ describe('without graphql documentations', () => {
itCreateAMultiWordSDLFileWithCRUD(baseArgs)
itCreatesAnSDLFileWithEnumDefinitions(baseArgs)
itCreatesAnSDLFileWithJsonDefinitions(baseArgs)
itCreatesAnSDLFileWithByteDefinitions(baseArgs)
})
})

Expand All @@ -251,6 +268,7 @@ describe('with graphql documentations', () => {
itCreateAMultiWordSDLFileWithCRUD(baseArgs)
itCreatesAnSDLFileWithEnumDefinitions(baseArgs)
itCreatesAnSDLFileWithJsonDefinitions(baseArgs)
itCreatesAnSDLFileWithByteDefinitions(baseArgs)
})

describe('in typescript mode', () => {
Expand All @@ -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')
Expand Down
11 changes: 8 additions & 3 deletions packages/cli/src/commands/generate/sdl/sdl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? '!' : ''
}`
Expand Down Expand Up @@ -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) {
Expand Down
13 changes: 12 additions & 1 deletion packages/cli/src/commands/generate/service/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand All @@ -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
Expand Down Expand Up @@ -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`
}
Expand All @@ -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()
}
Expand All @@ -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) => {
Expand Down
Loading

0 comments on commit a9705c1

Please sign in to comment.