Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Prisma Bytes and GraphQL scalar Byte #9847

Merged
merged 3 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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; }
dthyresson marked this conversation as resolved.
Show resolved Hide resolved
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
Loading