From dd9963cd35d414ebf61727bb4a5d9ad0c31100e0 Mon Sep 17 00:00:00 2001 From: Yiming Date: Fri, 10 Mar 2023 17:20:44 +0800 Subject: [PATCH 1/3] feat: OpenAPI & fastify adapter (#254) --- .github/ISSUE_TEMPLATE/bug_report.md | 19 +- package.json | 2 +- packages/language/package.json | 2 +- packages/language/src/generated/ast.ts | 60 +- packages/language/src/generated/grammar.ts | 304 +- packages/language/src/zmodel.langium | 13 +- .../language/syntaxes/zmodel.tmLanguage.json | 2 +- packages/next/package.json | 2 +- packages/plugins/openapi/.eslintrc.json | 14 + packages/plugins/openapi/LICENSE | 1 + packages/plugins/openapi/README.md | 5 + packages/plugins/openapi/jest.config.ts | 29 + packages/plugins/openapi/openapi.yaml | 3009 +++++++++++++++++ packages/plugins/openapi/package.json | 52 + packages/plugins/openapi/src/generator.ts | 736 ++++ packages/plugins/openapi/src/index.ts | 10 + packages/plugins/openapi/src/meta.ts | 25 + packages/plugins/openapi/src/plugin.zmodel | 9 + .../plugins/openapi/tests/openapi.test.ts | 106 + packages/plugins/openapi/tsconfig.json | 20 + packages/plugins/react/package.json | 2 +- packages/plugins/trpc/package.json | 2 +- .../plugins/trpc/src/zod/helpers/helpers.ts | 19 +- packages/plugins/trpc/src/zod/transformer.ts | 7 +- packages/runtime/package.json | 2 +- packages/runtime/src/version.ts | 6 +- packages/schema/package.json | 5 +- packages/schema/src/cli/cli-util.ts | 56 +- .../schema/src/language-server/constants.ts | 5 + .../validator/schema-validator.ts | 7 +- .../src/language-server/validator/utils.ts | 4 +- .../src/language-server/zmodel-linker.ts | 22 +- .../plugins/prisma/zmodel-code-generator.ts | 12 + packages/schema/tests/cli/cli.test.ts | 8 +- packages/sdk/package.json | 2 +- .../sdk/src/dmmf-helpers/aggregate-helpers.ts | 78 + .../sdk/src/dmmf-helpers/include-helpers.ts | 82 + packages/sdk/src/dmmf-helpers/index.ts | 6 + .../sdk/src/dmmf-helpers/model-helpers.ts | 36 + .../sdk/src/dmmf-helpers/modelArgs-helpers.ts | 62 + .../sdk/src/dmmf-helpers/select-helpers.ts | 155 + packages/sdk/src/dmmf-helpers/types.ts | 22 + packages/sdk/src/utils.ts | 34 + packages/server/.eslintrc.json | 14 + packages/server/LICENSE | 1 + packages/server/README.md | 5 + packages/server/jest.config.ts | 29 + packages/server/package.json | 42 + packages/server/src/fastify/index.ts | 1 + packages/server/src/fastify/plugin.ts | 58 + packages/server/src/openapi/index.ts | 188 + packages/server/src/openapi/utils.ts | 19 + packages/server/tests/fastify-plugin.test.ts | 133 + packages/server/tsconfig.json | 22 + packages/testtools/package.json | 2 +- packages/testtools/src/package.template.json | 6 +- packages/testtools/src/schema.ts | 9 +- pnpm-lock.yaml | 845 ++++- tests/integration/jest.config.ts | 15 + tests/integration/test-run/package-lock.json | 6 +- 60 files changed, 6188 insertions(+), 261 deletions(-) create mode 100644 packages/plugins/openapi/.eslintrc.json create mode 120000 packages/plugins/openapi/LICENSE create mode 100644 packages/plugins/openapi/README.md create mode 100644 packages/plugins/openapi/jest.config.ts create mode 100644 packages/plugins/openapi/openapi.yaml create mode 100644 packages/plugins/openapi/package.json create mode 100644 packages/plugins/openapi/src/generator.ts create mode 100644 packages/plugins/openapi/src/index.ts create mode 100644 packages/plugins/openapi/src/meta.ts create mode 100644 packages/plugins/openapi/src/plugin.zmodel create mode 100644 packages/plugins/openapi/tests/openapi.test.ts create mode 100644 packages/plugins/openapi/tsconfig.json create mode 100644 packages/sdk/src/dmmf-helpers/aggregate-helpers.ts create mode 100644 packages/sdk/src/dmmf-helpers/include-helpers.ts create mode 100644 packages/sdk/src/dmmf-helpers/index.ts create mode 100644 packages/sdk/src/dmmf-helpers/model-helpers.ts create mode 100644 packages/sdk/src/dmmf-helpers/modelArgs-helpers.ts create mode 100644 packages/sdk/src/dmmf-helpers/select-helpers.ts create mode 100644 packages/sdk/src/dmmf-helpers/types.ts create mode 100644 packages/server/.eslintrc.json create mode 120000 packages/server/LICENSE create mode 100644 packages/server/README.md create mode 100644 packages/server/jest.config.ts create mode 100644 packages/server/package.json create mode 100644 packages/server/src/fastify/index.ts create mode 100644 packages/server/src/fastify/plugin.ts create mode 100644 packages/server/src/openapi/index.ts create mode 100644 packages/server/src/openapi/utils.ts create mode 100644 packages/server/tests/fastify-plugin.test.ts create mode 100644 packages/server/tsconfig.json diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 71570693f..04372bc71 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,27 +6,16 @@ labels: '' assignees: '' --- -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -1. -2. -3. - -**Expected behavior** -A clear and concise description of what you expected to happen. +**Description and expected behavior** +A clear and concise description of what the bug is and what's the expected behavior. **Screenshots** If applicable, add screenshots to help explain your problem. **Environment (please complete the following information):** -- OS: [e.g. Mac OS 13.0] -- Node version: [e.g. 16.15.0] -- NPM version: [e.g. 8.19.2] +- ZenStack version: [e.g., 1.0.0-alpha.50] +- Prisma version: [e.g., 4.10.0] - Database type: [e.g. Postgresql] **Additional context** diff --git a/package.json b/package.json index 743d486c4..e49210d56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/language/package.json b/packages/language/package.json index c2aaa08e0..c592bcda9 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts index 226da0c0a..46d8e0e0d 100644 --- a/packages/language/src/generated/ast.ts +++ b/packages/language/src/generated/ast.ts @@ -24,7 +24,7 @@ export type DataModelAttributeName = string; export type DataModelFieldAttributeName = string; -export type Expression = ArrayExpr | BinaryExpr | InvocationExpr | LiteralExpr | MemberAccessExpr | NullExpr | ReferenceExpr | ThisExpr | UnaryExpr; +export type Expression = ArrayExpr | BinaryExpr | InvocationExpr | LiteralExpr | MemberAccessExpr | NullExpr | ObjectExpr | ReferenceExpr | ThisExpr | UnaryExpr; export const Expression = 'Expression'; @@ -32,7 +32,7 @@ export function isExpression(item: unknown): item is Expression { return reflection.isInstance(item, Expression); } -export type ExpressionType = 'Any' | 'Boolean' | 'DateTime' | 'Float' | 'Int' | 'Null' | 'String'; +export type ExpressionType = 'Any' | 'Boolean' | 'DateTime' | 'Float' | 'Int' | 'Null' | 'Object' | 'String'; export type QualifiedName = string; @@ -66,7 +66,7 @@ export function isArgument(item: unknown): item is Argument { } export interface ArrayExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; readonly $type: 'ArrayExpr'; items: Array } @@ -147,7 +147,7 @@ export function isAttributeParamType(item: unknown): item is AttributeParamType } export interface BinaryExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; readonly $type: 'BinaryExpr'; left: Expression operator: '!' | '!=' | '&&' | '<' | '<=' | '==' | '>' | '>=' | '?' | '^' | '||' @@ -286,6 +286,19 @@ export function isEnumField(item: unknown): item is EnumField { return reflection.isInstance(item, EnumField); } +export interface FieldInitializer extends AstNode { + readonly $container: ObjectExpr; + readonly $type: 'FieldInitializer'; + name: string + value: Expression +} + +export const FieldInitializer = 'FieldInitializer'; + +export function isFieldInitializer(item: unknown): item is FieldInitializer { + return reflection.isInstance(item, FieldInitializer); +} + export interface FunctionDecl extends AstNode { readonly $container: Model; readonly $type: 'FunctionDecl'; @@ -355,7 +368,7 @@ export function isGeneratorField(item: unknown): item is GeneratorField { } export interface InvocationExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; readonly $type: 'InvocationExpr'; args: Array function: Reference @@ -368,7 +381,7 @@ export function isInvocationExpr(item: unknown): item is InvocationExpr { } export interface LiteralExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; readonly $type: 'LiteralExpr'; value: boolean | number | string } @@ -380,7 +393,7 @@ export function isLiteralExpr(item: unknown): item is LiteralExpr { } export interface MemberAccessExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; readonly $type: 'MemberAccessExpr'; member: Reference operand: Expression @@ -404,7 +417,7 @@ export function isModel(item: unknown): item is Model { } export interface NullExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; readonly $type: 'NullExpr'; value: string } @@ -415,6 +428,18 @@ export function isNullExpr(item: unknown): item is NullExpr { return reflection.isInstance(item, NullExpr); } +export interface ObjectExpr extends AstNode { + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $type: 'ObjectExpr'; + fields: Array +} + +export const ObjectExpr = 'ObjectExpr'; + +export function isObjectExpr(item: unknown): item is ObjectExpr { + return reflection.isInstance(item, ObjectExpr); +} + export interface Plugin extends AstNode { readonly $container: Model; readonly $type: 'Plugin'; @@ -455,7 +480,7 @@ export function isReferenceArg(item: unknown): item is ReferenceArg { } export interface ReferenceExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; readonly $type: 'ReferenceExpr'; args: Array target: Reference @@ -468,7 +493,7 @@ export function isReferenceExpr(item: unknown): item is ReferenceExpr { } export interface ThisExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; readonly $type: 'ThisExpr'; value: string } @@ -480,7 +505,7 @@ export function isThisExpr(item: unknown): item is ThisExpr { } export interface UnaryExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; readonly $type: 'UnaryExpr'; operand: Expression operator: '!' @@ -512,6 +537,7 @@ export interface ZModelAstType { Enum: Enum EnumField: EnumField Expression: Expression + FieldInitializer: FieldInitializer FunctionDecl: FunctionDecl FunctionParam: FunctionParam FunctionParamType: FunctionParamType @@ -522,6 +548,7 @@ export interface ZModelAstType { MemberAccessExpr: MemberAccessExpr Model: Model NullExpr: NullExpr + ObjectExpr: ObjectExpr Plugin: Plugin PluginField: PluginField ReferenceArg: ReferenceArg @@ -535,7 +562,7 @@ export interface ZModelAstType { export class ZModelAstReflection extends AbstractAstReflection { getAllTypes(): string[] { - return ['AbstractDeclaration', 'Argument', 'ArrayExpr', 'Attribute', 'AttributeArg', 'AttributeAttribute', 'AttributeParam', 'AttributeParamType', 'BinaryExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Enum', 'EnumField', 'Expression', 'FunctionDecl', 'FunctionParam', 'FunctionParamType', 'GeneratorDecl', 'GeneratorField', 'InvocationExpr', 'LiteralExpr', 'MemberAccessExpr', 'Model', 'NullExpr', 'Plugin', 'PluginField', 'ReferenceArg', 'ReferenceExpr', 'ReferenceTarget', 'ThisExpr', 'TypeDeclaration', 'UnaryExpr']; + return ['AbstractDeclaration', 'Argument', 'ArrayExpr', 'Attribute', 'AttributeArg', 'AttributeAttribute', 'AttributeParam', 'AttributeParamType', 'BinaryExpr', 'DataModel', 'DataModelAttribute', 'DataModelField', 'DataModelFieldAttribute', 'DataModelFieldType', 'DataSource', 'DataSourceField', 'Enum', 'EnumField', 'Expression', 'FieldInitializer', 'FunctionDecl', 'FunctionParam', 'FunctionParamType', 'GeneratorDecl', 'GeneratorField', 'InvocationExpr', 'LiteralExpr', 'MemberAccessExpr', 'Model', 'NullExpr', 'ObjectExpr', 'Plugin', 'PluginField', 'ReferenceArg', 'ReferenceExpr', 'ReferenceTarget', 'ThisExpr', 'TypeDeclaration', 'UnaryExpr']; } protected override computeIsSubtype(subtype: string, supertype: string): boolean { @@ -546,6 +573,7 @@ export class ZModelAstReflection extends AbstractAstReflection { case LiteralExpr: case MemberAccessExpr: case NullExpr: + case ObjectExpr: case ReferenceExpr: case ThisExpr: case UnaryExpr: { @@ -756,6 +784,14 @@ export class ZModelAstReflection extends AbstractAstReflection { ] }; } + case 'ObjectExpr': { + return { + name: 'ObjectExpr', + mandatory: [ + { name: 'fields', type: 'array' } + ] + }; + } case 'Plugin': { return { name: 'Plugin', diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts index ad937f881..635c514e8 100644 --- a/packages/language/src/generated/grammar.ts +++ b/packages/language/src/generated/grammar.ts @@ -64,28 +64,28 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@26" + "$ref": "#/rules@28" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@29" + "$ref": "#/rules@31" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@31" + "$ref": "#/rules@33" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@39" + "$ref": "#/rules@41" }, "arguments": [] } @@ -107,7 +107,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -123,7 +123,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -167,7 +167,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -179,7 +179,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -237,7 +237,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -253,7 +253,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -297,7 +297,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -309,7 +309,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -360,7 +360,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -376,7 +376,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -420,7 +420,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -432,7 +432,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -504,21 +504,21 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@50" + "$ref": "#/rules@52" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@57" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@54" + "$ref": "#/rules@56" }, "arguments": [] } @@ -605,7 +605,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@52" + "$ref": "#/rules@54" }, "arguments": [] } @@ -627,7 +627,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@51" + "$ref": "#/rules@53" }, "arguments": [] } @@ -657,7 +657,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] }, @@ -802,7 +802,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@31" + "$ref": "#/rules@33" }, "deprecatedSyntax": false } @@ -814,7 +814,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@24" + "$ref": "#/rules@26" }, "arguments": [], "cardinality": "?" @@ -911,7 +911,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@27" + "$ref": "#/rules@29" }, "deprecatedSyntax": false } @@ -1312,6 +1312,124 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "$ref": "#/rules@17" }, "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@24" + }, + "arguments": [] + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "ObjectExpr", + "definition": { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "{" + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Assignment", + "feature": "fields", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@25" + }, + "arguments": [] + } + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "," + }, + { + "$type": "Assignment", + "feature": "fields", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@25" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + }, + { + "$type": "Keyword", + "value": ",", + "cardinality": "?" + } + ], + "cardinality": "?" + }, + { + "$type": "Keyword", + "value": "}" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "FieldInitializer", + "definition": { + "$type": "Group", + "elements": [ + { + "$type": "Assignment", + "feature": "name", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@55" + }, + "arguments": [] + } + }, + { + "$type": "Keyword", + "value": ":" + }, + { + "$type": "Assignment", + "feature": "value", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@8" + }, + "arguments": [] + } } ] }, @@ -1336,7 +1454,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@25" + "$ref": "#/rules@27" }, "arguments": [] } @@ -1355,7 +1473,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@25" + "$ref": "#/rules@27" }, "arguments": [] } @@ -1387,7 +1505,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -1433,7 +1551,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [] }, @@ -1450,7 +1568,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -1469,7 +1587,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@27" + "$ref": "#/rules@29" }, "arguments": [] } @@ -1481,7 +1599,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@43" + "$ref": "#/rules@45" }, "arguments": [] } @@ -1515,7 +1633,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [] }, @@ -1528,7 +1646,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -1540,7 +1658,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@28" + "$ref": "#/rules@30" }, "arguments": [] } @@ -1552,7 +1670,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@42" + "$ref": "#/rules@44" }, "arguments": [] }, @@ -1583,7 +1701,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@48" + "$ref": "#/rules@50" }, "arguments": [] } @@ -1600,7 +1718,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] }, @@ -1660,7 +1778,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [] }, @@ -1677,7 +1795,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -1696,7 +1814,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@30" + "$ref": "#/rules@32" }, "arguments": [] } @@ -1708,7 +1826,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@43" + "$ref": "#/rules@45" }, "arguments": [] } @@ -1742,7 +1860,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [] }, @@ -1755,7 +1873,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -1767,7 +1885,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@42" + "$ref": "#/rules@44" }, "arguments": [] }, @@ -1791,7 +1909,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -1807,7 +1925,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -1826,7 +1944,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@32" + "$ref": "#/rules@34" }, "arguments": [] } @@ -1845,7 +1963,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@32" + "$ref": "#/rules@34" }, "arguments": [] } @@ -1871,7 +1989,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@33" + "$ref": "#/rules@35" }, "arguments": [] } @@ -1915,7 +2033,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -1927,7 +2045,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -1943,7 +2061,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@33" + "$ref": "#/rules@35" }, "arguments": [] } @@ -1973,7 +2091,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@47" + "$ref": "#/rules@49" }, "arguments": [] } @@ -2030,7 +2148,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] }, @@ -2047,14 +2165,14 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@48" + "$ref": "#/rules@50" }, "arguments": [] } @@ -2086,7 +2204,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@34" + "$ref": "#/rules@36" }, "arguments": [] } @@ -2113,7 +2231,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@34" + "$ref": "#/rules@36" }, "arguments": [] } @@ -2140,7 +2258,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@34" + "$ref": "#/rules@36" }, "arguments": [] } @@ -2163,21 +2281,21 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@36" + "$ref": "#/rules@38" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@37" + "$ref": "#/rules@39" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@35" + "$ref": "#/rules@37" }, "arguments": [] } @@ -2199,7 +2317,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -2215,7 +2333,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@38" + "$ref": "#/rules@40" }, "arguments": [] } @@ -2234,7 +2352,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@40" + "$ref": "#/rules@42" }, "arguments": [] } @@ -2253,7 +2371,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@40" + "$ref": "#/rules@42" }, "arguments": [] } @@ -2275,7 +2393,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@44" + "$ref": "#/rules@46" }, "arguments": [] }, @@ -2299,7 +2417,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -2321,7 +2439,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -2337,7 +2455,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@41" + "$ref": "#/rules@43" }, "arguments": [] } @@ -2370,7 +2488,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@47" + "$ref": "#/rules@49" }, "arguments": [] }, @@ -2401,7 +2519,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] }, @@ -2461,12 +2579,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@39" + "$ref": "#/rules@41" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@37" + "$ref": "#/rules@39" }, "arguments": [] }, @@ -2483,7 +2601,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@45" + "$ref": "#/rules@47" }, "arguments": [], "cardinality": "?" @@ -2513,7 +2631,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@58" }, "arguments": [], "cardinality": "*" @@ -2525,12 +2643,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@39" + "$ref": "#/rules@41" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@36" + "$ref": "#/rules@38" }, "arguments": [] }, @@ -2547,7 +2665,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@45" + "$ref": "#/rules@47" }, "arguments": [], "cardinality": "?" @@ -2581,12 +2699,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@39" + "$ref": "#/rules@41" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@35" + "$ref": "#/rules@37" }, "arguments": [] }, @@ -2603,7 +2721,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@45" + "$ref": "#/rules@47" }, "arguments": [], "cardinality": "?" @@ -2638,7 +2756,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@46" + "$ref": "#/rules@48" }, "arguments": [] } @@ -2657,7 +2775,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@46" + "$ref": "#/rules@48" }, "arguments": [] } @@ -2689,7 +2807,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@55" }, "arguments": [] } @@ -2753,6 +2871,10 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "$type": "Keyword", "value": "Null" }, + { + "$type": "Keyword", + "value": "Object" + }, { "$type": "Keyword", "value": "Any" @@ -2940,7 +3062,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "AtomType", "refType": { - "$ref": "#/rules@32" + "$ref": "#/rules@34" }, "isArray": false, "isRef": false @@ -2948,7 +3070,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "AtomType", "refType": { - "$ref": "#/rules@27" + "$ref": "#/rules@29" }, "isArray": false, "isRef": false @@ -2956,7 +3078,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "AtomType", "refType": { - "$ref": "#/rules@30" + "$ref": "#/rules@32" }, "isArray": false, "isRef": false @@ -2970,7 +3092,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "AtomType", "refType": { - "$ref": "#/rules@26" + "$ref": "#/rules@28" }, "isArray": false, "isRef": false @@ -2978,7 +3100,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "AtomType", "refType": { - "$ref": "#/rules@29" + "$ref": "#/rules@31" }, "isArray": false, "isRef": false diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index aa6ff4735..95da83dbd 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -123,7 +123,16 @@ PrimaryExpr infers Expression: InvocationExpr | ArrayExpr| ReferenceExpr | - UnaryExpr; + UnaryExpr | + ObjectExpr; + +ObjectExpr: + '{' + (fields+=FieldInitializer (',' fields+=FieldInitializer)* ','?)? + '}'; + +FieldInitializer: + name=ID ':' value=(Expression); fragment ArgumentList: args+=Argument (',' args+=Argument)*; @@ -219,7 +228,7 @@ AttributeArg: (name=ID ':')? value=Expression; ExpressionType returns string: - 'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime' | 'Null' | 'Any'; + 'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime' | 'Null' | 'Object' | 'Any'; BuiltinType returns string: 'String' | 'Boolean' | 'Int' | 'BigInt' | 'Float' | 'Decimal' | 'DateTime' | 'Json' | 'Bytes'; diff --git a/packages/language/syntaxes/zmodel.tmLanguage.json b/packages/language/syntaxes/zmodel.tmLanguage.json index 820258417..0230e8ab2 100644 --- a/packages/language/syntaxes/zmodel.tmLanguage.json +++ b/packages/language/syntaxes/zmodel.tmLanguage.json @@ -10,7 +10,7 @@ }, { "name": "keyword.control.zmodel", - "match": "\\b(Any|Asc|attribute|BigInt|Boolean|Bytes|ContextType|datasource|DateTime|Decimal|Desc|enum|FieldReference|Float|function|generator|Int|Json|model|Null|plugin|sort|String|TransitiveFieldReference)\\b" + "match": "\\b(Any|Asc|attribute|BigInt|Boolean|Bytes|ContextType|datasource|DateTime|Decimal|Desc|enum|FieldReference|Float|function|generator|Int|Json|model|Null|Object|plugin|sort|String|TransitiveFieldReference)\\b" }, { "name": "string.quoted.double.zmodel", diff --git a/packages/next/package.json b/packages/next/package.json index 9bb209369..58119e37a 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/next", - "version": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "displayName": "ZenStack Next.js integration", "description": "ZenStack Next.js integration", "homepage": "https://zenstack.dev", diff --git a/packages/plugins/openapi/.eslintrc.json b/packages/plugins/openapi/.eslintrc.json new file mode 100644 index 000000000..0a913e874 --- /dev/null +++ b/packages/plugins/openapi/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ] +} diff --git a/packages/plugins/openapi/LICENSE b/packages/plugins/openapi/LICENSE new file mode 120000 index 000000000..30cff7403 --- /dev/null +++ b/packages/plugins/openapi/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/plugins/openapi/README.md b/packages/plugins/openapi/README.md new file mode 100644 index 000000000..605cb2a1a --- /dev/null +++ b/packages/plugins/openapi/README.md @@ -0,0 +1,5 @@ +# ZenStack OpenAPI plugin + +This package contains ZenStack plugin for generating OpenAPI specification. + +Visit [Homepage](https://zenstack.dev) for more details. diff --git a/packages/plugins/openapi/jest.config.ts b/packages/plugins/openapi/jest.config.ts new file mode 100644 index 000000000..917cf52f6 --- /dev/null +++ b/packages/plugins/openapi/jest.config.ts @@ -0,0 +1,29 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +export default { + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // The directory where Jest should output its coverage files + coverageDirectory: 'tests/coverage', + + // An array of regexp pattern strings used to skip coverage collection + coveragePathIgnorePatterns: ['/node_modules/', '/tests/'], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: 'v8', + + // A list of reporter names that Jest uses when writing coverage reports + coverageReporters: ['json', 'text', 'lcov', 'clover'], + + // A map from regular expressions to paths to transformers + transform: { '^.+\\.tsx?$': 'ts-jest' }, + + testTimeout: 300000, +}; diff --git a/packages/plugins/openapi/openapi.yaml b/packages/plugins/openapi/openapi.yaml new file mode 100644 index 000000000..3cfe0662c --- /dev/null +++ b/packages/plugins/openapi/openapi.yaml @@ -0,0 +1,3009 @@ +openapi: 3.0.0 +info: + title: ZenStack Generated API + version: 1.0.0 +components: + schemas: + Role: + type: string + enum: + - USER + - ADMIN + PostScalarFieldEnum: + type: string + enum: + - id + - createdAt + - updatedAt + - title + - authorId + - published + - viewCount + QueryMode: + type: string + enum: + - default + - insensitive + SortOrder: + type: string + enum: + - asc + - desc + UserScalarFieldEnum: + type: string + enum: + - id + - createdAt + - updatedAt + - email + - role + User: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + email: + type: string + role: + $ref: "#/components/schemas/Role" + posts: + type: array + items: + $ref: "#/components/schemas/Post" + required: + - id + - createdAt + - updatedAt + - email + - role + Post: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + title: + type: string + author: + $ref: "#/components/schemas/User" + authorId: + type: string + published: + type: boolean + viewCount: + type: integer + required: + - id + - createdAt + - updatedAt + - title + - published + - viewCount + UserWhereInput: + type: object + properties: + AND: + oneOf: + - $ref: "#/components/schemas/UserWhereInput" + - type: array + items: + $ref: "#/components/schemas/UserWhereInput" + OR: + type: array + items: + $ref: "#/components/schemas/UserWhereInput" + NOT: + oneOf: + - $ref: "#/components/schemas/UserWhereInput" + - type: array + items: + $ref: "#/components/schemas/UserWhereInput" + id: + oneOf: + - $ref: "#/components/schemas/StringFilter" + - type: string + createdAt: + oneOf: + - $ref: "#/components/schemas/DateTimeFilter" + - type: string + format: date-time + updatedAt: + oneOf: + - $ref: "#/components/schemas/DateTimeFilter" + - type: string + format: date-time + email: + oneOf: + - $ref: "#/components/schemas/StringFilter" + - type: string + role: + oneOf: + - $ref: "#/components/schemas/EnumRoleFilter" + - $ref: "#/components/schemas/Role" + posts: + $ref: "#/components/schemas/PostListRelationFilter" + UserOrderByWithRelationInput: + type: object + properties: + id: + $ref: "#/components/schemas/SortOrder" + createdAt: + $ref: "#/components/schemas/SortOrder" + updatedAt: + $ref: "#/components/schemas/SortOrder" + email: + $ref: "#/components/schemas/SortOrder" + role: + $ref: "#/components/schemas/SortOrder" + posts: + $ref: "#/components/schemas/PostOrderByRelationAggregateInput" + UserWhereUniqueInput: + type: object + properties: + id: + type: string + email: + type: string + UserScalarWhereWithAggregatesInput: + type: object + properties: + AND: + oneOf: + - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" + - type: array + items: + $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" + OR: + type: array + items: + $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" + NOT: + oneOf: + - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" + - type: array + items: + $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" + id: + oneOf: + - $ref: "#/components/schemas/StringWithAggregatesFilter" + - type: string + createdAt: + oneOf: + - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" + - type: string + format: date-time + updatedAt: + oneOf: + - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" + - type: string + format: date-time + email: + oneOf: + - $ref: "#/components/schemas/StringWithAggregatesFilter" + - type: string + role: + oneOf: + - $ref: "#/components/schemas/EnumRoleWithAggregatesFilter" + - $ref: "#/components/schemas/Role" + PostWhereInput: + type: object + properties: + AND: + oneOf: + - $ref: "#/components/schemas/PostWhereInput" + - type: array + items: + $ref: "#/components/schemas/PostWhereInput" + OR: + type: array + items: + $ref: "#/components/schemas/PostWhereInput" + NOT: + oneOf: + - $ref: "#/components/schemas/PostWhereInput" + - type: array + items: + $ref: "#/components/schemas/PostWhereInput" + id: + oneOf: + - $ref: "#/components/schemas/StringFilter" + - type: string + createdAt: + oneOf: + - $ref: "#/components/schemas/DateTimeFilter" + - type: string + format: date-time + updatedAt: + oneOf: + - $ref: "#/components/schemas/DateTimeFilter" + - type: string + format: date-time + title: + oneOf: + - $ref: "#/components/schemas/StringFilter" + - type: string + author: + oneOf: + - $ref: "#/components/schemas/UserRelationFilter" + - $ref: "#/components/schemas/UserWhereInput" + authorId: + oneOf: + - $ref: "#/components/schemas/StringNullableFilter" + - type: string + published: + oneOf: + - $ref: "#/components/schemas/BoolFilter" + - type: boolean + viewCount: + oneOf: + - $ref: "#/components/schemas/IntFilter" + - type: integer + PostOrderByWithRelationInput: + type: object + properties: + id: + $ref: "#/components/schemas/SortOrder" + createdAt: + $ref: "#/components/schemas/SortOrder" + updatedAt: + $ref: "#/components/schemas/SortOrder" + title: + $ref: "#/components/schemas/SortOrder" + author: + $ref: "#/components/schemas/UserOrderByWithRelationInput" + authorId: + $ref: "#/components/schemas/SortOrder" + published: + $ref: "#/components/schemas/SortOrder" + viewCount: + $ref: "#/components/schemas/SortOrder" + PostWhereUniqueInput: + type: object + properties: + id: + type: string + PostScalarWhereWithAggregatesInput: + type: object + properties: + AND: + oneOf: + - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" + - type: array + items: + $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" + OR: + type: array + items: + $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" + NOT: + oneOf: + - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" + - type: array + items: + $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" + id: + oneOf: + - $ref: "#/components/schemas/StringWithAggregatesFilter" + - type: string + createdAt: + oneOf: + - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" + - type: string + format: date-time + updatedAt: + oneOf: + - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" + - type: string + format: date-time + title: + oneOf: + - $ref: "#/components/schemas/StringWithAggregatesFilter" + - type: string + authorId: + oneOf: + - $ref: "#/components/schemas/StringNullableWithAggregatesFilter" + - type: string + published: + oneOf: + - $ref: "#/components/schemas/BoolWithAggregatesFilter" + - type: boolean + viewCount: + oneOf: + - $ref: "#/components/schemas/IntWithAggregatesFilter" + - type: integer + UserCreateInput: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + email: + type: string + role: + $ref: "#/components/schemas/Role" + posts: + $ref: "#/components/schemas/PostCreateNestedManyWithoutAuthorInput" + required: + - id + - email + UserUpdateInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + createdAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + updatedAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + email: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + role: + oneOf: + - $ref: "#/components/schemas/Role" + - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" + posts: + $ref: "#/components/schemas/PostUpdateManyWithoutAuthorNestedInput" + UserCreateManyInput: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + email: + type: string + role: + $ref: "#/components/schemas/Role" + required: + - id + - email + UserUpdateManyMutationInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + createdAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + updatedAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + email: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + role: + oneOf: + - $ref: "#/components/schemas/Role" + - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" + PostCreateInput: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + title: + type: string + author: + $ref: "#/components/schemas/UserCreateNestedOneWithoutPostsInput" + published: + type: boolean + viewCount: + type: integer + required: + - id + - title + PostUpdateInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + createdAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + updatedAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + title: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + author: + $ref: "#/components/schemas/UserUpdateOneWithoutPostsNestedInput" + published: + oneOf: + - type: boolean + - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" + viewCount: + oneOf: + - type: integer + - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" + PostCreateManyInput: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + title: + type: string + authorId: + type: string + published: + type: boolean + viewCount: + type: integer + required: + - id + - title + PostUpdateManyMutationInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + createdAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + updatedAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + title: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + published: + oneOf: + - type: boolean + - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" + viewCount: + oneOf: + - type: integer + - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" + StringFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + mode: + $ref: "#/components/schemas/QueryMode" + not: + oneOf: + - type: string + - $ref: "#/components/schemas/NestedStringFilter" + DateTimeFilter: + type: object + properties: + equals: + type: string + format: date-time + in: + type: array + items: + type: string + format: date-time + notIn: + type: array + items: + type: string + format: date-time + lt: + type: string + format: date-time + lte: + type: string + format: date-time + gt: + type: string + format: date-time + gte: + type: string + format: date-time + not: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/NestedDateTimeFilter" + EnumRoleFilter: + type: object + properties: + equals: + $ref: "#/components/schemas/Role" + in: + type: array + items: + $ref: "#/components/schemas/Role" + notIn: + type: array + items: + $ref: "#/components/schemas/Role" + not: + oneOf: + - $ref: "#/components/schemas/Role" + - $ref: "#/components/schemas/NestedEnumRoleFilter" + PostListRelationFilter: + type: object + properties: + every: + $ref: "#/components/schemas/PostWhereInput" + some: + $ref: "#/components/schemas/PostWhereInput" + none: + $ref: "#/components/schemas/PostWhereInput" + BoolFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: "#/components/schemas/NestedBoolFilter" + StringNullableFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + mode: + $ref: "#/components/schemas/QueryMode" + not: + oneOf: + - type: string + - $ref: "#/components/schemas/NestedStringNullableFilter" + PostOrderByRelationAggregateInput: + type: object + properties: + _count: + $ref: "#/components/schemas/SortOrder" + StringWithAggregatesFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + mode: + $ref: "#/components/schemas/QueryMode" + not: + oneOf: + - type: string + - $ref: "#/components/schemas/NestedStringWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntFilter" + _min: + $ref: "#/components/schemas/NestedStringFilter" + _max: + $ref: "#/components/schemas/NestedStringFilter" + DateTimeWithAggregatesFilter: + type: object + properties: + equals: + type: string + format: date-time + in: + type: array + items: + type: string + format: date-time + notIn: + type: array + items: + type: string + format: date-time + lt: + type: string + format: date-time + lte: + type: string + format: date-time + gt: + type: string + format: date-time + gte: + type: string + format: date-time + not: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/NestedDateTimeWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntFilter" + _min: + $ref: "#/components/schemas/NestedDateTimeFilter" + _max: + $ref: "#/components/schemas/NestedDateTimeFilter" + EnumRoleWithAggregatesFilter: + type: object + properties: + equals: + $ref: "#/components/schemas/Role" + in: + type: array + items: + $ref: "#/components/schemas/Role" + notIn: + type: array + items: + $ref: "#/components/schemas/Role" + not: + oneOf: + - $ref: "#/components/schemas/Role" + - $ref: "#/components/schemas/NestedEnumRoleWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntFilter" + _min: + $ref: "#/components/schemas/NestedEnumRoleFilter" + _max: + $ref: "#/components/schemas/NestedEnumRoleFilter" + BoolWithAggregatesFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: "#/components/schemas/NestedBoolWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntFilter" + _min: + $ref: "#/components/schemas/NestedBoolFilter" + _max: + $ref: "#/components/schemas/NestedBoolFilter" + StringNullableWithAggregatesFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + mode: + $ref: "#/components/schemas/QueryMode" + not: + oneOf: + - type: string + - $ref: "#/components/schemas/NestedStringNullableWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntNullableFilter" + _min: + $ref: "#/components/schemas/NestedStringNullableFilter" + _max: + $ref: "#/components/schemas/NestedStringNullableFilter" + UserRelationFilter: + type: object + properties: + is: + $ref: "#/components/schemas/UserWhereInput" + isNot: + $ref: "#/components/schemas/UserWhereInput" + IntFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: "#/components/schemas/NestedIntFilter" + IntWithAggregatesFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: "#/components/schemas/NestedIntWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntFilter" + _avg: + $ref: "#/components/schemas/NestedFloatFilter" + _sum: + $ref: "#/components/schemas/NestedIntFilter" + _min: + $ref: "#/components/schemas/NestedIntFilter" + _max: + $ref: "#/components/schemas/NestedIntFilter" + PostCreateNestedManyWithoutAuthorInput: + type: object + properties: + create: + oneOf: + - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" + - type: array + items: + $ref: "#/components/schemas/PostCreateWithoutAuthorInput" + - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" + - type: array + items: + $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" + connectOrCreate: + oneOf: + - $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" + - type: array + items: + $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" + createMany: + $ref: "#/components/schemas/PostCreateManyAuthorInputEnvelope" + connect: + oneOf: + - $ref: "#/components/schemas/PostWhereUniqueInput" + - type: array + items: + $ref: "#/components/schemas/PostWhereUniqueInput" + StringFieldUpdateOperationsInput: + type: object + properties: + set: + type: string + DateTimeFieldUpdateOperationsInput: + type: object + properties: + set: + type: string + format: date-time + EnumRoleFieldUpdateOperationsInput: + type: object + properties: + set: + $ref: "#/components/schemas/Role" + PostUpdateManyWithoutAuthorNestedInput: + type: object + properties: + create: + oneOf: + - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" + - type: array + items: + $ref: "#/components/schemas/PostCreateWithoutAuthorInput" + - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" + - type: array + items: + $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" + connectOrCreate: + oneOf: + - $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" + - type: array + items: + $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" + upsert: + oneOf: + - $ref: "#/components/schemas/PostUpsertWithWhereUniqueWithoutAuthorInput" + - type: array + items: + $ref: "#/components/schemas/PostUpsertWithWhereUniqueWithoutAuthorInput" + createMany: + $ref: "#/components/schemas/PostCreateManyAuthorInputEnvelope" + set: + oneOf: + - $ref: "#/components/schemas/PostWhereUniqueInput" + - type: array + items: + $ref: "#/components/schemas/PostWhereUniqueInput" + disconnect: + oneOf: + - $ref: "#/components/schemas/PostWhereUniqueInput" + - type: array + items: + $ref: "#/components/schemas/PostWhereUniqueInput" + delete: + oneOf: + - $ref: "#/components/schemas/PostWhereUniqueInput" + - type: array + items: + $ref: "#/components/schemas/PostWhereUniqueInput" + connect: + oneOf: + - $ref: "#/components/schemas/PostWhereUniqueInput" + - type: array + items: + $ref: "#/components/schemas/PostWhereUniqueInput" + update: + oneOf: + - $ref: "#/components/schemas/PostUpdateWithWhereUniqueWithoutAuthorInput" + - type: array + items: + $ref: "#/components/schemas/PostUpdateWithWhereUniqueWithoutAuthorInput" + updateMany: + oneOf: + - $ref: "#/components/schemas/PostUpdateManyWithWhereWithoutAuthorInput" + - type: array + items: + $ref: "#/components/schemas/PostUpdateManyWithWhereWithoutAuthorInput" + deleteMany: + oneOf: + - $ref: "#/components/schemas/PostScalarWhereInput" + - type: array + items: + $ref: "#/components/schemas/PostScalarWhereInput" + BoolFieldUpdateOperationsInput: + type: object + properties: + set: + type: boolean + UserCreateNestedOneWithoutPostsInput: + type: object + properties: + create: + oneOf: + - $ref: "#/components/schemas/UserCreateWithoutPostsInput" + - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" + connectOrCreate: + $ref: "#/components/schemas/UserCreateOrConnectWithoutPostsInput" + connect: + $ref: "#/components/schemas/UserWhereUniqueInput" + UserUpdateOneWithoutPostsNestedInput: + type: object + properties: + create: + oneOf: + - $ref: "#/components/schemas/UserCreateWithoutPostsInput" + - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" + connectOrCreate: + $ref: "#/components/schemas/UserCreateOrConnectWithoutPostsInput" + upsert: + $ref: "#/components/schemas/UserUpsertWithoutPostsInput" + disconnect: + type: boolean + delete: + type: boolean + connect: + $ref: "#/components/schemas/UserWhereUniqueInput" + update: + oneOf: + - $ref: "#/components/schemas/UserUpdateWithoutPostsInput" + - $ref: "#/components/schemas/UserUncheckedUpdateWithoutPostsInput" + IntFieldUpdateOperationsInput: + type: object + properties: + set: + type: integer + increment: + type: integer + decrement: + type: integer + multiply: + type: integer + divide: + type: integer + NestedStringFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + not: + oneOf: + - type: string + - $ref: "#/components/schemas/NestedStringFilter" + NestedDateTimeFilter: + type: object + properties: + equals: + type: string + format: date-time + in: + type: array + items: + type: string + format: date-time + notIn: + type: array + items: + type: string + format: date-time + lt: + type: string + format: date-time + lte: + type: string + format: date-time + gt: + type: string + format: date-time + gte: + type: string + format: date-time + not: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/NestedDateTimeFilter" + NestedEnumRoleFilter: + type: object + properties: + equals: + $ref: "#/components/schemas/Role" + in: + type: array + items: + $ref: "#/components/schemas/Role" + notIn: + type: array + items: + $ref: "#/components/schemas/Role" + not: + oneOf: + - $ref: "#/components/schemas/Role" + - $ref: "#/components/schemas/NestedEnumRoleFilter" + NestedBoolFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: "#/components/schemas/NestedBoolFilter" + NestedStringNullableFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + not: + oneOf: + - type: string + - $ref: "#/components/schemas/NestedStringNullableFilter" + NestedStringWithAggregatesFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + not: + oneOf: + - type: string + - $ref: "#/components/schemas/NestedStringWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntFilter" + _min: + $ref: "#/components/schemas/NestedStringFilter" + _max: + $ref: "#/components/schemas/NestedStringFilter" + NestedIntFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: "#/components/schemas/NestedIntFilter" + NestedDateTimeWithAggregatesFilter: + type: object + properties: + equals: + type: string + format: date-time + in: + type: array + items: + type: string + format: date-time + notIn: + type: array + items: + type: string + format: date-time + lt: + type: string + format: date-time + lte: + type: string + format: date-time + gt: + type: string + format: date-time + gte: + type: string + format: date-time + not: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/NestedDateTimeWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntFilter" + _min: + $ref: "#/components/schemas/NestedDateTimeFilter" + _max: + $ref: "#/components/schemas/NestedDateTimeFilter" + NestedEnumRoleWithAggregatesFilter: + type: object + properties: + equals: + $ref: "#/components/schemas/Role" + in: + type: array + items: + $ref: "#/components/schemas/Role" + notIn: + type: array + items: + $ref: "#/components/schemas/Role" + not: + oneOf: + - $ref: "#/components/schemas/Role" + - $ref: "#/components/schemas/NestedEnumRoleWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntFilter" + _min: + $ref: "#/components/schemas/NestedEnumRoleFilter" + _max: + $ref: "#/components/schemas/NestedEnumRoleFilter" + NestedBoolWithAggregatesFilter: + type: object + properties: + equals: + type: boolean + not: + oneOf: + - type: boolean + - $ref: "#/components/schemas/NestedBoolWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntFilter" + _min: + $ref: "#/components/schemas/NestedBoolFilter" + _max: + $ref: "#/components/schemas/NestedBoolFilter" + NestedStringNullableWithAggregatesFilter: + type: object + properties: + equals: + type: string + in: + type: array + items: + type: string + notIn: + type: array + items: + type: string + lt: + type: string + lte: + type: string + gt: + type: string + gte: + type: string + contains: + type: string + startsWith: + type: string + endsWith: + type: string + not: + oneOf: + - type: string + - $ref: "#/components/schemas/NestedStringNullableWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntNullableFilter" + _min: + $ref: "#/components/schemas/NestedStringNullableFilter" + _max: + $ref: "#/components/schemas/NestedStringNullableFilter" + NestedIntNullableFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: "#/components/schemas/NestedIntNullableFilter" + NestedIntWithAggregatesFilter: + type: object + properties: + equals: + type: integer + in: + type: array + items: + type: integer + notIn: + type: array + items: + type: integer + lt: + type: integer + lte: + type: integer + gt: + type: integer + gte: + type: integer + not: + oneOf: + - type: integer + - $ref: "#/components/schemas/NestedIntWithAggregatesFilter" + _count: + $ref: "#/components/schemas/NestedIntFilter" + _avg: + $ref: "#/components/schemas/NestedFloatFilter" + _sum: + $ref: "#/components/schemas/NestedIntFilter" + _min: + $ref: "#/components/schemas/NestedIntFilter" + _max: + $ref: "#/components/schemas/NestedIntFilter" + NestedFloatFilter: + type: object + properties: + equals: + type: number + in: + type: array + items: + type: number + notIn: + type: array + items: + type: number + lt: + type: number + lte: + type: number + gt: + type: number + gte: + type: number + not: + oneOf: + - type: number + - $ref: "#/components/schemas/NestedFloatFilter" + PostCreateWithoutAuthorInput: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + title: + type: string + published: + type: boolean + viewCount: + type: integer + required: + - id + - title + PostUncheckedCreateWithoutAuthorInput: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + title: + type: string + published: + type: boolean + viewCount: + type: integer + required: + - id + - title + PostCreateOrConnectWithoutAuthorInput: + type: object + properties: + where: + $ref: "#/components/schemas/PostWhereUniqueInput" + create: + oneOf: + - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" + - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" + required: + - where + - create + PostCreateManyAuthorInputEnvelope: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/PostCreateManyAuthorInput" + skipDuplicates: + type: boolean + required: + - data + PostUpsertWithWhereUniqueWithoutAuthorInput: + type: object + properties: + where: + $ref: "#/components/schemas/PostWhereUniqueInput" + update: + oneOf: + - $ref: "#/components/schemas/PostUpdateWithoutAuthorInput" + - $ref: "#/components/schemas/PostUncheckedUpdateWithoutAuthorInput" + create: + oneOf: + - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" + - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" + required: + - where + - update + - create + PostUpdateWithWhereUniqueWithoutAuthorInput: + type: object + properties: + where: + $ref: "#/components/schemas/PostWhereUniqueInput" + data: + oneOf: + - $ref: "#/components/schemas/PostUpdateWithoutAuthorInput" + - $ref: "#/components/schemas/PostUncheckedUpdateWithoutAuthorInput" + required: + - where + - data + PostUpdateManyWithWhereWithoutAuthorInput: + type: object + properties: + where: + $ref: "#/components/schemas/PostScalarWhereInput" + data: + oneOf: + - $ref: "#/components/schemas/PostUpdateManyMutationInput" + - $ref: "#/components/schemas/PostUncheckedUpdateManyWithoutPostsInput" + required: + - where + - data + PostScalarWhereInput: + type: object + properties: + AND: + oneOf: + - $ref: "#/components/schemas/PostScalarWhereInput" + - type: array + items: + $ref: "#/components/schemas/PostScalarWhereInput" + OR: + type: array + items: + $ref: "#/components/schemas/PostScalarWhereInput" + NOT: + oneOf: + - $ref: "#/components/schemas/PostScalarWhereInput" + - type: array + items: + $ref: "#/components/schemas/PostScalarWhereInput" + id: + oneOf: + - $ref: "#/components/schemas/StringFilter" + - type: string + createdAt: + oneOf: + - $ref: "#/components/schemas/DateTimeFilter" + - type: string + format: date-time + updatedAt: + oneOf: + - $ref: "#/components/schemas/DateTimeFilter" + - type: string + format: date-time + title: + oneOf: + - $ref: "#/components/schemas/StringFilter" + - type: string + authorId: + oneOf: + - $ref: "#/components/schemas/StringNullableFilter" + - type: string + published: + oneOf: + - $ref: "#/components/schemas/BoolFilter" + - type: boolean + viewCount: + oneOf: + - $ref: "#/components/schemas/IntFilter" + - type: integer + UserCreateWithoutPostsInput: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + email: + type: string + role: + $ref: "#/components/schemas/Role" + required: + - id + - email + UserUncheckedCreateWithoutPostsInput: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + email: + type: string + role: + $ref: "#/components/schemas/Role" + required: + - id + - email + UserCreateOrConnectWithoutPostsInput: + type: object + properties: + where: + $ref: "#/components/schemas/UserWhereUniqueInput" + create: + oneOf: + - $ref: "#/components/schemas/UserCreateWithoutPostsInput" + - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" + required: + - where + - create + UserUpsertWithoutPostsInput: + type: object + properties: + update: + oneOf: + - $ref: "#/components/schemas/UserUpdateWithoutPostsInput" + - $ref: "#/components/schemas/UserUncheckedUpdateWithoutPostsInput" + create: + oneOf: + - $ref: "#/components/schemas/UserCreateWithoutPostsInput" + - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" + required: + - update + - create + UserUpdateWithoutPostsInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + createdAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + updatedAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + email: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + role: + oneOf: + - $ref: "#/components/schemas/Role" + - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" + UserUncheckedUpdateWithoutPostsInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + createdAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + updatedAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + email: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + role: + oneOf: + - $ref: "#/components/schemas/Role" + - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" + PostCreateManyAuthorInput: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + title: + type: string + published: + type: boolean + viewCount: + type: integer + required: + - id + - title + PostUpdateWithoutAuthorInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + createdAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + updatedAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + title: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + published: + oneOf: + - type: boolean + - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" + viewCount: + oneOf: + - type: integer + - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" + PostUncheckedUpdateWithoutAuthorInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + createdAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + updatedAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + title: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + published: + oneOf: + - type: boolean + - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" + viewCount: + oneOf: + - type: integer + - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" + PostUncheckedUpdateManyWithoutPostsInput: + type: object + properties: + id: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + createdAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + updatedAt: + oneOf: + - type: string + format: date-time + - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" + title: + oneOf: + - type: string + - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" + published: + oneOf: + - type: boolean + - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" + viewCount: + oneOf: + - type: integer + - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" + UserArgs: + type: object + properties: + select: + $ref: "#/components/schemas/UserSelect" + include: + $ref: "#/components/schemas/UserInclude" + UserInclude: + type: object + properties: + posts: + oneOf: + - type: boolean + - $ref: "#/components/schemas/PostFindManyArgs" + _count: + oneOf: + - type: boolean + - $ref: "#/components/schemas/UserCountOutputTypeArgs" + PostInclude: + type: object + properties: + author: + oneOf: + - type: boolean + - $ref: "#/components/schemas/UserArgs" + UserCountOutputTypeSelect: + type: object + properties: + posts: + type: boolean + UserCountOutputTypeArgs: + type: object + properties: + select: + $ref: "#/components/schemas/UserCountOutputTypeSelect" + UserSelect: + type: object + properties: + id: + type: boolean + createdAt: + type: boolean + updatedAt: + type: boolean + email: + type: boolean + role: + type: boolean + posts: + oneOf: + - type: boolean + - $ref: "#/components/schemas/PostFindManyArgs" + _count: + oneOf: + - type: boolean + - $ref: "#/components/schemas/UserCountOutputTypeArgs" + PostSelect: + type: object + properties: + id: + type: boolean + createdAt: + type: boolean + updatedAt: + type: boolean + title: + type: boolean + author: + oneOf: + - type: boolean + - $ref: "#/components/schemas/UserArgs" + authorId: + type: boolean + published: + type: boolean + viewCount: + type: boolean + UserCountAggregateInput: + type: object + properties: + id: + type: boolean + createdAt: + type: boolean + updatedAt: + type: boolean + email: + type: boolean + role: + type: boolean + _all: + type: boolean + UserMinAggregateInput: + type: object + properties: + id: + type: boolean + createdAt: + type: boolean + updatedAt: + type: boolean + email: + type: boolean + role: + type: boolean + UserMaxAggregateInput: + type: object + properties: + id: + type: boolean + createdAt: + type: boolean + updatedAt: + type: boolean + email: + type: boolean + role: + type: boolean + PostCountAggregateInput: + type: object + properties: + id: + type: boolean + createdAt: + type: boolean + updatedAt: + type: boolean + title: + type: boolean + authorId: + type: boolean + published: + type: boolean + viewCount: + type: boolean + _all: + type: boolean + PostAvgAggregateInput: + type: object + properties: + viewCount: + type: boolean + PostSumAggregateInput: + type: object + properties: + viewCount: + type: boolean + PostMinAggregateInput: + type: object + properties: + id: + type: boolean + createdAt: + type: boolean + updatedAt: + type: boolean + title: + type: boolean + authorId: + type: boolean + published: + type: boolean + viewCount: + type: boolean + PostMaxAggregateInput: + type: object + properties: + id: + type: boolean + createdAt: + type: boolean + updatedAt: + type: boolean + title: + type: boolean + authorId: + type: boolean + published: + type: boolean + viewCount: + type: boolean + AggregateUser: + type: object + properties: + _count: + $ref: "#/components/schemas/UserCountAggregateOutputType" + _min: + $ref: "#/components/schemas/UserMinAggregateOutputType" + _max: + $ref: "#/components/schemas/UserMaxAggregateOutputType" + UserGroupByOutputType: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + email: + type: string + role: + $ref: "#/components/schemas/Role" + _count: + $ref: "#/components/schemas/UserCountAggregateOutputType" + _min: + $ref: "#/components/schemas/UserMinAggregateOutputType" + _max: + $ref: "#/components/schemas/UserMaxAggregateOutputType" + required: + - id + - createdAt + - updatedAt + - email + - role + AggregatePost: + type: object + properties: + _count: + $ref: "#/components/schemas/PostCountAggregateOutputType" + _avg: + $ref: "#/components/schemas/PostAvgAggregateOutputType" + _sum: + $ref: "#/components/schemas/PostSumAggregateOutputType" + _min: + $ref: "#/components/schemas/PostMinAggregateOutputType" + _max: + $ref: "#/components/schemas/PostMaxAggregateOutputType" + PostGroupByOutputType: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + title: + type: string + authorId: + type: string + published: + type: boolean + viewCount: + type: integer + _count: + $ref: "#/components/schemas/PostCountAggregateOutputType" + _avg: + $ref: "#/components/schemas/PostAvgAggregateOutputType" + _sum: + $ref: "#/components/schemas/PostSumAggregateOutputType" + _min: + $ref: "#/components/schemas/PostMinAggregateOutputType" + _max: + $ref: "#/components/schemas/PostMaxAggregateOutputType" + required: + - id + - createdAt + - updatedAt + - title + - published + - viewCount + UserCountAggregateOutputType: + type: object + properties: + id: + type: integer + createdAt: + type: integer + updatedAt: + type: integer + email: + type: integer + role: + type: integer + _all: + type: integer + required: + - id + - createdAt + - updatedAt + - email + - role + - _all + UserMinAggregateOutputType: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + email: + type: string + role: + $ref: "#/components/schemas/Role" + UserMaxAggregateOutputType: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + email: + type: string + role: + $ref: "#/components/schemas/Role" + PostCountAggregateOutputType: + type: object + properties: + id: + type: integer + createdAt: + type: integer + updatedAt: + type: integer + title: + type: integer + authorId: + type: integer + published: + type: integer + viewCount: + type: integer + _all: + type: integer + required: + - id + - createdAt + - updatedAt + - title + - authorId + - published + - viewCount + - _all + PostAvgAggregateOutputType: + type: object + properties: + viewCount: + type: number + PostSumAggregateOutputType: + type: object + properties: + viewCount: + type: integer + PostMinAggregateOutputType: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + title: + type: string + authorId: + type: string + published: + type: boolean + viewCount: + type: integer + PostMaxAggregateOutputType: + type: object + properties: + id: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + title: + type: string + authorId: + type: string + published: + type: boolean + viewCount: + type: integer + BatchPayload: + type: object + properties: + count: + type: integer + UserCreateArgs: + type: object + properties: + select: + $ref: "#/components/schemas/UserSelect" + include: + $ref: "#/components/schemas/UserInclude" + data: + $ref: "#/components/schemas/UserCreateInput" + UserCreateManyArgs: + type: object + properties: + data: + $ref: "#/components/schemas/UserCreateManyInput" + UserFindUniqueArgs: + type: object + properties: + select: + $ref: "#/components/schemas/UserSelect" + include: + $ref: "#/components/schemas/UserInclude" + where: + $ref: "#/components/schemas/UserWhereUniqueInput" + UserFindFirstArgs: + type: object + properties: + select: + $ref: "#/components/schemas/UserSelect" + include: + $ref: "#/components/schemas/UserInclude" + where: + $ref: "#/components/schemas/UserWhereInput" + UserFindManyArgs: + type: object + properties: + select: + $ref: "#/components/schemas/UserSelect" + include: + $ref: "#/components/schemas/UserInclude" + where: + $ref: "#/components/schemas/UserWhereInput" + UserUpdateArgs: + type: object + properties: + select: + $ref: "#/components/schemas/UserSelect" + include: + $ref: "#/components/schemas/UserInclude" + where: + $ref: "#/components/schemas/UserWhereUniqueInput" + data: + $ref: "#/components/schemas/UserUpdateInput" + UserUpdateManyArgs: + type: object + properties: + where: + $ref: "#/components/schemas/UserWhereInput" + data: + $ref: "#/components/schemas/UserUpdateManyMutationInput" + UserUpsertArgs: + type: object + properties: + select: + $ref: "#/components/schemas/UserSelect" + include: + $ref: "#/components/schemas/UserInclude" + where: + $ref: "#/components/schemas/UserWhereUniqueInput" + create: + $ref: "#/components/schemas/UserCreateInput" + update: + $ref: "#/components/schemas/UserUpdateInput" + UserDeleteUniqueArgs: + type: object + properties: + select: + $ref: "#/components/schemas/UserSelect" + include: + $ref: "#/components/schemas/UserInclude" + where: + $ref: "#/components/schemas/UserWhereUniqueInput" + UserDeleteManyArgs: + type: object + properties: + where: + $ref: "#/components/schemas/UserWhereInput" + UserCountArgs: + type: object + properties: + select: + $ref: "#/components/schemas/UserSelect" + where: + $ref: "#/components/schemas/UserWhereInput" + UserAggregateArgs: + type: object + properties: + where: + $ref: "#/components/schemas/UserWhereInput" + orderBy: + $ref: "#/components/schemas/UserOrderByWithRelationInput" + cursor: + $ref: "#/components/schemas/UserWhereUniqueInput" + take: + type: integer + skip: + type: integer + _count: + oneOf: + - type: boolean + - $ref: "#/components/schemas/UserCountAggregateInput" + _min: + $ref: "#/components/schemas/UserMinAggregateInput" + _max: + $ref: "#/components/schemas/UserMaxAggregateInput" + UserGroupByArgs: + type: object + properties: + where: + $ref: "#/components/schemas/UserWhereInput" + orderBy: + $ref: "#/components/schemas/UserOrderByWithRelationInput" + by: + $ref: "#/components/schemas/UserScalarFieldEnum" + having: + $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" + take: + type: integer + skip: + type: integer + _count: + oneOf: + - type: boolean + - $ref: "#/components/schemas/UserCountAggregateInput" + _min: + $ref: "#/components/schemas/UserMinAggregateInput" + _max: + $ref: "#/components/schemas/UserMaxAggregateInput" + PostCreateArgs: + type: object + properties: + select: + $ref: "#/components/schemas/PostSelect" + include: + $ref: "#/components/schemas/PostInclude" + data: + $ref: "#/components/schemas/PostCreateInput" + PostCreateManyArgs: + type: object + properties: + data: + $ref: "#/components/schemas/PostCreateManyInput" + PostFindUniqueArgs: + type: object + properties: + select: + $ref: "#/components/schemas/PostSelect" + include: + $ref: "#/components/schemas/PostInclude" + where: + $ref: "#/components/schemas/PostWhereUniqueInput" + PostFindFirstArgs: + type: object + properties: + select: + $ref: "#/components/schemas/PostSelect" + include: + $ref: "#/components/schemas/PostInclude" + where: + $ref: "#/components/schemas/PostWhereInput" + PostFindManyArgs: + type: object + properties: + select: + $ref: "#/components/schemas/PostSelect" + include: + $ref: "#/components/schemas/PostInclude" + where: + $ref: "#/components/schemas/PostWhereInput" + PostUpdateArgs: + type: object + properties: + select: + $ref: "#/components/schemas/PostSelect" + include: + $ref: "#/components/schemas/PostInclude" + where: + $ref: "#/components/schemas/PostWhereUniqueInput" + data: + $ref: "#/components/schemas/PostUpdateInput" + PostUpdateManyArgs: + type: object + properties: + where: + $ref: "#/components/schemas/PostWhereInput" + data: + $ref: "#/components/schemas/PostUpdateManyMutationInput" + PostUpsertArgs: + type: object + properties: + select: + $ref: "#/components/schemas/PostSelect" + include: + $ref: "#/components/schemas/PostInclude" + where: + $ref: "#/components/schemas/PostWhereUniqueInput" + create: + $ref: "#/components/schemas/PostCreateInput" + update: + $ref: "#/components/schemas/PostUpdateInput" + PostDeleteUniqueArgs: + type: object + properties: + select: + $ref: "#/components/schemas/PostSelect" + include: + $ref: "#/components/schemas/PostInclude" + where: + $ref: "#/components/schemas/PostWhereUniqueInput" + PostDeleteManyArgs: + type: object + properties: + where: + $ref: "#/components/schemas/PostWhereInput" + PostCountArgs: + type: object + properties: + select: + $ref: "#/components/schemas/PostSelect" + where: + $ref: "#/components/schemas/PostWhereInput" + PostAggregateArgs: + type: object + properties: + where: + $ref: "#/components/schemas/PostWhereInput" + orderBy: + $ref: "#/components/schemas/PostOrderByWithRelationInput" + cursor: + $ref: "#/components/schemas/PostWhereUniqueInput" + take: + type: integer + skip: + type: integer + _count: + oneOf: + - type: boolean + - $ref: "#/components/schemas/PostCountAggregateInput" + _min: + $ref: "#/components/schemas/PostMinAggregateInput" + _max: + $ref: "#/components/schemas/PostMaxAggregateInput" + _sum: + $ref: "#/components/schemas/PostSumAggregateInput" + _avg: + $ref: "#/components/schemas/PostAvgAggregateInput" + PostGroupByArgs: + type: object + properties: + where: + $ref: "#/components/schemas/PostWhereInput" + orderBy: + $ref: "#/components/schemas/PostOrderByWithRelationInput" + by: + $ref: "#/components/schemas/PostScalarFieldEnum" + having: + $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" + take: + type: integer + skip: + type: integer + _count: + oneOf: + - type: boolean + - $ref: "#/components/schemas/PostCountAggregateInput" + _min: + $ref: "#/components/schemas/PostMinAggregateInput" + _max: + $ref: "#/components/schemas/PostMaxAggregateInput" + _sum: + $ref: "#/components/schemas/PostSumAggregateInput" + _avg: + $ref: "#/components/schemas/PostAvgAggregateInput" +paths: + /user/create: + post: + operationId: createUser + responses: + "201": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/User" + "400": + description: Invalid request + "403": + description: Forbidden + requestBody: + description: Create a new User + content: + application/json: + schema: + $ref: "#/components/schemas/UserCreateArgs" + /user/createMany: + post: + operationId: createManyUser + responses: + "201": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/BatchPayload" + "400": + description: Invalid request + "403": + description: Forbidden + requestBody: + description: Create several User + content: + application/json: + schema: + $ref: "#/components/schemas/UserCreateManyArgs" + /user/findUnique: + get: + operationId: findUniqueUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/User" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/UserFindUniqueArgs" + /user/findFirst: + get: + operationId: findFirstUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/User" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/UserFindFirstArgs" + /user/findMany: + get: + operationId: findManyUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/User" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/UserFindManyArgs" + /user/update: + patch: + operationId: updateUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/User" + "400": + description: Invalid request + "403": + description: Forbidden + requestBody: + description: Update a User + content: + application/json: + schema: + $ref: "#/components/schemas/UserUpdateArgs" + /user/updateMany: + patch: + operationId: updateManyUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/BatchPayload" + "400": + description: Invalid request + "403": + description: Forbidden + requestBody: + description: Update Users matching the given condition + content: + application/json: + schema: + $ref: "#/components/schemas/UserUpdateManyArgs" + /user/upsert: + post: + operationId: upsertUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/User" + "400": + description: Invalid request + "403": + description: Forbidden + requestBody: + description: Upsert a User + content: + application/json: + schema: + $ref: "#/components/schemas/UserUpsertArgs" + /user/delete: + delete: + operationId: deleteUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/User" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/UserDeleteUniqueArgs" + /user/deleteMany: + delete: + operationId: deleteManyUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/BatchPayload" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/UserDeleteManyArgs" + /user/count: + get: + operationId: countUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + oneOf: + - type: integer + - $ref: "#/components/schemas/UserCountAggregateOutputType" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/UserCountArgs" + /user/aggregate: + get: + operationId: aggregateUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/AggregateUser" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/UserAggregateArgs" + /user/groupBy: + get: + operationId: groupByUser + responses: + "200": + description: Successful operation + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/UserGroupByOutputType" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/UserGroupByArgs" + /post/create: + post: + operationId: createPost + responses: + "201": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + "400": + description: Invalid request + "403": + description: Forbidden + requestBody: + description: Create a new Post + content: + application/json: + schema: + $ref: "#/components/schemas/PostCreateArgs" + /post/createMany: + post: + operationId: createManyPost + responses: + "201": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/BatchPayload" + "400": + description: Invalid request + "403": + description: Forbidden + requestBody: + description: Create several Post + content: + application/json: + schema: + $ref: "#/components/schemas/PostCreateManyArgs" + /post/findUnique: + get: + operationId: findUniquePost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/PostFindUniqueArgs" + /post/findFirst: + get: + operationId: findFirstPost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/PostFindFirstArgs" + /post/findMany: + get: + operationId: findManyPost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Post" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/PostFindManyArgs" + /post/update: + patch: + operationId: updatePost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + "400": + description: Invalid request + "403": + description: Forbidden + requestBody: + description: Update a Post + content: + application/json: + schema: + $ref: "#/components/schemas/PostUpdateArgs" + /post/updateMany: + patch: + operationId: updateManyPost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/BatchPayload" + "400": + description: Invalid request + "403": + description: Forbidden + requestBody: + description: Update Posts matching the given condition + content: + application/json: + schema: + $ref: "#/components/schemas/PostUpdateManyArgs" + /post/upsert: + post: + operationId: upsertPost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + "400": + description: Invalid request + "403": + description: Forbidden + requestBody: + description: Upsert a Post + content: + application/json: + schema: + $ref: "#/components/schemas/PostUpsertArgs" + /post/delete: + delete: + operationId: deletePost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/PostDeleteUniqueArgs" + /post/deleteMany: + delete: + operationId: deleteManyPost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/BatchPayload" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/PostDeleteManyArgs" + /post/count: + get: + operationId: countPost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + oneOf: + - type: integer + - $ref: "#/components/schemas/PostCountAggregateOutputType" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/PostCountArgs" + /post/aggregate: + get: + operationId: aggregatePost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/AggregatePost" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/PostAggregateArgs" + /post/groupBy: + get: + operationId: groupByPost + responses: + "200": + description: Successful operation + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/PostGroupByOutputType" + "400": + description: Invalid request + "403": + description: Forbidden + parameters: + - name: q + in: query + required: true + schema: + $ref: "#/components/schemas/PostGroupByArgs" diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json new file mode 100644 index 000000000..c7e608295 --- /dev/null +++ b/packages/plugins/openapi/package.json @@ -0,0 +1,52 @@ +{ + "name": "@zenstackhq/openapi", + "displayName": "ZenStack Plugin and Runtime for OpenAPI", + "version": "1.0.0-alpha.72", + "description": "ZenStack plugin and runtime supporting OpenAPI", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/zenstackhq/zenstack" + }, + "publishConfig": { + "directory": "dist", + "linkDirectory": true + }, + "scripts": { + "clean": "rimraf dist", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist && copyfiles -u 1 ./src/plugin.zmodel dist", + "watch": "tsc --watch", + "lint": "eslint src --ext ts", + "test": "jest", + "prepublishOnly": "pnpm build" + }, + "keywords": [], + "author": "ZenStack Team", + "license": "MIT", + "dependencies": { + "@prisma/generator-helper": "^4.7.1", + "@zenstackhq/runtime": "workspace:*", + "@zenstackhq/sdk": "workspace:*", + "change-case": "^4.1.2", + "openapi-types": "^12.1.0", + "tiny-invariant": "^1.3.1", + "yaml": "^2.2.1" + }, + "devDependencies": { + "@prisma/internals": "^4.7.1", + "@readme/openapi-parser": "^2.4.0", + "@types/jest": "^29.4.0", + "@types/tmp": "^0.2.3", + "@typescript-eslint/eslint-plugin": "^5.54.0", + "@typescript-eslint/parser": "^5.54.0", + "@zenstackhq/testtools": "workspace:*", + "copyfiles": "^2.4.1", + "eslint": "^8.35.0", + "jest": "^29.4.3", + "rimraf": "^3.0.2", + "tmp": "^0.2.1", + "ts-jest": "^29.0.5", + "typescript": "^4.9.5", + "zenstack": "workspace:*" + } +} diff --git a/packages/plugins/openapi/src/generator.ts b/packages/plugins/openapi/src/generator.ts new file mode 100644 index 000000000..edbeacec8 --- /dev/null +++ b/packages/plugins/openapi/src/generator.ts @@ -0,0 +1,736 @@ +// Inspired by: https://github.com/omar-dulaimi/prisma-trpc-generator + +import { DMMF } from '@prisma/generator-helper'; +import { AUXILIARY_FIELDS, hasAttribute, PluginError, PluginOptions } from '@zenstackhq/sdk'; +import { DataModel, isDataModel, type Model } from '@zenstackhq/sdk/ast'; +import { + addMissingInputObjectTypesForAggregate, + addMissingInputObjectTypesForInclude, + addMissingInputObjectTypesForModelArgs, + addMissingInputObjectTypesForSelect, + AggregateOperationSupport, + resolveAggregateOperationSupport, +} from '@zenstackhq/sdk/dmmf-helpers'; +import { camelCase } from 'change-case'; +import * as fs from 'fs'; +import type { OpenAPIV3_1 as OAPI } from 'openapi-types'; +import * as path from 'path'; +import invariant from 'tiny-invariant'; +import YAML from 'yaml'; +import { getModelResourceMeta } from './meta'; + +/** + * Generates OpenAPI specification. + */ +export class OpenAPIGenerator { + private inputObjectTypes: DMMF.InputType[] = []; + private outputObjectTypes: DMMF.OutputType[] = []; + private usedComponents: Set = new Set(); + private aggregateOperationSupport: AggregateOperationSupport; + + constructor(private model: Model, private options: PluginOptions, private dmmf: DMMF.Document) {} + + generate() { + const output = this.getOption('output', ''); + if (!output) { + throw new PluginError('"output" option is required'); + } + + // input types + this.inputObjectTypes.push(...this.dmmf.schema.inputObjectTypes.prisma); + this.outputObjectTypes.push(...this.dmmf.schema.outputObjectTypes.prisma); + + // add input object types that are missing from Prisma dmmf + addMissingInputObjectTypesForModelArgs(this.inputObjectTypes, this.dmmf.datamodel.models); + addMissingInputObjectTypesForInclude(this.inputObjectTypes, this.dmmf.datamodel.models); + addMissingInputObjectTypesForSelect(this.inputObjectTypes, this.outputObjectTypes, this.dmmf.datamodel.models); + addMissingInputObjectTypesForAggregate(this.inputObjectTypes, this.outputObjectTypes); + + this.aggregateOperationSupport = resolveAggregateOperationSupport(this.inputObjectTypes); + + const components = this.generateComponents(); + const paths = this.generatePaths(components); + + // prune unused component schemas + this.pruneComponents(components); + + const openapi: OAPI.Document = { + openapi: '3.0.0', + info: { + title: this.getOption('title', 'ZenStack Generated API'), + version: this.getOption('version', '1.0.0'), + }, + components, + paths, + }; + + const ext = path.extname(output); + if (ext && (ext.toLowerCase() === '.yaml' || ext.toLowerCase() === '.yml')) { + fs.writeFileSync(output, YAML.stringify(openapi)); + } else { + fs.writeFileSync(output, JSON.stringify(openapi, undefined, 2)); + } + } + + private pruneComponents(components: OAPI.ComponentsObject) { + const schemas = components.schemas; + if (schemas) { + // build a transitive closure for all reachable schemas from roots + const allUsed = new Set(this.usedComponents); + + let todo = [...allUsed]; + while (todo.length > 0) { + const curr = new Set(allUsed); + Object.entries(schemas) + .filter(([key]) => todo.includes(key)) + .forEach(([, value]) => { + this.collectUsedComponents(value, allUsed); + }); + todo = [...allUsed].filter((e) => !curr.has(e)); + } + + // prune unused schemas + Object.keys(schemas).forEach((key) => { + if (!allUsed.has(key)) { + delete schemas[key]; + } + }); + } + } + + private collectUsedComponents(value: unknown, allUsed: Set) { + if (!value) { + return; + } + + if (Array.isArray(value)) { + value.forEach((item) => { + this.collectUsedComponents(item, allUsed); + }); + } else if (typeof value === 'object') { + Object.entries(value).forEach(([subKey, subValue]) => { + if (subKey === '$ref') { + const ref = subValue as string; + const name = ref.split('/').pop(); + if (name && !allUsed.has(name)) { + allUsed.add(name); + } + } else { + this.collectUsedComponents(subValue, allUsed); + } + }); + } + } + + private generatePaths(components: OAPI.ComponentsObject): OAPI.PathsObject { + let result: OAPI.PathsObject = {}; + + const includeModels = this.model.declarations + .filter((d) => isDataModel(d) && !hasAttribute(d, '@@openapi.ignore')) + .map((d) => d.name); + + for (const model of this.dmmf.datamodel.models) { + if (includeModels.includes(model.name)) { + const zmodel = this.model.declarations.find( + (d) => isDataModel(d) && d.name === model.name + ) as DataModel; + if (zmodel) { + result = { + ...result, + ...this.generatePathsForModel(model, zmodel, components), + } as OAPI.PathsObject; + } else { + console.warn(`Unable to load ZModel definition for: ${model.name}}`); + } + } + } + return result; + } + + private generatePathsForModel( + model: DMMF.Model, + zmodel: DataModel, + components: OAPI.ComponentsObject + ): OAPI.PathItemObject { + const result: OAPI.PathItemObject & Record = {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const ops: (DMMF.ModelMapping & { createOne?: string | null } & Record) | undefined = + this.dmmf.mappings.modelOperations.find((ops) => ops.model === model.name); + if (!ops) { + throw new PluginError(`No operations found for model ${model.name}`); + } + + type OperationDefinition = { + method: 'get' | 'post' | 'put' | 'patch' | 'delete'; + operation: string; + description: string; + inputType?: object; + outputType: object; + successCode?: number; + }; + + const definitions: OperationDefinition[] = []; + + if (ops['createOne']) { + definitions.push({ + method: 'post', + operation: 'create', + inputType: this.component( + `${model.name}CreateArgs`, + { + type: 'object', + properties: { + select: this.ref(`${model.name}Select`), + include: this.ref(`${model.name}Include`), + data: this.ref(`${model.name}CreateInput`), + }, + }, + components + ), + outputType: this.ref(model.name), + description: `Create a new ${model.name}`, + successCode: 201, + }); + } + + if (ops['createMany']) { + definitions.push({ + method: 'post', + operation: 'createMany', + inputType: this.component( + `${model.name}CreateManyArgs`, + { + type: 'object', + properties: { + data: this.ref(`${model.name}CreateManyInput`), + }, + }, + components + ), + outputType: this.ref('BatchPayload'), + description: `Create several ${model.name}`, + successCode: 201, + }); + } + + if (ops['findUnique']) { + definitions.push({ + method: 'get', + operation: 'findUnique', + inputType: this.component( + `${model.name}FindUniqueArgs`, + { + type: 'object', + properties: { + select: this.ref(`${model.name}Select`), + include: this.ref(`${model.name}Include`), + where: this.ref(`${model.name}WhereUniqueInput`), + }, + }, + components + ), + outputType: this.ref(model.name), + description: `Find one unique ${model.name}`, + }); + } + + if (ops['findFirst']) { + definitions.push({ + method: 'get', + operation: 'findFirst', + inputType: this.component( + `${model.name}FindFirstArgs`, + { + type: 'object', + properties: { + select: this.ref(`${model.name}Select`), + include: this.ref(`${model.name}Include`), + where: this.ref(`${model.name}WhereInput`), + }, + }, + components + ), + outputType: this.ref(model.name), + description: `Find the first ${model.name} matching the given condition`, + }); + } + + if (ops['findMany']) { + definitions.push({ + method: 'get', + operation: 'findMany', + inputType: this.component( + `${model.name}FindManyArgs`, + { + type: 'object', + properties: { + select: this.ref(`${model.name}Select`), + include: this.ref(`${model.name}Include`), + where: this.ref(`${model.name}WhereInput`), + }, + }, + components + ), + outputType: this.array(this.ref(model.name)), + description: `Find a list of ${model.name}`, + }); + } + + if (ops['updateOne']) { + definitions.push({ + method: 'patch', + operation: 'update', + inputType: this.component( + `${model.name}UpdateArgs`, + { + type: 'object', + properties: { + select: this.ref(`${model.name}Select`), + include: this.ref(`${model.name}Include`), + where: this.ref(`${model.name}WhereUniqueInput`), + data: this.ref(`${model.name}UpdateInput`), + }, + }, + components + ), + outputType: this.ref(model.name), + description: `Update a ${model.name}`, + }); + } + + if (ops['updateMany']) { + definitions.push({ + operation: 'updateMany', + method: 'patch', + inputType: this.component( + `${model.name}UpdateManyArgs`, + { + type: 'object', + properties: { + where: this.ref(`${model.name}WhereInput`), + data: this.ref(`${model.name}UpdateManyMutationInput`), + }, + }, + components + ), + outputType: this.ref('BatchPayload'), + description: `Update ${model.name}s matching the given condition`, + }); + } + + if (ops['upsertOne']) { + definitions.push({ + method: 'post', + operation: 'upsert', + inputType: this.component( + `${model.name}UpsertArgs`, + { + type: 'object', + properties: { + select: this.ref(`${model.name}Select`), + include: this.ref(`${model.name}Include`), + where: this.ref(`${model.name}WhereUniqueInput`), + create: this.ref(`${model.name}CreateInput`), + update: this.ref(`${model.name}UpdateInput`), + }, + }, + components + ), + outputType: this.ref(model.name), + description: `Upsert a ${model.name}`, + }); + } + + if (ops['deleteOne']) { + definitions.push({ + method: 'delete', + operation: 'delete', + inputType: this.component( + `${model.name}DeleteUniqueArgs`, + { + type: 'object', + properties: { + select: this.ref(`${model.name}Select`), + include: this.ref(`${model.name}Include`), + where: this.ref(`${model.name}WhereUniqueInput`), + }, + }, + components + ), + outputType: this.ref(model.name), + description: `Delete one unique ${model.name}`, + }); + } + + if (ops['deleteMany']) { + definitions.push({ + method: 'delete', + operation: 'deleteMany', + inputType: this.component( + `${model.name}DeleteManyArgs`, + { + type: 'object', + properties: { + where: this.ref(`${model.name}WhereInput`), + }, + }, + components + ), + outputType: this.ref('BatchPayload'), + description: `Delete ${model.name}s matching the given condition`, + }); + } + + // somehow dmmf doesn't contain "count" operation, so we unconditionally add it here + definitions.push({ + method: 'get', + operation: 'count', + inputType: this.component( + `${model.name}CountArgs`, + { + type: 'object', + properties: { + select: this.ref(`${model.name}Select`), + where: this.ref(`${model.name}WhereInput`), + }, + }, + components + ), + outputType: this.oneOf({ type: 'integer' }, this.ref(`${model.name}CountAggregateOutputType`)), + description: `Find a list of ${model.name}`, + }); + + if (ops['aggregate']) { + definitions.push({ + method: 'get', + operation: 'aggregate', + inputType: this.component( + `${model.name}AggregateArgs`, + { + type: 'object', + properties: { + where: this.ref(`${model.name}WhereInput`), + orderBy: this.ref(`${model.name}OrderByWithRelationInput`), + cursor: this.ref(`${model.name}WhereUniqueInput`), + take: { type: 'integer' }, + skip: { type: 'integer' }, + ...this.aggregateFields(model), + }, + }, + components + ), + outputType: this.ref(`Aggregate${model.name}`), + description: `Aggregate ${model.name}s`, + }); + } + + if (ops['groupBy']) { + definitions.push({ + method: 'get', + operation: 'groupBy', + inputType: this.component( + `${model.name}GroupByArgs`, + { + type: 'object', + properties: { + where: this.ref(`${model.name}WhereInput`), + orderBy: this.ref(`${model.name}OrderByWithRelationInput`), + by: this.ref(`${model.name}ScalarFieldEnum`), + having: this.ref(`${model.name}ScalarWhereWithAggregatesInput`), + take: { type: 'integer' }, + skip: { type: 'integer' }, + ...this.aggregateFields(model), + }, + }, + components + ), + outputType: this.array(this.ref(`${model.name}GroupByOutputType`)), + description: `Group ${model.name}s by fields`, + }); + } + + // get meta specified with @@openapi.meta + const resourceMeta = getModelResourceMeta(zmodel); + + for (const { method, operation, description, inputType, outputType, successCode } of definitions) { + const meta = resourceMeta?.[operation]; + + if (meta?.ignore === true) { + continue; + } + + const resolvedMethod = meta?.method ?? method; + let resolvedPath = meta?.path ?? operation; + if (resolvedPath.startsWith('/')) { + resolvedPath = resolvedPath.substring(1); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const def: any = { + operationId: `${operation}${model.name}`, + description: meta?.description ?? description, + tags: meta?.tags, + summary: meta?.summary, + responses: { + [successCode !== undefined ? successCode : '200']: { + description: 'Successful operation', + content: { + 'application/json': { + schema: outputType, + }, + }, + }, + '400': { + description: 'Invalid request', + }, + '403': { + description: 'Forbidden', + }, + }, + }; + + if (inputType) { + if (['post', 'put', 'patch'].includes(resolvedMethod)) { + def.requestBody = { + content: { + 'application/json': { + schema: inputType, + }, + }, + }; + } else { + def.parameters = [ + { + name: 'q', + in: 'query', + required: true, + schema: inputType, + }, + ] satisfies OAPI.ParameterObject[]; + } + } + + result[`/${camelCase(model.name)}/${resolvedPath}`] = { + [resolvedMethod]: def, + }; + } + return result; + } + + private aggregateFields(model: DMMF.Model) { + const result: Record = {}; + const supportedOps = this.aggregateOperationSupport[model.name]; + if (supportedOps) { + if (supportedOps.count) { + result._count = this.oneOf({ type: 'boolean' }, this.ref(`${model.name}CountAggregateInput`)); + } + if (supportedOps.min) { + result._min = this.ref(`${model.name}MinAggregateInput`); + } + if (supportedOps.max) { + result._max = this.ref(`${model.name}MaxAggregateInput`); + } + if (supportedOps.sum) { + result._sum = this.ref(`${model.name}SumAggregateInput`); + } + if (supportedOps.avg) { + result._avg = this.ref(`${model.name}AvgAggregateInput`); + } + } + return result; + } + + private component(name: string, def: object, components: OAPI.ComponentsObject): object { + invariant(components.schemas); + components.schemas[name] = def; + return this.ref(name); + } + + private getOption(name: string, defaultValue: string) { + return this.options[name] ? (this.options[name] as string) : defaultValue; + } + + private generateComponents() { + const schemas: Record = {}; + const components: OAPI.ComponentsObject = { + schemas, + }; + + // user-defined and built-in enums + for (const _enum of [...(this.dmmf.schema.enumTypes.model ?? []), ...this.dmmf.schema.enumTypes.prisma]) { + schemas[_enum.name] = this.generateEnumComponent(_enum); + } + + // data models + for (const model of this.dmmf.datamodel.models) { + schemas[model.name] = this.generateEntityComponent(model); + } + + for (const input of this.inputObjectTypes) { + schemas[input.name] = this.generateInputComponent(input); + } + + for (const output of this.outputObjectTypes.filter((t) => !['Query', 'Mutation'].includes(t.name))) { + schemas[output.name] = this.generateOutputComponent(output); + } + + // misc types + schemas['BatchPayload'] = { + type: 'object', + properties: { + count: { type: 'integer' }, + }, + }; + + return components; + } + + private generateEnumComponent(_enum: DMMF.SchemaEnum): OAPI.SchemaObject { + const schema: OAPI.SchemaObject = { + type: 'string', + enum: _enum.values.filter((f) => !AUXILIARY_FIELDS.includes(f)), + }; + return schema; + } + + private generateEntityComponent(model: DMMF.Model): OAPI.SchemaObject { + const properties: Record = {}; + + const fields = model.fields.filter((f) => !AUXILIARY_FIELDS.includes(f.name)); + const required: string[] = []; + for (const field of fields) { + properties[field.name] = this.generateField(field); + if (field.isRequired && !(field.relationName && field.isList)) { + required.push(field.name); + } + } + + const result: OAPI.SchemaObject = { type: 'object', properties }; + if (required.length > 0) { + result.required = required; + } + return result; + } + + private generateField(def: { kind: DMMF.FieldKind; type: string; isList: boolean }) { + switch (def.kind) { + case 'scalar': + return this.wrapArray(this.prismaTypeToOpenAPIType(def.type), def.isList); + + case 'enum': + case 'object': + return this.wrapArray(this.ref(def.type, false), def.isList); + + default: + throw new PluginError(`Unsupported field kind: ${def.kind}`); + } + } + + private generateInputComponent(input: DMMF.InputType): OAPI.SchemaObject { + const properties: Record = {}; + const fields = input.fields.filter((f) => !AUXILIARY_FIELDS.includes(f.name)); + for (const field of fields) { + const options = field.inputTypes + .filter((f) => f.type !== 'Null') + .map((f) => { + return this.wrapArray(this.prismaTypeToOpenAPIType(f.type), f.isList); + }); + properties[field.name] = options.length > 1 ? { oneOf: options } : options[0]; + } + + const result: OAPI.SchemaObject = { type: 'object', properties }; + this.setInputRequired(fields, result); + return result; + } + + private generateOutputComponent(output: DMMF.OutputType): OAPI.SchemaObject { + const properties: Record = {}; + const fields = output.fields.filter((f) => !AUXILIARY_FIELDS.includes(f.name)); + for (const field of fields) { + let outputType: OAPI.ReferenceObject | OAPI.SchemaObject; + switch (field.outputType.location) { + case 'scalar': + case 'enumTypes': + outputType = this.prismaTypeToOpenAPIType(field.outputType.type); + break; + case 'outputObjectTypes': + outputType = this.prismaTypeToOpenAPIType( + typeof field.outputType.type === 'string' ? field.outputType.type : field.outputType.type.name + ); + break; + } + field.outputType; + properties[field.name] = this.wrapArray(outputType, field.outputType.isList); + } + + const result: OAPI.SchemaObject = { type: 'object', properties }; + this.setOutputRequired(fields, result); + return result; + } + + private setInputRequired(fields: { name: string; isRequired: boolean }[], result: OAPI.NonArraySchemaObject) { + const required = fields.filter((f) => f.isRequired).map((f) => f.name); + if (required.length > 0) { + result.required = required; + } + } + + private setOutputRequired( + fields: { name: string; isNullable?: boolean; outputType: DMMF.OutputTypeRef }[], + result: OAPI.NonArraySchemaObject + ) { + const required = fields.filter((f) => f.isNullable !== true).map((f) => f.name); + if (required.length > 0) { + result.required = required; + } + } + + private prismaTypeToOpenAPIType(type: DMMF.ArgType): OAPI.ReferenceObject | OAPI.SchemaObject { + switch (type) { + case 'String': + return { type: 'string' }; + case 'Int': + case 'BigInt': + return { type: 'integer' }; + case 'Float': + case 'Decimal': + return { type: 'number' }; + case 'Boolean': + case 'True': + return { type: 'boolean' }; + case 'DateTime': + return { type: 'string', format: 'date-time' }; + case 'JSON': + case 'Json': + return {}; + default: + return this.ref(type.toString(), false); + } + } + + private wrapArray( + schema: OAPI.ReferenceObject | OAPI.SchemaObject, + isArray: boolean + ): OAPI.ReferenceObject | OAPI.SchemaObject { + if (isArray) { + return { type: 'array', items: schema }; + } else { + return schema; + } + } + + private ref(type: string, rooted = true) { + if (rooted) { + this.usedComponents.add(type); + } + return { $ref: `#/components/schemas/${type}` }; + } + + private array(itemType: unknown) { + return { type: 'array', items: itemType }; + } + + private oneOf(...schemas: unknown[]) { + return { oneOf: schemas }; + } +} diff --git a/packages/plugins/openapi/src/index.ts b/packages/plugins/openapi/src/index.ts new file mode 100644 index 000000000..ec404c308 --- /dev/null +++ b/packages/plugins/openapi/src/index.ts @@ -0,0 +1,10 @@ +import { DMMF } from '@prisma/generator-helper'; +import { PluginOptions } from '@zenstackhq/sdk'; +import { Model } from '@zenstackhq/sdk/ast'; +import { OpenAPIGenerator } from './generator'; + +export const name = 'OpenAPI'; + +export default async function run(model: Model, options: PluginOptions, dmmf: DMMF.Document) { + return new OpenAPIGenerator(model, options, dmmf).generate(); +} diff --git a/packages/plugins/openapi/src/meta.ts b/packages/plugins/openapi/src/meta.ts new file mode 100644 index 000000000..2b7d1e450 --- /dev/null +++ b/packages/plugins/openapi/src/meta.ts @@ -0,0 +1,25 @@ +import { getObjectLiteral } from '@zenstackhq/sdk'; +import { DataModel } from '@zenstackhq/sdk/ast'; + +/** + * Metadata for a resource operation, expressed by @@openapi.meta attribute. + */ +export type OperationMeta = { + ignore: boolean; + method: string; + path: string; + summary?: string; + description?: string; + tags?: string[]; +}; + +/** + * Metadata for a resource, expressed by @@openapi.meta attribute. + */ +export type ResourceMeta = Record; + +export function getModelResourceMeta(model: DataModel) { + return getObjectLiteral( + model.attributes.find((attr) => attr.decl.ref?.name === '@@openapi.meta')?.args[0].value + ); +} diff --git a/packages/plugins/openapi/src/plugin.zmodel b/packages/plugins/openapi/src/plugin.zmodel new file mode 100644 index 000000000..37d909033 --- /dev/null +++ b/packages/plugins/openapi/src/plugin.zmodel @@ -0,0 +1,9 @@ +/* + * Mark a data model to be ignored when generating OpenAPI specification. + */ +attribute @@openapi.ignore() + +/* + * Provide metadata for a data model for generating OpenAPI specification. + */ +attribute @@openapi.meta(_ meta: Object) diff --git a/packages/plugins/openapi/tests/openapi.test.ts b/packages/plugins/openapi/tests/openapi.test.ts new file mode 100644 index 000000000..243fd0109 --- /dev/null +++ b/packages/plugins/openapi/tests/openapi.test.ts @@ -0,0 +1,106 @@ +/// + +import { getDMMF } from '@prisma/internals'; +import OpenAPIParser from '@readme/openapi-parser'; +import * as fs from 'fs'; +import * as tmp from 'tmp'; +import { loadDocument } from 'zenstack/cli/cli-util'; +import prismaPlugin from 'zenstack/plugins/prisma'; +import generate from '../src'; + +async function loadZModelAndDmmf(content: string) { + const prelude = ` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } +`; + + const { name: modelFile } = tmp.fileSync({ postfix: '.zmodel' }); + fs.writeFileSync(modelFile, `${prelude}\n${content}`); + + const model = await loadDocument(modelFile); + + const { name: prismaFile } = tmp.fileSync({ postfix: '.prisma' }); + await prismaPlugin(model, { schemaPath: modelFile, output: prismaFile, generateClient: false }); + + const prismaContent = fs.readFileSync(prismaFile, { encoding: 'utf-8' }); + + const dmmf = await getDMMF({ datamodel: prismaContent }); + return { model, dmmf, modelFile }; +} + +describe('Open API Plugin Tests', () => { + it('run plugin', async () => { + const { model, dmmf, modelFile } = await loadZModelAndDmmf(` + plugin openapi { + provider = '${process.cwd()}/dist' + } + + enum Role { + USER + ADMIN + } + + model User { + id String @id + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + email String @unique + role Role @default(USER) + posts Post[] + + @@openapi.meta({ + findMany: { + description: 'Find users matching the given conditions' + }, + delete: { + method: 'put', + path: 'dodelete', + description: 'Delete a unique user', + summary: 'Delete a user yeah yeah', + tags: ['delete', 'user'] + }, + }) + } + + model Post { + id String @id + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + title String + author User? @relation(fields: [authorId], references: [id]) + authorId String? + published Boolean @default(false) + viewCount Int @default(0) + + @@openapi.meta({ + findMany: { + ignore: true + } + }) + } + + model Foo { + id String @id + + @@openapi.ignore + } + `); + + const { name: output } = tmp.fileSync({ postfix: '.yaml' }); + generate(model, { schemaPath: modelFile, output }, dmmf); + + console.log('OpenAPI specification generated:', output); + + const api = await OpenAPIParser.validate(output); + expect(api.paths?.['/user/findMany']?.['get']?.description).toBe('Find users matching the given conditions'); + const del = api.paths?.['/user/dodelete']?.['put']; + expect(del?.description).toBe('Delete a unique user'); + expect(del?.summary).toBe('Delete a user yeah yeah'); + expect(del?.tags).toEqual(expect.arrayContaining(['delete', 'user'])); + expect(api.paths?.['/post/findMany']).toBeUndefined(); + + expect(api.paths?.['/foo/findMany']).toBeUndefined(); + }); +}); diff --git a/packages/plugins/openapi/tsconfig.json b/packages/plugins/openapi/tsconfig.json new file mode 100644 index 000000000..6eb0cff1b --- /dev/null +++ b/packages/plugins/openapi/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "CommonJS", + "lib": ["ESNext"], + "sourceMap": true, + "outDir": "dist", + "strict": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "resolveJsonModule": true, + "strictPropertyInitialization": false + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/plugins/react/package.json b/packages/plugins/react/package.json index 8f959a7f5..fda63ff18 100644 --- a/packages/plugins/react/package.json +++ b/packages/plugins/react/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/react", "displayName": "ZenStack plugin and runtime for ReactJS", - "version": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "description": "ZenStack plugin and runtime for ReactJS", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index f0d7015e2..f8d7c1601 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": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/src/zod/helpers/helpers.ts b/packages/plugins/trpc/src/zod/helpers/helpers.ts index 92224754d..82895f226 100644 --- a/packages/plugins/trpc/src/zod/helpers/helpers.ts +++ b/packages/plugins/trpc/src/zod/helpers/helpers.ts @@ -1,10 +1,12 @@ import { DMMF, ConnectorType, Dictionary } from '@prisma/generator-helper'; import Transformer from '../transformer'; import { addMissingInputObjectTypesForMongoDbRawOpsAndQueries } from './mongodb-helpers'; -import { addMissingInputObjectTypesForAggregate } from './aggregate-helpers'; -import { addMissingInputObjectTypesForSelect } from './select-helpers'; -import { addMissingInputObjectTypesForInclude } from './include-helpers'; -import { addMissingInputObjectTypesForModelArgs } from './modelArgs-helpers'; +import { + addMissingInputObjectTypesForAggregate, + addMissingInputObjectTypesForSelect, + addMissingInputObjectTypesForInclude, + addMissingInputObjectTypesForModelArgs, +} from '@zenstackhq/sdk/dmmf-helpers'; interface AddMissingInputObjectTypeOptions { isGenerateSelect: boolean; @@ -29,15 +31,10 @@ export function addMissingInputObjectTypes( Transformer.setIsGenerateSelect(true); } if (options.isGenerateSelect || options.isGenerateInclude) { - addMissingInputObjectTypesForModelArgs( - inputObjectTypes, - models, - options.isGenerateSelect, - options.isGenerateInclude - ); + addMissingInputObjectTypesForModelArgs(inputObjectTypes, models); } if (options.isGenerateInclude) { - addMissingInputObjectTypesForInclude(inputObjectTypes, models, options.isGenerateSelect); + addMissingInputObjectTypesForInclude(inputObjectTypes, models); Transformer.setIsGenerateInclude(true); } } diff --git a/packages/plugins/trpc/src/zod/transformer.ts b/packages/plugins/trpc/src/zod/transformer.ts index de896054a..e1601f2c1 100644 --- a/packages/plugins/trpc/src/zod/transformer.ts +++ b/packages/plugins/trpc/src/zod/transformer.ts @@ -60,12 +60,13 @@ export default class Transformer { async generateEnumSchemas() { for (const enumType of this.enumTypes) { const { name, values } = enumType; + const filteredValues = values.filter((v) => !AUXILIARY_FIELDS.includes(v)); await writeFileSafely( path.join(Transformer.outputPath, `schemas/enums/${name}.schema.ts`), `/* eslint-disable */\n${this.generateImportZodStatement()}\n${this.generateExportSchemaStatement( `${name}`, - `z.enum(${JSON.stringify(values)})` + `z.enum(${JSON.stringify(filteredValues)})` )}` ); } @@ -257,7 +258,9 @@ export default class Transformer { name = `${name}Type`; } const end = `export const ${exportName}ObjectSchema = Schema`; - return `const Schema: z.ZodType = ${schema};\n\n ${end}`; + return `const Schema: z.ZodType "'" + f + "'").join( + '|' + )}>> = ${schema};\n\n ${end}`; } addFinalWrappers({ zodStringFields }: { zodStringFields: string[] }) { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 388d235c1..832754774 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/runtime/src/version.ts b/packages/runtime/src/version.ts index 200cae236..9bf48c342 100644 --- a/packages/runtime/src/version.ts +++ b/packages/runtime/src/version.ts @@ -3,6 +3,10 @@ export function getVersion() { try { return require('./package.json').version; } catch { - return require('../package.json').version; + try { + return require('../package.json').version; + } catch { + return 'unknown'; + } } } diff --git a/packages/schema/package.json b/packages/schema/package.json index 0490147ab..84e932d5d 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "A toolkit for building secure CRUD apps with Next.js + Typescript", - "version": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "author": { "name": "ZenStack Team" }, @@ -134,6 +134,7 @@ "ts-node": "^10.9.1", "tsc-alias": "^1.7.0", "typescript": "^4.8.4", - "vsce": "^2.13.0" + "vsce": "^2.13.0", + "@zenstackhq/testtools": "workspace:*" } } diff --git a/packages/schema/src/cli/cli-util.ts b/packages/schema/src/cli/cli-util.ts index 25ee919a3..8c6d44886 100644 --- a/packages/schema/src/cli/cli-util.ts +++ b/packages/schema/src/cli/cli-util.ts @@ -1,13 +1,13 @@ -import { Model } from '@zenstackhq/language/ast'; -import { PluginError } from '@zenstackhq/sdk'; +import { isPlugin, Model } from '@zenstackhq/language/ast'; +import { getLiteral, PluginError } from '@zenstackhq/sdk'; import colors from 'colors'; import fs from 'fs'; -import { LangiumServices } from 'langium'; +import { LangiumDocument } from 'langium'; import { NodeFileSystem } from 'langium/node'; import path from 'path'; import { URI } from 'vscode-uri'; -import { STD_LIB_MODULE_NAME } from '../language-server/constants'; -import { createZModelServices } from '../language-server/zmodel-module'; +import { PLUGIN_MODULE_NAME, STD_LIB_MODULE_NAME } from '../language-server/constants'; +import { createZModelServices, ZModelServices } from '../language-server/zmodel-module'; import { Context } from '../types'; import { ensurePackage, installPackage, PackageManagers } from '../utils/pkg-utils'; import { CliError } from './cli-error'; @@ -80,7 +80,8 @@ Moving forward please edit this file and run "zenstack generate" to regenerate P * @param services Language services * @returns Parsed and validated AST */ -export async function loadDocument(fileName: string, services: LangiumServices): Promise { +export async function loadDocument(fileName: string): Promise { + const services = createZModelServices(NodeFileSystem).ZModel; const extensions = services.LanguageMetaData.fileExtensions; if (!extensions.includes(path.extname(fileName))) { console.error(colors.yellow(`Please choose a file with extension: ${extensions}.`)); @@ -97,11 +98,14 @@ export async function loadDocument(fileName: string, services: LangiumServices): URI.file(path.resolve(path.join(__dirname, '../res', STD_LIB_MODULE_NAME))) ); + // load documents provided by plugins + const pluginDocuments = await getPluginDocuments(services, fileName); + // load the document const document = services.shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName))); - // build the document together with standard library - await services.shared.workspace.DocumentBuilder.build([stdLib, document], { + // build the document together with standard library and plugin modules + await services.shared.workspace.DocumentBuilder.build([stdLib, ...pluginDocuments, document], { validationChecks: 'all', }); @@ -123,9 +127,41 @@ export async function loadDocument(fileName: string, services: LangiumServices): return document.parseResult.value as Model; } +export async function getPluginDocuments(services: ZModelServices, fileName: string): Promise { + // parse the user document (without validation) + const parseResult = services.parser.LangiumParser.parse(fs.readFileSync(fileName, { encoding: 'utf-8' })); + const parsed = parseResult.value as Model; + + // traverse plugins and collect "plugin.zmodel" documents + const result: LangiumDocument[] = []; + parsed.declarations.forEach((decl) => { + if (isPlugin(decl)) { + const providerField = decl.fields.find((f) => f.name === 'provider'); + if (providerField) { + const provider = getLiteral(providerField.value); + if (provider) { + try { + const pluginEntrance = require.resolve(`${provider}`); + const pluginModelFile = path.join(path.dirname(pluginEntrance), PLUGIN_MODULE_NAME); + if (fs.existsSync(pluginModelFile)) { + result.push( + services.shared.workspace.LangiumDocuments.getOrCreateDocument( + URI.file(pluginModelFile) + ) + ); + } + } catch { + console.warn(`Unable to load plugin from ${provider}, skipping`); + } + } + } + } + }); + return result; +} + export async function runPlugins(options: { schema: string; packageManager: PackageManagers | undefined }) { - const services = createZModelServices(NodeFileSystem).ZModel; - const model = await loadDocument(options.schema, services); + const model = await loadDocument(options.schema); const context: Context = { schema: model, diff --git a/packages/schema/src/language-server/constants.ts b/packages/schema/src/language-server/constants.ts index e2bd60339..448a2fae1 100644 --- a/packages/schema/src/language-server/constants.ts +++ b/packages/schema/src/language-server/constants.ts @@ -13,6 +13,11 @@ export const SCALAR_TYPES = ['String', 'Int', 'Float', 'Decimal', 'BigInt', 'Boo */ export const STD_LIB_MODULE_NAME = 'stdlib.zmodel'; +/** + * Name of module contributed by plugins + */ +export const PLUGIN_MODULE_NAME = 'plugin.zmodel'; + export enum IssueCodes { MissingOppositeRelation = 'miss-opposite-relation', } diff --git a/packages/schema/src/language-server/validator/schema-validator.ts b/packages/schema/src/language-server/validator/schema-validator.ts index e02ed68e3..3d3dc5836 100644 --- a/packages/schema/src/language-server/validator/schema-validator.ts +++ b/packages/schema/src/language-server/validator/schema-validator.ts @@ -1,4 +1,4 @@ -import { STD_LIB_MODULE_NAME } from '../constants'; +import { PLUGIN_MODULE_NAME, STD_LIB_MODULE_NAME } from '../constants'; import { isDataSource, Model } from '@zenstackhq/language/ast'; import { AstValidator } from '../types'; import { ValidationAcceptor } from 'langium'; @@ -11,7 +11,10 @@ export default class SchemaValidator implements AstValidator { validate(model: Model, accept: ValidationAcceptor): void { validateDuplicatedDeclarations(model.declarations, accept); - if (!model.$document?.uri.path.endsWith(STD_LIB_MODULE_NAME)) { + if ( + !model.$document?.uri.path.endsWith(STD_LIB_MODULE_NAME) && + !model.$document?.uri.path.endsWith(PLUGIN_MODULE_NAME) + ) { this.validateDataSources(model, accept); } } diff --git a/packages/schema/src/language-server/validator/utils.ts b/packages/schema/src/language-server/validator/utils.ts index 8fb507e85..163bef002 100644 --- a/packages/schema/src/language-server/validator/utils.ts +++ b/packages/schema/src/language-server/validator/utils.ts @@ -83,7 +83,7 @@ export function typeAssignable(destType: ExpressionType, sourceType: ExpressionT /** * Maps a ZModel builtin type to expression type */ -export function mapBuiltinTypeToExpressionType(type: BuiltinType | 'Any' | 'Null'): ExpressionType | 'Any' { +export function mapBuiltinTypeToExpressionType(type: BuiltinType | 'Any' | 'Object' | 'Null'): ExpressionType | 'Any' { switch (type) { case 'Any': case 'Boolean': @@ -100,6 +100,8 @@ export function mapBuiltinTypeToExpressionType(type: BuiltinType | 'Any' | 'Null case 'Json': case 'Bytes': return 'Any'; + case 'Object': + return 'Object'; } } diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index 0265892e9..cbeffab2b 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -12,18 +12,19 @@ import { FunctionParam, FunctionParamType, InvocationExpr, + isArrayExpr, + isDataModel, + isDataModelField, + isReferenceExpr, LiteralExpr, MemberAccessExpr, NullExpr, + ObjectExpr, ReferenceExpr, ReferenceTarget, ResolvedShape, ThisExpr, UnaryExpr, - isArrayExpr, - isDataModel, - isDataModelField, - isReferenceExpr, } from '@zenstackhq/language/ast'; import { AstNode, @@ -31,12 +32,12 @@ import { AstNodeDescriptionProvider, DefaultLinker, DocumentState, + interruptAndCheck, + isReference, LangiumDocument, LangiumServices, LinkingError, Reference, - interruptAndCheck, - isReference, streamContents, } from 'langium'; import { CancellationToken } from 'vscode-jsonrpc'; @@ -141,6 +142,10 @@ export class ZModelLinker extends DefaultLinker { this.resolveBinary(node as BinaryExpr, document, extraScopes); break; + case ObjectExpr: + this.resolveObject(node as ObjectExpr, document, extraScopes); + break; + case ThisExpr: this.resolveThis(node as ThisExpr, document, extraScopes); break; @@ -200,6 +205,11 @@ export class ZModelLinker extends DefaultLinker { node.$resolvedType = node.operand.$resolvedType; } + private resolveObject(node: ObjectExpr, document: LangiumDocument, extraScopes: ScopeProvider[]) { + node.fields.forEach((field) => this.resolve(field.value, document, extraScopes)); + this.resolveToBuiltinTypeOrDecl(node, 'Object'); + } + private resolveReference(node: ReferenceExpr, document: LangiumDocument, extraScopes: ScopeProvider[]) { this.linkReference(node, 'target', document, extraScopes); node.args.forEach((arg) => this.resolve(arg, document, extraScopes)); diff --git a/packages/schema/src/plugins/prisma/zmodel-code-generator.ts b/packages/schema/src/plugins/prisma/zmodel-code-generator.ts index 4b25c7900..d4489e278 100644 --- a/packages/schema/src/plugins/prisma/zmodel-code-generator.ts +++ b/packages/schema/src/plugins/prisma/zmodel-code-generator.ts @@ -7,10 +7,12 @@ import { DataModelAttribute, DataModelFieldAttribute, Expression, + FieldInitializer, InvocationExpr, LiteralExpr, MemberAccessExpr, NullExpr, + ObjectExpr, ReferenceArg, ReferenceExpr, ThisExpr, @@ -63,6 +65,8 @@ export default class ZModelCodeGenerator { return this.generateMemberExpr(ast as MemberAccessExpr); case InvocationExpr: return this.generateInvocationExpr(ast as InvocationExpr); + case ObjectExpr: + return this.generateObjectExpr(ast as ObjectExpr); case NullExpr: case ThisExpr: return (ast as NullExpr | ThisExpr).value; @@ -71,6 +75,14 @@ export default class ZModelCodeGenerator { } } + generateObjectExpr(ast: ObjectExpr) { + return `{ ${ast.fields.map((field) => this.generateObjectField(field)).join(', ')} }`; + } + + generateObjectField(field: FieldInitializer) { + return `${field.name}: ${this.generateExpression(field.value)}`; + } + generateArrayExpr(ast: ArrayExpr) { return `[${ast.items.map((item) => this.generateExpression(item)).join(', ')}]`; } diff --git a/packages/schema/tests/cli/cli.test.ts b/packages/schema/tests/cli/cli.test.ts index a3ead5df9..f0cb52fd2 100644 --- a/packages/schema/tests/cli/cli.test.ts +++ b/packages/schema/tests/cli/cli.test.ts @@ -1,5 +1,5 @@ +import { getWorkspaceNpmCacheFolder } from '@zenstackhq/testtools'; import * as fs from 'fs'; -import * as path from 'path'; import * as tmp from 'tmp'; import { createProgram } from '../../src/cli'; import { execSync } from '../../src/utils/exec-utils'; @@ -22,13 +22,13 @@ describe('CLI Tests', () => { }); function createNpmrc() { - fs.writeFileSync('.npmrc', `cache=${path.join(__dirname, '../.npmcache')}`); + fs.writeFileSync('.npmrc', `cache=${getWorkspaceNpmCacheFolder(__dirname)}`); } it('init project t3 std', async () => { execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', 'inherit', { npm_config_user_agent: 'npm', - npm_config_cache: path.join(__dirname, '../.npmcache'), + npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), }); createNpmrc(); @@ -41,7 +41,7 @@ describe('CLI Tests', () => { it('init project t3 non-std prisma schema', async () => { execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', 'inherit', { npm_config_user_agent: 'npm', - npm_config_cache: path.join(__dirname, '../.npmcache'), + npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), }); createNpmrc(); fs.renameSync('prisma/schema.prisma', 'prisma/my.prisma'); diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 36a09fd0f..7cb14631c 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/sdk/src/dmmf-helpers/aggregate-helpers.ts b/packages/sdk/src/dmmf-helpers/aggregate-helpers.ts new file mode 100644 index 000000000..cdc1973e6 --- /dev/null +++ b/packages/sdk/src/dmmf-helpers/aggregate-helpers.ts @@ -0,0 +1,78 @@ +import { DMMF } from '@prisma/generator-helper'; +import { AggregateOperationSupport } from './types'; + +const isAggregateOutputType = (name: string) => /(?:Count|Avg|Sum|Min|Max)AggregateOutputType$/.test(name); + +export const isAggregateInputType = (name: string) => + name.endsWith('CountAggregateInput') || + name.endsWith('SumAggregateInput') || + name.endsWith('AvgAggregateInput') || + name.endsWith('MinAggregateInput') || + name.endsWith('MaxAggregateInput'); + +export function addMissingInputObjectTypesForAggregate( + inputObjectTypes: DMMF.InputType[], + outputObjectTypes: DMMF.OutputType[] +) { + const aggregateOutputTypes = outputObjectTypes.filter(({ name }) => isAggregateOutputType(name)); + for (const aggregateOutputType of aggregateOutputTypes) { + const name = aggregateOutputType.name.replace(/(?:OutputType|Output)$/, ''); + inputObjectTypes.push({ + constraints: { maxNumFields: null, minNumFields: null }, + name: `${name}Input`, + fields: aggregateOutputType.fields.map((field) => ({ + name: field.name, + isNullable: false, + isRequired: false, + inputTypes: [ + { + isList: false, + type: 'True', + location: 'scalar', + }, + ], + })), + }); + } +} + +export function resolveAggregateOperationSupport(inputObjectTypes: DMMF.InputType[]) { + const aggregateOperationSupport: AggregateOperationSupport = {}; + for (const inputType of inputObjectTypes) { + if (isAggregateInputType(inputType.name)) { + const name = inputType.name.replace('AggregateInput', ''); + if (name.endsWith('Count')) { + const model = name.replace('Count', ''); + aggregateOperationSupport[model] = { + ...aggregateOperationSupport[model], + count: true, + }; + } else if (name.endsWith('Min')) { + const model = name.replace('Min', ''); + aggregateOperationSupport[model] = { + ...aggregateOperationSupport[model], + min: true, + }; + } else if (name.endsWith('Max')) { + const model = name.replace('Max', ''); + aggregateOperationSupport[model] = { + ...aggregateOperationSupport[model], + max: true, + }; + } else if (name.endsWith('Sum')) { + const model = name.replace('Sum', ''); + aggregateOperationSupport[model] = { + ...aggregateOperationSupport[model], + sum: true, + }; + } else if (name.endsWith('Avg')) { + const model = name.replace('Avg', ''); + aggregateOperationSupport[model] = { + ...aggregateOperationSupport[model], + avg: true, + }; + } + } + } + return aggregateOperationSupport; +} diff --git a/packages/sdk/src/dmmf-helpers/include-helpers.ts b/packages/sdk/src/dmmf-helpers/include-helpers.ts new file mode 100644 index 000000000..ddb5047d0 --- /dev/null +++ b/packages/sdk/src/dmmf-helpers/include-helpers.ts @@ -0,0 +1,82 @@ +import { DMMF } from '@prisma/generator-helper'; +import { checkIsModelRelationField, checkModelHasModelRelation, checkModelHasManyModelRelation } from './model-helpers'; + +export function addMissingInputObjectTypesForInclude(inputObjectTypes: DMMF.InputType[], models: DMMF.Model[]) { + // generate input object types necessary to support ModelInclude with relation support + const generatedIncludeInputObjectTypes = generateModelIncludeInputObjectTypes(models); + + for (const includeInputObjectType of generatedIncludeInputObjectTypes) { + inputObjectTypes.push(includeInputObjectType); + } +} +function generateModelIncludeInputObjectTypes(models: DMMF.Model[]) { + const modelIncludeInputObjectTypes: DMMF.InputType[] = []; + for (const model of models) { + const { name: modelName, fields: modelFields } = model; + const fields: DMMF.SchemaArg[] = []; + + for (const modelField of modelFields) { + const { name: modelFieldName, isList, type } = modelField; + + const isRelationField = checkIsModelRelationField(modelField); + + if (isRelationField) { + const field: DMMF.SchemaArg = { + name: modelFieldName, + isRequired: false, + isNullable: false, + inputTypes: [ + { isList: false, type: 'Boolean', location: 'scalar' }, + { + isList: false, + type: isList ? `${type}FindManyArgs` : `${type}Args`, + location: 'inputObjectTypes', + namespace: 'prisma', + }, + ], + }; + fields.push(field); + } + } + + /** + * include is not generated for models that do not have a relation with any other models + * -> continue onto the next model + */ + const hasRelationToAnotherModel = checkModelHasModelRelation(model); + if (!hasRelationToAnotherModel) { + continue; + } + + const hasManyRelationToAnotherModel = checkModelHasManyModelRelation(model); + + const shouldAddCountField = hasManyRelationToAnotherModel; + if (shouldAddCountField) { + const inputTypes: DMMF.SchemaArgInputType[] = [{ isList: false, type: 'Boolean', location: 'scalar' }]; + inputTypes.push({ + isList: false, + type: `${modelName}CountOutputTypeArgs`, + location: 'inputObjectTypes', + namespace: 'prisma', + }); + const _countField: DMMF.SchemaArg = { + name: '_count', + isRequired: false, + isNullable: false, + inputTypes, + }; + fields.push(_countField); + } + + const modelIncludeInputObjectType: DMMF.InputType = { + name: `${modelName}Include`, + constraints: { + maxNumFields: null, + minNumFields: null, + }, + fields, + }; + modelIncludeInputObjectTypes.push(modelIncludeInputObjectType); + } + return modelIncludeInputObjectTypes; +} diff --git a/packages/sdk/src/dmmf-helpers/index.ts b/packages/sdk/src/dmmf-helpers/index.ts new file mode 100644 index 000000000..09e55e5ed --- /dev/null +++ b/packages/sdk/src/dmmf-helpers/index.ts @@ -0,0 +1,6 @@ +export * from './aggregate-helpers'; +export * from './include-helpers'; +export * from './model-helpers'; +export * from './modelArgs-helpers'; +export * from './select-helpers'; +export * from './types'; diff --git a/packages/sdk/src/dmmf-helpers/model-helpers.ts b/packages/sdk/src/dmmf-helpers/model-helpers.ts new file mode 100644 index 000000000..255638bd8 --- /dev/null +++ b/packages/sdk/src/dmmf-helpers/model-helpers.ts @@ -0,0 +1,36 @@ +import { DMMF } from '@prisma/generator-helper'; + +export function checkModelHasModelRelation(model: DMMF.Model) { + const { fields: modelFields } = model; + for (const modelField of modelFields) { + const isRelationField = checkIsModelRelationField(modelField); + if (isRelationField) { + return true; + } + } + return false; +} + +export function checkModelHasManyModelRelation(model: DMMF.Model) { + const { fields: modelFields } = model; + for (const modelField of modelFields) { + const isManyRelationField = checkIsManyModelRelationField(modelField); + if (isManyRelationField) { + return true; + } + } + return false; +} + +export function checkIsModelRelationField(modelField: DMMF.Field) { + const { kind, relationName } = modelField; + return kind === 'object' && !!relationName; +} + +export function checkIsManyModelRelationField(modelField: DMMF.Field) { + return checkIsModelRelationField(modelField) && modelField.isList; +} + +export function findModelByName(models: DMMF.Model[], modelName: string) { + return models.find(({ name }) => name === modelName); +} diff --git a/packages/sdk/src/dmmf-helpers/modelArgs-helpers.ts b/packages/sdk/src/dmmf-helpers/modelArgs-helpers.ts new file mode 100644 index 000000000..9b90fbaf9 --- /dev/null +++ b/packages/sdk/src/dmmf-helpers/modelArgs-helpers.ts @@ -0,0 +1,62 @@ +import { DMMF } from '@prisma/generator-helper'; +import { checkModelHasModelRelation } from './model-helpers'; + +export function addMissingInputObjectTypesForModelArgs(inputObjectTypes: DMMF.InputType[], models: DMMF.Model[]) { + const modelArgsInputObjectTypes = generateModelArgsInputObjectTypes(models); + + for (const modelArgsInputObjectType of modelArgsInputObjectTypes) { + inputObjectTypes.push(modelArgsInputObjectType); + } +} +function generateModelArgsInputObjectTypes(models: DMMF.Model[]) { + const modelArgsInputObjectTypes: DMMF.InputType[] = []; + for (const model of models) { + const { name: modelName } = model; + const fields: DMMF.SchemaArg[] = []; + + const selectField: DMMF.SchemaArg = { + name: 'select', + isRequired: false, + isNullable: false, + inputTypes: [ + { + isList: false, + type: `${modelName}Select`, + location: 'inputObjectTypes', + namespace: 'prisma', + }, + ], + }; + fields.push(selectField); + + const hasRelationToAnotherModel = checkModelHasModelRelation(model); + + if (hasRelationToAnotherModel) { + const includeField: DMMF.SchemaArg = { + name: 'include', + isRequired: false, + isNullable: false, + inputTypes: [ + { + isList: false, + type: `${modelName}Include`, + location: 'inputObjectTypes', + namespace: 'prisma', + }, + ], + }; + fields.push(includeField); + } + + const modelArgsInputObjectType: DMMF.InputType = { + name: `${modelName}Args`, + constraints: { + maxNumFields: null, + minNumFields: null, + }, + fields, + }; + modelArgsInputObjectTypes.push(modelArgsInputObjectType); + } + return modelArgsInputObjectTypes; +} diff --git a/packages/sdk/src/dmmf-helpers/select-helpers.ts b/packages/sdk/src/dmmf-helpers/select-helpers.ts new file mode 100644 index 000000000..691bf0269 --- /dev/null +++ b/packages/sdk/src/dmmf-helpers/select-helpers.ts @@ -0,0 +1,155 @@ +import { DMMF } from '@prisma/generator-helper'; +import { checkIsModelRelationField, checkModelHasManyModelRelation } from './model-helpers'; + +export function addMissingInputObjectTypesForSelect( + inputObjectTypes: DMMF.InputType[], + outputObjectTypes: DMMF.OutputType[], + models: DMMF.Model[] +) { + // generate input object types necessary to support ModelSelect._count + const modelCountOutputTypes = getModelCountOutputTypes(outputObjectTypes); + const modelCountOutputTypeSelectInputObjectTypes = + generateModelCountOutputTypeSelectInputObjectTypes(modelCountOutputTypes); + const modelCountOutputTypeArgsInputObjectTypes = + generateModelCountOutputTypeArgsInputObjectTypes(modelCountOutputTypes); + + const modelSelectInputObjectTypes = generateModelSelectInputObjectTypes(models); + + const generatedInputObjectTypes = [ + modelCountOutputTypeSelectInputObjectTypes, + modelCountOutputTypeArgsInputObjectTypes, + modelSelectInputObjectTypes, + ].flat(); + + for (const inputObjectType of generatedInputObjectTypes) { + inputObjectTypes.push(inputObjectType); + } +} + +function getModelCountOutputTypes(outputObjectTypes: DMMF.OutputType[]) { + return outputObjectTypes.filter(({ name }) => name.includes('CountOutputType')); +} + +function generateModelCountOutputTypeSelectInputObjectTypes(modelCountOutputTypes: DMMF.OutputType[]) { + const modelCountOutputTypeSelectInputObjectTypes: DMMF.InputType[] = []; + for (const modelCountOutputType of modelCountOutputTypes) { + const { name: modelCountOutputTypeName, fields: modelCountOutputTypeFields } = modelCountOutputType; + const modelCountOutputTypeSelectInputObjectType: DMMF.InputType = { + name: `${modelCountOutputTypeName}Select`, + constraints: { + maxNumFields: null, + minNumFields: null, + }, + fields: modelCountOutputTypeFields.map(({ name }) => ({ + name, + isRequired: false, + isNullable: false, + inputTypes: [ + { + isList: false, + type: `Boolean`, + location: 'scalar', + }, + ], + })), + }; + modelCountOutputTypeSelectInputObjectTypes.push(modelCountOutputTypeSelectInputObjectType); + } + return modelCountOutputTypeSelectInputObjectTypes; +} + +function generateModelCountOutputTypeArgsInputObjectTypes(modelCountOutputTypes: DMMF.OutputType[]) { + const modelCountOutputTypeArgsInputObjectTypes: DMMF.InputType[] = []; + for (const modelCountOutputType of modelCountOutputTypes) { + const { name: modelCountOutputTypeName } = modelCountOutputType; + const modelCountOutputTypeArgsInputObjectType: DMMF.InputType = { + name: `${modelCountOutputTypeName}Args`, + constraints: { + maxNumFields: null, + minNumFields: null, + }, + fields: [ + { + name: 'select', + isRequired: false, + isNullable: false, + inputTypes: [ + { + isList: false, + type: `${modelCountOutputTypeName}Select`, + location: 'inputObjectTypes', + namespace: 'prisma', + }, + ], + }, + ], + }; + modelCountOutputTypeArgsInputObjectTypes.push(modelCountOutputTypeArgsInputObjectType); + } + return modelCountOutputTypeArgsInputObjectTypes; +} + +function generateModelSelectInputObjectTypes(models: DMMF.Model[]) { + const modelSelectInputObjectTypes: DMMF.InputType[] = []; + for (const model of models) { + const { name: modelName, fields: modelFields } = model; + const fields: DMMF.SchemaArg[] = []; + + for (const modelField of modelFields) { + const { name: modelFieldName, isList, type } = modelField; + + const isRelationField = checkIsModelRelationField(modelField); + + const field: DMMF.SchemaArg = { + name: modelFieldName, + isRequired: false, + isNullable: false, + inputTypes: [{ isList: false, type: 'Boolean', location: 'scalar' }], + }; + + if (isRelationField) { + const schemaArgInputType: DMMF.SchemaArgInputType = { + isList: false, + type: isList ? `${type}FindManyArgs` : `${type}Args`, + location: 'inputObjectTypes', + namespace: 'prisma', + }; + field.inputTypes.push(schemaArgInputType); + } + + fields.push(field); + } + + const hasManyRelationToAnotherModel = checkModelHasManyModelRelation(model); + + const shouldAddCountField = hasManyRelationToAnotherModel; + if (shouldAddCountField) { + const _countField: DMMF.SchemaArg = { + name: '_count', + isRequired: false, + isNullable: false, + inputTypes: [ + { isList: false, type: 'Boolean', location: 'scalar' }, + { + isList: false, + type: `${modelName}CountOutputTypeArgs`, + location: 'inputObjectTypes', + namespace: 'prisma', + }, + ], + }; + fields.push(_countField); + } + + const modelSelectInputObjectType: DMMF.InputType = { + name: `${modelName}Select`, + constraints: { + maxNumFields: null, + minNumFields: null, + }, + fields, + }; + modelSelectInputObjectTypes.push(modelSelectInputObjectType); + } + return modelSelectInputObjectTypes; +} diff --git a/packages/sdk/src/dmmf-helpers/types.ts b/packages/sdk/src/dmmf-helpers/types.ts new file mode 100644 index 000000000..a02b9ca7c --- /dev/null +++ b/packages/sdk/src/dmmf-helpers/types.ts @@ -0,0 +1,22 @@ +import { DMMF as PrismaDMMF } from '@prisma/generator-helper'; + +export type TransformerParams = { + enumTypes?: PrismaDMMF.SchemaEnum[]; + fields?: PrismaDMMF.SchemaArg[]; + name?: string; + models?: PrismaDMMF.Model[]; + modelOperations?: PrismaDMMF.ModelMapping[]; + aggregateOperationSupport?: AggregateOperationSupport; + isDefaultPrismaClientOutput?: boolean; + prismaClientOutputPath?: string; +}; + +export type AggregateOperationSupport = { + [model: string]: { + count?: boolean; + min?: boolean; + max?: boolean; + sum?: boolean; + avg?: boolean; + }; +}; diff --git a/packages/sdk/src/utils.ts b/packages/sdk/src/utils.ts index a1a61a652..5a29d6425 100644 --- a/packages/sdk/src/utils.ts +++ b/packages/sdk/src/utils.ts @@ -1,10 +1,15 @@ import { AstNode, + DataModel, DataModelAttribute, + DataModelField, DataModelFieldAttribute, + Enum, + EnumField, Expression, isArrayExpr, isLiteralExpr, + isObjectExpr, Reference, } from '@zenstackhq/language/ast'; @@ -40,11 +45,40 @@ export function getLiteralArray< return arr.map((item) => getLiteral(item)); } +export function getObjectLiteral(expr: Expression | undefined): T | undefined { + if (!expr || !isObjectExpr(expr)) { + return undefined; + } + const result: Record = {}; + for (const field of expr.fields) { + let fieldValue: unknown; + if (isLiteralExpr(field.value)) { + fieldValue = getLiteral(field.value); + } else if (isArrayExpr(field.value)) { + fieldValue = getLiteralArray(field.value); + } else if (isObjectExpr(field.value)) { + fieldValue = getObjectLiteral(field.value); + } + if (fieldValue === undefined) { + return undefined; + } else { + result[field.name] = fieldValue; + } + } + return result as T; +} + export default function indentString(string: string, count = 4): string { const indent = ' '; return string.replace(/^(?!\s*$)/gm, indent.repeat(count)); } +export function hasAttribute(decl: DataModel | DataModelField | Enum | EnumField, name: string) { + return !!(decl.attributes as (DataModelAttribute | DataModelFieldAttribute)[]).find( + (attr) => resolved(attr.decl).name === name + ); +} + export function getAttributeArgs(attr: DataModelAttribute | DataModelFieldAttribute): Record { const result: Record = {}; for (const arg of attr.args) { diff --git a/packages/server/.eslintrc.json b/packages/server/.eslintrc.json new file mode 100644 index 000000000..0a913e874 --- /dev/null +++ b/packages/server/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ] +} diff --git a/packages/server/LICENSE b/packages/server/LICENSE new file mode 120000 index 000000000..30cff7403 --- /dev/null +++ b/packages/server/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/server/README.md b/packages/server/README.md new file mode 100644 index 000000000..15c7646f2 --- /dev/null +++ b/packages/server/README.md @@ -0,0 +1,5 @@ +# ZenStack Fastify Plugin Library + +This package provides a Fastify plugin for ZenStack. + +Visit [Homepage](https://zenstack.dev) for more details. diff --git a/packages/server/jest.config.ts b/packages/server/jest.config.ts new file mode 100644 index 000000000..917cf52f6 --- /dev/null +++ b/packages/server/jest.config.ts @@ -0,0 +1,29 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +export default { + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // The directory where Jest should output its coverage files + coverageDirectory: 'tests/coverage', + + // An array of regexp pattern strings used to skip coverage collection + coveragePathIgnorePatterns: ['/node_modules/', '/tests/'], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: 'v8', + + // A list of reporter names that Jest uses when writing coverage reports + coverageReporters: ['json', 'text', 'lcov', 'clover'], + + // A map from regular expressions to paths to transformers + transform: { '^.+\\.tsx?$': 'ts-jest' }, + + testTimeout: 300000, +}; diff --git a/packages/server/package.json b/packages/server/package.json new file mode 100644 index 000000000..60f9b387d --- /dev/null +++ b/packages/server/package.json @@ -0,0 +1,42 @@ +{ + "name": "@zenstackhq/server", + "version": "1.0.0-alpha.72", + "displayName": "ZenStack Server-side Adapters", + "description": "ZenStack server-side adapters", + "homepage": "https://zenstack.dev", + "scripts": { + "clean": "rimraf dist", + "build": "pnpm lint && pnpm clean && tsc && copyfiles ./package.json ./README.md ./LICENSE dist", + "watch": "tsc --watch", + "lint": "eslint src --ext ts", + "test": "jest", + "prepublishOnly": "pnpm build", + "publish-dev": "pnpm publish --tag dev" + }, + "publishConfig": { + "directory": "dist", + "linkDirectory": true + }, + "keywords": [ + "fastify" + ], + "author": "", + "license": "MIT", + "dependencies": { + "@zenstackhq/openapi": "workspace:*", + "@zenstackhq/runtime": "workspace:*", + "@zenstackhq/sdk": "workspace:*", + "tiny-invariant": "^1.3.1" + }, + "devDependencies": { + "@types/jest": "^29.4.0", + "copyfiles": "^2.4.1", + "fastify": "^4.14.1", + "fastify-plugin": "^4.5.0", + "jest": "^29.4.3", + "rimraf": "^3.0.2", + "ts-jest": "^29.0.5", + "typescript": "^4.9.4", + "@zenstackhq/testtools": "workspace:*" + } +} diff --git a/packages/server/src/fastify/index.ts b/packages/server/src/fastify/index.ts new file mode 100644 index 000000000..665697985 --- /dev/null +++ b/packages/server/src/fastify/index.ts @@ -0,0 +1 @@ +export { default as ZenStackFastifyPlugin } from './plugin'; diff --git a/packages/server/src/fastify/plugin.ts b/packages/server/src/fastify/plugin.ts new file mode 100644 index 000000000..fd6735c9a --- /dev/null +++ b/packages/server/src/fastify/plugin.ts @@ -0,0 +1,58 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { DbClientContract } from '@zenstackhq/runtime'; +import { FastifyPluginCallback, FastifyReply, FastifyRequest } from 'fastify'; +import fp from 'fastify-plugin'; +import { handleRequest, LoggerConfig } from '../openapi'; + +/** + * Fastify plugin options + */ +export interface PluginOptions { + /** + * Url prefix, e.g.: /api + */ + prefix: string; + + /** + * Callback for gettign a PrismaClient for the given request + */ + getPrisma: (request: FastifyRequest, reply: FastifyReply) => unknown | Promise; + + /** + * Logger settings + */ + logger?: LoggerConfig; +} + +const pluginHandler: FastifyPluginCallback = (fastify, options, done) => { + const prefix = options.prefix ?? ''; + + if (options.logger?.info === undefined) { + console.log(`ZenStackPlugin installing routes at prefix: ${prefix}`); + } else { + options.logger?.info?.(`ZenStackPlugin installing routes at prefix: ${prefix}`); + } + + fastify.all(`${prefix}/*`, async (request, reply) => { + const prisma = (await options.getPrisma(request, reply)) as DbClientContract; + if (!prisma) { + throw new Error('unable to get prisma from request context'); + } + const query = request.query as Record; + + const response = await handleRequest({ + method: request.method, + path: (request.params as any)['*'], + query, + requestBody: request.body, + prisma, + logger: options.logger, + }); + + reply.status(response.status).send(response.body); + }); + + done(); +}; + +export default fp(pluginHandler); diff --git a/packages/server/src/openapi/index.ts b/packages/server/src/openapi/index.ts new file mode 100644 index 000000000..b6ea1f221 --- /dev/null +++ b/packages/server/src/openapi/index.ts @@ -0,0 +1,188 @@ +import { + DbClientContract, + DbOperations, + isPrismaClientKnownRequestError, + isPrismaClientUnknownRequestError, + isPrismaClientValidationError, +} from '@zenstackhq/runtime'; +import invariant from 'tiny-invariant'; +import { stripAuxFields } from './utils'; + +type LoggerMethod = (message: string, code?: string) => void; + +/** + * Logger config. + */ +export type LoggerConfig = { + debug?: LoggerMethod; + info?: LoggerMethod; + warn?: LoggerMethod; + error?: LoggerMethod; +}; + +/** + * Options for initializing a Next.js API endpoint request handler. + * @see requestHandler + */ +export type RequestHandlerOptions = { + /** + * Logger configuration. By default log to console. Set to null to turn off logging. + */ + logger?: LoggerConfig | null; +}; + +/** + * OpenApi request context. + */ +export type RequestContext = { + method: string; + path: string; + query: Record; + requestBody: unknown; + prisma: DbClientContract; + logger?: LoggerConfig; +}; + +/** + * OpenApi response. + */ +export type Response = { + status: number; + body: unknown; +}; + +/** + * Handles OpenApi requests + */ +export async function handleRequest({ + method, + path, + query, + requestBody, + prisma, + logger, +}: RequestContext): Promise { + const parts = path.split('/'); + if (parts.length < 2) { + return { status: 400, body: { error: 'invalid request path' } }; + } + + const op = parts.pop(); + const model = parts.pop(); + + invariant(op); + invariant(model); + + const dbOp = op as keyof DbOperations; + let args: unknown; + let resCode = 200; + + switch (dbOp) { + case 'create': + case 'createMany': + case 'upsert': + if (method !== 'POST') { + return { status: 400, body: { message: 'invalid request method, only POST is supported' } }; + } + args = requestBody; + // TODO: upsert's status code should be conditional + resCode = 201; + break; + + case 'findFirst': + case 'findUnique': + case 'findMany': + case 'aggregate': + case 'groupBy': + case 'count': + if (method !== 'GET') { + return { status: 400, body: { message: 'invalid request method, only GET is supported' } }; + } + args = query.q ? unmarshal(query.q as string) : {}; + break; + + case 'update': + case 'updateMany': + if (method !== 'PUT' && method !== 'PATCH') { + return { status: 400, body: { message: 'invalid request method, only PUT AND PATCH are supported' } }; + } + args = requestBody; + break; + + case 'delete': + case 'deleteMany': + if (method !== 'DELETE') { + return { status: 400, body: { message: 'invalid request method, only DELETE is supported' } }; + } + args = query.q ? unmarshal(query.q as string) : {}; + break; + + default: + return { status: 400, body: { message: 'invalid operation: ' + op } }; + } + + try { + if (!prisma[model]) { + return { status: 400, body: { message: `unknown model name: ${model}` } }; + } + const result = await prisma[model][dbOp](args); + stripAuxFields(result); + return { status: resCode, body: result }; + } catch (err) { + if (isPrismaClientKnownRequestError(err)) { + logError(logger, err.code, err.message); + if (err.code === 'P2004') { + // rejected by policy + return { + status: 403, + body: { + prisma: true, + rejectedByPolicy: true, + code: err.code, + message: err.message, + reason: err.meta?.reason, + }, + }; + } else { + return { + status: 400, + body: { + prisma: true, + code: err.code, + message: err.message, + reason: err.meta?.reason, + }, + }; + } + } else if (isPrismaClientUnknownRequestError(err) || isPrismaClientValidationError(err)) { + logError(logger, err.message); + return { + status: 400, + body: { + prisma: true, + message: err.message, + }, + }; + } else { + logError(logger, (err as Error).message); + return { + status: 400, + body: { + message: (err as Error).message, + }, + }; + } + } +} + +function unmarshal(value: string) { + return JSON.parse(value); +} + +function logError(logger: LoggerConfig | undefined | null, message: string, code?: string) { + if (logger === undefined) { + console.error(`@zenstackhq/openapi: error ${code ? '[' + code + ']' : ''}, ${message}`); + } else if (logger?.error) { + logger.error(message, code); + } +} diff --git a/packages/server/src/openapi/utils.ts b/packages/server/src/openapi/utils.ts new file mode 100644 index 000000000..9680ead31 --- /dev/null +++ b/packages/server/src/openapi/utils.ts @@ -0,0 +1,19 @@ +import { AUXILIARY_FIELDS } from '@zenstackhq/sdk'; + +/** + * Recursively strip auxiliary fields from the given data. + */ +export function stripAuxFields(data: unknown) { + if (Array.isArray(data)) { + return data.forEach(stripAuxFields); + } else if (data && typeof data === 'object') { + for (const [key, value] of Object.entries(data)) { + if (AUXILIARY_FIELDS.includes(key)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete (data as any)[key]; + } else { + stripAuxFields(value); + } + } + } +} diff --git a/packages/server/tests/fastify-plugin.test.ts b/packages/server/tests/fastify-plugin.test.ts new file mode 100644 index 000000000..202eae87b --- /dev/null +++ b/packages/server/tests/fastify-plugin.test.ts @@ -0,0 +1,133 @@ +/// + +import fastify from 'fastify'; +import { ZenStackFastifyPlugin } from '../src/fastify'; +import { loadSchema } from '@zenstackhq/testtools'; + +const schema = ` +model User { + id String @id @default(cuid()) + email String @unique + posts Post[] +} + +model Post { + id String @id @default(cuid()) + title String + author User? @relation(fields: [authorId], references: [id]) + authorId String? + published Boolean @default(false) + viewCount Int @default(0) +} +`; + +function makeUrl(path: string, q?: object) { + return q ? `${path}?q=${encodeURIComponent(JSON.stringify(q))}` : path; +} + +describe('Fastify plugin tests', () => { + it('run plugin', async () => { + const { prisma } = await loadSchema(schema); + + const app = fastify(); + app.register(ZenStackFastifyPlugin, { prefix: '/api', getPrisma: () => prisma }); + + let r = await app.inject({ + method: 'GET', + url: makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } }), + }); + expect(r.statusCode).toBe(200); + expect(r.json()).toHaveLength(0); + + r = await app.inject({ + method: 'POST', + url: '/api/user/create', + payload: { + include: { posts: true }, + data: { + id: 'user1', + email: 'user1@abc.com', + posts: { + create: [ + { title: 'post1', published: true, viewCount: 1 }, + { title: 'post2', published: false, viewCount: 2 }, + ], + }, + }, + }, + }); + + expect(r.statusCode).toBe(201); + expect(r.json()).toEqual( + expect.objectContaining({ + email: 'user1@abc.com', + posts: expect.arrayContaining([ + expect.objectContaining({ title: 'post1' }), + expect.objectContaining({ title: 'post2' }), + ]), + }) + ); + + // aux fields should have been removed + const data = r.json(); + expect(data.zenstack_guard).toBeUndefined(); + expect(data.zenstack_transaction).toBeUndefined(); + expect(data.posts[0].zenstack_guard).toBeUndefined(); + expect(data.posts[0].zenstack_transaction).toBeUndefined(); + + r = await app.inject({ + method: 'GET', + url: makeUrl('/api/post/findMany'), + }); + expect(r.statusCode).toBe(200); + expect(r.json()).toHaveLength(2); + + r = await app.inject({ + method: 'GET', + url: makeUrl('/api/post/findMany', { where: { viewCount: { gt: 1 } } }), + }); + expect(r.statusCode).toBe(200); + expect(r.json()).toHaveLength(1); + + r = await app.inject({ + method: 'PUT', + url: '/api/user/update', + payload: { where: { id: 'user1' }, data: { email: 'user1@def.com' } }, + }); + expect(r.statusCode).toBe(200); + expect(r.json().email).toBe('user1@def.com'); + + r = await app.inject({ + method: 'GET', + url: makeUrl('/api/post/count', { where: { viewCount: { gt: 1 } } }), + }); + expect(r.statusCode).toBe(200); + expect(r.json()).toBe(1); + + r = await app.inject({ + method: 'GET', + url: makeUrl('/api/post/aggregate', { _sum: { viewCount: true } }), + }); + expect(r.statusCode).toBe(200); + expect(r.json()._sum.viewCount).toBe(3); + + r = await app.inject({ + method: 'GET', + url: makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } }), + }); + expect(r.statusCode).toBe(200); + expect(r.json()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), + expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), + ]) + ); + + r = await app.inject({ + method: 'DELETE', + url: makeUrl('/api/user/deleteMany', { where: { id: 'user1' } }), + }); + expect(r.statusCode).toBe(200); + expect(r.json().count).toBe(1); + }); +}); diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json new file mode 100644 index 000000000..36d8bdce1 --- /dev/null +++ b/packages/server/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "CommonJS", + "lib": ["ESNext"], + "sourceMap": true, + "outDir": "dist", + "strict": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "resolveJsonModule": true, + "strictPropertyInitialization": false, + "paths": {} + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 7242aea6e..bb64aa998 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "description": "ZenStack Test Tools", "main": "index.js", "publishConfig": { diff --git a/packages/testtools/src/package.template.json b/packages/testtools/src/package.template.json index ef8723f55..e264f0bd0 100644 --- a/packages/testtools/src/package.template.json +++ b/packages/testtools/src/package.template.json @@ -3,15 +3,15 @@ "version": "1.0.0", "description": "", "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@prisma/client": "^4.0.0", "@zenstackhq/runtime": "file:/packages/runtime/dist", + "@zenstackhq/react": "file:/packages/plugins/react/dist", + "@zenstackhq/trpc": "file:/packages/plugins/trpc/dist", + "@zenstackhq/openapi": "file:/packages/plugins/openapi/dist", "prisma": "^4.0.0", "typescript": "^4.9.3", "zenstack": "file:/packages/schema/dist", diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index 25391f862..933a3ff94 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -29,7 +29,7 @@ export function run(cmd: string, env?: Record, cwd?: string) { }); } -function getWorkspaceRoot(start: string) { +export function getWorkspaceRoot(start: string) { let curr = start; while (curr && curr !== '/') { if (fs.existsSync(path.join(curr, 'pnpm-workspace.yaml'))) { @@ -41,6 +41,11 @@ function getWorkspaceRoot(start: string) { return undefined; } +export function getWorkspaceNpmCacheFolder(start: string) { + const root = getWorkspaceRoot(start); + return root ? path.join(root, '.npmcache') : './.npmcache'; +} + const MODEL_PRELUDE = ` datasource db { provider = 'sqlite' @@ -90,7 +95,7 @@ export async function loadSchema(schema: string, addPrelude = true, pushDb = tru const content = addPrelude ? `${MODEL_PRELUDE}\n${schema}` : schema; fs.writeFileSync('schema.zmodel', content); run('npm install'); - run('npx zenstack generate --no-dependency-check'); + run('npx zenstack generate --no-dependency-check', { NODE_PATH: './node_modules' }); if (pushDb) { run('npx prisma db push'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc51f8f7a..34bf09523 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,56 @@ importers: typescript: 4.9.4 publishDirectory: dist + packages/plugins/openapi: + specifiers: + '@prisma/generator-helper': ^4.7.1 + '@prisma/internals': ^4.7.1 + '@readme/openapi-parser': ^2.4.0 + '@types/jest': ^29.4.0 + '@types/tmp': ^0.2.3 + '@typescript-eslint/eslint-plugin': ^5.54.0 + '@typescript-eslint/parser': ^5.54.0 + '@zenstackhq/runtime': workspace:* + '@zenstackhq/sdk': workspace:* + '@zenstackhq/testtools': workspace:* + change-case: ^4.1.2 + copyfiles: ^2.4.1 + eslint: ^8.35.0 + jest: ^29.4.3 + openapi-types: ^12.1.0 + rimraf: ^3.0.2 + tiny-invariant: ^1.3.1 + tmp: ^0.2.1 + ts-jest: ^29.0.5 + typescript: ^4.9.5 + yaml: ^2.2.1 + zenstack: workspace:* + dependencies: + '@prisma/generator-helper': 4.7.1 + '@zenstackhq/runtime': link:../../runtime/dist + '@zenstackhq/sdk': link:../../sdk/dist + change-case: 4.1.2 + openapi-types: 12.1.0 + tiny-invariant: 1.3.1 + yaml: 2.2.1 + devDependencies: + '@prisma/internals': 4.7.1 + '@readme/openapi-parser': 2.4.0_openapi-types@12.1.0 + '@types/jest': 29.4.0 + '@types/tmp': 0.2.3 + '@typescript-eslint/eslint-plugin': 5.54.0_6mj2wypvdnknez7kws2nfdgupi + '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + '@zenstackhq/testtools': link:../../testtools/dist + copyfiles: 2.4.1 + eslint: 8.35.0 + jest: 29.4.3 + rimraf: 3.0.2 + tmp: 0.2.1 + ts-jest: 29.0.5_62v6np7q4ngunzmn4ekotpxy7y + typescript: 4.9.5 + zenstack: link:../../schema/dist + publishDirectory: dist + packages/plugins/react: specifiers: '@prisma/generator-helper': ^4.7.1 @@ -183,6 +233,7 @@ importers: '@zenstackhq/language': workspace:* '@zenstackhq/runtime': workspace:* '@zenstackhq/sdk': workspace:* + '@zenstackhq/testtools': workspace:* async-exit-hook: ^2.0.1 change-case: ^4.1.2 chevrotain: ^9.1.0 @@ -262,6 +313,7 @@ importers: '@types/vscode': 1.72.0 '@typescript-eslint/eslint-plugin': 5.42.0_ofgjrzjuekeo7s3hdyz2yuzw34 '@typescript-eslint/parser': 5.42.0_rmayb2veg2btbq6mbmnyivgasy + '@zenstackhq/testtools': link:../testtools/dist concurrently: 7.4.0 copyfiles: 2.4.1 dotenv: 16.0.3 @@ -296,6 +348,38 @@ importers: typescript: 4.9.4 publishDirectory: dist + packages/server: + specifiers: + '@types/jest': ^29.4.0 + '@zenstackhq/openapi': workspace:* + '@zenstackhq/runtime': workspace:* + '@zenstackhq/sdk': workspace:* + '@zenstackhq/testtools': workspace:* + copyfiles: ^2.4.1 + fastify: ^4.14.1 + fastify-plugin: ^4.5.0 + jest: ^29.4.3 + rimraf: ^3.0.2 + tiny-invariant: ^1.3.1 + ts-jest: ^29.0.5 + typescript: ^4.9.4 + dependencies: + '@zenstackhq/openapi': link:../plugins/openapi/dist + '@zenstackhq/runtime': link:../runtime/dist + '@zenstackhq/sdk': link:../sdk/dist + tiny-invariant: 1.3.1 + devDependencies: + '@types/jest': 29.4.0 + '@zenstackhq/testtools': link:../testtools/dist + copyfiles: 2.4.1 + fastify: 4.14.1 + fastify-plugin: 4.5.0 + jest: 29.4.3 + rimraf: 3.0.2 + ts-jest: 29.0.5_62v6np7q4ngunzmn4ekotpxy7y + typescript: 4.9.5 + publishDirectory: dist + packages/testtools: specifiers: '@prisma/client': ^4.7.0 @@ -394,6 +478,15 @@ packages: '@jridgewell/gen-mapping': 0.1.1 '@jridgewell/trace-mapping': 0.3.17 + /@apidevtools/openapi-schemas/2.1.0: + resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} + engines: {node: '>=10'} + dev: true + + /@apidevtools/swagger-methods/3.0.2: + resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + dev: true + /@babel/code-frame/7.18.6: resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} engines: {node: '>=6.9.0'} @@ -1279,6 +1372,50 @@ packages: - supports-color dev: true + /@eslint/eslintrc/2.0.0: + resolution: {integrity: sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.4.1 + globals: 13.19.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js/8.35.0: + resolution: {integrity: sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@fastify/ajv-compiler/3.5.0: + resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1_ajv@8.12.0 + fast-uri: 2.2.0 + dev: true + + /@fastify/deepmerge/1.3.0: + resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + dev: true + + /@fastify/error/3.2.0: + resolution: {integrity: sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==} + dev: true + + /@fastify/fast-json-stringify-compiler/4.2.0: + resolution: {integrity: sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==} + dependencies: + fast-json-stringify: 5.6.2 + dev: true + /@humanwhocodes/config-array/0.11.7: resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==} engines: {node: '>=10.10.0'} @@ -1306,6 +1443,11 @@ packages: engines: {node: '>=12.22'} dev: true + /@humanwhocodes/momoa/2.0.4: + resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==} + engines: {node: '>=10.10.0'} + dev: true + /@humanwhocodes/object-schema/1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true @@ -2079,6 +2221,10 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /@jsdevtools/ono/7.1.3: + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + dev: true + /@manypkg/find-root/1.1.0: resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: @@ -2227,7 +2373,6 @@ packages: /@opentelemetry/api/1.3.0: resolution: {integrity: sha512-YveTnGNsFFixTKJz09Oi4zYkiLT5af3WpZDu4aIUM7xX+2bHAkOJayFTVQd6zB8kkWPpbua4Ha6Ql00grdLlJQ==} engines: {node: '>=8.0.0'} - dev: false /@opentelemetry/core/1.8.0_@opentelemetry+api@1.3.0: resolution: {integrity: sha512-6SDjwBML4Am0AQmy7z1j6HGrWDgeK8awBRUvl1PGw6HayViMk4QpnUXvv4HTHisecgVBy43NE/cstWprm8tIfw==} @@ -2237,7 +2382,6 @@ packages: dependencies: '@opentelemetry/api': 1.3.0 '@opentelemetry/semantic-conventions': 1.8.0 - dev: false /@opentelemetry/resources/1.8.0_@opentelemetry+api@1.3.0: resolution: {integrity: sha512-KSyMH6Jvss/PFDy16z5qkCK0ERlpyqixb1xwb73wLMvVq+j7i89lobDjw3JkpCcd1Ws0J6jAI4fw28Zufj2ssg==} @@ -2248,7 +2392,6 @@ packages: '@opentelemetry/api': 1.3.0 '@opentelemetry/core': 1.8.0_@opentelemetry+api@1.3.0 '@opentelemetry/semantic-conventions': 1.8.0 - dev: false /@opentelemetry/sdk-trace-base/1.8.0_@opentelemetry+api@1.3.0: resolution: {integrity: sha512-iH41m0UTddnCKJzZx3M85vlhKzRcmT48pUeBbnzsGrq4nIay1oWVHKM5nhB5r8qRDGvd/n7f/YLCXClxwM0tvA==} @@ -2260,12 +2403,10 @@ packages: '@opentelemetry/core': 1.8.0_@opentelemetry+api@1.3.0 '@opentelemetry/resources': 1.8.0_@opentelemetry+api@1.3.0 '@opentelemetry/semantic-conventions': 1.8.0 - dev: false /@opentelemetry/semantic-conventions/1.8.0: resolution: {integrity: sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew==} engines: {node: '>=14'} - dev: false /@prisma/client/4.7.1: resolution: {integrity: sha512-/GbnOwIPtjiveZNUzGXOdp7RxTEkHL4DZP3vBaFNadfr6Sf0RshU5EULFzVaSi9i9PIK9PYd+1Rn7z2B2npb9w==} @@ -2302,7 +2443,6 @@ packages: strip-ansi: 6.0.1 transitivePeerDependencies: - supports-color - dev: false /@prisma/engine-core/4.7.1: resolution: {integrity: sha512-8NwCQcdB4OqOjpehChtKdVn6UVuDTwZewnRG13aeK+KmZurPqGO1LhwB3dTjlfLRlbYsLGb3Xzsai7Jl5QckBA==} @@ -2323,7 +2463,6 @@ packages: undici: 5.11.0 transitivePeerDependencies: - supports-color - dev: false /@prisma/engines-version/4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c: resolution: {integrity: sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==} @@ -2336,7 +2475,6 @@ packages: /@prisma/engines/4.7.1: resolution: {integrity: sha512-zWabHosTdLpXXlMefHmnouhXMoTB1+SCbUU3t4FCmdrtIOZcarPKU3Alto7gm/pZ9vHlGOXHCfVZ1G7OIrSbog==} requiresBuild: true - dev: false /@prisma/fetch-engine/4.7.1: resolution: {integrity: sha512-pZ3SiUV02FTMBJOwzoqGR4YcZ2jTo43Xw1QC/5YwmRkYa5NPydUnap5rvB84DgVNq5OnOrQxhWSsakOUPqcc6g==} @@ -2361,7 +2499,6 @@ packages: transitivePeerDependencies: - encoding - supports-color - dev: false /@prisma/generator-helper/4.7.1: resolution: {integrity: sha512-q9mjYOyLxkLWhqjvPB5sx5J1VRegWA2eC9mwrGgA0CSMo6SzZ7xwIe4rMJKnh4a7QMUO03+HQFdv/Nz7BOWCcg==} @@ -2372,7 +2509,6 @@ packages: cross-spawn: 7.0.3 transitivePeerDependencies: - supports-color - dev: false /@prisma/get-platform/4.7.1: resolution: {integrity: sha512-p160ToD9j53TgSrg0O0q3GkGZTGwB30UqIu3B0bu/NIvhnhHOwuEo4tyDXYKblLOE2TL0e1t0PWOSQAvH3nKYw==} @@ -2380,7 +2516,6 @@ packages: '@prisma/debug': 4.7.1 transitivePeerDependencies: - supports-color - dev: false /@prisma/internals/4.7.1: resolution: {integrity: sha512-YgG+5zLZuKdVUPnNdw9XA0XGfW1vZaOqrcGgRnla0mSIIIB3Gg3noRuHv9ZQAXeHLWh2v7MHxal6fxKElMN0ug==} @@ -2432,11 +2567,51 @@ packages: transitivePeerDependencies: - encoding - supports-color - dev: false /@prisma/prisma-fmt-wasm/4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c: resolution: {integrity: sha512-o9oSp2c5yDWHn9TT2Ntv3cb6LuJKPBy32gTtipYH1D166KfKBy+1RkPySobWZCKV/TrkUGlcBn5vcQgRBHPvVA==} - dev: false + + /@readme/better-ajv-errors/1.5.0_ajv@8.12.0: + resolution: {integrity: sha512-dJLAlfN5ahAb6J5t+zCv0YeJsf4mrRHllwBb6pIYZa4yfFKs3lOSAN+i+ChebbpnqCkw7IrwzPz9vzk8p5mCEw==} + engines: {node: '>=14'} + peerDependencies: + ajv: 4.11.8 - 8 + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/runtime': 7.20.7 + '@humanwhocodes/momoa': 2.0.4 + ajv: 8.12.0 + chalk: 4.1.2 + json-to-ast: 2.1.0 + jsonpointer: 5.0.1 + leven: 3.1.0 + dev: true + + /@readme/json-schema-ref-parser/1.2.0: + resolution: {integrity: sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==} + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.11 + call-me-maybe: 1.0.2 + js-yaml: 4.1.0 + dev: true + + /@readme/openapi-parser/2.4.0_openapi-types@12.1.0: + resolution: {integrity: sha512-KZMzLYsruShysgLgGaLNQFkinTdQu+rmVjrshhYlviHvvU6k28KYu0aOJciIQSBY0u38QHYFi74gsxU0IsmO1Q==} + engines: {node: '>=14'} + peerDependencies: + openapi-types: '>=7' + dependencies: + '@apidevtools/openapi-schemas': 2.1.0 + '@apidevtools/swagger-methods': 3.0.2 + '@jsdevtools/ono': 7.1.3 + '@readme/better-ajv-errors': 1.5.0_ajv@8.12.0 + '@readme/json-schema-ref-parser': 1.2.0 + ajv: 8.12.0 + ajv-draft-04: 1.0.0_ajv@8.12.0 + call-me-maybe: 1.0.2 + openapi-types: 12.1.0 + dev: true /@sinclair/typebox/0.24.47: resolution: {integrity: sha512-J4Xw0xYK4h7eC34MNOPQi6IkNxGRck6n4VJpWDzXIFVTW8I/D43Gf+NfWz/v/7NHlzWOPd3+T4PJ4OqklQ2u7A==} @@ -2478,7 +2653,6 @@ packages: /@tootallnate/once/2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} - dev: false /@ts-morph/common/0.17.0: resolution: {integrity: sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g==} @@ -2555,13 +2729,11 @@ packages: resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==} dependencies: '@types/node': 18.14.2 - dev: false /@types/debug/4.1.7: resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} dependencies: '@types/ms': 0.7.31 - dev: false /@types/fs-extra/11.0.1: resolution: {integrity: sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==} @@ -2635,7 +2807,6 @@ packages: /@types/ms/0.7.31: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} - dev: false /@types/node/12.20.55: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -2684,7 +2855,6 @@ packages: /@types/retry/0.12.0: resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} - dev: false /@types/scheduler/0.16.2: resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} @@ -2770,6 +2940,34 @@ packages: - supports-color dev: true + /@typescript-eslint/eslint-plugin/5.54.0_6mj2wypvdnknez7kws2nfdgupi: + resolution: {integrity: sha512-+hSN9BdSr629RF02d7mMtXhAJvDTyCbprNYJKrXETlul/Aml6YZwd90XioVbjejQeHbb3R8Dg0CkRgoJDxo8aw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + '@typescript-eslint/scope-manager': 5.54.0 + '@typescript-eslint/type-utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + '@typescript-eslint/utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + debug: 4.3.4 + eslint: 8.35.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + natural-compare-lite: 1.4.0 + regexpp: 3.2.0 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser/5.42.0_rmayb2veg2btbq6mbmnyivgasy: resolution: {integrity: sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2790,6 +2988,26 @@ packages: - supports-color dev: true + /@typescript-eslint/parser/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu: + resolution: {integrity: sha512-aAVL3Mu2qTi+h/r04WI/5PfNWvO6pdhpeMRWk9R7rEV4mwJNzoWf5CCU5vDKBsPIFQFjEq1xg7XBI2rjiMXQbQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 5.54.0 + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5 + debug: 4.3.4 + eslint: 8.35.0 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager/5.42.0: resolution: {integrity: sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2798,6 +3016,14 @@ packages: '@typescript-eslint/visitor-keys': 5.42.0 dev: true + /@typescript-eslint/scope-manager/5.54.0: + resolution: {integrity: sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/visitor-keys': 5.54.0 + dev: true + /@typescript-eslint/type-utils/5.42.0_rmayb2veg2btbq6mbmnyivgasy: resolution: {integrity: sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2818,11 +3044,36 @@ packages: - supports-color dev: true + /@typescript-eslint/type-utils/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu: + resolution: {integrity: sha512-WI+WMJ8+oS+LyflqsD4nlXMsVdzTMYTxl16myXPaCXnSgc7LWwMsjxQFZCK/rVmTZ3FN71Ct78ehO9bRC7erYQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5 + '@typescript-eslint/utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + debug: 4.3.4 + eslint: 8.35.0 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/types/5.42.0: resolution: {integrity: sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@typescript-eslint/types/5.54.0: + resolution: {integrity: sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /@typescript-eslint/typescript-estree/5.42.0_typescript@4.8.3: resolution: {integrity: sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2865,6 +3116,27 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree/5.54.0_typescript@4.9.5: + resolution: {integrity: sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/visitor-keys': 5.54.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils/5.42.0_rmayb2veg2btbq6mbmnyivgasy: resolution: {integrity: sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2905,6 +3177,26 @@ packages: - typescript dev: true + /@typescript-eslint/utils/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu: + resolution: {integrity: sha512-cuwm8D/Z/7AuyAeJ+T0r4WZmlnlxQ8wt7C7fLpFlKMR+dY6QO79Cq1WpJhvZbMA4ZeZGHiRWnht7ZJ8qkdAunw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@types/json-schema': 7.0.11 + '@types/semver': 7.3.13 + '@typescript-eslint/scope-manager': 5.54.0 + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5 + eslint: 8.35.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.35.0 + semver: 7.3.8 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys/5.42.0: resolution: {integrity: sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2913,6 +3205,25 @@ packages: eslint-visitor-keys: 3.3.0 dev: true + /@typescript-eslint/visitor-keys/5.54.0: + resolution: {integrity: sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.54.0 + eslint-visitor-keys: 3.3.0 + dev: true + + /abort-controller/3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: true + + /abstract-logging/2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + dev: true + /acorn-jsx/5.3.2_acorn@8.8.0: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2939,7 +3250,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: false /aggregate-error/3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} @@ -2947,7 +3257,28 @@ packages: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 - dev: false + + /ajv-draft-04/1.0.0_ajv@8.12.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.12.0 + dev: true + + /ajv-formats/2.1.1_ajv@8.12.0: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.12.0 + dev: true /ajv/6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -2958,6 +3289,15 @@ packages: uri-js: 4.4.1 dev: true + /ajv/8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: true + /ansi-colors/4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -3012,7 +3352,6 @@ packages: lodash.union: 4.6.0 normalize-path: 3.0.0 readable-stream: 2.3.7 - dev: false /archiver/5.3.1: resolution: {integrity: sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==} @@ -3025,7 +3364,10 @@ packages: readdir-glob: 1.1.2 tar-stream: 2.2.0 zip-stream: 4.1.0 - dev: false + + /archy/1.0.0: + resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} + dev: true /arg/4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -3033,7 +3375,6 @@ packages: /arg/5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: false /argparse/1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -3086,7 +3427,6 @@ packages: /astral-regex/2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} - dev: false /async-exit-hook/2.0.1: resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} @@ -3095,7 +3435,6 @@ packages: /async/3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} - dev: false /asynckit/0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -3106,11 +3445,26 @@ packages: engines: {node: '>= 4.0.0'} dev: true + /atomic-sleep/1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + dev: true + /available-typed-arrays/1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} dev: true + /avvio/8.2.1: + resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} + dependencies: + archy: 1.0.0 + debug: 4.3.4 + fastq: 1.15.0 + transitivePeerDependencies: + - supports-color + dev: true + /azure-devops-node-api/11.2.0: resolution: {integrity: sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==} dependencies: @@ -3331,7 +3685,6 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: false /braces/3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -3381,12 +3734,18 @@ packages: base64-js: 1.5.1 ieee754: 1.2.1 + /buffer/6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + /busboy/1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 - dev: false /call-bind/1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} @@ -3395,6 +3754,10 @@ packages: get-intrinsic: 1.1.3 dev: true + /call-me-maybe/1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + dev: true + /callsites/3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -3490,7 +3853,6 @@ packages: uuid: 8.3.2 transitivePeerDependencies: - encoding - dev: false /cheerio-select/2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} @@ -3561,7 +3923,6 @@ packages: /ci-info/3.3.0: resolution: {integrity: sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==} - dev: false /ci-info/3.4.0: resolution: {integrity: sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==} @@ -3583,19 +3944,16 @@ packages: /clean-stack/2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - dev: false /cli-cursor/3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} dependencies: restore-cursor: 3.1.0 - dev: false /cli-spinners/2.7.0: resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==} engines: {node: '>=6'} - dev: false /cli-truncate/2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} @@ -3603,7 +3961,6 @@ packages: dependencies: slice-ansi: 3.0.0 string-width: 4.2.3 - dev: false /cliui/6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} @@ -3643,6 +4000,11 @@ packages: resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==} dev: false + /code-error-fragment/0.0.230: + resolution: {integrity: sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==} + engines: {node: '>= 4'} + dev: true + /collect-v8-coverage/1.0.1: resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} dev: true @@ -3712,7 +4074,6 @@ packages: /commondir/1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - dev: false /component-emitter/1.3.0: resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} @@ -3726,7 +4087,6 @@ packages: crc32-stream: 4.0.2 normalize-path: 3.0.0 readable-stream: 3.6.0 - dev: false /concat-map/0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} @@ -3762,6 +4122,11 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true + /cookie/0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: true + /cookiejar/2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} dev: true @@ -3792,7 +4157,6 @@ packages: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} hasBin: true - dev: false /crc32-stream/4.0.2: resolution: {integrity: sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==} @@ -3800,7 +4164,6 @@ packages: dependencies: crc-32: 1.2.2 readable-stream: 3.6.0 - dev: false /create-require/1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -3833,7 +4196,6 @@ packages: /crypto-random-string/2.0.0: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} - dev: false /css-select/5.1.0: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} @@ -3974,7 +4336,6 @@ packages: p-map: 4.0.0 rimraf: 3.0.2 slash: 3.0.0 - dev: false /delayed-stream/1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -4119,7 +4480,6 @@ packages: /env-paths/2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - dev: false /error-ex/1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -4495,6 +4855,16 @@ packages: eslint-visitor-keys: 2.1.0 dev: true + /eslint-utils/3.0.0_eslint@8.35.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.35.0 + eslint-visitor-keys: 2.1.0 + dev: true + /eslint-visitor-keys/2.1.0: resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} engines: {node: '>=10'} @@ -4601,6 +4971,55 @@ packages: - supports-color dev: true + /eslint/8.35.0: + resolution: {integrity: sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 2.0.0 + '@eslint/js': 8.35.0 + '@humanwhocodes/config-array': 0.11.8 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.35.0 + eslint-visitor-keys: 3.3.0 + espree: 9.4.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.19.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-sdsl: 4.1.5 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /espree/9.4.1: resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4623,6 +5042,13 @@ packages: estraverse: 5.3.0 dev: true + /esquery/1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + /esrecurse/4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -4645,6 +5071,16 @@ packages: engines: {node: '>=0.10.0'} dev: true + /event-target-shim/5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: true + + /events/3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: true + /execa/5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -4726,6 +5162,14 @@ packages: tmp: 0.0.33 dev: true + /fast-content-type-parse/1.0.0: + resolution: {integrity: sha512-Xbc4XcysUXcsP5aHUU7Nq3OwvHq97C+WnbkeIefpeYLX+ryzFJlU6OStFJhs6Ol0LkUGpcK+wL0JwfM+FCU5IA==} + dev: true + + /fast-decode-uri-component/1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + dev: true + /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -4748,17 +5192,68 @@ packages: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true + /fast-json-stringify/5.6.2: + resolution: {integrity: sha512-F6xkRrXvtGbAiDSEI5Rk7qk2P63Y9kc8bO6Dnsd3Rt6sBNr2QxNFWs0JbKftgiyOfGxnJaRoHe4SizCTqeAyrA==} + dependencies: + '@fastify/deepmerge': 1.3.0 + ajv: 8.12.0 + ajv-formats: 2.1.1_ajv@8.12.0 + fast-deep-equal: 3.1.3 + fast-uri: 2.2.0 + rfdc: 1.3.0 + dev: true + /fast-levenshtein/2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true + /fast-querystring/1.1.1: + resolution: {integrity: sha512-qR2r+e3HvhEFmpdHMv//U8FnFlnYjaC6QKDuaXALDkw2kvHO8WDjxH+f/rHGR4Me4pnk8p9JAkRNTjYHAKRn2Q==} + dependencies: + fast-decode-uri-component: 1.0.1 + dev: true + + /fast-redact/3.1.2: + resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} + engines: {node: '>=6'} + dev: true + /fast-safe-stringify/2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} dev: true + /fast-uri/2.2.0: + resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} + dev: true + /fast-write-atomic/0.2.1: resolution: {integrity: sha512-WvJe06IfNYlr+6cO3uQkdKdy3Cb1LlCJSF8zRs2eT8yuhdbSlR9nIt+TgQ92RUxiRrQm+/S7RARnMfCs5iuAjw==} - dev: false + + /fastify-plugin/4.5.0: + resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} + dev: true + + /fastify/4.14.1: + resolution: {integrity: sha512-yjrDeXe77j9gRlSV2UJry8mcFWbD0NQ5JYjnPi4tkFjHZVaG3/BD5wxOmRzGnHPC0YvaBJ0XWrIfFPl2IHRa1w==} + dependencies: + '@fastify/ajv-compiler': 3.5.0 + '@fastify/error': 3.2.0 + '@fastify/fast-json-stringify-compiler': 4.2.0 + abstract-logging: 2.0.1 + avvio: 8.2.1 + fast-content-type-parse: 1.0.0 + find-my-way: 7.5.0 + light-my-request: 5.9.1 + pino: 8.11.0 + process-warning: 2.1.0 + proxy-addr: 2.0.7 + rfdc: 1.3.0 + secure-json-parse: 2.7.0 + semver: 7.3.8 + tiny-lru: 10.0.1 + transitivePeerDependencies: + - supports-color + dev: true /fastq/1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} @@ -4805,7 +5300,15 @@ packages: commondir: 1.0.1 make-dir: 3.1.0 pkg-dir: 4.2.0 - dev: false + + /find-my-way/7.5.0: + resolution: {integrity: sha512-3ehydSBhGcS0TtMA/BYEyMAKi9Sv0MqF8aqiMO5oGBXyCcSlyEJyfGWsbNxAx7BekTNWUwD1ttLJLURni2vmJg==} + engines: {node: '>=14'} + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.1 + safe-regex2: 2.0.0 + dev: true /find-replace/3.0.0: resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} @@ -4871,9 +5374,13 @@ packages: qs: 6.11.0 dev: true + /forwarded/0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: true + /fp-ts/2.13.1: resolution: {integrity: sha512-0eu5ULPS2c/jsa1lGFneEFFEdTbembJv8e4QKXeVJ3lm/5hyve06dlKZrpxmMwJt6rYen7sxmHHK2CLaXvWuWQ==} - dev: false /fs-constants/1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -4885,7 +5392,6 @@ packages: graceful-fs: 4.2.10 jsonfile: 6.1.0 universalify: 2.0.0 - dev: false /fs-extra/11.1.0: resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} @@ -4928,7 +5434,6 @@ packages: resolution: {integrity: sha512-Xn4fDhLydXkuzepZVsr02jakLlmoARPy+YWIclo4kh0GyNGUHnTqeH/w/qIsVn50dFxtp8otPL2t/HcPJBbxUA==} dependencies: minimatch: 5.1.0 - dev: false /fs.realpath/1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -5117,7 +5622,6 @@ packages: /has-yarn/2.1.0: resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} engines: {node: '>=8'} - dev: false /has/1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} @@ -5131,7 +5635,6 @@ packages: dependencies: is-stream: 2.0.1 type-fest: 0.8.1 - dev: false /header-case/2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} @@ -5177,7 +5680,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: false /https-proxy-agent/5.0.0: resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==} @@ -5197,7 +5699,6 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color - dev: false /human-id/1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} @@ -5278,6 +5779,11 @@ packages: side-channel: 1.0.4 dev: true + /ipaddr.js/1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: true + /is-array-buffer/3.0.1: resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==} dependencies: @@ -5338,7 +5844,6 @@ packages: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} hasBin: true - dev: false /is-extglob/2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} @@ -5362,7 +5867,6 @@ packages: /is-interactive/1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} - dev: false /is-negative-zero/2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} @@ -5383,7 +5887,6 @@ packages: /is-path-cwd/2.2.0: resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} engines: {node: '>=6'} - dev: false /is-path-inside/3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} @@ -5447,7 +5950,6 @@ packages: /is-unicode-supported/0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - dev: false /is-weakref/1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} @@ -5468,7 +5970,6 @@ packages: engines: {node: '>=8'} dependencies: is-docker: 2.2.1 - dev: false /isarray/0.0.1: resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} @@ -7044,10 +7545,22 @@ packages: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true + /json-schema-traverse/1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + dev: true + /json-stable-stringify-without-jsonify/1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true + /json-to-ast/2.1.0: + resolution: {integrity: sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==} + engines: {node: '>= 4'} + dependencies: + code-error-fragment: 0.0.230 + grapheme-splitter: 1.0.4 + dev: true + /json5/2.2.1: resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} engines: {node: '>=6'} @@ -7072,6 +7585,11 @@ packages: optionalDependencies: graceful-fs: 4.2.10 + /jsonpointer/5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + dev: true + /jsonschema/1.4.1: resolution: {integrity: sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==} dev: true @@ -7126,7 +7644,6 @@ packages: engines: {node: '>= 0.6.3'} dependencies: readable-stream: 2.3.7 - dev: false /leven/3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} @@ -7141,6 +7658,14 @@ packages: type-check: 0.4.0 dev: true + /light-my-request/5.9.1: + resolution: {integrity: sha512-UT7pUk8jNCR1wR7w3iWfIjx32DiB2f3hFdQSOwy3/EPQ3n3VocyipUxcyRZR0ahoev+fky69uA+GejPa9KuHKg==} + dependencies: + cookie: 0.5.0 + process-warning: 2.1.0 + set-cookie-parser: 2.5.1 + dev: true + /lines-and-columns/1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -7185,19 +7710,15 @@ packages: /lodash.defaults/4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - dev: false /lodash.difference/4.5.0: resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} - dev: false /lodash.flatten/4.4.0: resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} - dev: false /lodash.isplainobject/4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - dev: false /lodash.memoize/4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} @@ -7213,7 +7734,6 @@ packages: /lodash.union/4.6.0: resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} - dev: false /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -7224,7 +7744,6 @@ packages: dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 - dev: false /loose-envify/1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} @@ -7375,7 +7894,6 @@ packages: engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 - dev: false /minimist-options/4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -7418,7 +7936,6 @@ packages: /ms/2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: false /mute-stream/0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} @@ -7449,7 +7966,6 @@ packages: /new-github-issue-url/0.2.1: resolution: {integrity: sha512-md4cGoxuT4T4d/HDOXbrUHkTKrp/vp+m3aOA7XXVYwNsUNMK49g3SQicTSeV5GIz/5QVGAeYRAOlyp9OvlgsYA==} engines: {node: '>=10'} - dev: false /next/12.3.1_672uxklweod7ene3nqtsh262ca: resolution: {integrity: sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==} @@ -7585,6 +8101,10 @@ packages: object-keys: 1.1.1 dev: true + /on-exit-leak-free/2.1.0: + resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} + dev: true + /once/1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -7602,7 +8122,9 @@ packages: dependencies: is-docker: 2.2.1 is-wsl: 2.2.0 - dev: false + + /openapi-types/12.1.0: + resolution: {integrity: sha512-XpeCy01X6L5EpP+6Hc3jWN7rMZJ+/k1lwki/kTmWzbVhdPie3jd5O2ZtedEx8Yp58icJ0osVldLMrTB/zslQXA==} /optionator/0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} @@ -7629,7 +8151,6 @@ packages: log-symbols: 4.1.0 strip-ansi: 6.0.1 wcwidth: 1.0.1 - dev: false /os-tmpdir/1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} @@ -7679,7 +8200,6 @@ packages: engines: {node: '>=10'} dependencies: aggregate-error: 3.1.0 - dev: false /p-retry/4.6.2: resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} @@ -7687,7 +8207,6 @@ packages: dependencies: '@types/retry': 0.12.0 retry: 0.13.1 - dev: false /p-try/2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} @@ -7788,6 +8307,34 @@ packages: engines: {node: '>=6'} dev: true + /pino-abstract-transport/1.0.0: + resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} + dependencies: + readable-stream: 4.3.0 + split2: 4.1.0 + dev: true + + /pino-std-serializers/6.1.0: + resolution: {integrity: sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==} + dev: true + + /pino/8.11.0: + resolution: {integrity: sha512-Z2eKSvlrl2rH8p5eveNUnTdd4AjJk8tAsLkHYZQKGHP4WTh2Gi1cOSOs3eWPqaj+niS3gj4UkoreoaWgF3ZWYg==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.1.2 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 1.0.0 + pino-std-serializers: 6.1.0 + process-warning: 2.1.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.4.2 + sonic-boom: 3.2.1 + thread-stream: 2.3.0 + dev: true + /pirates/4.0.5: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} @@ -7910,10 +8457,18 @@ packages: /process-nextick-args/2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + /process-warning/2.1.0: + resolution: {integrity: sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg==} + dev: true + + /process/0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: true + /progress/2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} - dev: false /promise-polyfill/8.2.3: resolution: {integrity: sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg==} @@ -7932,6 +8487,14 @@ packages: kleur: 3.0.3 sisteransi: 1.0.5 + /proxy-addr/2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: true + /pseudomap/1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} dev: true @@ -7962,6 +8525,10 @@ packages: /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + /quick-format-unescaped/4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + dev: true + /quick-lru/4.0.1: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} @@ -8058,11 +8625,20 @@ packages: string_decoder: 1.3.0 util-deprecate: 1.0.2 + /readable-stream/4.3.0: + resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + dev: true + /readdir-glob/1.1.2: resolution: {integrity: sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==} dependencies: minimatch: 5.1.0 - dev: false /readdirp/3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} @@ -8071,6 +8647,11 @@ packages: picomatch: 2.3.1 dev: true + /real-require/0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + dev: true + /redent/3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -8127,13 +8708,17 @@ packages: /replace-string/3.1.0: resolution: {integrity: sha512-yPpxc4ZR2makceA9hy/jHNqc7QVkd4Je/N0WRHm6bs3PtivPuPynxE5ejU/mp5EhnCv8+uZL7vhz8rkluSlx+Q==} engines: {node: '>=8'} - dev: false /require-directory/2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} dev: true + /require-from-string/2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: true + /require-main-filename/2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: true @@ -8179,17 +8764,24 @@ packages: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 - dev: false + + /ret/0.2.2: + resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==} + engines: {node: '>=4'} + dev: true /retry/0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} - dev: false /reusify/1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + /rfdc/1.3.0: + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + dev: true + /rimraf/3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true @@ -8221,6 +8813,17 @@ packages: is-regex: 1.1.4 dev: true + /safe-regex2/2.0.0: + resolution: {integrity: sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==} + dependencies: + ret: 0.2.2 + dev: true + + /safe-stable-stringify/2.4.2: + resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==} + engines: {node: '>=10'} + dev: true + /safer-buffer/2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true @@ -8234,6 +8837,10 @@ packages: dependencies: loose-envify: 1.4.0 + /secure-json-parse/2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + dev: true + /semver/5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true @@ -8261,6 +8868,10 @@ packages: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true + /set-cookie-parser/2.5.1: + resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==} + dev: true + /shebang-command/1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -8328,7 +8939,6 @@ packages: ansi-styles: 4.3.0 astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 - dev: false /smartwrap/2.0.2: resolution: {integrity: sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==} @@ -8350,6 +8960,12 @@ packages: tslib: 2.4.1 dev: false + /sonic-boom/3.2.1: + resolution: {integrity: sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==} + dependencies: + atomic-sleep: 1.0.0 + dev: true + /source-map-js/1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -8395,6 +9011,11 @@ packages: /spdx-license-ids/3.0.12: resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + /split2/4.1.0: + resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} + engines: {node: '>= 10.x'} + dev: true + /sprintf-js/1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true @@ -8420,7 +9041,6 @@ packages: /streamsearch/1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - dev: false /string-length/4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} @@ -8633,12 +9253,10 @@ packages: /temp-dir/1.0.0: resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} engines: {node: '>=4'} - dev: false /temp-dir/2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} - dev: false /temp-write/4.0.0: resolution: {integrity: sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw==} @@ -8649,7 +9267,6 @@ packages: make-dir: 3.1.0 temp-dir: 1.0.0 uuid: 3.4.0 - dev: false /tempy/1.0.1: resolution: {integrity: sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==} @@ -8660,7 +9277,6 @@ packages: temp-dir: 2.0.0 type-fest: 0.16.0 unique-string: 2.0.0 - dev: false /term-size/2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} @@ -8687,6 +9303,12 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /thread-stream/2.3.0: + resolution: {integrity: sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==} + dependencies: + real-require: 0.2.0 + dev: true + /through2/2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} dependencies: @@ -8694,6 +9316,15 @@ packages: xtend: 4.0.2 dev: true + /tiny-invariant/1.3.1: + resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} + dev: false + + /tiny-lru/10.0.1: + resolution: {integrity: sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==} + engines: {node: '>=6'} + dev: true + /tmp/0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -8803,6 +9434,40 @@ packages: yargs-parser: 21.1.1 dev: true + /ts-jest/29.0.5_62v6np7q4ngunzmn4ekotpxy7y: + resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.20.5 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.4.3 + jest-util: 29.4.3 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.3.8 + typescript: 4.9.5 + yargs-parser: 21.1.1 + dev: true + /ts-jest/29.0.5_itrzs5lisv6omhzjwqg6f7v6de: resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8908,7 +9573,6 @@ packages: /ts-pattern/4.0.6: resolution: {integrity: sha512-sFHQYD4KoysBi7e7a2mzDPvRBeqA4w+vEyRE+P5MU9VLq8eEYxgKCgD9RNEAT+itGRWUTYN+hry94GDPLb1/Yw==} - dev: false /tsc-alias/1.7.0: resolution: {integrity: sha512-n/K6g8S7Ec7Y/A2Z77Ikp2Uv1S1ERtT63ni69XV4W1YPT4rnNmz8ItgIiJYvKfFnKfqcZQ81UPjoKpMTxaC/rg==} @@ -8949,6 +9613,16 @@ packages: typescript: 4.8.4 dev: true + /tsutils/3.21.0_typescript@4.9.5: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.9.5 + dev: true + /tty-table/4.1.6: resolution: {integrity: sha512-kRj5CBzOrakV4VRRY5kUWbNYvo/FpOsz65DzI5op9P+cHov3+IqPbo1JE1ZnQGkHdZgNFDsrEjrfqqy/Ply9fw==} engines: {node: '>=8.0.0'} @@ -8993,7 +9667,6 @@ packages: /type-fest/0.16.0: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} - dev: false /type-fest/0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} @@ -9095,14 +9768,12 @@ packages: engines: {node: '>=12.18'} dependencies: busboy: 1.6.0 - dev: false /unique-string/2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} dependencies: crypto-random-string: 2.0.0 - dev: false /universalify/0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} @@ -9164,12 +9835,10 @@ packages: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. hasBin: true - dev: false /uuid/8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - dev: false /uuid/9.0.0: resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} @@ -9408,6 +10077,11 @@ packages: /yallist/4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + /yaml/2.2.1: + resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} + engines: {node: '>= 14'} + dev: false + /yargs-parser/18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -9524,7 +10198,6 @@ packages: archiver-utils: 2.1.0 compress-commons: 4.1.1 readable-stream: 3.6.0 - dev: false /zod-validation-error/0.2.1_zod@3.19.1: resolution: {integrity: sha512-zGg6P5EHi5V0dvyEeC8HBZd2pzp7QDKTngkSWgWunljrY+0SHkHyjI519D+u8/37BHkGHAFseWgnZ2Uq8LNFKg==} diff --git a/tests/integration/jest.config.ts b/tests/integration/jest.config.ts index 9f13cb6b8..1c4aa31ae 100644 --- a/tests/integration/jest.config.ts +++ b/tests/integration/jest.config.ts @@ -14,4 +14,19 @@ export default { globalSetup: './global-setup.js', setupFilesAfterEnv: ['./test-setup.ts'], + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // The directory where Jest should output its coverage files + coverageDirectory: 'tests/coverage', + + // An array of regexp pattern strings used to skip coverage collection + coveragePathIgnorePatterns: ['/node_modules/', '/tests/'], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: 'v8', + + // A list of reporter names that Jest uses when writing coverage reports + coverageReporters: ['json', 'text', 'lcov', 'clover'], }; diff --git a/tests/integration/test-run/package-lock.json b/tests/integration/test-run/package-lock.json index ff46da152..84f29c18c 100644 --- a/tests/integration/test-run/package-lock.json +++ b/tests/integration/test-run/package-lock.json @@ -126,7 +126,7 @@ }, "../../../packages/runtime/dist": { "name": "@zenstackhq/runtime", - "version": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "license": "MIT", "dependencies": { "@types/bcryptjs": "^2.4.2", @@ -156,7 +156,7 @@ }, "../../../packages/schema/dist": { "name": "zenstack", - "version": "1.0.0-alpha.62", + "version": "1.0.0-alpha.72", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -203,6 +203,7 @@ "@types/vscode": "^1.56.0", "@typescript-eslint/eslint-plugin": "^5.42.0", "@typescript-eslint/parser": "^5.42.0", + "@zenstackhq/testtools": "workspace:*", "concurrently": "^7.4.0", "copyfiles": "^2.4.1", "dotenv": "^16.0.3", @@ -406,6 +407,7 @@ "@zenstackhq/language": "workspace:*", "@zenstackhq/runtime": "workspace:*", "@zenstackhq/sdk": "workspace:*", + "@zenstackhq/testtools": "workspace:*", "async-exit-hook": "^2.0.1", "change-case": "^4.1.2", "chevrotain": "^9.1.0", From 51c094e329df0d1ebb28239d5fe5ff4608065280 Mon Sep 17 00:00:00 2001 From: Yiming Date: Mon, 13 Mar 2023 23:36:17 +0800 Subject: [PATCH 2/3] feat: zod plugin and zod validation for open-api requests (#262) --- package.json | 2 +- packages/language/package.json | 2 +- packages/next/package.json | 2 +- packages/plugins/openapi/openapi.yaml | 3009 ----------------- packages/plugins/openapi/package.json | 2 +- packages/plugins/react/package.json | 2 +- packages/plugins/trpc/package.json | 2 +- packages/plugins/trpc/src/zod/generator.ts | 31 +- .../trpc/src/zod/helpers/aggregate-helpers.ts | 78 - .../trpc/src/zod/helpers/comments-helpers.ts | 112 - .../plugins/trpc/src/zod/helpers/helpers.ts | 49 - .../trpc/src/zod/helpers/include-helpers.ts | 88 - .../plugins/trpc/src/zod/helpers/index.ts | 4 - .../trpc/src/zod/helpers/model-helpers.ts | 36 - .../trpc/src/zod/helpers/modelArgs-helpers.ts | 73 - .../trpc/src/zod/helpers/mongodb-helpers.ts | 73 - .../trpc/src/zod/helpers/select-helpers.ts | 155 - packages/plugins/trpc/src/zod/transformer.ts | 56 +- packages/runtime/package.json | 2 +- packages/runtime/src/enhancements/preset.ts | 4 +- packages/runtime/src/zod.ts | 19 + packages/schema/package.json | 2 +- packages/schema/src/cli/plugin-runner.ts | 10 +- packages/schema/src/plugins/zod/generator.ts | 111 + packages/schema/src/plugins/zod/index.ts | 10 + .../schema/src/plugins/zod/transformer.ts | 610 ++++ packages/schema/src/plugins/zod/types.ts | 24 + .../schema/src/plugins/zod/utils/removeDir.ts | 15 + packages/schema/src/res/stdlib.zmodel | 10 + .../tests/generator/prisma-generator.test.ts | 10 +- packages/schema/tests/schema/cal-com.zmodel | 4 +- packages/sdk/package.json | 6 +- packages/sdk/src/code-gen.ts | 38 + packages/sdk/src/dmmf-helpers/index.ts | 1 + .../src/dmmf-helpers/missing-types-helper.ts | 16 + packages/sdk/src/index.ts | 1 + packages/server/package.json | 10 +- packages/server/src/fastify/plugin.ts | 7 + packages/server/src/openapi/index.ts | 62 +- packages/server/tests/fastify-plugin.test.ts | 8 +- packages/server/tests/open-api.test.ts | 172 + packages/testtools/package.json | 2 +- packages/testtools/src/schema.ts | 11 +- pnpm-lock.yaml | 8 + tests/integration/test-run/package-lock.json | 4 +- tests/integration/tests/schema/cal-com.zmodel | 9 +- tests/integration/tests/schema/todo.zmodel | 9 +- 47 files changed, 1182 insertions(+), 3789 deletions(-) delete mode 100644 packages/plugins/openapi/openapi.yaml delete mode 100644 packages/plugins/trpc/src/zod/helpers/aggregate-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/comments-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/include-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/index.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/model-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/modelArgs-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/mongodb-helpers.ts delete mode 100644 packages/plugins/trpc/src/zod/helpers/select-helpers.ts create mode 100644 packages/runtime/src/zod.ts create mode 100644 packages/schema/src/plugins/zod/generator.ts create mode 100644 packages/schema/src/plugins/zod/index.ts create mode 100644 packages/schema/src/plugins/zod/transformer.ts create mode 100644 packages/schema/src/plugins/zod/types.ts create mode 100644 packages/schema/src/plugins/zod/utils/removeDir.ts create mode 100644 packages/sdk/src/code-gen.ts create mode 100644 packages/sdk/src/dmmf-helpers/missing-types-helper.ts create mode 100644 packages/server/tests/open-api.test.ts diff --git a/package.json b/package.json index e49210d56..d8f040dcf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/language/package.json b/packages/language/package.json index c592bcda9..d0c886a33 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/next/package.json b/packages/next/package.json index 58119e37a..8d3fe8281 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/next", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "displayName": "ZenStack Next.js integration", "description": "ZenStack Next.js integration", "homepage": "https://zenstack.dev", diff --git a/packages/plugins/openapi/openapi.yaml b/packages/plugins/openapi/openapi.yaml deleted file mode 100644 index 3cfe0662c..000000000 --- a/packages/plugins/openapi/openapi.yaml +++ /dev/null @@ -1,3009 +0,0 @@ -openapi: 3.0.0 -info: - title: ZenStack Generated API - version: 1.0.0 -components: - schemas: - Role: - type: string - enum: - - USER - - ADMIN - PostScalarFieldEnum: - type: string - enum: - - id - - createdAt - - updatedAt - - title - - authorId - - published - - viewCount - QueryMode: - type: string - enum: - - default - - insensitive - SortOrder: - type: string - enum: - - asc - - desc - UserScalarFieldEnum: - type: string - enum: - - id - - createdAt - - updatedAt - - email - - role - User: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - posts: - type: array - items: - $ref: "#/components/schemas/Post" - required: - - id - - createdAt - - updatedAt - - email - - role - Post: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - author: - $ref: "#/components/schemas/User" - authorId: - type: string - published: - type: boolean - viewCount: - type: integer - required: - - id - - createdAt - - updatedAt - - title - - published - - viewCount - UserWhereInput: - type: object - properties: - AND: - oneOf: - - $ref: "#/components/schemas/UserWhereInput" - - type: array - items: - $ref: "#/components/schemas/UserWhereInput" - OR: - type: array - items: - $ref: "#/components/schemas/UserWhereInput" - NOT: - oneOf: - - $ref: "#/components/schemas/UserWhereInput" - - type: array - items: - $ref: "#/components/schemas/UserWhereInput" - id: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - createdAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - updatedAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - email: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - role: - oneOf: - - $ref: "#/components/schemas/EnumRoleFilter" - - $ref: "#/components/schemas/Role" - posts: - $ref: "#/components/schemas/PostListRelationFilter" - UserOrderByWithRelationInput: - type: object - properties: - id: - $ref: "#/components/schemas/SortOrder" - createdAt: - $ref: "#/components/schemas/SortOrder" - updatedAt: - $ref: "#/components/schemas/SortOrder" - email: - $ref: "#/components/schemas/SortOrder" - role: - $ref: "#/components/schemas/SortOrder" - posts: - $ref: "#/components/schemas/PostOrderByRelationAggregateInput" - UserWhereUniqueInput: - type: object - properties: - id: - type: string - email: - type: string - UserScalarWhereWithAggregatesInput: - type: object - properties: - AND: - oneOf: - - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - - type: array - items: - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - OR: - type: array - items: - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - NOT: - oneOf: - - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - - type: array - items: - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - id: - oneOf: - - $ref: "#/components/schemas/StringWithAggregatesFilter" - - type: string - createdAt: - oneOf: - - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" - - type: string - format: date-time - updatedAt: - oneOf: - - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" - - type: string - format: date-time - email: - oneOf: - - $ref: "#/components/schemas/StringWithAggregatesFilter" - - type: string - role: - oneOf: - - $ref: "#/components/schemas/EnumRoleWithAggregatesFilter" - - $ref: "#/components/schemas/Role" - PostWhereInput: - type: object - properties: - AND: - oneOf: - - $ref: "#/components/schemas/PostWhereInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereInput" - OR: - type: array - items: - $ref: "#/components/schemas/PostWhereInput" - NOT: - oneOf: - - $ref: "#/components/schemas/PostWhereInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereInput" - id: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - createdAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - updatedAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - title: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - author: - oneOf: - - $ref: "#/components/schemas/UserRelationFilter" - - $ref: "#/components/schemas/UserWhereInput" - authorId: - oneOf: - - $ref: "#/components/schemas/StringNullableFilter" - - type: string - published: - oneOf: - - $ref: "#/components/schemas/BoolFilter" - - type: boolean - viewCount: - oneOf: - - $ref: "#/components/schemas/IntFilter" - - type: integer - PostOrderByWithRelationInput: - type: object - properties: - id: - $ref: "#/components/schemas/SortOrder" - createdAt: - $ref: "#/components/schemas/SortOrder" - updatedAt: - $ref: "#/components/schemas/SortOrder" - title: - $ref: "#/components/schemas/SortOrder" - author: - $ref: "#/components/schemas/UserOrderByWithRelationInput" - authorId: - $ref: "#/components/schemas/SortOrder" - published: - $ref: "#/components/schemas/SortOrder" - viewCount: - $ref: "#/components/schemas/SortOrder" - PostWhereUniqueInput: - type: object - properties: - id: - type: string - PostScalarWhereWithAggregatesInput: - type: object - properties: - AND: - oneOf: - - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - - type: array - items: - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - OR: - type: array - items: - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - NOT: - oneOf: - - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - - type: array - items: - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - id: - oneOf: - - $ref: "#/components/schemas/StringWithAggregatesFilter" - - type: string - createdAt: - oneOf: - - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" - - type: string - format: date-time - updatedAt: - oneOf: - - $ref: "#/components/schemas/DateTimeWithAggregatesFilter" - - type: string - format: date-time - title: - oneOf: - - $ref: "#/components/schemas/StringWithAggregatesFilter" - - type: string - authorId: - oneOf: - - $ref: "#/components/schemas/StringNullableWithAggregatesFilter" - - type: string - published: - oneOf: - - $ref: "#/components/schemas/BoolWithAggregatesFilter" - - type: boolean - viewCount: - oneOf: - - $ref: "#/components/schemas/IntWithAggregatesFilter" - - type: integer - UserCreateInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - posts: - $ref: "#/components/schemas/PostCreateNestedManyWithoutAuthorInput" - required: - - id - - email - UserUpdateInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - email: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - role: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" - posts: - $ref: "#/components/schemas/PostUpdateManyWithoutAuthorNestedInput" - UserCreateManyInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - required: - - id - - email - UserUpdateManyMutationInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - email: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - role: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" - PostCreateInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - author: - $ref: "#/components/schemas/UserCreateNestedOneWithoutPostsInput" - published: - type: boolean - viewCount: - type: integer - required: - - id - - title - PostUpdateInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - title: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - author: - $ref: "#/components/schemas/UserUpdateOneWithoutPostsNestedInput" - published: - oneOf: - - type: boolean - - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" - viewCount: - oneOf: - - type: integer - - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" - PostCreateManyInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - authorId: - type: string - published: - type: boolean - viewCount: - type: integer - required: - - id - - title - PostUpdateManyMutationInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - title: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - published: - oneOf: - - type: boolean - - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" - viewCount: - oneOf: - - type: integer - - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" - StringFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - mode: - $ref: "#/components/schemas/QueryMode" - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringFilter" - DateTimeFilter: - type: object - properties: - equals: - type: string - format: date-time - in: - type: array - items: - type: string - format: date-time - notIn: - type: array - items: - type: string - format: date-time - lt: - type: string - format: date-time - lte: - type: string - format: date-time - gt: - type: string - format: date-time - gte: - type: string - format: date-time - not: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/NestedDateTimeFilter" - EnumRoleFilter: - type: object - properties: - equals: - $ref: "#/components/schemas/Role" - in: - type: array - items: - $ref: "#/components/schemas/Role" - notIn: - type: array - items: - $ref: "#/components/schemas/Role" - not: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/NestedEnumRoleFilter" - PostListRelationFilter: - type: object - properties: - every: - $ref: "#/components/schemas/PostWhereInput" - some: - $ref: "#/components/schemas/PostWhereInput" - none: - $ref: "#/components/schemas/PostWhereInput" - BoolFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: "#/components/schemas/NestedBoolFilter" - StringNullableFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - mode: - $ref: "#/components/schemas/QueryMode" - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringNullableFilter" - PostOrderByRelationAggregateInput: - type: object - properties: - _count: - $ref: "#/components/schemas/SortOrder" - StringWithAggregatesFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - mode: - $ref: "#/components/schemas/QueryMode" - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedStringFilter" - _max: - $ref: "#/components/schemas/NestedStringFilter" - DateTimeWithAggregatesFilter: - type: object - properties: - equals: - type: string - format: date-time - in: - type: array - items: - type: string - format: date-time - notIn: - type: array - items: - type: string - format: date-time - lt: - type: string - format: date-time - lte: - type: string - format: date-time - gt: - type: string - format: date-time - gte: - type: string - format: date-time - not: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/NestedDateTimeWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedDateTimeFilter" - _max: - $ref: "#/components/schemas/NestedDateTimeFilter" - EnumRoleWithAggregatesFilter: - type: object - properties: - equals: - $ref: "#/components/schemas/Role" - in: - type: array - items: - $ref: "#/components/schemas/Role" - notIn: - type: array - items: - $ref: "#/components/schemas/Role" - not: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/NestedEnumRoleWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedEnumRoleFilter" - _max: - $ref: "#/components/schemas/NestedEnumRoleFilter" - BoolWithAggregatesFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: "#/components/schemas/NestedBoolWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedBoolFilter" - _max: - $ref: "#/components/schemas/NestedBoolFilter" - StringNullableWithAggregatesFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - mode: - $ref: "#/components/schemas/QueryMode" - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringNullableWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntNullableFilter" - _min: - $ref: "#/components/schemas/NestedStringNullableFilter" - _max: - $ref: "#/components/schemas/NestedStringNullableFilter" - UserRelationFilter: - type: object - properties: - is: - $ref: "#/components/schemas/UserWhereInput" - isNot: - $ref: "#/components/schemas/UserWhereInput" - IntFilter: - type: object - properties: - equals: - type: integer - in: - type: array - items: - type: integer - notIn: - type: array - items: - type: integer - lt: - type: integer - lte: - type: integer - gt: - type: integer - gte: - type: integer - not: - oneOf: - - type: integer - - $ref: "#/components/schemas/NestedIntFilter" - IntWithAggregatesFilter: - type: object - properties: - equals: - type: integer - in: - type: array - items: - type: integer - notIn: - type: array - items: - type: integer - lt: - type: integer - lte: - type: integer - gt: - type: integer - gte: - type: integer - not: - oneOf: - - type: integer - - $ref: "#/components/schemas/NestedIntWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _avg: - $ref: "#/components/schemas/NestedFloatFilter" - _sum: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedIntFilter" - _max: - $ref: "#/components/schemas/NestedIntFilter" - PostCreateNestedManyWithoutAuthorInput: - type: object - properties: - create: - oneOf: - - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - connectOrCreate: - oneOf: - - $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" - createMany: - $ref: "#/components/schemas/PostCreateManyAuthorInputEnvelope" - connect: - oneOf: - - $ref: "#/components/schemas/PostWhereUniqueInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereUniqueInput" - StringFieldUpdateOperationsInput: - type: object - properties: - set: - type: string - DateTimeFieldUpdateOperationsInput: - type: object - properties: - set: - type: string - format: date-time - EnumRoleFieldUpdateOperationsInput: - type: object - properties: - set: - $ref: "#/components/schemas/Role" - PostUpdateManyWithoutAuthorNestedInput: - type: object - properties: - create: - oneOf: - - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - connectOrCreate: - oneOf: - - $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostCreateOrConnectWithoutAuthorInput" - upsert: - oneOf: - - $ref: "#/components/schemas/PostUpsertWithWhereUniqueWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostUpsertWithWhereUniqueWithoutAuthorInput" - createMany: - $ref: "#/components/schemas/PostCreateManyAuthorInputEnvelope" - set: - oneOf: - - $ref: "#/components/schemas/PostWhereUniqueInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereUniqueInput" - disconnect: - oneOf: - - $ref: "#/components/schemas/PostWhereUniqueInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereUniqueInput" - delete: - oneOf: - - $ref: "#/components/schemas/PostWhereUniqueInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereUniqueInput" - connect: - oneOf: - - $ref: "#/components/schemas/PostWhereUniqueInput" - - type: array - items: - $ref: "#/components/schemas/PostWhereUniqueInput" - update: - oneOf: - - $ref: "#/components/schemas/PostUpdateWithWhereUniqueWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostUpdateWithWhereUniqueWithoutAuthorInput" - updateMany: - oneOf: - - $ref: "#/components/schemas/PostUpdateManyWithWhereWithoutAuthorInput" - - type: array - items: - $ref: "#/components/schemas/PostUpdateManyWithWhereWithoutAuthorInput" - deleteMany: - oneOf: - - $ref: "#/components/schemas/PostScalarWhereInput" - - type: array - items: - $ref: "#/components/schemas/PostScalarWhereInput" - BoolFieldUpdateOperationsInput: - type: object - properties: - set: - type: boolean - UserCreateNestedOneWithoutPostsInput: - type: object - properties: - create: - oneOf: - - $ref: "#/components/schemas/UserCreateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" - connectOrCreate: - $ref: "#/components/schemas/UserCreateOrConnectWithoutPostsInput" - connect: - $ref: "#/components/schemas/UserWhereUniqueInput" - UserUpdateOneWithoutPostsNestedInput: - type: object - properties: - create: - oneOf: - - $ref: "#/components/schemas/UserCreateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" - connectOrCreate: - $ref: "#/components/schemas/UserCreateOrConnectWithoutPostsInput" - upsert: - $ref: "#/components/schemas/UserUpsertWithoutPostsInput" - disconnect: - type: boolean - delete: - type: boolean - connect: - $ref: "#/components/schemas/UserWhereUniqueInput" - update: - oneOf: - - $ref: "#/components/schemas/UserUpdateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedUpdateWithoutPostsInput" - IntFieldUpdateOperationsInput: - type: object - properties: - set: - type: integer - increment: - type: integer - decrement: - type: integer - multiply: - type: integer - divide: - type: integer - NestedStringFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringFilter" - NestedDateTimeFilter: - type: object - properties: - equals: - type: string - format: date-time - in: - type: array - items: - type: string - format: date-time - notIn: - type: array - items: - type: string - format: date-time - lt: - type: string - format: date-time - lte: - type: string - format: date-time - gt: - type: string - format: date-time - gte: - type: string - format: date-time - not: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/NestedDateTimeFilter" - NestedEnumRoleFilter: - type: object - properties: - equals: - $ref: "#/components/schemas/Role" - in: - type: array - items: - $ref: "#/components/schemas/Role" - notIn: - type: array - items: - $ref: "#/components/schemas/Role" - not: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/NestedEnumRoleFilter" - NestedBoolFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: "#/components/schemas/NestedBoolFilter" - NestedStringNullableFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringNullableFilter" - NestedStringWithAggregatesFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedStringFilter" - _max: - $ref: "#/components/schemas/NestedStringFilter" - NestedIntFilter: - type: object - properties: - equals: - type: integer - in: - type: array - items: - type: integer - notIn: - type: array - items: - type: integer - lt: - type: integer - lte: - type: integer - gt: - type: integer - gte: - type: integer - not: - oneOf: - - type: integer - - $ref: "#/components/schemas/NestedIntFilter" - NestedDateTimeWithAggregatesFilter: - type: object - properties: - equals: - type: string - format: date-time - in: - type: array - items: - type: string - format: date-time - notIn: - type: array - items: - type: string - format: date-time - lt: - type: string - format: date-time - lte: - type: string - format: date-time - gt: - type: string - format: date-time - gte: - type: string - format: date-time - not: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/NestedDateTimeWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedDateTimeFilter" - _max: - $ref: "#/components/schemas/NestedDateTimeFilter" - NestedEnumRoleWithAggregatesFilter: - type: object - properties: - equals: - $ref: "#/components/schemas/Role" - in: - type: array - items: - $ref: "#/components/schemas/Role" - notIn: - type: array - items: - $ref: "#/components/schemas/Role" - not: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/NestedEnumRoleWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedEnumRoleFilter" - _max: - $ref: "#/components/schemas/NestedEnumRoleFilter" - NestedBoolWithAggregatesFilter: - type: object - properties: - equals: - type: boolean - not: - oneOf: - - type: boolean - - $ref: "#/components/schemas/NestedBoolWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedBoolFilter" - _max: - $ref: "#/components/schemas/NestedBoolFilter" - NestedStringNullableWithAggregatesFilter: - type: object - properties: - equals: - type: string - in: - type: array - items: - type: string - notIn: - type: array - items: - type: string - lt: - type: string - lte: - type: string - gt: - type: string - gte: - type: string - contains: - type: string - startsWith: - type: string - endsWith: - type: string - not: - oneOf: - - type: string - - $ref: "#/components/schemas/NestedStringNullableWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntNullableFilter" - _min: - $ref: "#/components/schemas/NestedStringNullableFilter" - _max: - $ref: "#/components/schemas/NestedStringNullableFilter" - NestedIntNullableFilter: - type: object - properties: - equals: - type: integer - in: - type: array - items: - type: integer - notIn: - type: array - items: - type: integer - lt: - type: integer - lte: - type: integer - gt: - type: integer - gte: - type: integer - not: - oneOf: - - type: integer - - $ref: "#/components/schemas/NestedIntNullableFilter" - NestedIntWithAggregatesFilter: - type: object - properties: - equals: - type: integer - in: - type: array - items: - type: integer - notIn: - type: array - items: - type: integer - lt: - type: integer - lte: - type: integer - gt: - type: integer - gte: - type: integer - not: - oneOf: - - type: integer - - $ref: "#/components/schemas/NestedIntWithAggregatesFilter" - _count: - $ref: "#/components/schemas/NestedIntFilter" - _avg: - $ref: "#/components/schemas/NestedFloatFilter" - _sum: - $ref: "#/components/schemas/NestedIntFilter" - _min: - $ref: "#/components/schemas/NestedIntFilter" - _max: - $ref: "#/components/schemas/NestedIntFilter" - NestedFloatFilter: - type: object - properties: - equals: - type: number - in: - type: array - items: - type: number - notIn: - type: array - items: - type: number - lt: - type: number - lte: - type: number - gt: - type: number - gte: - type: number - not: - oneOf: - - type: number - - $ref: "#/components/schemas/NestedFloatFilter" - PostCreateWithoutAuthorInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - published: - type: boolean - viewCount: - type: integer - required: - - id - - title - PostUncheckedCreateWithoutAuthorInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - published: - type: boolean - viewCount: - type: integer - required: - - id - - title - PostCreateOrConnectWithoutAuthorInput: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - create: - oneOf: - - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - required: - - where - - create - PostCreateManyAuthorInputEnvelope: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/PostCreateManyAuthorInput" - skipDuplicates: - type: boolean - required: - - data - PostUpsertWithWhereUniqueWithoutAuthorInput: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - update: - oneOf: - - $ref: "#/components/schemas/PostUpdateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedUpdateWithoutAuthorInput" - create: - oneOf: - - $ref: "#/components/schemas/PostCreateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedCreateWithoutAuthorInput" - required: - - where - - update - - create - PostUpdateWithWhereUniqueWithoutAuthorInput: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - data: - oneOf: - - $ref: "#/components/schemas/PostUpdateWithoutAuthorInput" - - $ref: "#/components/schemas/PostUncheckedUpdateWithoutAuthorInput" - required: - - where - - data - PostUpdateManyWithWhereWithoutAuthorInput: - type: object - properties: - where: - $ref: "#/components/schemas/PostScalarWhereInput" - data: - oneOf: - - $ref: "#/components/schemas/PostUpdateManyMutationInput" - - $ref: "#/components/schemas/PostUncheckedUpdateManyWithoutPostsInput" - required: - - where - - data - PostScalarWhereInput: - type: object - properties: - AND: - oneOf: - - $ref: "#/components/schemas/PostScalarWhereInput" - - type: array - items: - $ref: "#/components/schemas/PostScalarWhereInput" - OR: - type: array - items: - $ref: "#/components/schemas/PostScalarWhereInput" - NOT: - oneOf: - - $ref: "#/components/schemas/PostScalarWhereInput" - - type: array - items: - $ref: "#/components/schemas/PostScalarWhereInput" - id: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - createdAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - updatedAt: - oneOf: - - $ref: "#/components/schemas/DateTimeFilter" - - type: string - format: date-time - title: - oneOf: - - $ref: "#/components/schemas/StringFilter" - - type: string - authorId: - oneOf: - - $ref: "#/components/schemas/StringNullableFilter" - - type: string - published: - oneOf: - - $ref: "#/components/schemas/BoolFilter" - - type: boolean - viewCount: - oneOf: - - $ref: "#/components/schemas/IntFilter" - - type: integer - UserCreateWithoutPostsInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - required: - - id - - email - UserUncheckedCreateWithoutPostsInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - required: - - id - - email - UserCreateOrConnectWithoutPostsInput: - type: object - properties: - where: - $ref: "#/components/schemas/UserWhereUniqueInput" - create: - oneOf: - - $ref: "#/components/schemas/UserCreateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" - required: - - where - - create - UserUpsertWithoutPostsInput: - type: object - properties: - update: - oneOf: - - $ref: "#/components/schemas/UserUpdateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedUpdateWithoutPostsInput" - create: - oneOf: - - $ref: "#/components/schemas/UserCreateWithoutPostsInput" - - $ref: "#/components/schemas/UserUncheckedCreateWithoutPostsInput" - required: - - update - - create - UserUpdateWithoutPostsInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - email: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - role: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" - UserUncheckedUpdateWithoutPostsInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - email: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - role: - oneOf: - - $ref: "#/components/schemas/Role" - - $ref: "#/components/schemas/EnumRoleFieldUpdateOperationsInput" - PostCreateManyAuthorInput: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - published: - type: boolean - viewCount: - type: integer - required: - - id - - title - PostUpdateWithoutAuthorInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - title: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - published: - oneOf: - - type: boolean - - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" - viewCount: - oneOf: - - type: integer - - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" - PostUncheckedUpdateWithoutAuthorInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - title: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - published: - oneOf: - - type: boolean - - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" - viewCount: - oneOf: - - type: integer - - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" - PostUncheckedUpdateManyWithoutPostsInput: - type: object - properties: - id: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - createdAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - updatedAt: - oneOf: - - type: string - format: date-time - - $ref: "#/components/schemas/DateTimeFieldUpdateOperationsInput" - title: - oneOf: - - type: string - - $ref: "#/components/schemas/StringFieldUpdateOperationsInput" - published: - oneOf: - - type: boolean - - $ref: "#/components/schemas/BoolFieldUpdateOperationsInput" - viewCount: - oneOf: - - type: integer - - $ref: "#/components/schemas/IntFieldUpdateOperationsInput" - UserArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - UserInclude: - type: object - properties: - posts: - oneOf: - - type: boolean - - $ref: "#/components/schemas/PostFindManyArgs" - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserCountOutputTypeArgs" - PostInclude: - type: object - properties: - author: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserArgs" - UserCountOutputTypeSelect: - type: object - properties: - posts: - type: boolean - UserCountOutputTypeArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserCountOutputTypeSelect" - UserSelect: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - email: - type: boolean - role: - type: boolean - posts: - oneOf: - - type: boolean - - $ref: "#/components/schemas/PostFindManyArgs" - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserCountOutputTypeArgs" - PostSelect: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - title: - type: boolean - author: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserArgs" - authorId: - type: boolean - published: - type: boolean - viewCount: - type: boolean - UserCountAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - email: - type: boolean - role: - type: boolean - _all: - type: boolean - UserMinAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - email: - type: boolean - role: - type: boolean - UserMaxAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - email: - type: boolean - role: - type: boolean - PostCountAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - title: - type: boolean - authorId: - type: boolean - published: - type: boolean - viewCount: - type: boolean - _all: - type: boolean - PostAvgAggregateInput: - type: object - properties: - viewCount: - type: boolean - PostSumAggregateInput: - type: object - properties: - viewCount: - type: boolean - PostMinAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - title: - type: boolean - authorId: - type: boolean - published: - type: boolean - viewCount: - type: boolean - PostMaxAggregateInput: - type: object - properties: - id: - type: boolean - createdAt: - type: boolean - updatedAt: - type: boolean - title: - type: boolean - authorId: - type: boolean - published: - type: boolean - viewCount: - type: boolean - AggregateUser: - type: object - properties: - _count: - $ref: "#/components/schemas/UserCountAggregateOutputType" - _min: - $ref: "#/components/schemas/UserMinAggregateOutputType" - _max: - $ref: "#/components/schemas/UserMaxAggregateOutputType" - UserGroupByOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - _count: - $ref: "#/components/schemas/UserCountAggregateOutputType" - _min: - $ref: "#/components/schemas/UserMinAggregateOutputType" - _max: - $ref: "#/components/schemas/UserMaxAggregateOutputType" - required: - - id - - createdAt - - updatedAt - - email - - role - AggregatePost: - type: object - properties: - _count: - $ref: "#/components/schemas/PostCountAggregateOutputType" - _avg: - $ref: "#/components/schemas/PostAvgAggregateOutputType" - _sum: - $ref: "#/components/schemas/PostSumAggregateOutputType" - _min: - $ref: "#/components/schemas/PostMinAggregateOutputType" - _max: - $ref: "#/components/schemas/PostMaxAggregateOutputType" - PostGroupByOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - authorId: - type: string - published: - type: boolean - viewCount: - type: integer - _count: - $ref: "#/components/schemas/PostCountAggregateOutputType" - _avg: - $ref: "#/components/schemas/PostAvgAggregateOutputType" - _sum: - $ref: "#/components/schemas/PostSumAggregateOutputType" - _min: - $ref: "#/components/schemas/PostMinAggregateOutputType" - _max: - $ref: "#/components/schemas/PostMaxAggregateOutputType" - required: - - id - - createdAt - - updatedAt - - title - - published - - viewCount - UserCountAggregateOutputType: - type: object - properties: - id: - type: integer - createdAt: - type: integer - updatedAt: - type: integer - email: - type: integer - role: - type: integer - _all: - type: integer - required: - - id - - createdAt - - updatedAt - - email - - role - - _all - UserMinAggregateOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - UserMaxAggregateOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - email: - type: string - role: - $ref: "#/components/schemas/Role" - PostCountAggregateOutputType: - type: object - properties: - id: - type: integer - createdAt: - type: integer - updatedAt: - type: integer - title: - type: integer - authorId: - type: integer - published: - type: integer - viewCount: - type: integer - _all: - type: integer - required: - - id - - createdAt - - updatedAt - - title - - authorId - - published - - viewCount - - _all - PostAvgAggregateOutputType: - type: object - properties: - viewCount: - type: number - PostSumAggregateOutputType: - type: object - properties: - viewCount: - type: integer - PostMinAggregateOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - authorId: - type: string - published: - type: boolean - viewCount: - type: integer - PostMaxAggregateOutputType: - type: object - properties: - id: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - title: - type: string - authorId: - type: string - published: - type: boolean - viewCount: - type: integer - BatchPayload: - type: object - properties: - count: - type: integer - UserCreateArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - data: - $ref: "#/components/schemas/UserCreateInput" - UserCreateManyArgs: - type: object - properties: - data: - $ref: "#/components/schemas/UserCreateManyInput" - UserFindUniqueArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereUniqueInput" - UserFindFirstArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereInput" - UserFindManyArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereInput" - UserUpdateArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereUniqueInput" - data: - $ref: "#/components/schemas/UserUpdateInput" - UserUpdateManyArgs: - type: object - properties: - where: - $ref: "#/components/schemas/UserWhereInput" - data: - $ref: "#/components/schemas/UserUpdateManyMutationInput" - UserUpsertArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereUniqueInput" - create: - $ref: "#/components/schemas/UserCreateInput" - update: - $ref: "#/components/schemas/UserUpdateInput" - UserDeleteUniqueArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - include: - $ref: "#/components/schemas/UserInclude" - where: - $ref: "#/components/schemas/UserWhereUniqueInput" - UserDeleteManyArgs: - type: object - properties: - where: - $ref: "#/components/schemas/UserWhereInput" - UserCountArgs: - type: object - properties: - select: - $ref: "#/components/schemas/UserSelect" - where: - $ref: "#/components/schemas/UserWhereInput" - UserAggregateArgs: - type: object - properties: - where: - $ref: "#/components/schemas/UserWhereInput" - orderBy: - $ref: "#/components/schemas/UserOrderByWithRelationInput" - cursor: - $ref: "#/components/schemas/UserWhereUniqueInput" - take: - type: integer - skip: - type: integer - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserCountAggregateInput" - _min: - $ref: "#/components/schemas/UserMinAggregateInput" - _max: - $ref: "#/components/schemas/UserMaxAggregateInput" - UserGroupByArgs: - type: object - properties: - where: - $ref: "#/components/schemas/UserWhereInput" - orderBy: - $ref: "#/components/schemas/UserOrderByWithRelationInput" - by: - $ref: "#/components/schemas/UserScalarFieldEnum" - having: - $ref: "#/components/schemas/UserScalarWhereWithAggregatesInput" - take: - type: integer - skip: - type: integer - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/UserCountAggregateInput" - _min: - $ref: "#/components/schemas/UserMinAggregateInput" - _max: - $ref: "#/components/schemas/UserMaxAggregateInput" - PostCreateArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - data: - $ref: "#/components/schemas/PostCreateInput" - PostCreateManyArgs: - type: object - properties: - data: - $ref: "#/components/schemas/PostCreateManyInput" - PostFindUniqueArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - PostFindFirstArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereInput" - PostFindManyArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereInput" - PostUpdateArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - data: - $ref: "#/components/schemas/PostUpdateInput" - PostUpdateManyArgs: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereInput" - data: - $ref: "#/components/schemas/PostUpdateManyMutationInput" - PostUpsertArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - create: - $ref: "#/components/schemas/PostCreateInput" - update: - $ref: "#/components/schemas/PostUpdateInput" - PostDeleteUniqueArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - include: - $ref: "#/components/schemas/PostInclude" - where: - $ref: "#/components/schemas/PostWhereUniqueInput" - PostDeleteManyArgs: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereInput" - PostCountArgs: - type: object - properties: - select: - $ref: "#/components/schemas/PostSelect" - where: - $ref: "#/components/schemas/PostWhereInput" - PostAggregateArgs: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereInput" - orderBy: - $ref: "#/components/schemas/PostOrderByWithRelationInput" - cursor: - $ref: "#/components/schemas/PostWhereUniqueInput" - take: - type: integer - skip: - type: integer - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/PostCountAggregateInput" - _min: - $ref: "#/components/schemas/PostMinAggregateInput" - _max: - $ref: "#/components/schemas/PostMaxAggregateInput" - _sum: - $ref: "#/components/schemas/PostSumAggregateInput" - _avg: - $ref: "#/components/schemas/PostAvgAggregateInput" - PostGroupByArgs: - type: object - properties: - where: - $ref: "#/components/schemas/PostWhereInput" - orderBy: - $ref: "#/components/schemas/PostOrderByWithRelationInput" - by: - $ref: "#/components/schemas/PostScalarFieldEnum" - having: - $ref: "#/components/schemas/PostScalarWhereWithAggregatesInput" - take: - type: integer - skip: - type: integer - _count: - oneOf: - - type: boolean - - $ref: "#/components/schemas/PostCountAggregateInput" - _min: - $ref: "#/components/schemas/PostMinAggregateInput" - _max: - $ref: "#/components/schemas/PostMaxAggregateInput" - _sum: - $ref: "#/components/schemas/PostSumAggregateInput" - _avg: - $ref: "#/components/schemas/PostAvgAggregateInput" -paths: - /user/create: - post: - operationId: createUser - responses: - "201": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Create a new User - content: - application/json: - schema: - $ref: "#/components/schemas/UserCreateArgs" - /user/createMany: - post: - operationId: createManyUser - responses: - "201": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Create several User - content: - application/json: - schema: - $ref: "#/components/schemas/UserCreateManyArgs" - /user/findUnique: - get: - operationId: findUniqueUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserFindUniqueArgs" - /user/findFirst: - get: - operationId: findFirstUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserFindFirstArgs" - /user/findMany: - get: - operationId: findManyUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserFindManyArgs" - /user/update: - patch: - operationId: updateUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Update a User - content: - application/json: - schema: - $ref: "#/components/schemas/UserUpdateArgs" - /user/updateMany: - patch: - operationId: updateManyUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Update Users matching the given condition - content: - application/json: - schema: - $ref: "#/components/schemas/UserUpdateManyArgs" - /user/upsert: - post: - operationId: upsertUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Upsert a User - content: - application/json: - schema: - $ref: "#/components/schemas/UserUpsertArgs" - /user/delete: - delete: - operationId: deleteUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/User" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserDeleteUniqueArgs" - /user/deleteMany: - delete: - operationId: deleteManyUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserDeleteManyArgs" - /user/count: - get: - operationId: countUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - oneOf: - - type: integer - - $ref: "#/components/schemas/UserCountAggregateOutputType" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserCountArgs" - /user/aggregate: - get: - operationId: aggregateUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/AggregateUser" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserAggregateArgs" - /user/groupBy: - get: - operationId: groupByUser - responses: - "200": - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/UserGroupByOutputType" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/UserGroupByArgs" - /post/create: - post: - operationId: createPost - responses: - "201": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Create a new Post - content: - application/json: - schema: - $ref: "#/components/schemas/PostCreateArgs" - /post/createMany: - post: - operationId: createManyPost - responses: - "201": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Create several Post - content: - application/json: - schema: - $ref: "#/components/schemas/PostCreateManyArgs" - /post/findUnique: - get: - operationId: findUniquePost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostFindUniqueArgs" - /post/findFirst: - get: - operationId: findFirstPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostFindFirstArgs" - /post/findMany: - get: - operationId: findManyPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostFindManyArgs" - /post/update: - patch: - operationId: updatePost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Update a Post - content: - application/json: - schema: - $ref: "#/components/schemas/PostUpdateArgs" - /post/updateMany: - patch: - operationId: updateManyPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Update Posts matching the given condition - content: - application/json: - schema: - $ref: "#/components/schemas/PostUpdateManyArgs" - /post/upsert: - post: - operationId: upsertPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - requestBody: - description: Upsert a Post - content: - application/json: - schema: - $ref: "#/components/schemas/PostUpsertArgs" - /post/delete: - delete: - operationId: deletePost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/Post" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostDeleteUniqueArgs" - /post/deleteMany: - delete: - operationId: deleteManyPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/BatchPayload" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostDeleteManyArgs" - /post/count: - get: - operationId: countPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - oneOf: - - type: integer - - $ref: "#/components/schemas/PostCountAggregateOutputType" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostCountArgs" - /post/aggregate: - get: - operationId: aggregatePost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - $ref: "#/components/schemas/AggregatePost" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostAggregateArgs" - /post/groupBy: - get: - operationId: groupByPost - responses: - "200": - description: Successful operation - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/PostGroupByOutputType" - "400": - description: Invalid request - "403": - description: Forbidden - parameters: - - name: q - in: query - required: true - schema: - $ref: "#/components/schemas/PostGroupByArgs" diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index c7e608295..3d502a4bf 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": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/react/package.json b/packages/plugins/react/package.json index fda63ff18..74af05145 100644 --- a/packages/plugins/react/package.json +++ b/packages/plugins/react/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/react", "displayName": "ZenStack plugin and runtime for ReactJS", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "ZenStack plugin and runtime for ReactJS", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index f8d7c1601..ffa1ca224 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": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/src/zod/generator.ts b/packages/plugins/trpc/src/zod/generator.ts index 16c8d3fdc..a1cd1018b 100644 --- a/packages/plugins/trpc/src/zod/generator.ts +++ b/packages/plugins/trpc/src/zod/generator.ts @@ -1,17 +1,14 @@ import { ConnectorType, DMMF } from '@prisma/generator-helper'; import { Dictionary } from '@prisma/internals'; -import { PluginOptions, getLiteral } from '@zenstackhq/sdk'; -import { DataSource, Model, isDataSource } from '@zenstackhq/sdk/ast'; -import { promises as fs } from 'fs'; +import { getLiteral, PluginOptions } from '@zenstackhq/sdk'; +import { DataSource, isDataSource, Model } from '@zenstackhq/sdk/ast'; import { addMissingInputObjectTypes, - hideInputObjectTypesAndRelatedFields, - resolveAddMissingInputObjectTypeOptions, - resolveModelsComments, -} from './helpers'; -import { resolveAggregateOperationSupport } from './helpers/aggregate-helpers'; + AggregateOperationSupport, + resolveAggregateOperationSupport, +} from '@zenstackhq/sdk/dmmf-helpers'; +import { promises as fs } from 'fs'; import Transformer from './transformer'; -import { AggregateOperationSupport } from './types'; import removeDir from './utils/removeDir'; export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { @@ -22,11 +19,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. const modelOperations = prismaClientDmmf.mappings.modelOperations; const inputObjectTypes = prismaClientDmmf.schema.inputObjectTypes.prisma; const outputObjectTypes = prismaClientDmmf.schema.outputObjectTypes.prisma; - const enumTypes = prismaClientDmmf.schema.enumTypes; const models: DMMF.Model[] = prismaClientDmmf.datamodel.models; - const hiddenModels: string[] = []; - const hiddenFields: string[] = []; - resolveModelsComments(models, modelOperations, enumTypes, hiddenModels, hiddenFields); await generateEnumSchemas(prismaClientDmmf.schema.enumTypes.prisma, prismaClientDmmf.schema.enumTypes.model ?? []); @@ -41,20 +34,10 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. const generatorConfigOptions: Dictionary = {}; Object.entries(options).forEach(([k, v]) => (generatorConfigOptions[k] = v as string)); - const addMissingInputObjectTypeOptions = resolveAddMissingInputObjectTypeOptions(generatorConfigOptions); - addMissingInputObjectTypes( - inputObjectTypes, - outputObjectTypes, - models, - modelOperations, - dataSourceProvider, - addMissingInputObjectTypeOptions - ); + addMissingInputObjectTypes(inputObjectTypes, outputObjectTypes, models); const aggregateOperationSupport = resolveAggregateOperationSupport(inputObjectTypes); - hideInputObjectTypesAndRelatedFields(inputObjectTypes, hiddenModels, hiddenFields); - await generateObjectSchemas(inputObjectTypes); await generateModelSchemas(models, modelOperations, aggregateOperationSupport); } diff --git a/packages/plugins/trpc/src/zod/helpers/aggregate-helpers.ts b/packages/plugins/trpc/src/zod/helpers/aggregate-helpers.ts deleted file mode 100644 index 23ebd8ddf..000000000 --- a/packages/plugins/trpc/src/zod/helpers/aggregate-helpers.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import { AggregateOperationSupport } from '../types'; - -const isAggregateOutputType = (name: string) => /(?:Count|Avg|Sum|Min|Max)AggregateOutputType$/.test(name); - -export const isAggregateInputType = (name: string) => - name.endsWith('CountAggregateInput') || - name.endsWith('SumAggregateInput') || - name.endsWith('AvgAggregateInput') || - name.endsWith('MinAggregateInput') || - name.endsWith('MaxAggregateInput'); - -export function addMissingInputObjectTypesForAggregate( - inputObjectTypes: DMMF.InputType[], - outputObjectTypes: DMMF.OutputType[] -) { - const aggregateOutputTypes = outputObjectTypes.filter(({ name }) => isAggregateOutputType(name)); - for (const aggregateOutputType of aggregateOutputTypes) { - const name = aggregateOutputType.name.replace(/(?:OutputType|Output)$/, ''); - inputObjectTypes.push({ - constraints: { maxNumFields: null, minNumFields: null }, - name: `${name}Input`, - fields: aggregateOutputType.fields.map((field) => ({ - name: field.name, - isNullable: false, - isRequired: false, - inputTypes: [ - { - isList: false, - type: 'True', - location: 'scalar', - }, - ], - })), - }); - } -} - -export function resolveAggregateOperationSupport(inputObjectTypes: DMMF.InputType[]) { - const aggregateOperationSupport: AggregateOperationSupport = {}; - for (const inputType of inputObjectTypes) { - if (isAggregateInputType(inputType.name)) { - const name = inputType.name.replace('AggregateInput', ''); - if (name.endsWith('Count')) { - const model = name.replace('Count', ''); - aggregateOperationSupport[model] = { - ...aggregateOperationSupport[model], - count: true, - }; - } else if (name.endsWith('Min')) { - const model = name.replace('Min', ''); - aggregateOperationSupport[model] = { - ...aggregateOperationSupport[model], - min: true, - }; - } else if (name.endsWith('Max')) { - const model = name.replace('Max', ''); - aggregateOperationSupport[model] = { - ...aggregateOperationSupport[model], - max: true, - }; - } else if (name.endsWith('Sum')) { - const model = name.replace('Sum', ''); - aggregateOperationSupport[model] = { - ...aggregateOperationSupport[model], - sum: true, - }; - } else if (name.endsWith('Avg')) { - const model = name.replace('Avg', ''); - aggregateOperationSupport[model] = { - ...aggregateOperationSupport[model], - avg: true, - }; - } - } - } - return aggregateOperationSupport; -} diff --git a/packages/plugins/trpc/src/zod/helpers/comments-helpers.ts b/packages/plugins/trpc/src/zod/helpers/comments-helpers.ts deleted file mode 100644 index 13526f53b..000000000 --- a/packages/plugins/trpc/src/zod/helpers/comments-helpers.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; - -const modelAttributeRegex = /(@@Gen\.)+([A-z])+(\()+(.+)+(\))+/; -const attributeNameRegex = /(?:\.)+([A-Za-z])+(?:\()+/; -const attributeArgsRegex = /(?:\()+([A-Za-z])+:+(.+)+(?:\))+/; - -export function resolveModelsComments( - models: DMMF.Model[], - modelOperations: DMMF.ModelMapping[], - enumTypes: { model?: DMMF.SchemaEnum[]; prisma: DMMF.SchemaEnum[] }, - hiddenModels: string[], - hiddenFields: string[] -) { - models = collectHiddenModels(models, hiddenModels); - collectHiddenFields(models, hiddenModels, hiddenFields); - hideModelOperations(models, modelOperations); - hideEnums(enumTypes, hiddenModels); -} - -function collectHiddenModels(models: DMMF.Model[], hiddenModels: string[]) { - return models - .map((model) => { - if (model.documentation) { - const attribute = model.documentation?.match(modelAttributeRegex)?.[0]; - const attributeName = attribute?.match(attributeNameRegex)?.[0]?.slice(1, -1); - if (attributeName !== 'model') model; - const rawAttributeArgs = attribute?.match(attributeArgsRegex)?.[0]?.slice(1, -1); - - const parsedAttributeArgs: Record = {}; - if (rawAttributeArgs) { - const rawAttributeArgsParts = rawAttributeArgs - .split(':') - .map((it) => it.trim()) - .map((part) => (part.startsWith('[') ? part : part.split(','))) - .flat() - .map((it) => it.trim()); - - for (let i = 0; i < rawAttributeArgsParts.length; i += 2) { - const key = rawAttributeArgsParts[i]; - const value = rawAttributeArgsParts[i + 1]; - parsedAttributeArgs[key] = JSON.parse(value); - } - } - if (parsedAttributeArgs.hide) { - hiddenModels.push(model.name); - return null as unknown as DMMF.Model; - } - } - return model; - }) - .filter(Boolean); -} - -function collectHiddenFields(models: DMMF.Model[], hiddenModels: string[], hiddenFields: string[]) { - models.forEach((model) => { - model.fields.forEach((field) => { - if (hiddenModels.includes(field.type)) { - hiddenFields.push(field.name); - if (field.relationFromFields) { - field.relationFromFields.forEach((item) => hiddenFields.push(item)); - } - } - }); - }); -} -function hideEnums(enumTypes: { model?: DMMF.SchemaEnum[]; prisma: DMMF.SchemaEnum[] }, hiddenModels: string[]) { - enumTypes.prisma = enumTypes.prisma.filter((item) => !hiddenModels.find((model) => item.name.startsWith(model))); -} - -function hideModelOperations(models: DMMF.Model[], modelOperations: DMMF.ModelMapping[]) { - let i = modelOperations.length; - while (i >= 0) { - --i; - const modelOperation = modelOperations[i]; - if ( - modelOperation && - !models.find((model) => { - return model.name === modelOperation.model; - }) - ) { - modelOperations.splice(i, 1); - } - } -} - -export function hideInputObjectTypesAndRelatedFields( - inputObjectTypes: DMMF.InputType[], - hiddenModels: string[], - hiddenFields: string[] -) { - let j = inputObjectTypes.length; - while (j >= 0) { - --j; - const inputType = inputObjectTypes[j]; - if ( - inputType && - (hiddenModels.includes(inputType?.meta?.source as string) || - hiddenModels.find((model) => inputType.name.startsWith(model))) - ) { - inputObjectTypes.splice(j, 1); - } else { - let k = inputType?.fields?.length ?? 0; - while (k >= 0) { - --k; - const field = inputType?.fields?.[k]; - if (field && hiddenFields.includes(field.name)) { - inputObjectTypes[j].fields.splice(k, 1); - } - } - } - } -} diff --git a/packages/plugins/trpc/src/zod/helpers/helpers.ts b/packages/plugins/trpc/src/zod/helpers/helpers.ts deleted file mode 100644 index 82895f226..000000000 --- a/packages/plugins/trpc/src/zod/helpers/helpers.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { DMMF, ConnectorType, Dictionary } from '@prisma/generator-helper'; -import Transformer from '../transformer'; -import { addMissingInputObjectTypesForMongoDbRawOpsAndQueries } from './mongodb-helpers'; -import { - addMissingInputObjectTypesForAggregate, - addMissingInputObjectTypesForSelect, - addMissingInputObjectTypesForInclude, - addMissingInputObjectTypesForModelArgs, -} from '@zenstackhq/sdk/dmmf-helpers'; - -interface AddMissingInputObjectTypeOptions { - isGenerateSelect: boolean; - isGenerateInclude: boolean; -} - -export function addMissingInputObjectTypes( - inputObjectTypes: DMMF.InputType[], - outputObjectTypes: DMMF.OutputType[], - models: DMMF.Model[], - modelOperations: DMMF.ModelMapping[], - dataSourceProvider: ConnectorType, - options: AddMissingInputObjectTypeOptions -) { - // TODO: remove once Prisma fix this issue: https://github.com/prisma/prisma/issues/14900 - if (dataSourceProvider === 'mongodb') { - addMissingInputObjectTypesForMongoDbRawOpsAndQueries(modelOperations, outputObjectTypes, inputObjectTypes); - } - addMissingInputObjectTypesForAggregate(inputObjectTypes, outputObjectTypes); - if (options.isGenerateSelect) { - addMissingInputObjectTypesForSelect(inputObjectTypes, outputObjectTypes, models); - Transformer.setIsGenerateSelect(true); - } - if (options.isGenerateSelect || options.isGenerateInclude) { - addMissingInputObjectTypesForModelArgs(inputObjectTypes, models); - } - if (options.isGenerateInclude) { - addMissingInputObjectTypesForInclude(inputObjectTypes, models); - Transformer.setIsGenerateInclude(true); - } -} - -export function resolveAddMissingInputObjectTypeOptions( - generatorConfigOptions: Dictionary -): AddMissingInputObjectTypeOptions { - return { - isGenerateSelect: generatorConfigOptions.isGenerateSelect !== 'false', - isGenerateInclude: generatorConfigOptions.isGenerateInclude !== 'false', - }; -} diff --git a/packages/plugins/trpc/src/zod/helpers/include-helpers.ts b/packages/plugins/trpc/src/zod/helpers/include-helpers.ts deleted file mode 100644 index 686b678a7..000000000 --- a/packages/plugins/trpc/src/zod/helpers/include-helpers.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import { checkIsModelRelationField, checkModelHasModelRelation, checkModelHasManyModelRelation } from './model-helpers'; - -export function addMissingInputObjectTypesForInclude( - inputObjectTypes: DMMF.InputType[], - models: DMMF.Model[], - isGenerateSelect: boolean -) { - // generate input object types necessary to support ModelInclude with relation support - const generatedIncludeInputObjectTypes = generateModelIncludeInputObjectTypes(models, isGenerateSelect); - - for (const includeInputObjectType of generatedIncludeInputObjectTypes) { - inputObjectTypes.push(includeInputObjectType); - } -} -function generateModelIncludeInputObjectTypes(models: DMMF.Model[], isGenerateSelect: boolean) { - const modelIncludeInputObjectTypes: DMMF.InputType[] = []; - for (const model of models) { - const { name: modelName, fields: modelFields } = model; - const fields: DMMF.SchemaArg[] = []; - - for (const modelField of modelFields) { - const { name: modelFieldName, isList, type } = modelField; - - const isRelationField = checkIsModelRelationField(modelField); - - if (isRelationField) { - const field: DMMF.SchemaArg = { - name: modelFieldName, - isRequired: false, - isNullable: false, - inputTypes: [ - { isList: false, type: 'Boolean', location: 'scalar' }, - { - isList: false, - type: isList ? `${type}FindManyArgs` : `${type}Args`, - location: 'inputObjectTypes', - namespace: 'prisma', - }, - ], - }; - fields.push(field); - } - } - - /** - * include is not generated for models that do not have a relation with any other models - * -> continue onto the next model - */ - const hasRelationToAnotherModel = checkModelHasModelRelation(model); - if (!hasRelationToAnotherModel) { - continue; - } - - const hasManyRelationToAnotherModel = checkModelHasManyModelRelation(model); - - const shouldAddCountField = hasManyRelationToAnotherModel; - if (shouldAddCountField) { - const inputTypes: DMMF.SchemaArgInputType[] = [{ isList: false, type: 'Boolean', location: 'scalar' }]; - if (isGenerateSelect) { - inputTypes.push({ - isList: false, - type: `${modelName}CountOutputTypeArgs`, - location: 'inputObjectTypes', - namespace: 'prisma', - }); - } - const _countField: DMMF.SchemaArg = { - name: '_count', - isRequired: false, - isNullable: false, - inputTypes, - }; - fields.push(_countField); - } - - const modelIncludeInputObjectType: DMMF.InputType = { - name: `${modelName}Include`, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields, - }; - modelIncludeInputObjectTypes.push(modelIncludeInputObjectType); - } - return modelIncludeInputObjectTypes; -} diff --git a/packages/plugins/trpc/src/zod/helpers/index.ts b/packages/plugins/trpc/src/zod/helpers/index.ts deleted file mode 100644 index 64ce0cf0c..000000000 --- a/packages/plugins/trpc/src/zod/helpers/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './comments-helpers'; -export * from './helpers'; -export * from './model-helpers'; -export * from './mongodb-helpers'; diff --git a/packages/plugins/trpc/src/zod/helpers/model-helpers.ts b/packages/plugins/trpc/src/zod/helpers/model-helpers.ts deleted file mode 100644 index 255638bd8..000000000 --- a/packages/plugins/trpc/src/zod/helpers/model-helpers.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; - -export function checkModelHasModelRelation(model: DMMF.Model) { - const { fields: modelFields } = model; - for (const modelField of modelFields) { - const isRelationField = checkIsModelRelationField(modelField); - if (isRelationField) { - return true; - } - } - return false; -} - -export function checkModelHasManyModelRelation(model: DMMF.Model) { - const { fields: modelFields } = model; - for (const modelField of modelFields) { - const isManyRelationField = checkIsManyModelRelationField(modelField); - if (isManyRelationField) { - return true; - } - } - return false; -} - -export function checkIsModelRelationField(modelField: DMMF.Field) { - const { kind, relationName } = modelField; - return kind === 'object' && !!relationName; -} - -export function checkIsManyModelRelationField(modelField: DMMF.Field) { - return checkIsModelRelationField(modelField) && modelField.isList; -} - -export function findModelByName(models: DMMF.Model[], modelName: string) { - return models.find(({ name }) => name === modelName); -} diff --git a/packages/plugins/trpc/src/zod/helpers/modelArgs-helpers.ts b/packages/plugins/trpc/src/zod/helpers/modelArgs-helpers.ts deleted file mode 100644 index e03759fe6..000000000 --- a/packages/plugins/trpc/src/zod/helpers/modelArgs-helpers.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import { checkModelHasModelRelation } from './model-helpers'; - -export function addMissingInputObjectTypesForModelArgs( - inputObjectTypes: DMMF.InputType[], - models: DMMF.Model[], - isGenerateSelect: boolean, - isGenerateInclude: boolean -) { - const modelArgsInputObjectTypes = generateModelArgsInputObjectTypes(models, isGenerateSelect, isGenerateInclude); - - for (const modelArgsInputObjectType of modelArgsInputObjectTypes) { - inputObjectTypes.push(modelArgsInputObjectType); - } -} -function generateModelArgsInputObjectTypes( - models: DMMF.Model[], - isGenerateSelect: boolean, - isGenerateInclude: boolean -) { - const modelArgsInputObjectTypes: DMMF.InputType[] = []; - for (const model of models) { - const { name: modelName } = model; - const fields: DMMF.SchemaArg[] = []; - - if (isGenerateSelect) { - const selectField: DMMF.SchemaArg = { - name: 'select', - isRequired: false, - isNullable: false, - inputTypes: [ - { - isList: false, - type: `${modelName}Select`, - location: 'inputObjectTypes', - namespace: 'prisma', - }, - ], - }; - fields.push(selectField); - } - - const hasRelationToAnotherModel = checkModelHasModelRelation(model); - - if (isGenerateInclude && hasRelationToAnotherModel) { - const includeField: DMMF.SchemaArg = { - name: 'include', - isRequired: false, - isNullable: false, - inputTypes: [ - { - isList: false, - type: `${modelName}Include`, - location: 'inputObjectTypes', - namespace: 'prisma', - }, - ], - }; - fields.push(includeField); - } - - const modelArgsInputObjectType: DMMF.InputType = { - name: `${modelName}Args`, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields, - }; - modelArgsInputObjectTypes.push(modelArgsInputObjectType); - } - return modelArgsInputObjectTypes; -} diff --git a/packages/plugins/trpc/src/zod/helpers/mongodb-helpers.ts b/packages/plugins/trpc/src/zod/helpers/mongodb-helpers.ts deleted file mode 100644 index 9377925b4..000000000 --- a/packages/plugins/trpc/src/zod/helpers/mongodb-helpers.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import Transformer from '../transformer'; - -export function addMissingInputObjectTypesForMongoDbRawOpsAndQueries( - modelOperations: DMMF.ModelMapping[], - outputObjectTypes: DMMF.OutputType[], - inputObjectTypes: DMMF.InputType[] -) { - const rawOpsMap = resolveMongoDbRawOperations(modelOperations); - Transformer.rawOpsMap = rawOpsMap ?? {}; - - const mongoDbRawQueryInputObjectTypes = resolveMongoDbRawQueryInputObjectTypes(outputObjectTypes); - for (const mongoDbRawQueryInputType of mongoDbRawQueryInputObjectTypes) { - inputObjectTypes.push(mongoDbRawQueryInputType); - } -} - -function resolveMongoDbRawOperations(modelOperations: DMMF.ModelMapping[]) { - const rawOpsMap: { [name: string]: string } = {}; - const rawOpsNames = [ - ...new Set( - modelOperations.reduce((result, current) => { - const keys = Object.keys(current); - keys?.forEach((key) => { - if (key.includes('Raw')) { - result.push(key); - } - }); - return result; - }, []) - ), - ]; - - const modelNames = modelOperations.map((item) => item.model); - - rawOpsNames.forEach((opName) => { - modelNames.forEach((modelName) => { - const isFind = opName === 'findRaw'; - const opWithModel = `${opName.replace('Raw', '')}${modelName}Raw`; - rawOpsMap[opWithModel] = isFind ? `${modelName}FindRawArgs` : `${modelName}AggregateRawArgs`; - }); - }); - - return rawOpsMap; -} - -function resolveMongoDbRawQueryInputObjectTypes(outputObjectTypes: DMMF.OutputType[]) { - const mongoDbRawQueries = getMongoDbRawQueries(outputObjectTypes); - const mongoDbRawQueryInputObjectTypes = mongoDbRawQueries.map((item) => ({ - name: item.name, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields: item.args.map((arg) => ({ - name: arg.name, - isRequired: arg.isRequired, - isNullable: arg.isNullable, - inputTypes: arg.inputTypes, - })), - })); - return mongoDbRawQueryInputObjectTypes; -} - -function getMongoDbRawQueries(outputObjectTypes: DMMF.OutputType[]) { - const queryOutputTypes = outputObjectTypes.filter((item) => item.name === 'Query'); - - const mongodbRawQueries = queryOutputTypes?.[0].fields.filter((field) => field.name.includes('Raw')) ?? []; - - return mongodbRawQueries; -} - -export const isMongodbRawOp = (name: string) => /find([^]*?)Raw/.test(name) || /aggregate([^]*?)Raw/.test(name); diff --git a/packages/plugins/trpc/src/zod/helpers/select-helpers.ts b/packages/plugins/trpc/src/zod/helpers/select-helpers.ts deleted file mode 100644 index 691bf0269..000000000 --- a/packages/plugins/trpc/src/zod/helpers/select-helpers.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { DMMF } from '@prisma/generator-helper'; -import { checkIsModelRelationField, checkModelHasManyModelRelation } from './model-helpers'; - -export function addMissingInputObjectTypesForSelect( - inputObjectTypes: DMMF.InputType[], - outputObjectTypes: DMMF.OutputType[], - models: DMMF.Model[] -) { - // generate input object types necessary to support ModelSelect._count - const modelCountOutputTypes = getModelCountOutputTypes(outputObjectTypes); - const modelCountOutputTypeSelectInputObjectTypes = - generateModelCountOutputTypeSelectInputObjectTypes(modelCountOutputTypes); - const modelCountOutputTypeArgsInputObjectTypes = - generateModelCountOutputTypeArgsInputObjectTypes(modelCountOutputTypes); - - const modelSelectInputObjectTypes = generateModelSelectInputObjectTypes(models); - - const generatedInputObjectTypes = [ - modelCountOutputTypeSelectInputObjectTypes, - modelCountOutputTypeArgsInputObjectTypes, - modelSelectInputObjectTypes, - ].flat(); - - for (const inputObjectType of generatedInputObjectTypes) { - inputObjectTypes.push(inputObjectType); - } -} - -function getModelCountOutputTypes(outputObjectTypes: DMMF.OutputType[]) { - return outputObjectTypes.filter(({ name }) => name.includes('CountOutputType')); -} - -function generateModelCountOutputTypeSelectInputObjectTypes(modelCountOutputTypes: DMMF.OutputType[]) { - const modelCountOutputTypeSelectInputObjectTypes: DMMF.InputType[] = []; - for (const modelCountOutputType of modelCountOutputTypes) { - const { name: modelCountOutputTypeName, fields: modelCountOutputTypeFields } = modelCountOutputType; - const modelCountOutputTypeSelectInputObjectType: DMMF.InputType = { - name: `${modelCountOutputTypeName}Select`, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields: modelCountOutputTypeFields.map(({ name }) => ({ - name, - isRequired: false, - isNullable: false, - inputTypes: [ - { - isList: false, - type: `Boolean`, - location: 'scalar', - }, - ], - })), - }; - modelCountOutputTypeSelectInputObjectTypes.push(modelCountOutputTypeSelectInputObjectType); - } - return modelCountOutputTypeSelectInputObjectTypes; -} - -function generateModelCountOutputTypeArgsInputObjectTypes(modelCountOutputTypes: DMMF.OutputType[]) { - const modelCountOutputTypeArgsInputObjectTypes: DMMF.InputType[] = []; - for (const modelCountOutputType of modelCountOutputTypes) { - const { name: modelCountOutputTypeName } = modelCountOutputType; - const modelCountOutputTypeArgsInputObjectType: DMMF.InputType = { - name: `${modelCountOutputTypeName}Args`, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields: [ - { - name: 'select', - isRequired: false, - isNullable: false, - inputTypes: [ - { - isList: false, - type: `${modelCountOutputTypeName}Select`, - location: 'inputObjectTypes', - namespace: 'prisma', - }, - ], - }, - ], - }; - modelCountOutputTypeArgsInputObjectTypes.push(modelCountOutputTypeArgsInputObjectType); - } - return modelCountOutputTypeArgsInputObjectTypes; -} - -function generateModelSelectInputObjectTypes(models: DMMF.Model[]) { - const modelSelectInputObjectTypes: DMMF.InputType[] = []; - for (const model of models) { - const { name: modelName, fields: modelFields } = model; - const fields: DMMF.SchemaArg[] = []; - - for (const modelField of modelFields) { - const { name: modelFieldName, isList, type } = modelField; - - const isRelationField = checkIsModelRelationField(modelField); - - const field: DMMF.SchemaArg = { - name: modelFieldName, - isRequired: false, - isNullable: false, - inputTypes: [{ isList: false, type: 'Boolean', location: 'scalar' }], - }; - - if (isRelationField) { - const schemaArgInputType: DMMF.SchemaArgInputType = { - isList: false, - type: isList ? `${type}FindManyArgs` : `${type}Args`, - location: 'inputObjectTypes', - namespace: 'prisma', - }; - field.inputTypes.push(schemaArgInputType); - } - - fields.push(field); - } - - const hasManyRelationToAnotherModel = checkModelHasManyModelRelation(model); - - const shouldAddCountField = hasManyRelationToAnotherModel; - if (shouldAddCountField) { - const _countField: DMMF.SchemaArg = { - name: '_count', - isRequired: false, - isNullable: false, - inputTypes: [ - { isList: false, type: 'Boolean', location: 'scalar' }, - { - isList: false, - type: `${modelName}CountOutputTypeArgs`, - location: 'inputObjectTypes', - namespace: 'prisma', - }, - ], - }; - fields.push(_countField); - } - - const modelSelectInputObjectType: DMMF.InputType = { - name: `${modelName}Select`, - constraints: { - maxNumFields: null, - minNumFields: null, - }, - fields, - }; - modelSelectInputObjectTypes.push(modelSelectInputObjectType); - } - return modelSelectInputObjectTypes; -} diff --git a/packages/plugins/trpc/src/zod/transformer.ts b/packages/plugins/trpc/src/zod/transformer.ts index e1601f2c1..3349c0471 100644 --- a/packages/plugins/trpc/src/zod/transformer.ts +++ b/packages/plugins/trpc/src/zod/transformer.ts @@ -1,10 +1,9 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import type { DMMF as PrismaDMMF } from '@prisma/generator-helper'; import { AUXILIARY_FIELDS } from '@zenstackhq/sdk'; +import { checkModelHasModelRelation, findModelByName, isAggregateInputType } from '@zenstackhq/sdk/dmmf-helpers'; import indentString from '@zenstackhq/sdk/utils'; import path from 'path'; -import { checkModelHasModelRelation, findModelByName, isMongodbRawOp } from './helpers'; -import { isAggregateInputType } from './helpers/aggregate-helpers'; import { AggregateOperationSupport, TransformerParams } from './types'; import { writeFileSafely } from './utils/writeFileSafely'; @@ -24,8 +23,6 @@ export default class Transformer { private hasJson = false; private static prismaClientOutputPath = '@prisma/client'; private static isCustomPrismaClientOutputPath = false; - private static isGenerateSelect = false; - private static isGenerateInclude = false; constructor(params: TransformerParams) { this.name = params.name ?? ''; @@ -40,14 +37,6 @@ export default class Transformer { this.outputPath = outPath; } - static setIsGenerateSelect(isGenerateSelect: boolean) { - this.isGenerateSelect = isGenerateSelect; - } - - static setIsGenerateInclude(isGenerateInclude: boolean) { - this.isGenerateInclude = isGenerateInclude; - } - static getOutputPath() { return this.outputPath; } @@ -83,10 +72,9 @@ export default class Transformer { async generateObjectSchema() { const zodObjectSchemaFields = this.generateObjectSchemaFields(); const objectSchema = this.prepareObjectSchema(zodObjectSchemaFields); - const objectSchemaName = this.resolveObjectSchemaName(); await writeFileSafely( - path.join(Transformer.outputPath, `schemas/objects/${objectSchemaName}.schema.ts`), + path.join(Transformer.outputPath, `schemas/objects/${this.name}.schema.ts`), '/* eslint-disable */\n' + objectSchema ); } @@ -246,18 +234,11 @@ export default class Transformer { generateExportObjectSchemaStatement(schema: string) { let name = this.name; - let exportName = this.name; - if (Transformer.provider === 'mongodb') { - if (isMongodbRawOp(name)) { - name = Transformer.rawOpsMap[name]; - exportName = name.replace('Args', ''); - } - } if (isAggregateInputType(name)) { name = `${name}Type`; } - const end = `export const ${exportName}ObjectSchema = Schema`; + const end = `export const ${this.name}ObjectSchema = Schema`; return `const Schema: z.ZodType "'" + f + "'").join( '|' )}>> = ${schema};\n\n ${end}`; @@ -374,16 +355,6 @@ export default class Transformer { return wrapped; } - resolveObjectSchemaName() { - let name = this.name; - let exportName = this.name; - if (isMongodbRawOp(name)) { - name = Transformer.rawOpsMap[name]; - exportName = name.replace('Args', ''); - } - return exportName; - } - async generateModelSchemas() { const globalImports: string[] = []; let globalExport = ''; @@ -605,27 +576,22 @@ export default schemas; const hasRelationToAnotherModel = checkModelHasModelRelation(model); - const selectImport = Transformer.isGenerateSelect - ? `import { ${modelName}SelectObjectSchema } from './objects/${modelName}Select.schema'` - : ''; + const selectImport = `import { ${modelName}SelectObjectSchema } from './objects/${modelName}Select.schema'`; - const includeImport = - Transformer.isGenerateInclude && hasRelationToAnotherModel - ? `import { ${modelName}IncludeObjectSchema } from './objects/${modelName}Include.schema'` - : ''; + const includeImport = hasRelationToAnotherModel + ? `import { ${modelName}IncludeObjectSchema } from './objects/${modelName}Include.schema'` + : ''; let selectZodSchemaLine = ''; let includeZodSchemaLine = ''; let selectZodSchemaLineLazy = ''; let includeZodSchemaLineLazy = ''; - if (Transformer.isGenerateSelect) { - const zodSelectObjectSchema = `${modelName}SelectObjectSchema.optional()`; - selectZodSchemaLine = `select: ${zodSelectObjectSchema},`; - selectZodSchemaLineLazy = `select: z.lazy(() => ${zodSelectObjectSchema}),`; - } + const zodSelectObjectSchema = `${modelName}SelectObjectSchema.optional()`; + selectZodSchemaLine = `select: ${zodSelectObjectSchema},`; + selectZodSchemaLineLazy = `select: z.lazy(() => ${zodSelectObjectSchema}),`; - if (Transformer.isGenerateInclude && hasRelationToAnotherModel) { + if (hasRelationToAnotherModel) { const zodIncludeObjectSchema = `${modelName}IncludeObjectSchema.optional()`; includeZodSchemaLine = `include: ${zodIncludeObjectSchema},`; includeZodSchemaLineLazy = `include: z.lazy(() => ${zodIncludeObjectSchema}),`; diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 832754774..1a0367eb3 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/runtime/src/enhancements/preset.ts b/packages/runtime/src/enhancements/preset.ts index e6764a56e..e28bdbb2e 100644 --- a/packages/runtime/src/enhancements/preset.ts +++ b/packages/runtime/src/enhancements/preset.ts @@ -11,9 +11,9 @@ import { ModelMeta, PolicyDef } from './types'; * * @param prisma The Prisma client to enhance. * @param context The context to for evaluating access policies. - * @param policy The access policy data, generated by @zenstack/access-policy plugin. + * @param policy The access policy data, generated by @core/access-policy plugin. * You only need to pass it if you configured the plugin to generate into custom location. - * @param modelMeta The model metadata, generated by @zenstack/model-meta plugin. + * @param modelMeta The model metadata, generated by @core/model-meta plugin. * You only need to pass it if you configured the plugin to generate into custom location. */ export function withPresets( diff --git a/packages/runtime/src/zod.ts b/packages/runtime/src/zod.ts new file mode 100644 index 000000000..0d2375163 --- /dev/null +++ b/packages/runtime/src/zod.ts @@ -0,0 +1,19 @@ +import z from 'zod'; +import { DbOperations } from './types'; + +/** + * Mapping from model type name to zod schema for Prisma operations + */ +export type ModelZodSchema = Record>; + +/** + * Load zod schema from standard location. + */ +export function getModelZodSchemas(): ModelZodSchema { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('.zenstack/zod').default; + } catch { + throw new Error('Model meta cannot be loaded'); + } +} diff --git a/packages/schema/package.json b/packages/schema/package.json index 84e932d5d..72b23ddd9 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "A toolkit for building secure CRUD apps with Next.js + Typescript", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "author": { "name": "ZenStack Team" }, diff --git a/packages/schema/src/cli/plugin-runner.ts b/packages/schema/src/cli/plugin-runner.ts index 82b67b573..721358170 100644 --- a/packages/schema/src/cli/plugin-runner.ts +++ b/packages/schema/src/cli/plugin-runner.ts @@ -30,7 +30,7 @@ export class PluginRunner { }> = []; const pluginDecls = context.schema.declarations.filter((d): d is Plugin => isPlugin(d)); - const prereqPlugins = ['@zenstack/prisma', '@zenstack/model-meta', '@zenstack/access-policy']; + const prereqPlugins = ['@core/prisma', '@core/model-meta', '@core/access-policy', '@core/zod']; const allPluginProviders = prereqPlugins.concat( pluginDecls .map((p) => this.getPluginProvider(p)) @@ -72,7 +72,7 @@ export class PluginRunner { options, }); - if (pluginProvider === '@zenstack/prisma' && options.output) { + if (pluginProvider === '@core/prisma' && options.output) { // record custom prisma output path prismaOutput = options.output as string; } @@ -93,7 +93,7 @@ export class PluginRunner { let dmmf: DMMF.Document | undefined = undefined; for (const { name, provider, run, options } of plugins) { await this.runPlugin(name, run, context, options, dmmf, warnings); - if (provider === '@zenstack/prisma') { + if (provider === '@core/prisma') { // load prisma DMMF dmmf = await getDMMF({ datamodel: fs.readFileSync(prismaOutput, { encoding: 'utf-8' }), @@ -152,8 +152,8 @@ export class PluginRunner { private getPluginModulePath(provider: string) { let pluginModulePath = provider; - if (pluginModulePath.startsWith('@zenstack/')) { - pluginModulePath = pluginModulePath.replace(/^@zenstack/, path.join(__dirname, '../plugins')); + if (pluginModulePath.startsWith('@core/')) { + pluginModulePath = pluginModulePath.replace(/^@core/, path.join(__dirname, '../plugins')); } return pluginModulePath; } diff --git a/packages/schema/src/plugins/zod/generator.ts b/packages/schema/src/plugins/zod/generator.ts new file mode 100644 index 000000000..50d135101 --- /dev/null +++ b/packages/schema/src/plugins/zod/generator.ts @@ -0,0 +1,111 @@ +import { ConnectorType, DMMF } from '@prisma/generator-helper'; +import { Dictionary } from '@prisma/internals'; +import { emitProject, getLiteral, PluginOptions } from '@zenstackhq/sdk'; +import { DataSource, isDataSource, Model } from '@zenstackhq/sdk/ast'; +import { + addMissingInputObjectTypes, + AggregateOperationSupport, + resolveAggregateOperationSupport, +} from '@zenstackhq/sdk/dmmf-helpers'; +import { promises as fs } from 'fs'; +import path from 'path'; +import { Project } from 'ts-morph'; +import { getDefaultOutputFolder } from '../plugin-utils'; +import Transformer from './transformer'; +import removeDir from './utils/removeDir'; + +export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { + let output = options.output as string; + if (!output) { + const defaultOutputFolder = getDefaultOutputFolder(); + if (defaultOutputFolder) { + output = path.join(defaultOutputFolder, 'zod'); + } else { + output = './generated/zod'; + } + } + await handleGeneratorOutputValue(output); + + const prismaClientDmmf = dmmf; + + const modelOperations = prismaClientDmmf.mappings.modelOperations; + const inputObjectTypes = prismaClientDmmf.schema.inputObjectTypes.prisma; + const outputObjectTypes = prismaClientDmmf.schema.outputObjectTypes.prisma; + const models: DMMF.Model[] = prismaClientDmmf.datamodel.models; + + const project = new Project(); + + await generateEnumSchemas( + prismaClientDmmf.schema.enumTypes.prisma, + prismaClientDmmf.schema.enumTypes.model ?? [], + project + ); + + const dataSource = model.declarations.find((d): d is DataSource => isDataSource(d)); + + const dataSourceProvider = getLiteral( + dataSource?.fields.find((f) => f.name === 'provider')?.value + ) as ConnectorType; + + Transformer.provider = dataSourceProvider; + + const generatorConfigOptions: Dictionary = {}; + Object.entries(options).forEach(([k, v]) => (generatorConfigOptions[k] = v as string)); + + addMissingInputObjectTypes(inputObjectTypes, outputObjectTypes, models); + + const aggregateOperationSupport = resolveAggregateOperationSupport(inputObjectTypes); + + await generateObjectSchemas(inputObjectTypes, project); + await generateModelSchemas(models, modelOperations, aggregateOperationSupport, project); + + await emitProject(project); +} + +async function handleGeneratorOutputValue(output: string) { + // create the output directory and delete contents that might exist from a previous run + await fs.mkdir(output, { recursive: true }); + const isRemoveContentsOnly = true; + await removeDir(output, isRemoveContentsOnly); + + Transformer.setOutputPath(output); +} + +async function generateEnumSchemas( + prismaSchemaEnum: DMMF.SchemaEnum[], + modelSchemaEnum: DMMF.SchemaEnum[], + project: Project +) { + const enumTypes = [...prismaSchemaEnum, ...modelSchemaEnum]; + const enumNames = enumTypes.map((enumItem) => enumItem.name); + Transformer.enumNames = enumNames ?? []; + const transformer = new Transformer({ + enumTypes, + project, + }); + await transformer.generateEnumSchemas(); +} + +async function generateObjectSchemas(inputObjectTypes: DMMF.InputType[], project: Project) { + for (let i = 0; i < inputObjectTypes.length; i += 1) { + const fields = inputObjectTypes[i]?.fields; + const name = inputObjectTypes[i]?.name; + const transformer = new Transformer({ name, fields, project }); + await transformer.generateObjectSchema(); + } +} + +async function generateModelSchemas( + models: DMMF.Model[], + modelOperations: DMMF.ModelMapping[], + aggregateOperationSupport: AggregateOperationSupport, + project: Project +) { + const transformer = new Transformer({ + models, + modelOperations, + aggregateOperationSupport, + project, + }); + await transformer.generateModelSchemas(); +} diff --git a/packages/schema/src/plugins/zod/index.ts b/packages/schema/src/plugins/zod/index.ts new file mode 100644 index 000000000..aee2371d4 --- /dev/null +++ b/packages/schema/src/plugins/zod/index.ts @@ -0,0 +1,10 @@ +import { DMMF } from '@prisma/generator-helper'; +import { PluginOptions } from '@zenstackhq/sdk'; +import { Model } from '@zenstackhq/sdk/ast'; +import { generate } from './generator'; + +export const name = 'Zod'; + +export default async function run(model: Model, options: PluginOptions, dmmf: DMMF.Document) { + return generate(model, options, dmmf); +} diff --git a/packages/schema/src/plugins/zod/transformer.ts b/packages/schema/src/plugins/zod/transformer.ts new file mode 100644 index 000000000..8b73fe825 --- /dev/null +++ b/packages/schema/src/plugins/zod/transformer.ts @@ -0,0 +1,610 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import type { DMMF as PrismaDMMF } from '@prisma/generator-helper'; +import { AUXILIARY_FIELDS } from '@zenstackhq/sdk'; +import { checkModelHasModelRelation, findModelByName, isAggregateInputType } from '@zenstackhq/sdk/dmmf-helpers'; +import indentString from '@zenstackhq/sdk/utils'; +import path from 'path'; +import { Project } from 'ts-morph'; +import { AggregateOperationSupport, TransformerParams } from './types'; + +export default class Transformer { + name: string; + fields: PrismaDMMF.SchemaArg[]; + schemaImports = new Set(); + models: PrismaDMMF.Model[]; + modelOperations: PrismaDMMF.ModelMapping[]; + aggregateOperationSupport: AggregateOperationSupport; + enumTypes: PrismaDMMF.SchemaEnum[]; + + static enumNames: string[] = []; + static rawOpsMap: { [name: string]: string } = {}; + static provider: string; + private static outputPath = './generated'; + private hasJson = false; + private static prismaClientOutputPath = '@prisma/client'; + private static isCustomPrismaClientOutputPath = false; + private project: Project; + + constructor(params: TransformerParams) { + this.name = params.name ?? ''; + this.fields = params.fields ?? []; + this.models = params.models ?? []; + this.modelOperations = params.modelOperations ?? []; + this.aggregateOperationSupport = params.aggregateOperationSupport ?? {}; + this.enumTypes = params.enumTypes ?? []; + this.project = params.project; + } + + static setOutputPath(outPath: string) { + this.outputPath = outPath; + } + + static getOutputPath() { + return this.outputPath; + } + + static setPrismaClientOutputPath(prismaClientCustomPath: string) { + this.prismaClientOutputPath = prismaClientCustomPath; + this.isCustomPrismaClientOutputPath = prismaClientCustomPath !== '@prisma/client'; + } + + async generateEnumSchemas() { + for (const enumType of this.enumTypes) { + const { name, values } = enumType; + const filteredValues = values.filter((v) => !AUXILIARY_FIELDS.includes(v)); + + 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(filteredValues)})` + )}`; + this.project.createSourceFile(filePath, content, { overwrite: true }); + } + } + + generateImportZodStatement() { + return "import { z } from 'zod';\n"; + } + + generateExportSchemaStatement(name: string, schema: string) { + return `export const ${name}Schema = ${schema}`; + } + + async generateObjectSchema() { + const zodObjectSchemaFields = this.generateObjectSchemaFields(); + const objectSchema = this.prepareObjectSchema(zodObjectSchemaFields); + const objectSchemaName = this.resolveObjectSchemaName(); + + const filePath = path.join(Transformer.outputPath, `objects/${objectSchemaName}.schema.ts`); + const content = '/* eslint-disable */\n' + objectSchema; + this.project.createSourceFile(filePath, content, { overwrite: true }); + } + + generateObjectSchemaFields() { + const zodObjectSchemaFields = this.fields + .filter((field) => !AUXILIARY_FIELDS.includes(field.name)) + .map((field) => this.generateObjectSchemaField(field)) + .flatMap((item) => item) + .map((item) => { + const [zodStringWithMainType, field, skipValidators] = item; + + const value = skipValidators + ? zodStringWithMainType + : this.generateFieldValidators(zodStringWithMainType, field); + + return value.trim(); + }); + return zodObjectSchemaFields; + } + + generateObjectSchemaField(field: PrismaDMMF.SchemaArg): [string, PrismaDMMF.SchemaArg, boolean][] { + const lines = field.inputTypes; + + if (lines.length === 0) { + return []; + } + + let alternatives = lines.reduce((result, inputType) => { + if (inputType.type === 'String') { + result.push(this.wrapWithZodValidators('z.string()', field, inputType)); + } else if (inputType.type === 'Int' || inputType.type === 'Float' || inputType.type === 'Decimal') { + result.push(this.wrapWithZodValidators('z.number()', field, inputType)); + } else if (inputType.type === 'BigInt') { + result.push(this.wrapWithZodValidators('z.bigint()', field, inputType)); + } else if (inputType.type === 'Boolean') { + result.push(this.wrapWithZodValidators('z.boolean()', field, inputType)); + } else if (inputType.type === 'DateTime') { + result.push(this.wrapWithZodValidators('z.date()', field, inputType)); + } else if (inputType.type === 'Json') { + this.hasJson = true; + + result.push(this.wrapWithZodValidators('jsonSchema', field, inputType)); + } else if (inputType.type === 'True') { + result.push(this.wrapWithZodValidators('z.literal(true)', field, inputType)); + } else { + const isEnum = inputType.location === 'enumTypes'; + + if (inputType.namespace === 'prisma' || isEnum) { + if (inputType.type !== this.name && typeof inputType.type === 'string') { + this.addSchemaImport(inputType.type); + } + + result.push(this.generatePrismaStringLine(field, inputType, lines.length)); + } + } + + return result; + }, []); + + if (alternatives.length === 0) { + return []; + } + + if (alternatives.length > 1) { + alternatives = alternatives.map((alter) => alter.replace('.optional()', '')); + } + + const fieldName = alternatives.some((alt) => alt.includes(':')) ? '' : ` ${field.name}:`; + + const opt = !field.isRequired ? '.optional()' : ''; + + let resString = + alternatives.length === 1 ? alternatives.join(',\r\n') : `z.union([${alternatives.join(',\r\n')}])${opt}`; + + if (field.isNullable) { + resString += '.nullable()'; + } + + return [[` ${fieldName} ${resString} `, field, true]]; + } + + wrapWithZodValidators( + mainValidator: string, + field: PrismaDMMF.SchemaArg, + inputType: PrismaDMMF.SchemaArgInputType + ) { + let line = ''; + line = mainValidator; + + if (inputType.isList) { + line += '.array()'; + } + + if (!field.isRequired) { + line += '.optional()'; + } + + return line; + } + + addSchemaImport(name: string) { + this.schemaImports.add(name); + } + + generatePrismaStringLine( + field: PrismaDMMF.SchemaArg, + inputType: PrismaDMMF.SchemaArgInputType, + inputsLength: number + ) { + const isEnum = inputType.location === 'enumTypes'; + + const { isModelQueryType, modelName, queryName } = this.checkIsModelQueryType(inputType.type as string); + + const objectSchemaLine = isModelQueryType + ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.resolveModelQuerySchemaName(modelName!, queryName!) + : `${inputType.type}ObjectSchema`; + const enumSchemaLine = `${inputType.type}Schema`; + + const schema = inputType.type === this.name ? objectSchemaLine : isEnum ? enumSchemaLine : objectSchemaLine; + + const arr = inputType.isList ? '.array()' : ''; + + const opt = !field.isRequired ? '.optional()' : ''; + + return inputsLength === 1 + ? ` ${field.name}: z.lazy(() => ${schema})${arr}${opt}` + : `z.lazy(() => ${schema})${arr}${opt}`; + } + + generateFieldValidators(zodStringWithMainType: string, field: PrismaDMMF.SchemaArg) { + const { isRequired, isNullable } = field; + + if (!isRequired) { + zodStringWithMainType += '.optional()'; + } + + if (isNullable) { + zodStringWithMainType += '.nullable()'; + } + + return zodStringWithMainType; + } + + prepareObjectSchema(zodObjectSchemaFields: string[]) { + const objectSchema = `${this.generateExportObjectSchemaStatement( + this.addFinalWrappers({ zodStringFields: zodObjectSchemaFields }) + )}\n`; + + const prismaImportStatement = this.generateImportPrismaStatement(); + + const json = this.generateJsonSchemaImplementation(); + + return `${this.generateObjectSchemaImportStatements()}${prismaImportStatement}${json}${objectSchema}`; + } + + generateExportObjectSchemaStatement(schema: string) { + let name = this.name; + + if (isAggregateInputType(name)) { + name = `${name}Type`; + } + const end = `export const ${this.name}ObjectSchema = Schema`; + return `const Schema: z.ZodType "'" + f + "'").join( + '|' + )}>> = ${schema};\n\n ${end}`; + } + + addFinalWrappers({ zodStringFields }: { zodStringFields: string[] }) { + const fields = [...zodStringFields]; + + return this.wrapWithZodObject(fields) + '.strict()'; + } + + generateImportPrismaStatement() { + let prismaClientImportPath: string; + if (Transformer.isCustomPrismaClientOutputPath) { + /** + * If a custom location was designated for the prisma client, we need to figure out the + * relative path from {outputPath}/objects to {prismaClientCustomPath} + */ + const fromPath = path.join(Transformer.outputPath, 'objects'); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const toPath = Transformer.prismaClientOutputPath!; + const relativePathFromOutputToPrismaClient = path + .relative(fromPath, toPath) + .split(path.sep) + .join(path.posix.sep); + prismaClientImportPath = relativePathFromOutputToPrismaClient; + } else { + /** + * If the default output path for prisma client (@prisma/client) is being used, we can import from it directly + * without having to resolve a relative path + */ + prismaClientImportPath = Transformer.prismaClientOutputPath; + } + return `import type { Prisma } from '${prismaClientImportPath}';\n\n`; + } + + generateJsonSchemaImplementation() { + let jsonSchemaImplementation = ''; + + if (this.hasJson) { + jsonSchemaImplementation += `\n`; + jsonSchemaImplementation += `const literalSchema = z.union([z.string(), z.number(), z.boolean()]);\n`; + jsonSchemaImplementation += `const jsonSchema: z.ZodType = z.lazy(() =>\n`; + jsonSchemaImplementation += ` z.union([literalSchema, z.array(jsonSchema.nullable()), z.record(jsonSchema.nullable())])\n`; + jsonSchemaImplementation += `);\n\n`; + } + + return jsonSchemaImplementation; + } + + generateObjectSchemaImportStatements() { + let generatedImports = this.generateImportZodStatement(); + generatedImports += this.generateSchemaImports(); + generatedImports += '\n\n'; + return generatedImports; + } + + generateSchemaImports() { + return [...this.schemaImports] + .map((name) => { + const { isModelQueryType, modelName } = this.checkIsModelQueryType(name); + if (isModelQueryType) { + return `import { ${modelName}Schema } from '../${modelName}.schema'`; + } else if (Transformer.enumNames.includes(name)) { + return `import { ${name}Schema } from '../enums/${name}.schema'`; + } else { + return `import { ${name}ObjectSchema } from './${name}.schema'`; + } + }) + .join(';\r\n'); + } + + checkIsModelQueryType(type: string) { + const modelQueryTypeSuffixToQueryName: Record = { + FindManyArgs: 'findMany', + }; + for (const modelQueryType of ['FindManyArgs']) { + if (type.includes(modelQueryType)) { + const modelQueryTypeSuffixIndex = type.indexOf(modelQueryType); + return { + isModelQueryType: true, + modelName: type.substring(0, modelQueryTypeSuffixIndex), + queryName: modelQueryTypeSuffixToQueryName[modelQueryType], + }; + } + } + return { isModelQueryType: false }; + } + + resolveModelQuerySchemaName(modelName: string, queryName: string) { + const modelNameCapitalized = modelName.charAt(0).toUpperCase() + modelName.slice(1); + return `${modelNameCapitalized}Schema.${queryName}`; + } + + wrapWithZodUnion(zodStringFields: string[]) { + let wrapped = ''; + + wrapped += 'z.union(['; + wrapped += '\n'; + wrapped += ' ' + zodStringFields.join(','); + wrapped += '\n'; + wrapped += '])'; + return wrapped; + } + + wrapWithZodObject(zodStringFields: string | string[]) { + let wrapped = ''; + + wrapped += 'z.object({'; + wrapped += '\n'; + wrapped += ' ' + zodStringFields; + wrapped += '\n'; + wrapped += '})'; + return wrapped; + } + + resolveObjectSchemaName() { + return this.name; + } + + async generateModelSchemas() { + const globalImports: string[] = []; + let globalExport = ''; + + for (const modelOperation of this.modelOperations) { + const { + model: modelName, + findUnique, + findFirst, + findMany, + // @ts-expect-error + createOne, + createMany, + // @ts-expect-error + deleteOne, + // @ts-expect-error + updateOne, + deleteMany, + updateMany, + // @ts-expect-error + upsertOne, + aggregate, + groupBy, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } = modelOperation; + + globalImports.push(`import { ${modelName}Schema } from './${modelName}.schema'`); + globalExport += `${modelName}: ${modelName}Schema,`; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const model = findModelByName(this.models, modelName)!; + + const { selectImport, includeImport, selectZodSchemaLineLazy, includeZodSchemaLineLazy } = + this.resolveSelectIncludeImportAndZodSchemaLine(model); + + let imports = [`import { z } from 'zod'`, selectImport, includeImport]; + let codeBody = ''; + + if (findUnique) { + imports.push( + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` + ); + codeBody += `findUnique: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema }),`; + } + + if (findFirst) { + imports.push( + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'`, + `import { ${modelName}OrderByWithRelationInputObjectSchema } from './objects/${modelName}OrderByWithRelationInput.schema'`, + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'`, + `import { ${modelName}ScalarFieldEnumSchema } from './enums/${modelName}ScalarFieldEnum.schema'` + ); + codeBody += `findFirst: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional() }),`; + } + + if (findMany) { + imports.push( + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'`, + `import { ${modelName}OrderByWithRelationInputObjectSchema } from './objects/${modelName}OrderByWithRelationInput.schema'`, + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'`, + `import { ${modelName}ScalarFieldEnumSchema } from './enums/${modelName}ScalarFieldEnum.schema'` + ); + codeBody += `findMany: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional() }),`; + } + + if (createOne) { + imports.push( + `import { ${modelName}CreateInputObjectSchema } from './objects/${modelName}CreateInput.schema'` + ); + codeBody += `create: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: ${modelName}CreateInputObjectSchema }),`; + } + + if (createMany) { + imports.push( + `import { ${modelName}CreateManyInputObjectSchema } from './objects/${modelName}CreateManyInput.schema'` + ); + codeBody += `createMany: z.object({ data: ${modelName}CreateManyInputObjectSchema }),`; + } + + if (deleteOne) { + imports.push( + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` + ); + codeBody += `'delete': z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema }),`; + } + + if (deleteMany) { + imports.push( + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'` + ); + codeBody += `deleteMany: z.object({ where: ${modelName}WhereInputObjectSchema.optional() }),`; + } + + if (updateOne) { + imports.push( + `import { ${modelName}UpdateInputObjectSchema } from './objects/${modelName}UpdateInput.schema'`, + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` + ); + codeBody += `update: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: ${modelName}UpdateInputObjectSchema, where: ${modelName}WhereUniqueInputObjectSchema }),`; + } + + if (updateMany) { + imports.push( + `import { ${modelName}UpdateManyMutationInputObjectSchema } from './objects/${modelName}UpdateManyMutationInput.schema'`, + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'` + ); + codeBody += `updateMany: z.object({ data: ${modelName}UpdateManyMutationInputObjectSchema, where: ${modelName}WhereInputObjectSchema.optional() }),`; + } + + if (upsertOne) { + imports.push( + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'`, + `import { ${modelName}CreateInputObjectSchema } from './objects/${modelName}CreateInput.schema'`, + `import { ${modelName}UpdateInputObjectSchema } from './objects/${modelName}UpdateInput.schema'` + ); + codeBody += `upsert: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema, create: ${modelName}CreateInputObjectSchema, update: ${modelName}UpdateInputObjectSchema }),`; + } + + const aggregateOperations = []; + if (this.aggregateOperationSupport[modelName].count) { + imports.push( + `import { ${modelName}CountAggregateInputObjectSchema } from './objects/${modelName}CountAggregateInput.schema'` + ); + aggregateOperations.push( + `_count: z.union([ z.literal(true), ${modelName}CountAggregateInputObjectSchema ]).optional()` + ); + } + if (this.aggregateOperationSupport[modelName].min) { + imports.push( + `import { ${modelName}MinAggregateInputObjectSchema } from './objects/${modelName}MinAggregateInput.schema'` + ); + aggregateOperations.push(`_min: ${modelName}MinAggregateInputObjectSchema.optional()`); + } + if (this.aggregateOperationSupport[modelName].max) { + imports.push( + `import { ${modelName}MaxAggregateInputObjectSchema } from './objects/${modelName}MaxAggregateInput.schema'` + ); + aggregateOperations.push(`_max: ${modelName}MaxAggregateInputObjectSchema.optional()`); + } + if (this.aggregateOperationSupport[modelName].avg) { + imports.push( + `import { ${modelName}AvgAggregateInputObjectSchema } from './objects/${modelName}AvgAggregateInput.schema'` + ); + aggregateOperations.push(`_avg: ${modelName}AvgAggregateInputObjectSchema.optional()`); + } + if (this.aggregateOperationSupport[modelName].sum) { + imports.push( + `import { ${modelName}SumAggregateInputObjectSchema } from './objects/${modelName}SumAggregateInput.schema'` + ); + aggregateOperations.push(`_sum: ${modelName}SumAggregateInputObjectSchema.optional()`); + } + + if (aggregate) { + imports.push( + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'`, + `import { ${modelName}OrderByWithRelationInputObjectSchema } from './objects/${modelName}OrderByWithRelationInput.schema'`, + `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` + ); + + codeBody += `aggregate: z.object({ where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), ${aggregateOperations.join( + ', ' + )} }),`; + } + + if (groupBy) { + imports.push( + `import { ${modelName}WhereInputObjectSchema } from './objects/${modelName}WhereInput.schema'`, + `import { ${modelName}OrderByWithAggregationInputObjectSchema } from './objects/${modelName}OrderByWithAggregationInput.schema'`, + `import { ${modelName}ScalarWhereWithAggregatesInputObjectSchema } from './objects/${modelName}ScalarWhereWithAggregatesInput.schema'`, + `import { ${modelName}ScalarFieldEnumSchema } from './enums/${modelName}ScalarFieldEnum.schema'` + ); + codeBody += `groupBy: z.object({ where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithAggregationInputObjectSchema, ${modelName}OrderByWithAggregationInputObjectSchema.array()]).optional(), having: ${modelName}ScalarWhereWithAggregatesInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), by: z.array(${modelName}ScalarFieldEnumSchema), ${aggregateOperations.join( + ', ' + )} }),`; + } + + imports = [...new Set(imports)]; + + const filePath = path.join(Transformer.outputPath, `${modelName}.schema.ts`); + const content = ` + /* eslint-disable */ + ${imports.join(';\n')} + + export const ${modelName}Schema = { + ${indentString(codeBody, 4)} + }; + `; + + this.project.createSourceFile(filePath, content, { overwrite: true }); + } + + const indexFilePath = path.join(Transformer.outputPath, 'index.ts'); + const indexContent = ` +/* eslint-disable */ +${globalImports.join(';\n')} + +const schemas = { +${indentString(globalExport, 4)} +}; + +export default schemas; +`; + this.project.createSourceFile(indexFilePath, indexContent, { overwrite: true }); + } + + generateImportStatements(imports: (string | undefined)[]) { + let generatedImports = this.generateImportZodStatement(); + generatedImports += imports?.filter((importItem) => !!importItem).join(';\r\n') ?? ''; + generatedImports += '\n\n'; + return generatedImports; + } + + resolveSelectIncludeImportAndZodSchemaLine(model: PrismaDMMF.Model) { + const { name: modelName } = model; + + const hasRelationToAnotherModel = checkModelHasModelRelation(model); + + const selectImport = `import { ${modelName}SelectObjectSchema } from './objects/${modelName}Select.schema'`; + + const includeImport = hasRelationToAnotherModel + ? `import { ${modelName}IncludeObjectSchema } from './objects/${modelName}Include.schema'` + : ''; + + let selectZodSchemaLine = ''; + let includeZodSchemaLine = ''; + let selectZodSchemaLineLazy = ''; + let includeZodSchemaLineLazy = ''; + + const zodSelectObjectSchema = `${modelName}SelectObjectSchema.optional()`; + selectZodSchemaLine = `select: ${zodSelectObjectSchema},`; + selectZodSchemaLineLazy = `select: z.lazy(() => ${zodSelectObjectSchema}),`; + + if (hasRelationToAnotherModel) { + const zodIncludeObjectSchema = `${modelName}IncludeObjectSchema.optional()`; + includeZodSchemaLine = `include: ${zodIncludeObjectSchema},`; + includeZodSchemaLineLazy = `include: z.lazy(() => ${zodIncludeObjectSchema}),`; + } + + return { + selectImport, + includeImport, + selectZodSchemaLine, + includeZodSchemaLine, + selectZodSchemaLineLazy, + includeZodSchemaLineLazy, + }; + } +} diff --git a/packages/schema/src/plugins/zod/types.ts b/packages/schema/src/plugins/zod/types.ts new file mode 100644 index 000000000..49c35c023 --- /dev/null +++ b/packages/schema/src/plugins/zod/types.ts @@ -0,0 +1,24 @@ +import { DMMF as PrismaDMMF } from '@prisma/generator-helper'; +import { Project } from 'ts-morph'; + +export type TransformerParams = { + enumTypes?: PrismaDMMF.SchemaEnum[]; + fields?: PrismaDMMF.SchemaArg[]; + name?: string; + models?: PrismaDMMF.Model[]; + modelOperations?: PrismaDMMF.ModelMapping[]; + aggregateOperationSupport?: AggregateOperationSupport; + isDefaultPrismaClientOutput?: boolean; + prismaClientOutputPath?: string; + project: Project; +}; + +export type AggregateOperationSupport = { + [model: string]: { + count?: boolean; + min?: boolean; + max?: boolean; + sum?: boolean; + avg?: boolean; + }; +}; diff --git a/packages/schema/src/plugins/zod/utils/removeDir.ts b/packages/schema/src/plugins/zod/utils/removeDir.ts new file mode 100644 index 000000000..03f8d74f5 --- /dev/null +++ b/packages/schema/src/plugins/zod/utils/removeDir.ts @@ -0,0 +1,15 @@ +import path from 'path'; +import { promises as fs } from 'fs'; + +export default async function removeDir(dirPath: string, onlyContent: boolean) { + const dirEntries = await fs.readdir(dirPath, { withFileTypes: true }); + await Promise.all( + dirEntries.map(async (dirEntry) => { + const fullPath = path.join(dirPath, dirEntry.name); + return dirEntry.isDirectory() ? await removeDir(fullPath, false) : await fs.unlink(fullPath); + }) + ); + if (!onlyContent) { + await fs.rmdir(dirPath); + } +} diff --git a/packages/schema/src/res/stdlib.zmodel b/packages/schema/src/res/stdlib.zmodel index 52f354749..0e1ad71a1 100644 --- a/packages/schema/src/res/stdlib.zmodel +++ b/packages/schema/src/res/stdlib.zmodel @@ -153,6 +153,16 @@ attribute @map(_ name: String) @@@prisma */ attribute @@map(_ name: String) @@@prisma +/* +* Exclude a field from the Prisma Client (for example, a model that you do not want Prisma users to update). + */ +attribute @ignore() @@@prisma + +/* +* Exclude a model from the Prisma Client (for example, a model that you do not want Prisma users to update). + */ +attribute @@ignore() @@@prisma + /* * Automatically stores the time when a record was last updated. */ diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index aff1db9fa..263c335e6 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -24,7 +24,7 @@ describe('Prisma generator test', () => { const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { - provider: '@zenstack/prisma', + provider: '@core/prisma', schemaPath: 'schema.zmodel', output: name, }); @@ -55,7 +55,7 @@ describe('Prisma generator test', () => { const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { - provider: '@zenstack/prisma', + provider: '@core/prisma', schemaPath: 'schema.zmodel', output: name, }); @@ -90,7 +90,7 @@ describe('Prisma generator test', () => { const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { - provider: '@zenstack/prisma', + provider: '@core/prisma', schemaPath: 'schema.zmodel', output: name, }); @@ -129,7 +129,7 @@ describe('Prisma generator test', () => { const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { - provider: '@zenstack/prisma', + provider: '@core/prisma', schemaPath: 'schema.zmodel', output: name, }); @@ -179,7 +179,7 @@ describe('Prisma generator test', () => { const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { - provider: '@zenstack/prisma', + provider: '@core/prisma', schemaPath: 'schema.zmodel', output: name, generateClient: false, diff --git a/packages/schema/tests/schema/cal-com.zmodel b/packages/schema/tests/schema/cal-com.zmodel index 26dea14a7..9d3f40196 100644 --- a/packages/schema/tests/schema/cal-com.zmodel +++ b/packages/schema/tests/schema/cal-com.zmodel @@ -13,12 +13,12 @@ generator client { } plugin meta { - provider = '@zenstack/model-meta' + provider = '@core/model-meta' output = '.zenstack' } plugin policy { - provider = '@zenstack/access-policy' + provider = '@core/access-policy' output = '.zenstack' } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 7cb14631c..2d4e8b028 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { @@ -20,7 +20,9 @@ "license": "MIT", "dependencies": { "@prisma/generator-helper": "^4.7.1", - "@zenstackhq/language": "workspace:*" + "@zenstackhq/language": "workspace:*", + "prettier": "^2.8.3", + "ts-morph": "^16.0.0" }, "devDependencies": { "copyfiles": "^2.4.1", diff --git a/packages/sdk/src/code-gen.ts b/packages/sdk/src/code-gen.ts new file mode 100644 index 000000000..f58e6c9fd --- /dev/null +++ b/packages/sdk/src/code-gen.ts @@ -0,0 +1,38 @@ +import prettier from 'prettier'; +import { Project, SourceFile } from 'ts-morph'; + +const formatOptions = { + trailingComma: 'all', + tabWidth: 4, + printWidth: 120, + bracketSpacing: true, + semi: true, + singleQuote: true, + useTabs: false, + parser: 'typescript', +} as const; + +async function formatFile(sourceFile: SourceFile) { + try { + const content = sourceFile.getFullText(); + const formatted = prettier.format(content, formatOptions); + sourceFile.replaceWithText(formatted); + await sourceFile.save(); + } catch { + /* empty */ + } +} + +/** + * Emit a TS project to JS files. + */ +export async function emitProject(project: Project, format = true) { + if (format) { + await Promise.all( + project.getSourceFiles().map(async (sf) => { + await formatFile(sf); + }) + ); + } + await project.emit(); +} diff --git a/packages/sdk/src/dmmf-helpers/index.ts b/packages/sdk/src/dmmf-helpers/index.ts index 09e55e5ed..07c5b5a8a 100644 --- a/packages/sdk/src/dmmf-helpers/index.ts +++ b/packages/sdk/src/dmmf-helpers/index.ts @@ -4,3 +4,4 @@ export * from './model-helpers'; export * from './modelArgs-helpers'; export * from './select-helpers'; export * from './types'; +export * from './missing-types-helper'; diff --git a/packages/sdk/src/dmmf-helpers/missing-types-helper.ts b/packages/sdk/src/dmmf-helpers/missing-types-helper.ts new file mode 100644 index 000000000..8a6b553e4 --- /dev/null +++ b/packages/sdk/src/dmmf-helpers/missing-types-helper.ts @@ -0,0 +1,16 @@ +import { DMMF } from '@prisma/generator-helper'; +import { addMissingInputObjectTypesForAggregate } from './aggregate-helpers'; +import { addMissingInputObjectTypesForInclude } from './include-helpers'; +import { addMissingInputObjectTypesForModelArgs } from './modelArgs-helpers'; +import { addMissingInputObjectTypesForSelect } from './select-helpers'; + +export function addMissingInputObjectTypes( + inputObjectTypes: DMMF.InputType[], + outputObjectTypes: DMMF.OutputType[], + models: DMMF.Model[] +) { + addMissingInputObjectTypesForAggregate(inputObjectTypes, outputObjectTypes); + addMissingInputObjectTypesForSelect(inputObjectTypes, outputObjectTypes, models); + addMissingInputObjectTypesForModelArgs(inputObjectTypes, models); + addMissingInputObjectTypesForInclude(inputObjectTypes, models); +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 5aca99340..130e9ea8d 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,3 +1,4 @@ +export * from './code-gen'; export * from './constants'; export * from './types'; export * from './utils'; diff --git a/packages/server/package.json b/packages/server/package.json index 60f9b387d..d99ea1560 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", @@ -26,17 +26,19 @@ "@zenstackhq/openapi": "workspace:*", "@zenstackhq/runtime": "workspace:*", "@zenstackhq/sdk": "workspace:*", - "tiny-invariant": "^1.3.1" + "change-case": "^4.1.2", + "tiny-invariant": "^1.3.1", + "zod-validation-error": "^0.2.1" }, "devDependencies": { "@types/jest": "^29.4.0", + "@zenstackhq/testtools": "workspace:*", "copyfiles": "^2.4.1", "fastify": "^4.14.1", "fastify-plugin": "^4.5.0", "jest": "^29.4.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.5", - "typescript": "^4.9.4", - "@zenstackhq/testtools": "workspace:*" + "typescript": "^4.9.4" } } diff --git a/packages/server/src/fastify/plugin.ts b/packages/server/src/fastify/plugin.ts index fd6735c9a..37e03c6ad 100644 --- a/packages/server/src/fastify/plugin.ts +++ b/packages/server/src/fastify/plugin.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { DbClientContract } from '@zenstackhq/runtime'; +import { ModelZodSchema } from '@zenstackhq/runtime/zod'; import { FastifyPluginCallback, FastifyReply, FastifyRequest } from 'fastify'; import fp from 'fastify-plugin'; import { handleRequest, LoggerConfig } from '../openapi'; @@ -22,6 +23,11 @@ export interface PluginOptions { * Logger settings */ logger?: LoggerConfig; + + /** + * Path to the generated zod schemas + */ + zodSchemas?: ModelZodSchema; } const pluginHandler: FastifyPluginCallback = (fastify, options, done) => { @@ -47,6 +53,7 @@ const pluginHandler: FastifyPluginCallback = (fastify, options, d requestBody: request.body, prisma, logger: options.logger, + zodSchemas: options.zodSchemas, }); reply.status(response.status).send(response.body); diff --git a/packages/server/src/openapi/index.ts b/packages/server/src/openapi/index.ts index b6ea1f221..d150386b6 100644 --- a/packages/server/src/openapi/index.ts +++ b/packages/server/src/openapi/index.ts @@ -5,7 +5,10 @@ import { isPrismaClientUnknownRequestError, isPrismaClientValidationError, } from '@zenstackhq/runtime'; +import { getModelZodSchemas, ModelZodSchema } from '@zenstackhq/runtime/zod'; +import { capitalCase } from 'change-case'; import invariant from 'tiny-invariant'; +import { fromZodError } from 'zod-validation-error'; import { stripAuxFields } from './utils'; type LoggerMethod = (message: string, code?: string) => void; @@ -37,10 +40,11 @@ export type RequestHandlerOptions = { export type RequestContext = { method: string; path: string; - query: Record; - requestBody: unknown; + query?: Record; + requestBody?: unknown; prisma: DbClientContract; logger?: LoggerConfig; + zodSchemas?: ModelZodSchema; }; /** @@ -51,6 +55,38 @@ export type Response = { body: unknown; }; +function getZodSchema(zodSchemas: ModelZodSchema | undefined, model: string, operation: keyof DbOperations) { + if (!zodSchemas) { + zodSchemas = getModelZodSchemas(); + } + if (zodSchemas[model]) { + return zodSchemas[model][operation]; + } else if (zodSchemas[capitalCase(model)]) { + return zodSchemas[capitalCase(model)][operation]; + } else { + return undefined; + } +} + +function zodValidate( + zodSchemas: ModelZodSchema | undefined, + model: string, + operation: keyof DbOperations, + args: unknown +) { + const zodSchema = getZodSchema(zodSchemas, model, operation); + if (zodSchema) { + const parseResult = zodSchema.safeParse(args); + if (parseResult.success) { + return { data: parseResult.data, error: undefined }; + } else { + return { data: undefined, error: fromZodError(parseResult.error).message }; + } + } else { + return { data: args, error: undefined }; + } +} + /** * Handles OpenApi requests */ @@ -61,12 +97,14 @@ export async function handleRequest({ requestBody, prisma, logger, + zodSchemas, }: RequestContext): Promise { const parts = path.split('/'); if (parts.length < 2) { return { status: 400, body: { error: 'invalid request path' } }; } + method = method.toUpperCase(); const op = parts.pop(); const model = parts.pop(); @@ -84,7 +122,12 @@ export async function handleRequest({ if (method !== 'POST') { return { status: 400, body: { message: 'invalid request method, only POST is supported' } }; } + if (!requestBody) { + return { status: 400, body: { message: 'missing request body' } }; + } + args = requestBody; + // TODO: upsert's status code should be conditional resCode = 201; break; @@ -98,7 +141,7 @@ export async function handleRequest({ if (method !== 'GET') { return { status: 400, body: { message: 'invalid request method, only GET is supported' } }; } - args = query.q ? unmarshal(query.q as string) : {}; + args = query?.q ? unmarshal(query.q as string) : {}; break; case 'update': @@ -106,6 +149,10 @@ export async function handleRequest({ if (method !== 'PUT' && method !== 'PATCH') { return { status: 400, body: { message: 'invalid request method, only PUT AND PATCH are supported' } }; } + if (!requestBody) { + return { status: 400, body: { message: 'missing request body' } }; + } + args = requestBody; break; @@ -114,13 +161,20 @@ export async function handleRequest({ if (method !== 'DELETE') { return { status: 400, body: { message: 'invalid request method, only DELETE is supported' } }; } - args = query.q ? unmarshal(query.q as string) : {}; + args = query?.q ? unmarshal(query.q as string) : {}; break; default: return { status: 400, body: { message: 'invalid operation: ' + op } }; } + const { data, error } = zodValidate(zodSchemas, model, dbOp, args); + if (error) { + return { status: 400, body: { message: error } }; + } else { + args = data; + } + try { if (!prisma[model]) { return { status: 400, body: { message: `unknown model name: ${model}` } }; diff --git a/packages/server/tests/fastify-plugin.test.ts b/packages/server/tests/fastify-plugin.test.ts index 202eae87b..8b6f4e120 100644 --- a/packages/server/tests/fastify-plugin.test.ts +++ b/packages/server/tests/fastify-plugin.test.ts @@ -27,10 +27,14 @@ function makeUrl(path: string, q?: object) { describe('Fastify plugin tests', () => { it('run plugin', async () => { - const { prisma } = await loadSchema(schema); + const { prisma, zodSchemas } = await loadSchema(schema); const app = fastify(); - app.register(ZenStackFastifyPlugin, { prefix: '/api', getPrisma: () => prisma }); + app.register(ZenStackFastifyPlugin, { + prefix: '/api', + getPrisma: () => prisma, + zodSchemas, + }); let r = await app.inject({ method: 'GET', diff --git a/packages/server/tests/open-api.test.ts b/packages/server/tests/open-api.test.ts new file mode 100644 index 000000000..dee845e03 --- /dev/null +++ b/packages/server/tests/open-api.test.ts @@ -0,0 +1,172 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/// + +import { loadSchema } from '@zenstackhq/testtools'; +import { handleRequest } from '../src/openapi'; + +const schema = ` +model User { + id String @id @default(cuid()) + email String @unique + posts Post[] +} + +model Post { + id String @id @default(cuid()) + title String + author User? @relation(fields: [authorId], references: [id]) + authorId String? + published Boolean @default(false) + viewCount Int @default(0) +} +`; + +describe('OpenAPI server tests', () => { + it('crud', async () => { + const { prisma, zodSchemas } = await loadSchema(schema); + + let r = await handleRequest({ + method: 'get', + path: '/api/post/findMany', + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect(r.body).toHaveLength(0); + + r = await handleRequest({ + method: 'post', + path: '/api/user/create', + query: {}, + requestBody: { + include: { posts: true }, + data: { + id: 'user1', + email: 'user1@abc.com', + posts: { + create: [ + { title: 'post1', published: true, viewCount: 1 }, + { title: 'post2', published: false, viewCount: 2 }, + ], + }, + }, + }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(201); + expect(r.body).toEqual( + expect.objectContaining({ + email: 'user1@abc.com', + posts: expect.arrayContaining([ + expect.objectContaining({ title: 'post1' }), + expect.objectContaining({ title: 'post2' }), + ]), + }) + ); + const data: any = r.body; + expect(data.zenstack_guard).toBeUndefined(); + expect(data.zenstack_transaction).toBeUndefined(); + expect(data.posts[0].zenstack_guard).toBeUndefined(); + expect(data.posts[0].zenstack_transaction).toBeUndefined(); + + r = await handleRequest({ + method: 'get', + path: '/api/post/findMany', + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect(r.body).toHaveLength(2); + + r = await handleRequest({ + method: 'get', + path: '/api/post/findMany', + query: { q: JSON.stringify({ where: { viewCount: { gt: 1 } } }) }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect(r.body).toHaveLength(1); + + r = await handleRequest({ + method: 'put', + path: '/api/user/update', + requestBody: { where: { id: 'user1' }, data: { email: 'user1@def.com' } }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect((r.body as any).email).toBe('user1@def.com'); + + r = await handleRequest({ + method: 'get', + path: '/api/post/count', + query: { q: JSON.stringify({ where: { viewCount: { gt: 1 } } }) }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect(r.body).toBe(1); + + r = await handleRequest({ + method: 'get', + path: '/api/post/aggregate', + query: { q: JSON.stringify({ _sum: { viewCount: true } }) }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect((r.body as any)._sum.viewCount).toBe(3); + + r = await handleRequest({ + method: 'get', + path: '/api/post/groupBy', + query: { q: JSON.stringify({ by: ['published'], _sum: { viewCount: true } }) }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect(r.body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ published: true, _sum: { viewCount: 1 } }), + expect.objectContaining({ published: false, _sum: { viewCount: 2 } }), + ]) + ); + + r = await handleRequest({ + method: 'delete', + path: '/api/user/deleteMany', + query: { q: JSON.stringify({ where: { id: 'user1' } }) }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(200); + expect((r.body as any).count).toBe(1); + }); + + it('validation error', async () => { + const { prisma, zodSchemas } = await loadSchema(schema); + + let r = await handleRequest({ + method: 'get', + path: '/api/post/findUnique', + prisma, + zodSchemas, + }); + expect(r.status).toBe(400); + expect((r.body as any).message).toContain('Validation error'); + expect((r.body as any).message).toContain('where'); + + r = await handleRequest({ + method: 'post', + path: '/api/post/create', + requestBody: { data: {} }, + prisma, + zodSchemas, + }); + expect(r.status).toBe(400); + expect((r.body as any).message).toContain('Validation error'); + expect((r.body as any).message).toContain('data.title'); + }); +}); diff --git a/packages/testtools/package.json b/packages/testtools/package.json index bb64aa998..3df6f673c 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "description": "ZenStack Test Tools", "main": "index.js", "publishConfig": { diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index 933a3ff94..985d32937 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -59,14 +59,19 @@ generator js { } plugin meta { - provider = '@zenstack/model-meta' + provider = '@core/model-meta' output = '.zenstack' } plugin policy { - provider = '@zenstack/access-policy' + provider = '@core/access-policy' output = '.zenstack' } + +plugin zod { + provider = '@core/zod' + output = '.zenstack/zod' +} `; export async function loadSchemaFromFile(schemaFile: string, addPrelude = true, pushDb = true) { @@ -106,6 +111,7 @@ export async function loadSchema(schema: string, addPrelude = true, pushDb = tru const policy = require(path.join(workDir, '.zenstack/policy')).default; const modelMeta = require(path.join(workDir, '.zenstack/model-meta')).default; + const zodSchemas = require(path.join(workDir, '.zenstack/zod')).default; return { prisma, @@ -113,5 +119,6 @@ export async function loadSchema(schema: string, addPrelude = true, pushDb = tru withOmit: () => withOmit(prisma, modelMeta), withPassword: () => withPassword(prisma, modelMeta), withPresets: (user?: AuthUser) => withPresets(prisma, { user }, policy, modelMeta), + zodSchemas, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34bf09523..f1ac5d157 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -337,11 +337,15 @@ importers: '@prisma/generator-helper': ^4.7.1 '@zenstackhq/language': workspace:* copyfiles: ^2.4.1 + prettier: ^2.8.3 rimraf: ^3.0.2 + ts-morph: ^16.0.0 typescript: ^4.9.4 dependencies: '@prisma/generator-helper': 4.7.1 '@zenstackhq/language': link:../language/dist + prettier: 2.8.3 + ts-morph: 16.0.0 devDependencies: copyfiles: 2.4.1 rimraf: 3.0.2 @@ -355,6 +359,7 @@ importers: '@zenstackhq/runtime': workspace:* '@zenstackhq/sdk': workspace:* '@zenstackhq/testtools': workspace:* + change-case: ^4.1.2 copyfiles: ^2.4.1 fastify: ^4.14.1 fastify-plugin: ^4.5.0 @@ -363,11 +368,14 @@ importers: tiny-invariant: ^1.3.1 ts-jest: ^29.0.5 typescript: ^4.9.4 + zod-validation-error: ^0.2.1 dependencies: '@zenstackhq/openapi': link:../plugins/openapi/dist '@zenstackhq/runtime': link:../runtime/dist '@zenstackhq/sdk': link:../sdk/dist + change-case: 4.1.2 tiny-invariant: 1.3.1 + zod-validation-error: 0.2.1_zod@3.19.1 devDependencies: '@types/jest': 29.4.0 '@zenstackhq/testtools': link:../testtools/dist diff --git a/tests/integration/test-run/package-lock.json b/tests/integration/test-run/package-lock.json index 84f29c18c..123f67a13 100644 --- a/tests/integration/test-run/package-lock.json +++ b/tests/integration/test-run/package-lock.json @@ -126,7 +126,7 @@ }, "../../../packages/runtime/dist": { "name": "@zenstackhq/runtime", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "license": "MIT", "dependencies": { "@types/bcryptjs": "^2.4.2", @@ -156,7 +156,7 @@ }, "../../../packages/schema/dist": { "name": "zenstack", - "version": "1.0.0-alpha.72", + "version": "1.0.0-alpha.73", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/tests/integration/tests/schema/cal-com.zmodel b/tests/integration/tests/schema/cal-com.zmodel index 26dea14a7..8984bc758 100644 --- a/tests/integration/tests/schema/cal-com.zmodel +++ b/tests/integration/tests/schema/cal-com.zmodel @@ -13,15 +13,20 @@ generator client { } plugin meta { - provider = '@zenstack/model-meta' + provider = '@core/model-meta' output = '.zenstack' } plugin policy { - provider = '@zenstack/access-policy' + provider = '@core/access-policy' output = '.zenstack' } +plugin zod { + provider = '@core/zod' + output = '.zenstack/zod' +} + enum SchedulingType { ROUND_ROBIN @map("roundRobin") COLLECTIVE @map("collective") diff --git a/tests/integration/tests/schema/todo.zmodel b/tests/integration/tests/schema/todo.zmodel index 313cb04ac..19697086c 100644 --- a/tests/integration/tests/schema/todo.zmodel +++ b/tests/integration/tests/schema/todo.zmodel @@ -14,15 +14,20 @@ generator js { } plugin meta { - provider = '@zenstack/model-meta' + provider = '@core/model-meta' output = '.zenstack' } plugin policy { - provider = '@zenstack/access-policy' + provider = '@core/access-policy' output = '.zenstack' } +plugin zod { + provider = '@core/zod' + output = '.zenstack/zod' +} + /* * Model for a space in which users can collaborate on Lists and Todos */ From b7548d17999df1862bd15be470b611625e8e5445 Mon Sep 17 00:00:00 2001 From: Yiming Date: Tue, 14 Mar 2023 14:58:58 +0800 Subject: [PATCH 3/3] fix: make sure zod schemas are lazily loaded (#265) --- packages/plugins/trpc/src/zod/transformer.ts | 22 +++++++------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/plugins/trpc/src/zod/transformer.ts b/packages/plugins/trpc/src/zod/transformer.ts index 3349c0471..ca14fb2f4 100644 --- a/packages/plugins/trpc/src/zod/transformer.ts +++ b/packages/plugins/trpc/src/zod/transformer.ts @@ -387,14 +387,8 @@ export default class Transformer { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const model = findModelByName(this.models, modelName)!; - const { - selectImport, - includeImport, - selectZodSchemaLine, - includeZodSchemaLine, - selectZodSchemaLineLazy, - includeZodSchemaLineLazy, - } = this.resolveSelectIncludeImportAndZodSchemaLine(model); + const { selectImport, includeImport, selectZodSchemaLineLazy, includeZodSchemaLineLazy } = + this.resolveSelectIncludeImportAndZodSchemaLine(model); let imports = [`import { z } from 'zod'`, selectImport, includeImport]; let codeBody = ''; @@ -403,7 +397,7 @@ export default class Transformer { imports.push( `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` ); - codeBody += `findUnique: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereUniqueInputObjectSchema }),`; + codeBody += `findUnique: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema }),`; } if (findFirst) { @@ -413,7 +407,7 @@ export default class Transformer { `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'`, `import { ${modelName}ScalarFieldEnumSchema } from './enums/${modelName}ScalarFieldEnum.schema'` ); - codeBody += `findFirst: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional() }),`; + codeBody += `findFirst: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereInputObjectSchema.optional(), orderBy: z.union([${modelName}OrderByWithRelationInputObjectSchema, ${modelName}OrderByWithRelationInputObjectSchema.array()]).optional(), cursor: ${modelName}WhereUniqueInputObjectSchema.optional(), take: z.number().optional(), skip: z.number().optional(), distinct: z.array(${modelName}ScalarFieldEnumSchema).optional() }),`; } if (findMany) { @@ -430,7 +424,7 @@ export default class Transformer { imports.push( `import { ${modelName}CreateInputObjectSchema } from './objects/${modelName}CreateInput.schema'` ); - codeBody += `create: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} data: ${modelName}CreateInputObjectSchema }),`; + codeBody += `create: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: ${modelName}CreateInputObjectSchema }),`; } if (createMany) { @@ -444,7 +438,7 @@ export default class Transformer { imports.push( `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` ); - codeBody += `'delete': z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereUniqueInputObjectSchema }),`; + codeBody += `'delete': z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema }),`; } if (deleteMany) { @@ -459,7 +453,7 @@ export default class Transformer { `import { ${modelName}UpdateInputObjectSchema } from './objects/${modelName}UpdateInput.schema'`, `import { ${modelName}WhereUniqueInputObjectSchema } from './objects/${modelName}WhereUniqueInput.schema'` ); - codeBody += `update: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} data: ${modelName}UpdateInputObjectSchema, where: ${modelName}WhereUniqueInputObjectSchema }),`; + codeBody += `update: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} data: ${modelName}UpdateInputObjectSchema, where: ${modelName}WhereUniqueInputObjectSchema }),`; } if (updateMany) { @@ -476,7 +470,7 @@ export default class Transformer { `import { ${modelName}CreateInputObjectSchema } from './objects/${modelName}CreateInput.schema'`, `import { ${modelName}UpdateInputObjectSchema } from './objects/${modelName}UpdateInput.schema'` ); - codeBody += `upsert: z.object({ ${selectZodSchemaLine} ${includeZodSchemaLine} where: ${modelName}WhereUniqueInputObjectSchema, create: ${modelName}CreateInputObjectSchema, update: ${modelName}UpdateInputObjectSchema }),`; + codeBody += `upsert: z.object({ ${selectZodSchemaLineLazy} ${includeZodSchemaLineLazy} where: ${modelName}WhereUniqueInputObjectSchema, create: ${modelName}CreateInputObjectSchema, update: ${modelName}UpdateInputObjectSchema }),`; } if (aggregate) {