From 1d1fec0019fdc14faf7704c671bbfdc919ab797b Mon Sep 17 00:00:00 2001 From: Yiming Date: Thu, 7 Nov 2024 10:18:22 -0800 Subject: [PATCH 1/2] fix(json): false error when using typed json in multi-file schemas (#1836) --- package.json | 2 +- packages/ide/jetbrains/build.gradle.kts | 2 +- packages/ide/jetbrains/package.json | 2 +- packages/language/package.json | 2 +- packages/misc/redwood/package.json | 2 +- packages/plugins/openapi/package.json | 2 +- packages/plugins/swr/package.json | 2 +- packages/plugins/tanstack-query/package.json | 2 +- packages/plugins/trpc/package.json | 2 +- packages/runtime/package.json | 2 +- packages/schema/package.json | 2 +- .../language-server/validator/datamodel-validator.ts | 5 ----- packages/schema/src/plugins/prisma/schema-generator.ts | 10 ++++++++++ packages/sdk/package.json | 2 +- packages/server/package.json | 2 +- packages/testtools/package.json | 2 +- .../tests/enhancements/json/validation.test.ts | 6 +++--- 17 files changed, 27 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 519137c35..cdaf1a920 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "2.8.0", + "version": "2.8.1", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/ide/jetbrains/build.gradle.kts b/packages/ide/jetbrains/build.gradle.kts index 067e6567e..442557a34 100644 --- a/packages/ide/jetbrains/build.gradle.kts +++ b/packages/ide/jetbrains/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.zenstack" -version = "2.8.0" +version = "2.8.1" repositories { mavenCentral() diff --git a/packages/ide/jetbrains/package.json b/packages/ide/jetbrains/package.json index 372462b17..389060d74 100644 --- a/packages/ide/jetbrains/package.json +++ b/packages/ide/jetbrains/package.json @@ -1,6 +1,6 @@ { "name": "jetbrains", - "version": "2.8.0", + "version": "2.8.1", "displayName": "ZenStack JetBrains IDE Plugin", "description": "ZenStack JetBrains IDE plugin", "homepage": "https://zenstack.dev", diff --git a/packages/language/package.json b/packages/language/package.json index cf01ce9ad..ca9b3cc89 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "2.8.0", + "version": "2.8.1", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/misc/redwood/package.json b/packages/misc/redwood/package.json index 554556519..1f74d793b 100644 --- a/packages/misc/redwood/package.json +++ b/packages/misc/redwood/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/redwood", "displayName": "ZenStack RedwoodJS Integration", - "version": "2.8.0", + "version": "2.8.1", "description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.", "repository": { "type": "git", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index 41cfaf7eb..a55d40ac1 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/openapi", "displayName": "ZenStack Plugin and Runtime for OpenAPI", - "version": "2.8.0", + "version": "2.8.1", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index b72fb3810..2918da8f7 100644 --- a/packages/plugins/swr/package.json +++ b/packages/plugins/swr/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/swr", "displayName": "ZenStack plugin for generating SWR hooks", - "version": "2.8.0", + "version": "2.8.1", "description": "ZenStack plugin for generating SWR hooks", "main": "index.js", "repository": { diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index 4ac5a58bb..87a8a2bd9 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/tanstack-query", "displayName": "ZenStack plugin for generating tanstack-query hooks", - "version": "2.8.0", + "version": "2.8.1", "description": "ZenStack plugin for generating tanstack-query hooks", "main": "index.js", "exports": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index 676f40b2c..fc6c86e26 100644 --- a/packages/plugins/trpc/package.json +++ b/packages/plugins/trpc/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/trpc", "displayName": "ZenStack plugin for tRPC", - "version": "2.8.0", + "version": "2.8.1", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index f63f40ced..983fbe248 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "2.8.0", + "version": "2.8.1", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index a99ebeee7..5ea95e5d8 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "FullStack enhancement for Prisma ORM: seamless integration from database to UI", - "version": "2.8.0", + "version": "2.8.1", "author": { "name": "ZenStack Team" }, diff --git a/packages/schema/src/language-server/validator/datamodel-validator.ts b/packages/schema/src/language-server/validator/datamodel-validator.ts index f2a3d6737..9054c82c6 100644 --- a/packages/schema/src/language-server/validator/datamodel-validator.ts +++ b/packages/schema/src/language-server/validator/datamodel-validator.ts @@ -9,7 +9,6 @@ import { isTypeDef, } from '@zenstackhq/language/ast'; import { - getDataSourceProvider, getModelFieldsWithBases, getModelIdFields, getModelUniqueFields, @@ -108,10 +107,6 @@ export default class DataModelValidator implements AstValidator { if (!hasAttribute(field, '@json')) { accept('error', 'Custom-typed field must have @json attribute', { node: field }); } - - if (getDataSourceProvider(field.$container.$container) !== 'postgresql') { - accept('error', 'Custom-typed field is only supported with "postgresql" provider', { node: field }); - } } } diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 9a787ab8b..b3d382795 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -39,6 +39,7 @@ import { getAttribute, getAttributeArg, getAttributeArgLiteral, + getDataSourceProvider, getInheritedFromDelegate, getLiteral, getRelationKeyPairs, @@ -81,6 +82,7 @@ import { const MODEL_PASSTHROUGH_ATTR = '@@prisma.passthrough'; const FIELD_PASSTHROUGH_ATTR = '@prisma.passthrough'; const PROVIDERS_SUPPORTING_NAMED_CONSTRAINTS = ['postgresql', 'mysql', 'cockroachdb']; +const PROVIDERS_SUPPORTING_TYPEDEF_FIELDS = ['postgresql']; // Some database providers like postgres and mysql have default limit to the length of identifiers // Here we use a conservative value that should work for most cases, and truncate names if needed @@ -794,6 +796,7 @@ export class PrismaSchemaGenerator { } else if (field.type.reference?.ref) { // model, enum, or type-def if (isTypeDef(field.type.reference.ref)) { + this.ensureSupportingTypeDefFields(this.zmodel); fieldType = 'Json'; } else { fieldType = field.type.reference.ref.name; @@ -846,6 +849,13 @@ export class PrismaSchemaGenerator { return result; } + private ensureSupportingTypeDefFields(zmodel: Model) { + const dsProvider = getDataSourceProvider(zmodel); + if (dsProvider && !PROVIDERS_SUPPORTING_TYPEDEF_FIELDS.includes(dsProvider)) { + throw new PluginError(name, `Datasource provider "${dsProvider}" does not support "@json" fields`); + } + } + private setDummyDefault(result: ModelField, field: DataModelField) { const dummyDefaultValue = match(field.type.type) .with('String', () => new AttributeArgValue('String', '')) diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 6222c5500..b2be27d13 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "2.8.0", + "version": "2.8.1", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 064fd1ea1..d21932ca4 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "2.8.0", + "version": "2.8.1", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", diff --git a/packages/testtools/package.json b/packages/testtools/package.json index ec5208c81..c03a28af8 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "2.8.0", + "version": "2.8.1", "description": "ZenStack Test Tools", "main": "index.js", "private": true, diff --git a/tests/integration/tests/enhancements/json/validation.test.ts b/tests/integration/tests/enhancements/json/validation.test.ts index 2643056fe..27f5e5067 100644 --- a/tests/integration/tests/enhancements/json/validation.test.ts +++ b/tests/integration/tests/enhancements/json/validation.test.ts @@ -1,9 +1,9 @@ -import { loadModelWithError } from '@zenstackhq/testtools'; +import { loadModelWithError, loadSchema } from '@zenstackhq/testtools'; describe('JSON field typing', () => { it('is only supported by postgres', async () => { await expect( - loadModelWithError( + loadSchema( ` type Profile { age Int @gt(0) @@ -16,7 +16,7 @@ describe('JSON field typing', () => { } ` ) - ).resolves.toContain('Custom-typed field is only supported with "postgresql" provider'); + ).rejects.toThrow('Datasource provider "sqlite" does not support "@json" fields'); }); it('requires field to have @json attribute', async () => { From 1bd1b8f8b7de13042a69aff4742519c6b7c1e5b6 Mon Sep 17 00:00:00 2001 From: Yiming Date: Thu, 7 Nov 2024 12:03:28 -0800 Subject: [PATCH 2/2] fix(json): support enums in type declarations (#1837) --- packages/language/src/generated/ast.ts | 23 ++-- packages/language/src/generated/grammar.ts | 31 +++++- packages/language/src/zmodel.langium | 8 +- .../enhance/model-typedef-generator.ts | 24 ++++- .../schema/src/plugins/zod/transformer.ts | 23 +++- .../tests/enhancements/json/crud.test.ts | 77 +++++++++++++ .../tests/enhancements/json/typing.test.ts | 102 ++++++++++++++++++ 7 files changed, 267 insertions(+), 21 deletions(-) diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts index b4d7e6d82..1705f019b 100644 --- a/packages/language/src/generated/ast.ts +++ b/packages/language/src/generated/ast.ts @@ -98,6 +98,14 @@ export function isTypeDeclaration(item: unknown): item is TypeDeclaration { return reflection.isInstance(item, TypeDeclaration); } +export type TypeDefFieldTypes = Enum | TypeDef; + +export const TypeDefFieldTypes = 'TypeDefFieldTypes'; + +export function isTypeDefFieldTypes(item: unknown): item is TypeDefFieldTypes { + return reflection.isInstance(item, TypeDefFieldTypes); +} + export interface Argument extends AstNode { readonly $container: InvocationExpr; readonly $type: 'Argument'; @@ -654,7 +662,7 @@ export interface TypeDefFieldType extends AstNode { readonly $type: 'TypeDefFieldType'; array: boolean optional: boolean - reference?: Reference + reference?: Reference type?: BuiltinType } @@ -738,6 +746,7 @@ export type ZModelAstType = { TypeDef: TypeDef TypeDefField: TypeDefField TypeDefFieldType: TypeDefFieldType + TypeDefFieldTypes: TypeDefFieldTypes UnaryExpr: UnaryExpr UnsupportedFieldType: UnsupportedFieldType } @@ -745,7 +754,7 @@ export type ZModelAstType = { export class ZModelAstReflection extends AbstractAstReflection { getAllTypes(): string[] { - return ['AbstractDeclaration', 'Argument', 'ArrayExpr', 'Attribute', 'AttributeArg', 'AttributeParam', 'AttributeParamType', 'BinaryExpr', 'BooleanLiteral', 'ConfigArrayExpr', 'ConfigExpr', 'ConfigField', 'ConfigInvocationArg', 'ConfigInvocationExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'Enum', 'EnumField', 'Expression', 'FieldInitializer', 'FunctionDecl', 'FunctionParam', 'FunctionParamType', 'GeneratorDecl', 'InternalAttribute', 'InvocationExpr', 'LiteralExpr', 'MemberAccessExpr', 'Model', 'ModelImport', 'NullExpr', 'NumberLiteral', 'ObjectExpr', 'Plugin', 'PluginField', 'ReferenceArg', 'ReferenceExpr', 'ReferenceTarget', 'StringLiteral', 'ThisExpr', 'TypeDeclaration', 'TypeDef', 'TypeDefField', 'TypeDefFieldType', 'UnaryExpr', 'UnsupportedFieldType']; + return ['AbstractDeclaration', 'Argument', 'ArrayExpr', 'Attribute', 'AttributeArg', 'AttributeParam', 'AttributeParamType', 'BinaryExpr', 'BooleanLiteral', 'ConfigArrayExpr', 'ConfigExpr', 'ConfigField', 'ConfigInvocationArg', 'ConfigInvocationExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'Enum', 'EnumField', 'Expression', 'FieldInitializer', 'FunctionDecl', 'FunctionParam', 'FunctionParamType', 'GeneratorDecl', 'InternalAttribute', 'InvocationExpr', 'LiteralExpr', 'MemberAccessExpr', 'Model', 'ModelImport', 'NullExpr', 'NumberLiteral', 'ObjectExpr', 'Plugin', 'PluginField', 'ReferenceArg', 'ReferenceExpr', 'ReferenceTarget', 'StringLiteral', 'ThisExpr', 'TypeDeclaration', 'TypeDef', 'TypeDefField', 'TypeDefFieldType', 'TypeDefFieldTypes', 'UnaryExpr', 'UnsupportedFieldType']; } protected override computeIsSubtype(subtype: string, supertype: string): boolean { @@ -775,9 +784,7 @@ export class ZModelAstReflection extends AbstractAstReflection { case ConfigArrayExpr: { return this.isSubtype(ConfigExpr, supertype); } - case DataModel: - case Enum: - case TypeDef: { + case DataModel: { return this.isSubtype(AbstractDeclaration, supertype) || this.isSubtype(TypeDeclaration, supertype); } case DataModelField: @@ -785,6 +792,10 @@ export class ZModelAstReflection extends AbstractAstReflection { case FunctionParam: { return this.isSubtype(ReferenceTarget, supertype); } + case Enum: + case TypeDef: { + return this.isSubtype(AbstractDeclaration, supertype) || this.isSubtype(TypeDeclaration, supertype) || this.isSubtype(TypeDefFieldTypes, supertype); + } case InvocationExpr: case LiteralExpr: { return this.isSubtype(ConfigExpr, supertype) || this.isSubtype(Expression, supertype); @@ -821,7 +832,7 @@ export class ZModelAstReflection extends AbstractAstReflection { return ReferenceTarget; } case 'TypeDefFieldType:reference': { - return TypeDef; + return TypeDefFieldTypes; } default: { throw new Error(`${referenceId} is not a valid reference id.`); diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts index 01c492bd5..43beb12a2 100644 --- a/packages/language/src/generated/grammar.ts +++ b/packages/language/src/generated/grammar.ts @@ -2165,7 +2165,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/types@1" + "$ref": "#/types@2" }, "terminal": { "$type": "RuleCall", @@ -2267,7 +2267,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel }, "arguments": [] }, - "cardinality": "+" + "cardinality": "*" }, { "$type": "Keyword", @@ -2375,7 +2375,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@40" + "$ref": "#/types@1" }, "terminal": { "$type": "RuleCall", @@ -2827,7 +2827,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/types@1" + "$ref": "#/types@2" }, "terminal": { "$type": "RuleCall", @@ -3255,7 +3255,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/types@1" + "$ref": "#/types@2" }, "terminal": { "$type": "RuleCall", @@ -3838,6 +3838,27 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel ] } }, + { + "$type": "Type", + "name": "TypeDefFieldTypes", + "type": { + "$type": "UnionType", + "types": [ + { + "$type": "SimpleType", + "typeRef": { + "$ref": "#/rules@40" + } + }, + { + "$type": "SimpleType", + "typeRef": { + "$ref": "#/rules@44" + } + } + ] + } + }, { "$type": "Type", "name": "TypeDeclaration", diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index d66ea5f32..ef5a1f883 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -183,15 +183,17 @@ TypeDef: (comments+=TRIPLE_SLASH_COMMENT)* 'type' name=RegularID '{' ( fields+=TypeDefField - )+ + )* '}'; +type TypeDefFieldTypes = TypeDef | Enum; + TypeDefField: - (comments+=TRIPLE_SLASH_COMMENT)* + (comments+=TRIPLE_SLASH_COMMENT)* name=RegularIDWithTypeNames type=TypeDefFieldType (attributes+=DataModelFieldAttribute)*; TypeDefFieldType: - (type=BuiltinType | reference=[TypeDef:RegularID]) (array?='[' ']')? (optional?='?')?; + (type=BuiltinType | reference=[TypeDefFieldTypes:RegularID]) (array?='[' ']')? (optional?='?')?; UnsupportedFieldType: 'Unsupported' '(' (value=LiteralExpr) ')'; diff --git a/packages/schema/src/plugins/enhancer/enhance/model-typedef-generator.ts b/packages/schema/src/plugins/enhancer/enhance/model-typedef-generator.ts index 40bc3e5b4..21ede7a64 100644 --- a/packages/schema/src/plugins/enhancer/enhance/model-typedef-generator.ts +++ b/packages/schema/src/plugins/enhancer/enhance/model-typedef-generator.ts @@ -1,5 +1,5 @@ -import { PluginError } from '@zenstackhq/sdk'; -import { BuiltinType, TypeDef, TypeDefFieldType } from '@zenstackhq/sdk/ast'; +import { getDataModels, PluginError } from '@zenstackhq/sdk'; +import { BuiltinType, Enum, isEnum, TypeDef, TypeDefFieldType } from '@zenstackhq/sdk/ast'; import { SourceFile } from 'ts-morph'; import { match } from 'ts-pattern'; import { name } from '..'; @@ -36,7 +36,11 @@ function zmodelTypeToTsType(type: TypeDefFieldType) { if (type.type) { result = builtinTypeToTsType(type.type); } else if (type.reference?.ref) { - result = type.reference.ref.name; + if (isEnum(type.reference.ref)) { + result = makeEnumTypeReference(type.reference.ref); + } else { + result = type.reference.ref.name; + } } else { throw new PluginError(name, `Unsupported field type: ${type}`); } @@ -61,3 +65,17 @@ function builtinTypeToTsType(type: BuiltinType) { .with('Json', () => 'unknown') .exhaustive(); } + +function makeEnumTypeReference(enumDecl: Enum) { + const zmodel = enumDecl.$container; + const models = getDataModels(zmodel); + + if (models.some((model) => model.fields.some((field) => field.type.reference?.ref === enumDecl))) { + // if the enum is referenced by any data model, Prisma already generates its type, + // we just need to reference it + return enumDecl.name; + } else { + // otherwise, we need to inline the enum + return enumDecl.fields.map((field) => `'${field.name}'`).join(' | '); + } +} diff --git a/packages/schema/src/plugins/zod/transformer.ts b/packages/schema/src/plugins/zod/transformer.ts index 6b83e5723..081dcaf4a 100644 --- a/packages/schema/src/plugins/zod/transformer.ts +++ b/packages/schema/src/plugins/zod/transformer.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { indentString, isDiscriminatorField, type PluginOptions } from '@zenstackhq/sdk'; -import { DataModel, isDataModel, isTypeDef, type Model } from '@zenstackhq/sdk/ast'; +import { DataModel, Enum, isDataModel, isEnum, isTypeDef, type Model } from '@zenstackhq/sdk/ast'; import { checkModelHasModelRelation, findModelByName, isAggregateInputType } from '@zenstackhq/sdk/dmmf-helpers'; import { supportCreateMany, type DMMF as PrismaDMMF } from '@zenstackhq/sdk/prisma'; import path from 'path'; @@ -53,6 +53,9 @@ export default class Transformer { } async generateEnumSchemas() { + const generated: string[] = []; + + // generate for enums in DMMF for (const enumType of this.enumTypes) { const name = upperCaseFirst(enumType.name); const filePath = path.join(Transformer.outputPath, `enums/${name}.schema.ts`); @@ -61,14 +64,26 @@ export default class Transformer { `z.enum(${JSON.stringify(enumType.values)})` )}`; this.sourceFiles.push(this.project.createSourceFile(filePath, content, { overwrite: true })); + generated.push(enumType.name); + } + + // enums not referenced by data models are not in DMMF, deal with them separately + const extraEnums = this.zmodel.declarations.filter((d): d is Enum => isEnum(d) && !generated.includes(d.name)); + for (const enumDecl of extraEnums) { + const name = upperCaseFirst(enumDecl.name); + const filePath = path.join(Transformer.outputPath, `enums/${name}.schema.ts`); + const content = `/* eslint-disable */\n${this.generateImportZodStatement()}\n${this.generateExportSchemaStatement( + `${name}`, + `z.enum(${JSON.stringify(enumDecl.fields.map((f) => f.name))})` + )}`; + this.sourceFiles.push(this.project.createSourceFile(filePath, content, { overwrite: true })); + generated.push(enumDecl.name); } this.sourceFiles.push( this.project.createSourceFile( path.join(Transformer.outputPath, `enums/index.ts`), - this.enumTypes - .map((enumType) => `export * from './${upperCaseFirst(enumType.name)}.schema';`) - .join('\n'), + generated.map((name) => `export * from './${upperCaseFirst(name)}.schema';`).join('\n'), { overwrite: true } ) ); diff --git a/tests/integration/tests/enhancements/json/crud.test.ts b/tests/integration/tests/enhancements/json/crud.test.ts index af3705a95..9cd7ff8a4 100644 --- a/tests/integration/tests/enhancements/json/crud.test.ts +++ b/tests/integration/tests/enhancements/json/crud.test.ts @@ -191,6 +191,83 @@ describe('Json field CRUD', () => { ).toResolveTruthy(); }); + it('respects enums used by data models', async () => { + const params = await loadSchema( + ` + enum Role { + USER + ADMIN + } + + type Profile { + role Role + } + + model User { + id Int @id @default(autoincrement()) + profile Profile @json + @@allow('all', true) + } + + model Foo { + id Int @id @default(autoincrement()) + role Role + } + `, + { + provider: 'postgresql', + dbUrl, + } + ); + + prisma = params.prisma; + const db = params.enhance(); + + await expect(db.user.create({ data: { profile: { role: 'MANAGER' } } })).toBeRejectedByPolicy(); + await expect(db.user.create({ data: { profile: { role: 'ADMIN' } } })).resolves.toMatchObject({ + profile: { role: 'ADMIN' }, + }); + await expect(db.user.findFirst()).resolves.toMatchObject({ + profile: { role: 'ADMIN' }, + }); + }); + + it('respects enums unused by data models', async () => { + const params = await loadSchema( + ` + enum Role { + USER + ADMIN + } + + type Profile { + role Role + } + + model User { + id Int @id @default(autoincrement()) + profile Profile @json + @@allow('all', true) + } + `, + { + provider: 'postgresql', + dbUrl, + } + ); + + prisma = params.prisma; + const db = params.enhance(); + + await expect(db.user.create({ data: { profile: { role: 'MANAGER' } } })).toBeRejectedByPolicy(); + await expect(db.user.create({ data: { profile: { role: 'ADMIN' } } })).resolves.toMatchObject({ + profile: { role: 'ADMIN' }, + }); + await expect(db.user.findFirst()).resolves.toMatchObject({ + profile: { role: 'ADMIN' }, + }); + }); + it('respects @default', async () => { const params = await loadSchema( ` diff --git a/tests/integration/tests/enhancements/json/typing.test.ts b/tests/integration/tests/enhancements/json/typing.test.ts index 9681bf015..a73e04f03 100644 --- a/tests/integration/tests/enhancements/json/typing.test.ts +++ b/tests/integration/tests/enhancements/json/typing.test.ts @@ -179,6 +179,108 @@ async function main() { ); }); + it('works with enums used in models', async () => { + await loadSchema( + ` + enum Role { + USER + ADMIN + } + + type Profile { + role Role + } + + model User { + id Int @id @default(autoincrement()) + profile Profile @json + @@allow('all', true) + } + + model Foo { + id Int @id @default(autoincrement()) + role Role + } + `, + { + provider: 'postgresql', + pushDb: false, + compile: true, + extraSourceFiles: [ + { + name: 'main.ts', + content: ` +import type { Profile } from '.zenstack/models'; +import { enhance } from '.zenstack/enhance'; +import { PrismaClient } from '@prisma/client'; +import { Role } from '@prisma/client'; +const prisma = new PrismaClient(); +const db = enhance(prisma); + +async function main() { + const profile: Profile = { + role: Role.ADMIN, + } + + await db.user.create({ data: { profile: { role: Role.ADMIN } } }); + const user = await db.user.findFirstOrThrow(); + console.log(user.profile.role === Role.ADMIN); +} +`, + }, + ], + } + ); + }); + + it('works with enums unused in models', async () => { + await loadSchema( + ` + enum Role { + USER + ADMIN + } + + type Profile { + role Role + } + + model User { + id Int @id @default(autoincrement()) + profile Profile @json + @@allow('all', true) + } + `, + { + provider: 'postgresql', + pushDb: false, + compile: true, + extraSourceFiles: [ + { + name: 'main.ts', + content: ` +import type { Profile } from '.zenstack/models'; +import { enhance } from '.zenstack/enhance'; +import { PrismaClient } from '@prisma/client'; +const prisma = new PrismaClient(); +const db = enhance(prisma); + +async function main() { + const profile: Profile = { + role: 'ADMIN', + } + + await db.user.create({ data: { profile: { role: 'ADMIN' } } }); + const user = await db.user.findFirstOrThrow(); + console.log(user.profile.role === 'ADMIN'); +} +`, + }, + ], + } + ); + }); + it('type coverage', async () => { await loadSchema( `