From 9b1b0a617e78f05ff4f1eb42872a04e2ebe787b1 Mon Sep 17 00:00:00 2001 From: JG Date: Wed, 29 Mar 2023 20:48:45 +0100 Subject: [PATCH 01/12] feat: support abstract model --- packages/language/src/generated/ast.ts | 9 +++- packages/language/src/generated/grammar.ts | 54 +++++++++++++++++++ packages/language/src/zmodel.langium | 3 +- .../validator/datamodel-validator.ts | 2 +- .../src/language-server/zmodel-linker.ts | 30 +++++++++++ .../src/plugins/prisma/schema-generator.ts | 2 + .../tests/generator/prisma-generator.test.ts | 35 ++++++++++++ packages/schema/tests/schema/abstract.test.ts | 12 +++++ packages/schema/tests/schema/abstract.zmodel | 33 ++++++++++++ 9 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 packages/schema/tests/schema/abstract.test.ts create mode 100644 packages/schema/tests/schema/abstract.zmodel diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts index 8e00e671e..8f3e38ffb 100644 --- a/packages/language/src/generated/ast.ts +++ b/packages/language/src/generated/ast.ts @@ -166,7 +166,9 @@ export interface DataModel extends AstNode { attributes: Array comments: Array fields: Array + isAbstract: boolean name: string + superTypes: Array> } export const DataModel = 'DataModel'; @@ -615,6 +617,9 @@ export class ZModelAstReflection extends AbstractAstReflection { case 'FunctionParamType:reference': { return TypeDeclaration; } + case 'DataModel:superTypes': { + return DataModel; + } case 'InvocationExpr:function': { return FunctionDecl; } @@ -680,7 +685,9 @@ export class ZModelAstReflection extends AbstractAstReflection { mandatory: [ { name: 'attributes', type: 'array' }, { name: 'comments', type: 'array' }, - { name: 'fields', type: 'array' } + { name: 'fields', type: 'array' }, + { name: 'isAbstract', type: 'boolean' }, + { name: 'superTypes', type: 'array' } ] }; } diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts index 14d2c86d4..ae8004bed 100644 --- a/packages/language/src/generated/grammar.ts +++ b/packages/language/src/generated/grammar.ts @@ -1619,6 +1619,16 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel }, "cardinality": "*" }, + { + "$type": "Assignment", + "feature": "isAbstract", + "operator": "?=", + "terminal": { + "$type": "Keyword", + "value": "abstract" + }, + "cardinality": "?" + }, { "$type": "Keyword", "value": "model" @@ -1635,6 +1645,50 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "arguments": [] } }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "extends" + }, + { + "$type": "Assignment", + "feature": "superTypes", + "operator": "+=", + "terminal": { + "$type": "CrossReference", + "type": { + "$ref": "#/rules@29" + }, + "deprecatedSyntax": false + } + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "," + }, + { + "$type": "Assignment", + "feature": "superTypes", + "operator": "+=", + "terminal": { + "$type": "CrossReference", + "type": { + "$ref": "#/rules@29" + }, + "deprecatedSyntax": false + } + } + ], + "cardinality": "*" + } + ], + "cardinality": "?" + }, { "$type": "Keyword", "value": "{" diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index 40566e697..6964578d1 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -150,7 +150,8 @@ Argument: // model DataModel: (comments+=TRIPLE_SLASH_COMMENT)* - 'model' name=ID '{' ( + (isAbstract?='abstract')? 'model' name=ID + ('extends' superTypes+=[DataModel] (',' superTypes+=[DataModel])*)? '{' ( fields+=DataModelField | attributes+=DataModelAttribute )+ diff --git a/packages/schema/src/language-server/validator/datamodel-validator.ts b/packages/schema/src/language-server/validator/datamodel-validator.ts index 1468311c8..2b09322cf 100644 --- a/packages/schema/src/language-server/validator/datamodel-validator.ts +++ b/packages/schema/src/language-server/validator/datamodel-validator.ts @@ -67,7 +67,7 @@ export default class DataModelValidator implements AstValidator { field.attributes.forEach((attr) => validateAttributeApplication(attr, accept)); - if (isDataModel(field.type.reference?.ref)) { + if (isDataModel(field.type.reference?.ref) && !(field.$container as DataModel).isAbstract) { this.validateRelationField(field, accept); } } diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index c98c0890c..5bbedbf4c 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -19,6 +19,7 @@ import { isReferenceExpr, LiteralExpr, MemberAccessExpr, + Model, NullExpr, ObjectExpr, ReferenceExpr, @@ -38,6 +39,7 @@ import { LangiumDocument, LangiumServices, LinkingError, + Mutable, Reference, streamContents, } from 'langium'; @@ -70,6 +72,8 @@ export class ZModelLinker extends DefaultLinker { return; } + this.mergeAbstractBaseModel(document); + for (const node of streamContents(document.parseResult.value)) { await interruptAndCheck(cancelToken); this.resolve(node, document); @@ -92,6 +96,32 @@ export class ZModelLinker extends DefaultLinker { //#endregion + //#region abstract DataModel + + private mergeAbstractBaseModel(document: LangiumDocument) { + const model = document.parseResult.value as Model; + + model.declarations.forEach((decl) => { + if (decl.$type === 'DataModel') { + const dataModel = decl as DataModel; + if (dataModel.superTypes.length > 0) { + const superType = dataModel.superTypes[0].ref as DataModel; + + superType.fields.forEach((field) => { + const cloneField = Object.assign({}, field); + const mutable = cloneField as Mutable; + // update container + mutable.$container = dataModel; + mutable.$containerIndex = mutable.$containerIndex || 0 + dataModel.fields.length; + dataModel.fields.push(mutable as DataModelField); + }); + } + } + }); + } + + //#endregion + //#region Expression type resolving private resolveFromScopeProviders( diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 4066270e3..0d026b934 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -192,6 +192,8 @@ export default class PrismaSchemaGenerator { } private generateModel(prisma: PrismaModel, decl: DataModel) { + if (decl.isAbstract) return; + const model = prisma.addModel(decl.name); for (const field of decl.fields) { this.generateModelField(model, field); diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index 263c335e6..66a3e8e80 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -191,4 +191,39 @@ describe('Prisma generator test', () => { expect(content).toContain('@@schema("base")'); expect(content).toContain('schemas = ["base","transactional"]'); }); + + it('abstract model', async () => { + const model = await loadModel(` + datasource db { + provider = 'postgresql' + url = env('URL') + } + abstract model Base { + id String @id + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + } + + model Post extends Base { + title String + published Boolean @default(false) + } + `); + + const { name } = tmp.fileSync({ postfix: '.prisma' }); + await new PrismaSchemaGenerator().generate(model, { + provider: '@core/prisma', + schemaPath: 'schema.zmodel', + output: name, + generateClient: false, + }); + + const content = fs.readFileSync(name, 'utf-8'); + const dmmf = await getDMMF({ datamodel: content }); + + expect(dmmf.datamodel.models.length).toBe(1); + const post = dmmf.datamodel.models[0]; + expect(post.name).toBe('Post'); + expect(post.fields.length).toBe(6); + }); }); diff --git a/packages/schema/tests/schema/abstract.test.ts b/packages/schema/tests/schema/abstract.test.ts new file mode 100644 index 000000000..a3364bc2e --- /dev/null +++ b/packages/schema/tests/schema/abstract.test.ts @@ -0,0 +1,12 @@ +import * as fs from 'fs'; +import path from 'path'; +import { loadModel } from '../utils'; + +describe('Abstract Schema Tests', () => { + it('model loading', async () => { + const content = fs.readFileSync(path.join(__dirname, './abstract.zmodel'), { + encoding: 'utf-8', + }); + await loadModel(content); + }); +}); diff --git a/packages/schema/tests/schema/abstract.zmodel b/packages/schema/tests/schema/abstract.zmodel new file mode 100644 index 000000000..d49a95640 --- /dev/null +++ b/packages/schema/tests/schema/abstract.zmodel @@ -0,0 +1,33 @@ +datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') +} + +generator js { + provider = 'prisma-client-js' +} + +abstract model Base { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user User @relation(fields: [userId], references: [id]) + userId String +} + + +model Post extends Base { + title String + published Boolean @default(false) +} + +model Todo extends Base { + description String + isDone Boolean @default(false) +} + +model User { + id String @id + todos Todo[] + posts Post[] +} \ No newline at end of file From 9f417d606833dfed186f797c3833842bc1b5d9fd Mon Sep 17 00:00:00 2001 From: JG Date: Mon, 3 Apr 2023 16:52:57 +0100 Subject: [PATCH 02/12] fix: merge abstract model after passing validation --- packages/language/src/ast.ts | 7 ++++ packages/schema/src/cli/cli-util.ts | 7 +++- .../validator/datamodel-validator.ts | 13 ++++---- .../src/language-server/zmodel-code-action.ts | 4 +-- .../src/language-server/zmodel-linker.ts | 32 ++++++++----------- .../src/plugins/prisma/schema-generator.ts | 2 -- packages/schema/src/utils/ast-utils.ts | 31 ++++++++++++++++-- packages/schema/tests/utils.ts | 4 +++ 8 files changed, 69 insertions(+), 31 deletions(-) diff --git a/packages/language/src/ast.ts b/packages/language/src/ast.ts index b9888eb9d..c4a9424e5 100644 --- a/packages/language/src/ast.ts +++ b/packages/language/src/ast.ts @@ -43,6 +43,13 @@ declare module './generated/ast' { */ $resolvedParam?: AttributeParam; } + + interface DataModel { + /** + * Resolved fields, include inherited fields + */ + $resolvedFields: Array; + } } declare module 'langium' { diff --git a/packages/schema/src/cli/cli-util.ts b/packages/schema/src/cli/cli-util.ts index 8c6d44886..ff3034fea 100644 --- a/packages/schema/src/cli/cli-util.ts +++ b/packages/schema/src/cli/cli-util.ts @@ -9,6 +9,7 @@ import { URI } from 'vscode-uri'; 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 { mergeBaseModel } from '../utils/ast-utils'; import { ensurePackage, installPackage, PackageManagers } from '../utils/pkg-utils'; import { CliError } from './cli-error'; import { PluginRunner } from './plugin-runner'; @@ -124,7 +125,11 @@ export async function loadDocument(fileName: string): Promise { throw new CliError('schema validation errors'); } - return document.parseResult.value as Model; + const result = document.parseResult.value as Model; + + mergeBaseModel(result); + + return result; } export async function getPluginDocuments(services: ZModelServices, fileName: string): Promise { diff --git a/packages/schema/src/language-server/validator/datamodel-validator.ts b/packages/schema/src/language-server/validator/datamodel-validator.ts index 2b09322cf..557e931a5 100644 --- a/packages/schema/src/language-server/validator/datamodel-validator.ts +++ b/packages/schema/src/language-server/validator/datamodel-validator.ts @@ -19,13 +19,13 @@ import { getLiteral } from '@zenstackhq/sdk'; */ export default class DataModelValidator implements AstValidator { validate(dm: DataModel, accept: ValidationAcceptor): void { - validateDuplicatedDeclarations(dm.fields, accept); + validateDuplicatedDeclarations(dm.$resolvedFields, accept); this.validateAttributes(dm, accept); this.validateFields(dm, accept); } private validateFields(dm: DataModel, accept: ValidationAcceptor) { - const idFields = dm.fields.filter((f) => f.attributes.find((attr) => attr.decl.ref?.name === '@id')); + const idFields = dm.$resolvedFields.filter((f) => f.attributes.find((attr) => attr.decl.ref?.name === '@id')); const modelLevelIds = getIdFields(dm); if (idFields.length === 0 && modelLevelIds.length === 0) { @@ -57,7 +57,7 @@ export default class DataModelValidator implements AstValidator { }); } - dm.fields.forEach((field) => this.validateField(field, accept)); + dm.$resolvedFields.forEach((field) => this.validateField(field, accept)); } private validateField(field: DataModelField, accept: ValidationAcceptor): void { @@ -172,8 +172,9 @@ export default class DataModelValidator implements AstValidator { if (relationName) { // field's relation points to another type, and that type's opposite relation field // points back - const oppositeModelFields = field.type.reference?.ref?.fields as DataModelField[]; - if (oppositeModelFields) { + const oppositeModel = field.type.reference?.ref as DataModel; + if (oppositeModel) { + const oppositeModelFields = oppositeModel.$resolvedFields as DataModelField[]; for (const oppositeField of oppositeModelFields) { // find the opposite relation with the matching name const relAttr = oppositeField.attributes.find((a) => a.decl.ref?.name === '@relation'); @@ -201,7 +202,7 @@ export default class DataModelValidator implements AstValidator { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const oppositeModel = field.type.reference!.ref! as DataModel; - let oppositeFields = oppositeModel.fields.filter((f) => f.type.reference?.ref === field.$container); + let oppositeFields = oppositeModel.$resolvedFields.filter((f) => f.type.reference?.ref === field.$container); oppositeFields = oppositeFields.filter((f) => { const fieldRel = this.parseRelation(f); return fieldRel.valid && fieldRel.name === thisRelation.name; diff --git a/packages/schema/src/language-server/zmodel-code-action.ts b/packages/schema/src/language-server/zmodel-code-action.ts index d8d5d69bb..a9b714721 100644 --- a/packages/schema/src/language-server/zmodel-code-action.ts +++ b/packages/schema/src/language-server/zmodel-code-action.ts @@ -78,7 +78,7 @@ export class ZModelCodeActionProvider implements CodeActionProvider { let newText = ''; if (astNode.type.array) { //post Post[] - const idField = container.fields.find((f) => + const idField = container.$resolvedFields.find((f) => f.attributes.find((attr) => attr.decl.ref?.name === '@id') ) as DataModelField; @@ -96,7 +96,7 @@ export class ZModelCodeActionProvider implements CodeActionProvider { const idFieldName = idField.name; const referenceIdFieldName = fieldName + this.upperCaseFirstLetter(idFieldName); - if (!oppositeModel.fields.find((f) => f.name === referenceIdFieldName)) { + if (!oppositeModel.$resolvedFields.find((f) => f.name === referenceIdFieldName)) { referenceField = '\n' + indent + `${referenceIdFieldName} ${idField.type.type}`; } diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index 5bbedbf4c..538205d9b 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -39,7 +39,6 @@ import { LangiumDocument, LangiumServices, LinkingError, - Mutable, Reference, streamContents, } from 'langium'; @@ -72,7 +71,7 @@ export class ZModelLinker extends DefaultLinker { return; } - this.mergeAbstractBaseModel(document); + this.resolveBaseModels(document); for (const node of streamContents(document.parseResult.value)) { await interruptAndCheck(cancelToken); @@ -98,24 +97,21 @@ export class ZModelLinker extends DefaultLinker { //#region abstract DataModel - private mergeAbstractBaseModel(document: LangiumDocument) { + private resolveBaseModels(document: LangiumDocument) { const model = document.parseResult.value as Model; model.declarations.forEach((decl) => { if (decl.$type === 'DataModel') { const dataModel = decl as DataModel; - if (dataModel.superTypes.length > 0) { - const superType = dataModel.superTypes[0].ref as DataModel; - - superType.fields.forEach((field) => { - const cloneField = Object.assign({}, field); - const mutable = cloneField as Mutable; - // update container - mutable.$container = dataModel; - mutable.$containerIndex = mutable.$containerIndex || 0 + dataModel.fields.length; - dataModel.fields.push(mutable as DataModelField); - }); - } + dataModel.$resolvedFields = [...dataModel.fields]; + dataModel.superTypes.forEach((superType) => { + const superTypeDecl = superType.ref; + if (superTypeDecl) { + superTypeDecl.fields.forEach((field) => { + dataModel.$resolvedFields.push(field); + }); + } + }); } }); } @@ -327,7 +323,7 @@ export class ZModelLinker extends DefaultLinker { if (operandResolved && !operandResolved.array && isDataModel(operandResolved.decl)) { const modelDecl = operandResolved.decl as DataModel; - const provider = (name: string) => modelDecl.fields.find((f) => f.name === name); + const provider = (name: string) => modelDecl.$resolvedFields.find((f) => f.name === name); extraScopes = [provider, ...extraScopes]; } @@ -343,7 +339,7 @@ export class ZModelLinker extends DefaultLinker { const resolvedType = node.left.$resolvedType; if (resolvedType && isDataModel(resolvedType.decl) && resolvedType.array) { const dataModelDecl = resolvedType.decl; - const provider = (name: string) => dataModelDecl.fields.find((f) => f.name === name); + const provider = (name: string) => dataModelDecl.$resolvedFields.find((f) => f.name === name); extraScopes = [provider, ...extraScopes]; this.resolve(node.right, document, extraScopes); this.resolveToBuiltinTypeOrDecl(node, 'Boolean'); @@ -405,7 +401,7 @@ export class ZModelLinker extends DefaultLinker { const transtiveDataModel = attrAppliedOn.type.reference?.ref as DataModel; if (transtiveDataModel) { // resolve references in the context of the transitive data model - const scopeProvider = (name: string) => transtiveDataModel.fields.find((f) => f.name === name); + const scopeProvider = (name: string) => transtiveDataModel.$resolvedFields.find((f) => f.name === name); if (isArrayExpr(node.value)) { node.value.items.forEach((item) => { if (isReferenceExpr(item)) { diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 0d026b934..4066270e3 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -192,8 +192,6 @@ export default class PrismaSchemaGenerator { } private generateModel(prisma: PrismaModel, decl: DataModel) { - if (decl.isAbstract) return; - const model = prisma.addModel(decl.name); for (const field of decl.fields) { this.generateModelField(model, field); diff --git a/packages/schema/src/utils/ast-utils.ts b/packages/schema/src/utils/ast-utils.ts index 5456a2670..37a562ab8 100644 --- a/packages/schema/src/utils/ast-utils.ts +++ b/packages/schema/src/utils/ast-utils.ts @@ -15,6 +15,7 @@ import { } from '@zenstackhq/language/ast'; import { PolicyOperationKind } from '@zenstackhq/runtime'; import { getLiteral } from '@zenstackhq/sdk'; +import { AstNode, Mutable } from 'langium'; import { isFromStdlib } from '../language-server/utils'; export function extractDataModelsWithAllowRules(model: Model): DataModel[] { @@ -31,7 +32,7 @@ export function analyzePolicies(dataModel: DataModel) { const read = toStaticPolicy('read', allows, denies); const update = toStaticPolicy('update', allows, denies); const del = toStaticPolicy('delete', allows, denies); - const hasFieldValidation = dataModel.fields.some((field) => + const hasFieldValidation = dataModel.$resolvedFields.some((field) => field.attributes.some((attr) => VALIDATION_ATTRIBUTES.includes(attr.decl.$refText)) ); @@ -48,6 +49,30 @@ export function analyzePolicies(dataModel: DataModel) { }; } +export function mergeBaseModel(model: Model) { + model.declarations + .filter((x) => x.$type === 'DataModel') + .forEach((decl) => { + const dataModel = decl as DataModel; + + dataModel.superTypes.forEach((superType) => { + const superTypeDecl = superType.ref; + if (superTypeDecl) { + superTypeDecl.fields.forEach((field) => { + const cloneField = Object.assign({}, field); + const mutable = cloneField as Mutable; + // update container + mutable.$container = dataModel; + dataModel.fields.push(mutable as DataModelField); + }); + } + }); + }); + + // remove abstract models + model.declarations = model.declarations.filter((x) => !(x.$type == 'DataModel' && x.isAbstract)); +} + function toStaticPolicy( operation: PolicyOperationKind, allows: DataModelAttribute[], @@ -104,7 +129,9 @@ export const VALIDATION_ATTRIBUTES = [ ]; export function getIdFields(dataModel: DataModel) { - const fieldLevelId = dataModel.fields.find((f) => f.attributes.some((attr) => attr.decl.$refText === '@id')); + const fieldLevelId = dataModel.$resolvedFields.find((f) => + f.attributes.some((attr) => attr.decl.$refText === '@id') + ); if (fieldLevelId) { return [fieldLevelId]; } else { diff --git a/packages/schema/tests/utils.ts b/packages/schema/tests/utils.ts index 91eb81cde..f362b4019 100644 --- a/packages/schema/tests/utils.ts +++ b/packages/schema/tests/utils.ts @@ -5,6 +5,7 @@ import * as path from 'path'; import * as tmp from 'tmp'; import { URI } from 'vscode-uri'; import { createZModelServices } from '../src/language-server/zmodel-module'; +import { mergeBaseModel } from '../src/utils/ast-utils'; export class SchemaLoadingError extends Error { constructor(public readonly errors: string[]) { @@ -49,6 +50,9 @@ export async function loadModel(content: string, validate = true, verbose = true } const model = (await doc.parseResult.value) as Model; + + mergeBaseModel(model); + return model; } From c68e301d7234cbb9dd188aad7c9f1b03b17c46db Mon Sep 17 00:00:00 2001 From: JG Date: Mon, 3 Apr 2023 16:54:57 +0100 Subject: [PATCH 03/12] fix: add missing file --- .vscode/launch.json | 2 +- packages/language/syntaxes/zmodel.tmLanguage.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 45d27c896..193903fc4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -41,7 +41,7 @@ "request": "attach", "skipFiles": ["/**"], "sourceMaps": true, - "outFiles": ["${workspaceFolder}/bundle/**/*.js"] + "outFiles": ["${workspaceFolder}/packages/schema/bundle/**/*.js"] }, { "name": "Todo sample: debug server-side", diff --git a/packages/language/syntaxes/zmodel.tmLanguage.json b/packages/language/syntaxes/zmodel.tmLanguage.json index a98fc252e..62b36c7a9 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|BigInt|Boolean|Bytes|ContextType|DateTime|Decimal|Desc|FieldReference|Float|Int|Json|Null|Object|String|TransitiveFieldReference|attribute|datasource|enum|function|generator|in|model|plugin|sort)\\b" + "match": "\\b(Any|Asc|BigInt|Boolean|Bytes|ContextType|DateTime|Decimal|Desc|FieldReference|Float|Int|Json|Null|Object|String|TransitiveFieldReference|abstract|attribute|datasource|enum|extends|function|generator|in|model|plugin|sort)\\b" }, { "name": "string.quoted.double.zmodel", From 1286c8c9329d56910cb6daf2dce2f29bba8b39ea Mon Sep 17 00:00:00 2001 From: JG Date: Mon, 3 Apr 2023 23:32:19 +0100 Subject: [PATCH 04/12] fix: add isInherited in DataModelField --- packages/language/src/ast.ts | 4 ++ .../validator/datamodel-validator.ts | 46 +++++++++++-------- .../src/language-server/validator/utils.ts | 10 +++- .../src/language-server/zmodel-linker.ts | 8 +++- 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/packages/language/src/ast.ts b/packages/language/src/ast.ts index c4a9424e5..c8637115a 100644 --- a/packages/language/src/ast.ts +++ b/packages/language/src/ast.ts @@ -50,6 +50,10 @@ declare module './generated/ast' { */ $resolvedFields: Array; } + + interface DataModelField { + $isInherited?: boolean; + } } declare module 'langium' { diff --git a/packages/schema/src/language-server/validator/datamodel-validator.ts b/packages/schema/src/language-server/validator/datamodel-validator.ts index 557e931a5..5448f8807 100644 --- a/packages/schema/src/language-server/validator/datamodel-validator.ts +++ b/packages/schema/src/language-server/validator/datamodel-validator.ts @@ -57,19 +57,23 @@ export default class DataModelValidator implements AstValidator { }); } - dm.$resolvedFields.forEach((field) => this.validateField(field, accept)); + dm.fields.forEach((field) => this.validateField(field, dm, accept)); + + if (!dm.isAbstract) { + dm.$resolvedFields + .filter((x) => isDataModel(x.type.reference?.ref)) + .forEach((y) => { + this.validateRelationField(y, accept); + }); + } } - private validateField(field: DataModelField, accept: ValidationAcceptor): void { + private validateField(field: DataModelField, dataModel: DataModel, accept: ValidationAcceptor): void { if (field.type.array && field.type.optional) { accept('error', 'Optional lists are not supported. Use either `Type[]` or `Type?`', { node: field.type }); } field.attributes.forEach((attr) => validateAttributeApplication(attr, accept)); - - if (isDataModel(field.type.reference?.ref) && !(field.$container as DataModel).isAbstract) { - this.validateRelationField(field, accept); - } } private validateAttributes(dm: DataModel, accept: ValidationAcceptor) { @@ -216,20 +220,22 @@ export default class DataModelValidator implements AstValidator { ); return; } else if (oppositeFields.length > 1) { - oppositeFields.forEach((f) => { - if (this.isSelfRelation(f)) { - // self relations are partial - // https://www.prisma.io/docs/concepts/components/prisma-schema/relations/self-relations - } else { - accept( - 'error', - `Fields ${oppositeFields.map((f) => '"' + f.name + '"').join(', ')} on model "${ - oppositeModel.name - }" refer to the same relation to model "${field.$container.name}"`, - { node: f } - ); - } - }); + oppositeFields + .filter((x) => !x.$isInherited) + .forEach((f) => { + if (this.isSelfRelation(f)) { + // self relations are partial + // https://www.prisma.io/docs/concepts/components/prisma-schema/relations/self-relations + } else { + accept( + 'error', + `Fields ${oppositeFields.map((f) => '"' + f.name + '"').join(', ')} on model "${ + oppositeModel.name + }" refer to the same relation to model "${field.$container.name}"`, + { node: f } + ); + } + }); return; } diff --git a/packages/schema/src/language-server/validator/utils.ts b/packages/schema/src/language-server/validator/utils.ts index 7cdbc9037..e4514693f 100644 --- a/packages/schema/src/language-server/validator/utils.ts +++ b/packages/schema/src/language-server/validator/utils.ts @@ -37,8 +37,16 @@ export function validateDuplicatedDeclarations( for (const [name, decls] of Object.entries(groupByName)) { if (decls.length > 1) { + let errorField = decls[1]; + if (decls[0].$type === 'DataModelField') { + const nonInheritedFields = decls.filter((x) => !(x as DataModelField).$isInherited); + if (nonInheritedFields.length > 0) { + errorField = nonInheritedFields.slice(-1)[0]; + } + } + accept('error', `Duplicated declaration name "${name}"`, { - node: decls[1], + node: errorField, }); } } diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index 538205d9b..d56ecb6ce 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -39,6 +39,7 @@ import { LangiumDocument, LangiumServices, LinkingError, + Mutable, Reference, streamContents, } from 'langium'; @@ -108,7 +109,12 @@ export class ZModelLinker extends DefaultLinker { const superTypeDecl = superType.ref; if (superTypeDecl) { superTypeDecl.fields.forEach((field) => { - dataModel.$resolvedFields.push(field); + const cloneField = Object.assign({}, field); + cloneField.$isInherited = true; + const mutable = cloneField as Mutable; + // update container + mutable.$container = dataModel; + dataModel.$resolvedFields.push(cloneField); }); } }); From 416a7396d719348d1b29c1e801736d05c4179546 Mon Sep 17 00:00:00 2001 From: JG Date: Fri, 28 Apr 2023 12:16:21 +0100 Subject: [PATCH 05/12] merge with dev --- .github/workflows/build-test.yml | 11 +- package.json | 2 +- packages/language/package.json | 2 +- packages/language/src/generated/ast.ts | 91 +- packages/language/src/generated/grammar.ts | 664 ++-- packages/language/src/zmodel.langium | 72 +- .../language/syntaxes/zmodel.tmLanguage.json | 2 +- packages/next/package.json | 11 +- packages/plugins/openapi/package.json | 10 +- packages/plugins/openapi/src/generator.ts | 95 +- packages/plugins/openapi/src/meta.ts | 18 +- packages/plugins/openapi/src/schema.ts | 43 + .../plugins/openapi/tests/openapi.test.ts | 236 +- packages/plugins/react/package.json | 15 +- packages/plugins/react/src/generator/index.ts | 17 + .../react/src/generator/react-query.ts | 327 ++ .../swr.ts} | 42 +- packages/plugins/react/src/index.ts | 8 +- packages/plugins/react/src/runtime/index.ts | 20 + .../plugins/react/src/runtime/react-query.ts | 141 + .../react/src/{runtime.ts => runtime/swr.ts} | 59 +- packages/plugins/react/src/runtime/utils.ts | 25 + .../plugins/react/src/serialization-utils.ts | 9 + .../plugins/react/tests/react-hooks.test.ts | 40 +- packages/plugins/trpc/package.json | 4 +- packages/plugins/trpc/src/generator.ts | 130 +- packages/plugins/trpc/src/helpers.ts | 38 +- packages/plugins/trpc/src/zod/generator.ts | 23 +- packages/plugins/trpc/src/zod/transformer.ts | 49 +- packages/runtime/package.json | 8 +- .../src/enhancements/policy/policy-utils.ts | 6 +- packages/runtime/src/enhancements/proxy.ts | 4 +- packages/runtime/src/version.ts | 1 + packages/schema/package.json | 30 +- packages/schema/src/cli/cli-util.ts | 135 +- packages/schema/src/cli/config.ts | 40 + packages/schema/src/cli/index.ts | 48 +- packages/schema/src/cli/plugin-runner.ts | 25 +- .../validator/datamodel-validator.ts | 7 +- .../validator/expression-validator.ts | 21 +- .../validator/schema-validator.ts | 23 +- .../src/language-server/validator/utils.ts | 6 +- .../validator/zmodel-validator.ts | 4 +- .../src/language-server/zmodel-code-action.ts | 6 +- .../src/language-server/zmodel-linker.ts | 51 +- .../src/language-server/zmodel-module.ts | 5 +- .../src/language-server/zmodel-scope.ts | 77 +- .../zmodel-workspace-manager.ts | 123 +- .../access-policy/expression-writer.ts | 41 +- .../access-policy/policy-guard-generator.ts | 57 +- .../typescript-expression-transformer.ts | 103 +- .../access-policy/zod-schema-generator.ts | 6 +- .../schema/src/plugins/model-meta/index.ts | 32 +- packages/schema/src/plugins/plugin-utils.ts | 3 +- packages/schema/src/plugins/prisma/index.ts | 10 +- .../src/plugins/prisma/schema-generator.ts | 55 +- packages/schema/src/plugins/zod/generator.ts | 31 +- .../schema/src/plugins/zod/transformer.ts | 51 +- packages/schema/src/res/starter.zmodel | 2 +- packages/schema/src/res/stdlib.zmodel | 5 +- packages/schema/src/telemetry.ts | 30 +- packages/schema/src/utils/ast-utils.ts | 64 + packages/schema/src/utils/pkg-utils.ts | 33 +- packages/schema/src/utils/version-utils.ts | 1 + packages/schema/tests/cli/cli.test.ts | 77 - packages/schema/tests/cli/command.test.ts | 129 + packages/schema/tests/cli/config.test.ts | 101 + packages/schema/tests/cli/plugins.test.ts | 84 + .../tests/generator/expression-writer.test.ts | 163 +- .../tests/generator/prisma-generator.test.ts | 95 +- .../tests/generator/zmodel/schema.zmodel | 14 + .../tests/generator/zmodel/user/user.zmodel | 17 + packages/schema/tests/schema/cal-com.zmodel | 4 + .../schema/mutil-files/multi-files.test.ts | 12 + .../tests/schema/mutil-files/schema.zmodel | 19 + .../tests/schema/mutil-files/user.zmodel | 10 + packages/schema/tests/schema/parser.test.ts | 2 + .../validation/attribute-validation.test.ts | 13 +- .../validation/datamodel-validation.test.ts | 35 + .../validation/schema-validation.test.ts | 4 - packages/sdk/package.json | 2 +- packages/sdk/src/code-gen.ts | 58 +- packages/sdk/src/constants.ts | 5 + packages/sdk/src/index.ts | 1 + packages/sdk/src/policy.ts | 82 + packages/sdk/src/types.ts | 3 +- packages/sdk/src/utils.ts | 2 +- packages/server/package.json | 6 +- packages/server/src/express/middleware.ts | 3 +- packages/server/src/fastify/plugin.ts | 3 +- packages/server/src/openapi/index.ts | 6 +- packages/server/tests/open-api.test.ts | 2 + packages/server/tests/utils.ts | 4 + packages/testtools/package.json | 4 +- packages/testtools/src/package.template.json | 2 +- packages/testtools/src/schema.ts | 109 +- pnpm-lock.yaml | 3389 +++-------------- tests/integration/package.json | 10 +- tests/integration/test-run/package-lock.json | 76 +- tests/integration/test-run/package.json | 2 +- .../e2e/filter-function-coverage.test.ts | 90 +- .../tests/e2e/prisma-methods.test.ts | 2 +- .../tests/nextjs/test-project/package.json | 3 +- .../tests/nextjs/test-project/postgres.zmodel | 16 +- .../tests/nextjs/test-project/sqlite.zmodel | 16 +- tests/integration/tests/schema/cal-com.zmodel | 6 + tests/integration/tests/schema/todo.zmodel | 6 + .../tests/trpc/test-project/package-lock.json | 2 +- .../tests/trpc/test-project/package.json | 3 +- .../tests/trpc/test-project/todo.zmodel | 16 +- .../tests/with-policy/auth.test.ts | 1 - .../tests/with-policy/deep-nested.test.ts | 1 - .../with-policy/field-validation.test.ts | 4 +- .../with-policy/multi-field-unique.test.ts | 1 - .../tests/with-policy/nested-to-many.test.ts | 1 - .../tests/with-policy/nested-to-one.test.ts | 1 - .../tests/with-policy/post-update.test.ts | 1 - .../with-policy/toplevel-operations.test.ts | 1 - 118 files changed, 4208 insertions(+), 3859 deletions(-) create mode 100644 packages/plugins/openapi/src/schema.ts create mode 100644 packages/plugins/react/src/generator/index.ts create mode 100644 packages/plugins/react/src/generator/react-query.ts rename packages/plugins/react/src/{react-hooks-generator.ts => generator/swr.ts} (93%) create mode 100644 packages/plugins/react/src/runtime/index.ts create mode 100644 packages/plugins/react/src/runtime/react-query.ts rename packages/plugins/react/src/{runtime.ts => runtime/swr.ts} (69%) create mode 100644 packages/plugins/react/src/runtime/utils.ts create mode 100644 packages/schema/src/cli/config.ts delete mode 100644 packages/schema/tests/cli/cli.test.ts create mode 100644 packages/schema/tests/cli/command.test.ts create mode 100644 packages/schema/tests/cli/config.test.ts create mode 100644 packages/schema/tests/cli/plugins.test.ts create mode 100644 packages/schema/tests/generator/zmodel/schema.zmodel create mode 100644 packages/schema/tests/generator/zmodel/user/user.zmodel create mode 100644 packages/schema/tests/schema/mutil-files/multi-files.test.ts create mode 100644 packages/schema/tests/schema/mutil-files/schema.zmodel create mode 100644 packages/schema/tests/schema/mutil-files/user.zmodel create mode 100644 packages/sdk/src/policy.ts diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 28481aecf..77e25f701 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -9,7 +9,7 @@ env: on: pull_request: - branches: ['dev', 'main', 'canary'] + branches: ['dev', 'main'] jobs: build-test: @@ -18,7 +18,6 @@ jobs: strategy: matrix: node-version: [16.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v3 @@ -32,13 +31,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: 'pnpm' - run: pnpm install --frozen-lockfile - - run: | - if [[ $GITHUB_REF == 'refs/heads/canary' ]]; then - DEFAULT_NPM_TAG=canary pnpm run build - else - DEFAULT_NPM_TAG=latest pnpm run build - fi - + - run: DEFAULT_NPM_TAG=latest pnpm run build - run: pnpm lint # install again for internal dependencies diff --git a/package.json b/package.json index 99d47e57b..8bf006dcc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "1.0.0-alpha.87", + "version": "1.0.0-alpha.102", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/language/package.json b/packages/language/package.json index aa64b9810..c03d697c1 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "1.0.0-alpha.87", + "version": "1.0.0-alpha.102", "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 8f3e38ffb..9783b2186 100644 --- a/packages/language/src/generated/ast.ts +++ b/packages/language/src/generated/ast.ts @@ -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' | 'Object' | 'String'; +export type ExpressionType = 'Any' | 'Boolean' | 'DateTime' | 'Float' | 'Int' | 'Null' | 'Object' | 'String' | 'Unsupported'; export type QualifiedName = string; @@ -44,6 +44,8 @@ export function isReferenceTarget(item: unknown): item is ReferenceTarget { return reflection.isInstance(item, ReferenceTarget); } +export type RegularID = 'in' | string; + export type TypeDeclaration = DataModel | Enum; export const TypeDeclaration = 'TypeDeclaration'; @@ -55,7 +57,7 @@ export function isTypeDeclaration(item: unknown): item is TypeDeclaration { export interface Argument extends AstNode { readonly $container: InvocationExpr; readonly $type: 'Argument'; - name?: string + name?: RegularID value: Expression } @@ -66,7 +68,7 @@ export function isArgument(item: unknown): item is Argument { } export interface ArrayExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType; readonly $type: 'ArrayExpr'; items: Array } @@ -94,7 +96,7 @@ export function isAttribute(item: unknown): item is Attribute { export interface AttributeArg extends AstNode { readonly $container: AttributeAttribute | DataModelAttribute | DataModelFieldAttribute; readonly $type: 'AttributeArg'; - name?: string + name?: RegularID value: Expression } @@ -121,7 +123,7 @@ export interface AttributeParam extends AstNode { readonly $container: Attribute; readonly $type: 'AttributeParam'; default: boolean - name: string + name: RegularID type: AttributeParamType } @@ -147,7 +149,7 @@ export function isAttributeParamType(item: unknown): item is AttributeParamType } export interface BinaryExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType; readonly $type: 'BinaryExpr'; left: Expression operator: '!' | '!=' | '&&' | '<' | '<=' | '==' | '>' | '>=' | '?' | '^' | 'in' | '||' @@ -167,7 +169,7 @@ export interface DataModel extends AstNode { comments: Array fields: Array isAbstract: boolean - name: string + name: RegularID superTypes: Array> } @@ -195,7 +197,7 @@ export interface DataModelField extends AstNode { readonly $type: 'DataModelField'; attributes: Array comments: Array - name: string + name: RegularID type: DataModelFieldType } @@ -225,6 +227,7 @@ export interface DataModelFieldType extends AstNode { optional: boolean reference?: Reference type?: BuiltinType + unsupported?: UnsupportedFieldType } export const DataModelFieldType = 'DataModelFieldType'; @@ -237,7 +240,7 @@ export interface DataSource extends AstNode { readonly $container: Model; readonly $type: 'DataSource'; fields: Array - name: string + name: RegularID } export const DataSource = 'DataSource'; @@ -249,7 +252,7 @@ export function isDataSource(item: unknown): item is DataSource { export interface DataSourceField extends AstNode { readonly $container: DataSource; readonly $type: 'DataSourceField'; - name: string + name: RegularID value: ArrayExpr | InvocationExpr | LiteralExpr } @@ -265,7 +268,7 @@ export interface Enum extends AstNode { attributes: Array comments: Array fields: Array - name: string + name: RegularID } export const Enum = 'Enum'; @@ -279,7 +282,7 @@ export interface EnumField extends AstNode { readonly $type: 'EnumField'; attributes: Array comments: Array - name: string + name: RegularID } export const EnumField = 'EnumField'; @@ -291,7 +294,7 @@ export function isEnumField(item: unknown): item is EnumField { export interface FieldInitializer extends AstNode { readonly $container: ObjectExpr; readonly $type: 'FieldInitializer'; - name: string + name: RegularID value: Expression } @@ -305,7 +308,7 @@ export interface FunctionDecl extends AstNode { readonly $container: Model; readonly $type: 'FunctionDecl'; expression?: Expression - name: string + name: RegularID params: Array returnType: FunctionParamType } @@ -319,7 +322,7 @@ export function isFunctionDecl(item: unknown): item is FunctionDecl { export interface FunctionParam extends AstNode { readonly $container: DataModel | Enum | FunctionDecl; readonly $type: 'FunctionParam'; - name: string + name: RegularID optional: boolean type: FunctionParamType } @@ -348,7 +351,7 @@ export interface GeneratorDecl extends AstNode { readonly $container: Model; readonly $type: 'GeneratorDecl'; fields: Array - name: string + name: RegularID } export const GeneratorDecl = 'GeneratorDecl'; @@ -360,7 +363,7 @@ export function isGeneratorDecl(item: unknown): item is GeneratorDecl { export interface GeneratorField extends AstNode { readonly $container: GeneratorDecl; readonly $type: 'GeneratorField'; - name: string + name: RegularID value: ArrayExpr | LiteralExpr } @@ -371,7 +374,7 @@ export function isGeneratorField(item: unknown): item is GeneratorField { } export interface InvocationExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType; readonly $type: 'InvocationExpr'; args: Array function: Reference @@ -384,7 +387,7 @@ export function isInvocationExpr(item: unknown): item is InvocationExpr { } export interface LiteralExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType; readonly $type: 'LiteralExpr'; value: boolean | number | string } @@ -396,7 +399,7 @@ export function isLiteralExpr(item: unknown): item is LiteralExpr { } export interface MemberAccessExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType; readonly $type: 'MemberAccessExpr'; member: Reference operand: Expression @@ -411,6 +414,7 @@ export function isMemberAccessExpr(item: unknown): item is MemberAccessExpr { export interface Model extends AstNode { readonly $type: 'Model'; declarations: Array + imports: Array } export const Model = 'Model'; @@ -419,8 +423,20 @@ export function isModel(item: unknown): item is Model { return reflection.isInstance(item, Model); } +export interface ModelImport extends AstNode { + readonly $container: Model; + readonly $type: 'ModelImport'; + path: string +} + +export const ModelImport = 'ModelImport'; + +export function isModelImport(item: unknown): item is ModelImport { + return reflection.isInstance(item, ModelImport); +} + export interface NullExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType; readonly $type: 'NullExpr'; value: string } @@ -432,7 +448,7 @@ export function isNullExpr(item: unknown): item is NullExpr { } export interface ObjectExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType; readonly $type: 'ObjectExpr'; fields: Array } @@ -447,7 +463,7 @@ export interface Plugin extends AstNode { readonly $container: Model; readonly $type: 'Plugin'; fields: Array - name: string + name: RegularID } export const Plugin = 'Plugin'; @@ -459,8 +475,8 @@ export function isPlugin(item: unknown): item is Plugin { export interface PluginField extends AstNode { readonly $container: Plugin; readonly $type: 'PluginField'; - name: string - value: ArrayExpr | LiteralExpr + name: RegularID + value: ArrayExpr | LiteralExpr | ObjectExpr } export const PluginField = 'PluginField'; @@ -483,7 +499,7 @@ export function isReferenceArg(item: unknown): item is ReferenceArg { } export interface ReferenceExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType; readonly $type: 'ReferenceExpr'; args: Array target: Reference @@ -496,7 +512,7 @@ export function isReferenceExpr(item: unknown): item is ReferenceExpr { } export interface ThisExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType; readonly $type: 'ThisExpr'; value: string } @@ -508,7 +524,7 @@ export function isThisExpr(item: unknown): item is ThisExpr { } export interface UnaryExpr extends AstNode { - readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; + readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr | UnsupportedFieldType; readonly $type: 'UnaryExpr'; operand: Expression operator: '!' @@ -520,6 +536,18 @@ export function isUnaryExpr(item: unknown): item is UnaryExpr { return reflection.isInstance(item, UnaryExpr); } +export interface UnsupportedFieldType extends AstNode { + readonly $container: DataModelFieldType; + readonly $type: 'UnsupportedFieldType'; + value: LiteralExpr +} + +export const UnsupportedFieldType = 'UnsupportedFieldType'; + +export function isUnsupportedFieldType(item: unknown): item is UnsupportedFieldType { + return reflection.isInstance(item, UnsupportedFieldType); +} + export interface ZModelAstType { AbstractDeclaration: AbstractDeclaration Argument: Argument @@ -550,6 +578,7 @@ export interface ZModelAstType { LiteralExpr: LiteralExpr MemberAccessExpr: MemberAccessExpr Model: Model + ModelImport: ModelImport NullExpr: NullExpr ObjectExpr: ObjectExpr Plugin: Plugin @@ -560,12 +589,13 @@ export interface ZModelAstType { ThisExpr: ThisExpr TypeDeclaration: TypeDeclaration UnaryExpr: UnaryExpr + UnsupportedFieldType: UnsupportedFieldType } 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', 'FieldInitializer', 'FunctionDecl', 'FunctionParam', 'FunctionParamType', 'GeneratorDecl', 'GeneratorField', 'InvocationExpr', 'LiteralExpr', 'MemberAccessExpr', 'Model', 'NullExpr', 'ObjectExpr', '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', 'ModelImport', 'NullExpr', 'ObjectExpr', 'Plugin', 'PluginField', 'ReferenceArg', 'ReferenceExpr', 'ReferenceTarget', 'ThisExpr', 'TypeDeclaration', 'UnaryExpr', 'UnsupportedFieldType']; } protected override computeIsSubtype(subtype: string, supertype: string): boolean { @@ -804,7 +834,8 @@ export class ZModelAstReflection extends AbstractAstReflection { return { name: 'Model', mandatory: [ - { name: 'declarations', type: 'array' } + { name: 'declarations', type: 'array' }, + { name: 'imports', type: 'array' } ] }; } diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts index ae8004bed..646a97650 100644 --- a/packages/language/src/generated/grammar.ts +++ b/packages/language/src/generated/grammar.ts @@ -16,17 +16,35 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "name": "Model", "entry": true, "definition": { - "$type": "Assignment", - "feature": "declarations", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$ref": "#/rules@1" + "$type": "Group", + "elements": [ + { + "$type": "Assignment", + "feature": "imports", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@1" + }, + "arguments": [] + }, + "cardinality": "*" }, - "arguments": [] - }, - "cardinality": "*" + { + "$type": "Assignment", + "feature": "declarations", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@2" + }, + "arguments": [] + }, + "cardinality": "*" + } + ] }, "definesHiddenTokens": false, "fragment": false, @@ -34,6 +52,42 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "parameters": [], "wildcard": false }, + { + "$type": "ParserRule", + "name": "ModelImport", + "definition": { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "import" + }, + { + "$type": "Assignment", + "feature": "path", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@60" + }, + "arguments": [] + } + }, + { + "$type": "Keyword", + "value": ";", + "cardinality": "?" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, { "$type": "ParserRule", "name": "AbstractDeclaration", @@ -43,49 +97,49 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@2" + "$ref": "#/rules@3" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@5" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@6" + "$ref": "#/rules@7" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@29" + "$ref": "#/rules@30" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@32" + "$ref": "#/rules@34" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@34" + "$ref": "#/rules@36" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@42" + "$ref": "#/rules@45" }, "arguments": [] } @@ -107,7 +161,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -123,7 +177,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -139,7 +193,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@3" + "$ref": "#/rules@4" }, "arguments": [] }, @@ -167,7 +221,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -179,7 +233,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -198,21 +252,21 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@10" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@16" + "$ref": "#/rules@19" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@10" + "$ref": "#/rules@11" }, "arguments": [] } @@ -237,7 +291,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -253,7 +307,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -269,7 +323,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@5" + "$ref": "#/rules@6" }, "arguments": [] }, @@ -297,7 +351,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -309,7 +363,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -328,14 +382,14 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@10" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@10" + "$ref": "#/rules@11" }, "arguments": [] } @@ -360,7 +414,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -376,7 +430,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -392,7 +446,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@7" + "$ref": "#/rules@8" }, "arguments": [] }, @@ -420,7 +474,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -432,7 +486,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -451,14 +505,21 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@10" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@10" + "$ref": "#/rules@11" + }, + "arguments": [] + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@17" }, "arguments": [] } @@ -480,7 +541,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "definition": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@23" + "$ref": "#/rules@26" }, "arguments": [] }, @@ -504,21 +565,21 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@56" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@61" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@57" + "$ref": "#/rules@60" }, "arguments": [] } @@ -552,7 +613,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@9" }, "arguments": [] } @@ -571,7 +632,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@9" }, "arguments": [] } @@ -605,7 +666,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@58" }, "arguments": [] } @@ -627,7 +688,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@54" + "$ref": "#/rules@57" }, "arguments": [] } @@ -657,7 +718,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] }, @@ -674,7 +735,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@14" + "$ref": "#/rules@15" }, "arguments": [] }, @@ -708,7 +769,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@15" + "$ref": "#/rules@16" }, "arguments": [] } @@ -727,7 +788,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@15" + "$ref": "#/rules@16" }, "arguments": [] } @@ -789,6 +850,117 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "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@18" + }, + "arguments": [] + } + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "," + }, + { + "$type": "Assignment", + "feature": "fields", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@18" + }, + "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@40" + }, + "arguments": [] + } + }, + { + "$type": "Keyword", + "value": ":" + }, + { + "$type": "Assignment", + "feature": "value", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@9" + }, + "arguments": [] + } + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, { "$type": "ParserRule", "name": "InvocationExpr", @@ -802,7 +974,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@34" + "$ref": "#/rules@36" }, "deprecatedSyntax": false } @@ -814,7 +986,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@27" + "$ref": "#/rules@28" }, "arguments": [], "cardinality": "?" @@ -854,7 +1026,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@9" }, "arguments": [] } @@ -881,7 +1053,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@24" + "$ref": "#/rules@27" }, "arguments": [] }, @@ -911,7 +1083,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@30" + "$ref": "#/rules@31" }, "deprecatedSyntax": false } @@ -943,7 +1115,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@18" + "$ref": "#/rules@21" }, "arguments": [] }, @@ -992,7 +1164,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@9" }, "arguments": [] } @@ -1026,7 +1198,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@19" + "$ref": "#/rules@22" }, "arguments": [] }, @@ -1058,7 +1230,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@19" + "$ref": "#/rules@22" }, "arguments": [] } @@ -1088,7 +1260,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@20" + "$ref": "#/rules@23" }, "arguments": [] }, @@ -1137,7 +1309,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@20" + "$ref": "#/rules@23" }, "arguments": [] } @@ -1167,7 +1339,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@21" + "$ref": "#/rules@24" }, "arguments": [] }, @@ -1208,7 +1380,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@21" + "$ref": "#/rules@24" }, "arguments": [] } @@ -1238,7 +1410,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@22" + "$ref": "#/rules@25" }, "arguments": [] }, @@ -1279,7 +1451,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@22" + "$ref": "#/rules@25" }, "arguments": [] } @@ -1316,7 +1488,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@9" }, "arguments": [] }, @@ -1329,56 +1501,56 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@12" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@12" + "$ref": "#/rules@13" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@10" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@16" + "$ref": "#/rules@19" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@10" + "$ref": "#/rules@11" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@13" + "$ref": "#/rules@14" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@17" + "$ref": "#/rules@20" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@25" + "$ref": "#/rules@17" }, "arguments": [] } @@ -1391,117 +1563,6 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "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@26" - }, - "arguments": [] - } - }, - { - "$type": "Group", - "elements": [ - { - "$type": "Keyword", - "value": "," - }, - { - "$type": "Assignment", - "feature": "fields", - "operator": "+=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$ref": "#/rules@26" - }, - "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@56" - }, - "arguments": [] - } - }, - { - "$type": "Keyword", - "value": ":" - }, - { - "$type": "Assignment", - "feature": "value", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": { - "$ref": "#/rules@8" - }, - "arguments": [] - } - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, { "$type": "ParserRule", "name": "ArgumentList", @@ -1516,7 +1577,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@28" + "$ref": "#/rules@29" }, "arguments": [] } @@ -1535,7 +1596,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@28" + "$ref": "#/rules@29" }, "arguments": [] } @@ -1567,7 +1628,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -1586,7 +1647,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@9" }, "arguments": [] } @@ -1613,7 +1674,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [] }, @@ -1640,7 +1701,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -1659,7 +1720,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@29" + "$ref": "#/rules@30" }, "deprecatedSyntax": false } @@ -1678,7 +1739,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@29" + "$ref": "#/rules@30" }, "deprecatedSyntax": false } @@ -1703,7 +1764,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@30" + "$ref": "#/rules@31" }, "arguments": [] } @@ -1715,7 +1776,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@46" + "$ref": "#/rules@49" }, "arguments": [] } @@ -1749,7 +1810,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [] }, @@ -1762,7 +1823,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -1774,7 +1835,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@31" + "$ref": "#/rules@32" }, "arguments": [] } @@ -1786,7 +1847,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@45" + "$ref": "#/rules@48" }, "arguments": [] }, @@ -1817,7 +1878,19 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@51" + "$ref": "#/rules@54" + }, + "arguments": [] + } + }, + { + "$type": "Assignment", + "feature": "unsupported", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@33" }, "arguments": [] } @@ -1834,7 +1907,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] }, @@ -1881,6 +1954,45 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "parameters": [], "wildcard": false }, + { + "$type": "ParserRule", + "name": "UnsupportedFieldType", + "definition": { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "Unsupported" + }, + { + "$type": "Keyword", + "value": "(" + }, + { + "$type": "Assignment", + "feature": "value", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@10" + }, + "arguments": [] + } + }, + { + "$type": "Keyword", + "value": ")" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, { "$type": "ParserRule", "name": "Enum", @@ -1894,7 +2006,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [] }, @@ -1911,7 +2023,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -1930,7 +2042,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@33" + "$ref": "#/rules@35" }, "arguments": [] } @@ -1942,7 +2054,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@46" + "$ref": "#/rules@49" }, "arguments": [] } @@ -1976,7 +2088,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [] }, @@ -1989,7 +2101,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -2001,7 +2113,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@45" + "$ref": "#/rules@48" }, "arguments": [] }, @@ -2025,7 +2137,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -2041,7 +2153,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -2060,7 +2172,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@35" + "$ref": "#/rules@37" }, "arguments": [] } @@ -2079,7 +2191,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@35" + "$ref": "#/rules@37" }, "arguments": [] } @@ -2105,7 +2217,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@36" + "$ref": "#/rules@38" }, "arguments": [] } @@ -2121,7 +2233,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@9" }, "arguments": [] }, @@ -2149,7 +2261,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -2161,7 +2273,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -2177,7 +2289,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@36" + "$ref": "#/rules@38" }, "arguments": [] } @@ -2217,7 +2329,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@50" + "$ref": "#/rules@53" }, "arguments": [] } @@ -2231,6 +2343,13 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "type": { "$ref": "#/types@1" }, + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@40" + }, + "arguments": [] + }, "deprecatedSyntax": false } } @@ -2274,7 +2393,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@59" }, "arguments": [] }, @@ -2291,14 +2410,14 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@59" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@51" + "$ref": "#/rules@54" }, "arguments": [] } @@ -2316,6 +2435,33 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "parameters": [], "wildcard": false }, + { + "$type": "ParserRule", + "name": "RegularID", + "dataType": "string", + "definition": { + "$type": "Alternatives", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@59" + }, + "arguments": [] + }, + { + "$type": "Keyword", + "value": "in" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, { "$type": "ParserRule", "name": "AttributeAttributeName", @@ -2330,7 +2476,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@37" + "$ref": "#/rules@39" }, "arguments": [] } @@ -2357,7 +2503,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@37" + "$ref": "#/rules@39" }, "arguments": [] } @@ -2384,7 +2530,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@37" + "$ref": "#/rules@39" }, "arguments": [] } @@ -2407,21 +2553,21 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@39" + "$ref": "#/rules@42" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@40" + "$ref": "#/rules@43" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@38" + "$ref": "#/rules@41" }, "arguments": [] } @@ -2443,7 +2589,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -2459,7 +2605,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@41" + "$ref": "#/rules@44" }, "arguments": [] } @@ -2478,7 +2624,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@43" + "$ref": "#/rules@46" }, "arguments": [] } @@ -2497,7 +2643,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@43" + "$ref": "#/rules@46" }, "arguments": [] } @@ -2519,7 +2665,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@47" + "$ref": "#/rules@50" }, "arguments": [] }, @@ -2543,7 +2689,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -2565,7 +2711,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -2581,7 +2727,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@44" + "$ref": "#/rules@47" }, "arguments": [] } @@ -2614,7 +2760,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@50" + "$ref": "#/rules@53" }, "arguments": [] }, @@ -2645,7 +2791,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] }, @@ -2705,12 +2851,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@42" + "$ref": "#/rules@45" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@40" + "$ref": "#/rules@43" }, "arguments": [] }, @@ -2727,7 +2873,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@48" + "$ref": "#/rules@51" }, "arguments": [], "cardinality": "?" @@ -2757,7 +2903,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@59" + "$ref": "#/rules@62" }, "arguments": [], "cardinality": "*" @@ -2769,12 +2915,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@42" + "$ref": "#/rules@45" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@39" + "$ref": "#/rules@42" }, "arguments": [] }, @@ -2791,7 +2937,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@48" + "$ref": "#/rules@51" }, "arguments": [], "cardinality": "?" @@ -2825,12 +2971,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@42" + "$ref": "#/rules@45" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@38" + "$ref": "#/rules@41" }, "arguments": [] }, @@ -2847,7 +2993,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@48" + "$ref": "#/rules@51" }, "arguments": [], "cardinality": "?" @@ -2882,7 +3028,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@49" + "$ref": "#/rules@52" }, "arguments": [] } @@ -2901,7 +3047,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@49" + "$ref": "#/rules@52" }, "arguments": [] } @@ -2933,7 +3079,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@40" }, "arguments": [] } @@ -2952,7 +3098,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@9" }, "arguments": [] } @@ -3004,6 +3150,10 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "Keyword", "value": "Any" + }, + { + "$type": "Keyword", + "value": "Unsupported" } ] }, @@ -3191,19 +3341,19 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "SimpleType", "typeRef": { - "$ref": "#/rules@35" + "$ref": "#/rules@37" } }, { "$type": "SimpleType", "typeRef": { - "$ref": "#/rules@30" + "$ref": "#/rules@31" } }, { "$type": "SimpleType", "typeRef": { - "$ref": "#/rules@33" + "$ref": "#/rules@35" } } ] @@ -3218,13 +3368,13 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "SimpleType", "typeRef": { - "$ref": "#/rules@29" + "$ref": "#/rules@30" } }, { "$type": "SimpleType", "typeRef": { - "$ref": "#/rules@32" + "$ref": "#/rules@34" } } ] diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index 6964578d1..629c2e10e 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -1,33 +1,38 @@ grammar ZModel + entry Model: + imports+=ModelImport* ( declarations+=AbstractDeclaration )*; +ModelImport: + 'import' path=STRING ';'?; + AbstractDeclaration: DataSource | GeneratorDecl| Plugin | DataModel | Enum | FunctionDecl | Attribute; // datasource DataSource: - TRIPLE_SLASH_COMMENT* 'datasource' name=ID '{' (fields+=DataSourceField)* '}'; + TRIPLE_SLASH_COMMENT* 'datasource' name=RegularID '{' (fields+=DataSourceField)* '}'; DataSourceField: - TRIPLE_SLASH_COMMENT* name=ID '=' value=(LiteralExpr | InvocationExpr | ArrayExpr); + TRIPLE_SLASH_COMMENT* name=RegularID '=' value=(LiteralExpr | InvocationExpr | ArrayExpr); // generator GeneratorDecl: - TRIPLE_SLASH_COMMENT* 'generator' name=ID '{' (fields+=GeneratorField)* '}'; + TRIPLE_SLASH_COMMENT* 'generator' name=RegularID '{' (fields+=GeneratorField)* '}'; GeneratorField: - TRIPLE_SLASH_COMMENT* name=ID '=' value=(LiteralExpr | ArrayExpr); + TRIPLE_SLASH_COMMENT* name=RegularID '=' value=(LiteralExpr | ArrayExpr); // plugin Plugin: - TRIPLE_SLASH_COMMENT* 'plugin' name=ID '{' (fields+=PluginField)* '}'; + TRIPLE_SLASH_COMMENT* 'plugin' name=RegularID '{' (fields+=PluginField)* '}'; PluginField: - TRIPLE_SLASH_COMMENT* name=ID '=' value=(LiteralExpr | ArrayExpr); + TRIPLE_SLASH_COMMENT* name=RegularID '=' value=(LiteralExpr | ArrayExpr | ObjectExpr); // expression Expression: @@ -48,7 +53,7 @@ NullExpr: value=NULL; ReferenceExpr: - target=[ReferenceTarget:ID] ('(' ReferenceArgList ')')?; + target=[ReferenceTarget:RegularID] ('(' ReferenceArgList ')')?; fragment ReferenceArgList: args+=ReferenceArg (',' args+=ReferenceArg)*; @@ -56,6 +61,15 @@ fragment ReferenceArgList: ReferenceArg: name=('sort') ':' value=('Asc' | 'Desc'); + +ObjectExpr: + '{' + (fields+=FieldInitializer (',' fields+=FieldInitializer)* ','?)? + '}'; + +FieldInitializer: + name=RegularID ':' value=(Expression); + InvocationExpr: function=[FunctionDecl] '(' ArgumentList? ')'; @@ -133,41 +147,36 @@ PrimaryExpr infers Expression: UnaryExpr | ObjectExpr; -ObjectExpr: - '{' - (fields+=FieldInitializer (',' fields+=FieldInitializer)* ','?)? - '}'; - -FieldInitializer: - name=ID ':' value=(Expression); - fragment ArgumentList: args+=Argument (',' args+=Argument)*; Argument: - (name=ID ':')? value=Expression; + (name=RegularID ':')? value=Expression; // model DataModel: (comments+=TRIPLE_SLASH_COMMENT)* - (isAbstract?='abstract')? 'model' name=ID + (isAbstract?='abstract')? 'model' name=RegularID ('extends' superTypes+=[DataModel] (',' superTypes+=[DataModel])*)? '{' ( - fields+=DataModelField + fields+=DataModelField | attributes+=DataModelAttribute )+ '}'; DataModelField: (comments+=TRIPLE_SLASH_COMMENT)* - name=ID type=DataModelFieldType (attributes+=DataModelFieldAttribute)*; + name=RegularID type=DataModelFieldType (attributes+=DataModelFieldAttribute)*; DataModelFieldType: - (type=BuiltinType | reference=[TypeDeclaration:ID]) (array?='[' ']')? (optional?='?')?; + (type=BuiltinType | unsupported=UnsupportedFieldType | reference=[TypeDeclaration:RegularID]) (array?='[' ']')? (optional?='?')?; + +UnsupportedFieldType: + 'Unsupported' '(' (value=LiteralExpr) ')'; // enum Enum: (comments+=TRIPLE_SLASH_COMMENT)* - 'enum' name=ID '{' ( + 'enum' name=RegularID '{' ( fields+=EnumField | attributes+=DataModelAttribute )+ @@ -175,22 +184,27 @@ Enum: EnumField: (comments+=TRIPLE_SLASH_COMMENT)* - name=ID (attributes+=DataModelFieldAttribute)*; + name=RegularID (attributes+=DataModelFieldAttribute)*; // function FunctionDecl: - TRIPLE_SLASH_COMMENT* 'function' name=ID '(' (params+=FunctionParam (',' params+=FunctionParam)*)? ')' ':' returnType=FunctionParamType '{' (expression=Expression)? '}'; + TRIPLE_SLASH_COMMENT* 'function' name=RegularID '(' (params+=FunctionParam (',' params+=FunctionParam)*)? ')' ':' returnType=FunctionParamType '{' (expression=Expression)? '}'; FunctionParam: - TRIPLE_SLASH_COMMENT* name=ID ':' type=FunctionParamType (optional?='?')?; + TRIPLE_SLASH_COMMENT* name=RegularID ':' type=FunctionParamType (optional?='?')?; FunctionParamType: - (type=ExpressionType | reference=[TypeDeclaration]) (array?='[' ']')?; + (type=ExpressionType | reference=[TypeDeclaration:RegularID]) (array?='[' ']')?; QualifiedName returns string: // TODO: is this the right way to deal with token precedence? ID ('.' (ID|BuiltinType))*; +// https://github.com/langium/langium/discussions/1012 +RegularID returns string: + // include keywords that we'd like to work as ID in most places + ID | 'in'; + // attribute-level attribute AttributeAttributeName returns string: '@@@' QualifiedName; @@ -211,12 +225,12 @@ Attribute: TRIPLE_SLASH_COMMENT* 'attribute' name=AttributeName '(' (params+=AttributeParam (',' params+=AttributeParam)*)? ')' (attributes+=AttributeAttribute)*; AttributeParam: - TRIPLE_SLASH_COMMENT* (default?='_')? name=ID ':' type=AttributeParamType; + TRIPLE_SLASH_COMMENT* (default?='_')? name=RegularID ':' type=AttributeParamType; // FieldReference refers to fields declared in the current model // TransitiveFieldReference refers to fields declared in the model type of the current field AttributeParamType: - (type=(ExpressionType | 'FieldReference' | 'TransitiveFieldReference' | 'ContextType') | reference=[TypeDeclaration:ID]) (array?='[' ']')? (optional?='?')?; + (type=(ExpressionType | 'FieldReference' | 'TransitiveFieldReference' | 'ContextType') | reference=[TypeDeclaration:RegularID]) (array?='[' ']')? (optional?='?')?; type TypeDeclaration = DataModel | Enum; @@ -233,10 +247,10 @@ fragment AttributeArgList: args+=AttributeArg (',' args+=AttributeArg)*; AttributeArg: - (name=ID ':')? value=Expression; + (name=RegularID ':')? value=Expression; ExpressionType returns string: - 'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime' | 'Null' | 'Object' | 'Any'; + 'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime' | 'Null' | 'Object' | 'Any' | 'Unsupported'; 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 62b36c7a9..0afd6301d 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|BigInt|Boolean|Bytes|ContextType|DateTime|Decimal|Desc|FieldReference|Float|Int|Json|Null|Object|String|TransitiveFieldReference|abstract|attribute|datasource|enum|extends|function|generator|in|model|plugin|sort)\\b" + "match": "\\b(Any|Asc|BigInt|Boolean|Bytes|ContextType|DateTime|Decimal|Desc|FieldReference|Float|Int|Json|Null|Object|String|TransitiveFieldReference|Unsupported|abstract|attribute|datasource|enum|extends|function|generator|import|in|model|plugin|sort)\\b" }, { "name": "string.quoted.double.zmodel", diff --git a/packages/next/package.json b/packages/next/package.json index 5481df120..22bf1af87 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/next", - "version": "1.0.0-alpha.87", + "version": "1.0.0-alpha.102", "displayName": "ZenStack Next.js integration", "description": "ZenStack Next.js integration", "homepage": "https://zenstack.dev", @@ -21,20 +21,19 @@ "author": "", "license": "MIT", "peerDependencies": { - "next": "^12.3.1 || ^13", - "react": "^17.0.2 || ^18" + "next": "^12.3.1 || ^13" }, "dependencies": { "@zenstackhq/runtime": "workspace:*", - "@zenstackhq/testtools": "workspace:*", "tmp": "^0.2.1" }, "devDependencies": { - "@types/jest": "^29.4.0", + "@types/jest": "^29.5.0", "@types/react": "^18.0.26", "@types/supertest": "^2.0.12", + "@zenstackhq/testtools": "workspace:*", "copyfiles": "^2.4.1", - "jest": "^29.4.3", + "jest": "^29.5.0", "react": "^17.0.2 || ^18", "rimraf": "^3.0.2", "superjson": "^1.11.0", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index c9d696b18..0eb0db53a 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.87", + "version": "1.0.0-alpha.102", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { @@ -30,19 +30,21 @@ "change-case": "^4.1.2", "openapi-types": "^12.1.0", "tiny-invariant": "^1.3.1", - "yaml": "^2.2.1" + "yaml": "^2.2.1", + "zod": "3.21.1", + "zod-validation-error": "^0.2.1" }, "devDependencies": { "@prisma/internals": "^4.7.1", "@readme/openapi-parser": "^2.4.0", - "@types/jest": "^29.4.0", + "@types/jest": "^29.5.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", + "jest": "^29.5.0", "rimraf": "^3.0.2", "tmp": "^0.2.1", "ts-jest": "^29.0.5", diff --git a/packages/plugins/openapi/src/generator.ts b/packages/plugins/openapi/src/generator.ts index e501c328b..e29b4ee68 100644 --- a/packages/plugins/openapi/src/generator.ts +++ b/packages/plugins/openapi/src/generator.ts @@ -1,7 +1,14 @@ // Inspired by: https://github.com/omar-dulaimi/prisma-trpc-generator import { DMMF } from '@prisma/generator-helper'; -import { AUXILIARY_FIELDS, getDataModels, hasAttribute, PluginError, PluginOptions } from '@zenstackhq/sdk'; +import { + analyzePolicies, + AUXILIARY_FIELDS, + getDataModels, + hasAttribute, + PluginError, + PluginOptions, +} from '@zenstackhq/sdk'; import { DataModel, isDataModel, type Model } from '@zenstackhq/sdk/ast'; import { addMissingInputObjectTypesForAggregate, @@ -17,7 +24,9 @@ import type { OpenAPIV3_1 as OAPI } from 'openapi-types'; import * as path from 'path'; import invariant from 'tiny-invariant'; import YAML from 'yaml'; +import { fromZodError } from 'zod-validation-error'; import { getModelResourceMeta } from './meta'; +import { SecuritySchemesSchema } from './schema'; /** * Generates OpenAPI specification. @@ -54,23 +63,34 @@ export class OpenAPIGenerator { const components = this.generateComponents(); const paths = this.generatePaths(components); + // generate security schemes, and root-level security + this.generateSecuritySchemes(components); + let security: OAPI.Document['security'] | undefined = undefined; + if (components.securitySchemes && Object.keys(components.securitySchemes).length > 0) { + security = Object.keys(components.securitySchemes).map((scheme) => ({ [scheme]: [] })); + } + // prune unused component schemas this.pruneComponents(components); const openapi: OAPI.Document = { - openapi: '3.1.0', + openapi: this.getOption('specVersion', '3.1.0'), info: { title: this.getOption('title', 'ZenStack Generated API'), version: this.getOption('version', '1.0.0'), - description: this.getOption('description', undefined), - summary: this.getOption('summary', undefined), + description: this.getOption('description'), + summary: this.getOption('summary'), }, - tags: this.includedModels.map((model) => ({ - name: camelCase(model.name), - description: `${model.name} operations`, - })), + tags: this.includedModels.map((model) => { + const meta = getModelResourceMeta(model); + return { + name: camelCase(model.name), + description: meta?.tagDescription ?? `${model.name} operations`, + }; + }), components, paths, + security, }; const ext = path.extname(output); @@ -83,6 +103,17 @@ export class OpenAPIGenerator { return this.warnings; } + private generateSecuritySchemes(components: OAPI.ComponentsObject) { + const securitySchemes = this.getOption[]>('securitySchemes'); + if (securitySchemes) { + const parsed = SecuritySchemesSchema.safeParse(securitySchemes); + if (!parsed.success) { + throw new PluginError(`"securitySchemes" option is invalid: ${fromZodError(parsed.error)}`); + } + components.securitySchemes = parsed.data; + } + } + private pruneComponents(components: OAPI.ComponentsObject) { const schemas = components.schemas; if (schemas) { @@ -177,9 +208,14 @@ export class OpenAPIGenerator { inputType?: object; outputType: object; successCode?: number; + security?: Array>; }; const definitions: OperationDefinition[] = []; + const hasRelation = zmodel.fields.some((f) => isDataModel(f.type.reference?.ref)); + + // analyze access policies to determine default security + const { create, read, update, delete: del } = analyzePolicies(zmodel); if (ops['createOne']) { definitions.push({ @@ -191,7 +227,7 @@ export class OpenAPIGenerator { type: 'object', properties: { select: this.ref(`${model.name}Select`), - include: this.ref(`${model.name}Include`), + include: hasRelation ? this.ref(`${model.name}Include`) : undefined, data: this.ref(`${model.name}CreateInput`), }, }, @@ -200,6 +236,7 @@ export class OpenAPIGenerator { outputType: this.ref(model.name), description: `Create a new ${model.name}`, successCode: 201, + security: create === true ? [] : undefined, }); } @@ -220,6 +257,7 @@ export class OpenAPIGenerator { outputType: this.ref('BatchPayload'), description: `Create several ${model.name}`, successCode: 201, + security: create === true ? [] : undefined, }); } @@ -233,7 +271,7 @@ export class OpenAPIGenerator { type: 'object', properties: { select: this.ref(`${model.name}Select`), - include: this.ref(`${model.name}Include`), + include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereUniqueInput`), }, }, @@ -241,6 +279,7 @@ export class OpenAPIGenerator { ), outputType: this.ref(model.name), description: `Find one unique ${model.name}`, + security: read === true ? [] : undefined, }); } @@ -254,7 +293,7 @@ export class OpenAPIGenerator { type: 'object', properties: { select: this.ref(`${model.name}Select`), - include: this.ref(`${model.name}Include`), + include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereInput`), }, }, @@ -262,6 +301,7 @@ export class OpenAPIGenerator { ), outputType: this.ref(model.name), description: `Find the first ${model.name} matching the given condition`, + security: read === true ? [] : undefined, }); } @@ -275,7 +315,7 @@ export class OpenAPIGenerator { type: 'object', properties: { select: this.ref(`${model.name}Select`), - include: this.ref(`${model.name}Include`), + include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereInput`), }, }, @@ -283,6 +323,7 @@ export class OpenAPIGenerator { ), outputType: this.array(this.ref(model.name)), description: `Find a list of ${model.name}`, + security: read === true ? [] : undefined, }); } @@ -296,7 +337,7 @@ export class OpenAPIGenerator { type: 'object', properties: { select: this.ref(`${model.name}Select`), - include: this.ref(`${model.name}Include`), + include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereUniqueInput`), data: this.ref(`${model.name}UpdateInput`), }, @@ -305,6 +346,7 @@ export class OpenAPIGenerator { ), outputType: this.ref(model.name), description: `Update a ${model.name}`, + security: update === true ? [] : undefined, }); } @@ -325,6 +367,7 @@ export class OpenAPIGenerator { ), outputType: this.ref('BatchPayload'), description: `Update ${model.name}s matching the given condition`, + security: update === true ? [] : undefined, }); } @@ -338,7 +381,7 @@ export class OpenAPIGenerator { type: 'object', properties: { select: this.ref(`${model.name}Select`), - include: this.ref(`${model.name}Include`), + include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereUniqueInput`), create: this.ref(`${model.name}CreateInput`), update: this.ref(`${model.name}UpdateInput`), @@ -348,6 +391,7 @@ export class OpenAPIGenerator { ), outputType: this.ref(model.name), description: `Upsert a ${model.name}`, + security: create === true && update == true ? [] : undefined, }); } @@ -361,7 +405,7 @@ export class OpenAPIGenerator { type: 'object', properties: { select: this.ref(`${model.name}Select`), - include: this.ref(`${model.name}Include`), + include: hasRelation ? this.ref(`${model.name}Include`) : undefined, where: this.ref(`${model.name}WhereUniqueInput`), }, }, @@ -369,6 +413,7 @@ export class OpenAPIGenerator { ), outputType: this.ref(model.name), description: `Delete one unique ${model.name}`, + security: del === true ? [] : undefined, }); } @@ -388,6 +433,7 @@ export class OpenAPIGenerator { ), outputType: this.ref('BatchPayload'), description: `Delete ${model.name}s matching the given condition`, + security: del === true ? [] : undefined, }); } @@ -408,6 +454,7 @@ export class OpenAPIGenerator { ), outputType: this.oneOf({ type: 'integer' }, this.ref(`${model.name}CountAggregateOutputType`)), description: `Find a list of ${model.name}`, + security: read === true ? [] : undefined, }); if (ops['aggregate']) { @@ -431,6 +478,7 @@ export class OpenAPIGenerator { ), outputType: this.ref(`Aggregate${model.name}`), description: `Aggregate ${model.name}s`, + security: read === true ? [] : undefined, }); } @@ -456,13 +504,14 @@ export class OpenAPIGenerator { ), outputType: this.array(this.ref(`${model.name}GroupByOutputType`)), description: `Group ${model.name}s by fields`, + security: read === true ? [] : undefined, }); } // get meta specified with @@openapi.meta const resourceMeta = getModelResourceMeta(zmodel); - for (const { method, operation, description, inputType, outputType, successCode } of definitions) { + for (const { method, operation, description, inputType, outputType, successCode, security } of definitions) { const meta = resourceMeta?.[operation]; if (meta?.ignore === true) { @@ -481,11 +530,14 @@ export class OpenAPIGenerator { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - const def: any = { + const def: OAPI.OperationObject = { operationId: `${operation}${model.name}`, description: meta?.description ?? description, tags: meta?.tags || [camelCase(model.name)], summary: meta?.summary, + // security priority: operation-level > model-level > inferred + security: meta?.security ?? resourceMeta?.security ?? security, + deprecated: meta?.deprecated, responses: { [successCode !== undefined ? successCode : '200']: { description: 'Successful operation', @@ -565,14 +617,13 @@ export class OpenAPIGenerator { return this.ref(name); } - private getOption( - name: string, - defaultValue: T - ): T extends string ? string : string | undefined { + private getOption(name: string): T | undefined; + private getOption(name: string, defaultValue: D): T; + private getOption(name: string, defaultValue?: T): T | undefined { const value = this.options[name]; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - return typeof value === 'string' ? value : defaultValue; + return value === undefined ? defaultValue : value; } private generateComponents() { diff --git a/packages/plugins/openapi/src/meta.ts b/packages/plugins/openapi/src/meta.ts index 2b7d1e450..6a038938a 100644 --- a/packages/plugins/openapi/src/meta.ts +++ b/packages/plugins/openapi/src/meta.ts @@ -1,22 +1,32 @@ import { getObjectLiteral } from '@zenstackhq/sdk'; import { DataModel } from '@zenstackhq/sdk/ast'; +/** + * Metadata for a resource, expressed by @@openapi.meta attribute. + */ +export type ModelMeta = { + tagDescription?: string; + security?: Array>; +}; + /** * Metadata for a resource operation, expressed by @@openapi.meta attribute. */ export type OperationMeta = { - ignore: boolean; - method: string; - path: string; + ignore?: boolean; + method?: string; + path?: string; summary?: string; description?: string; tags?: string[]; + deprecated?: boolean; + security?: Array>; }; /** * Metadata for a resource, expressed by @@openapi.meta attribute. */ -export type ResourceMeta = Record; +export type ResourceMeta = ModelMeta & Record; export function getModelResourceMeta(model: DataModel) { return getObjectLiteral( diff --git a/packages/plugins/openapi/src/schema.ts b/packages/plugins/openapi/src/schema.ts new file mode 100644 index 000000000..ed44f144f --- /dev/null +++ b/packages/plugins/openapi/src/schema.ts @@ -0,0 +1,43 @@ +import z from 'zod'; + +/** + * Zod schema for OpenAPI security schemes: https://swagger.io/docs/specification/authentication/ + */ +export const SecuritySchemesSchema = z.record( + z.union([ + z.object({ type: z.literal('http'), scheme: z.literal('basic') }), + z.object({ type: z.literal('http'), scheme: z.literal('bearer'), bearerFormat: z.string().optional() }), + z.object({ + type: z.literal('apiKey'), + in: z.union([z.literal('header'), z.literal('query'), z.literal('cookie')]), + name: z.string(), + }), + z.object({ + type: z.literal('oauth2'), + description: z.string(), + flows: z.object({ + authorizationCode: z.object({ + authorizationUrl: z.string(), + tokenUrl: z.string(), + refreshUrl: z.string(), + scopes: z.record(z.string()), + }), + implicit: z.object({ + authorizationUrl: z.string(), + refreshUrl: z.string(), + scopes: z.record(z.string()), + }), + password: z.object({ + tokenUrl: z.string(), + refreshUrl: z.string(), + scopes: z.record(z.string()), + }), + clientCredentials: z.object({ + tokenUrl: z.string(), + refreshUrl: z.string(), + scopes: z.record(z.string()), + }), + }), + }), + ]) +); diff --git a/packages/plugins/openapi/tests/openapi.test.ts b/packages/plugins/openapi/tests/openapi.test.ts index 70aae2e83..a0fb7d678 100644 --- a/packages/plugins/openapi/tests/openapi.test.ts +++ b/packages/plugins/openapi/tests/openapi.test.ts @@ -1,8 +1,13 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /// import OpenAPIParser from '@readme/openapi-parser'; +import { getLiteral, getObjectLiteral } from '@zenstackhq/sdk'; +import { isPlugin, Model, Plugin } from '@zenstackhq/sdk/ast'; import { loadZModelAndDmmf } from '@zenstackhq/testtools'; +import * as fs from 'fs'; import * as tmp from 'tmp'; +import YAML from 'yaml'; import generate from '../src'; describe('Open API Plugin Tests', () => { @@ -34,7 +39,8 @@ model User { path: 'dodelete', description: 'Delete a unique user', summary: 'Delete a user yeah yeah', - tags: ['delete', 'user'] + tags: ['delete', 'user'], + deprecated: true }, }) } @@ -50,6 +56,7 @@ model Post { viewCount Int @default(0) @@openapi.meta({ + tagDescription: 'Post-related operations', findMany: { ignore: true } @@ -68,19 +75,242 @@ model Bar { `); const { name: output } = tmp.fileSync({ postfix: '.yaml' }); - generate(model, { schemaPath: modelFile, output }, dmmf); + + const options = buildOptions(model, modelFile, output); + await generate(model, options, dmmf); console.log('OpenAPI specification generated:', output); + const parsed = YAML.parse(fs.readFileSync(output, 'utf-8')); + expect(parsed.openapi).toBe('3.1.0'); + const api = await OpenAPIParser.validate(output); + + expect(api.tags).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: 'user', description: 'User operations' }), + expect.objectContaining({ name: 'post', description: 'Post-related operations' }), + ]) + ); + 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(del?.deprecated).toBe(true); expect(api.paths?.['/post/findMany']).toBeUndefined(); - expect(api.paths?.['/foo/findMany']).toBeUndefined(); expect(api.paths?.['/bar/findMany']).toBeUndefined(); }); + + it('options', async () => { + const { model, dmmf, modelFile } = await loadZModelAndDmmf(` +plugin openapi { + provider = '${process.cwd()}/dist' + specVersion = '3.0.0' + title = 'My Awesome API' + version = '1.0.0' + description = 'awesome api' + prefix = '/myapi' +} + +model User { + id String @id +} + `); + + const { name: output } = tmp.fileSync({ postfix: '.yaml' }); + const options = buildOptions(model, modelFile, output); + await generate(model, options, dmmf); + + console.log('OpenAPI specification generated:', output); + + const parsed = YAML.parse(fs.readFileSync(output, 'utf-8')); + expect(parsed.openapi).toBe('3.0.0'); + + const api = await OpenAPIParser.validate(output); + expect(api.info).toEqual( + expect.objectContaining({ + title: 'My Awesome API', + version: '1.0.0', + description: 'awesome api', + }) + ); + + expect(api.paths?.['/myapi/user/findMany']).toBeTruthy(); + }); + + it('security schemes valid', async () => { + const { model, dmmf, modelFile } = await loadZModelAndDmmf(` +plugin openapi { + provider = '${process.cwd()}/dist' + securitySchemes = { + myBasic: { type: 'http', scheme: 'basic' }, + myBearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, + myApiKey: { type: 'apiKey', in: 'header', name: 'X-API-KEY' } + } +} + +model User { + id String @id +} + `); + + const { name: output } = tmp.fileSync({ postfix: '.yaml' }); + const options = buildOptions(model, modelFile, output); + await generate(model, options, dmmf); + + console.log('OpenAPI specification generated:', output); + await OpenAPIParser.validate(output); + + const parsed = YAML.parse(fs.readFileSync(output, 'utf-8')); + expect(parsed.components.securitySchemes).toEqual( + expect.objectContaining({ + myBasic: { type: 'http', scheme: 'basic' }, + myBearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, + myApiKey: { type: 'apiKey', in: 'header', name: 'X-API-KEY' }, + }) + ); + expect(parsed.security).toEqual(expect.arrayContaining([{ myBasic: [] }, { myBearer: [] }])); + }); + + it('security schemes invalid', async () => { + const { model, dmmf, modelFile } = await loadZModelAndDmmf(` +plugin openapi { + provider = '${process.cwd()}/dist' + securitySchemes = { + myBasic: { type: 'invalid', scheme: 'basic' } + } +} + +model User { + id String @id +} + `); + + const { name: output } = tmp.fileSync({ postfix: '.yaml' }); + const options = buildOptions(model, modelFile, output); + await expect(generate(model, options, dmmf)).rejects.toEqual( + expect.objectContaining({ message: expect.stringContaining('"securitySchemes" option is invalid') }) + ); + }); + + it('security model level override', async () => { + const { model, dmmf, modelFile } = await loadZModelAndDmmf(` +plugin openapi { + provider = '${process.cwd()}/dist' + securitySchemes = { + myBasic: { type: 'http', scheme: 'basic' } + } +} + +model User { + id String @id + + @@openapi.meta({ + security: [] + }) +} + `); + + const { name: output } = tmp.fileSync({ postfix: '.yaml' }); + const options = buildOptions(model, modelFile, output); + await generate(model, options, dmmf); + + console.log('OpenAPI specification generated:', output); + + const api = await OpenAPIParser.validate(output); + expect(api.paths?.['/user/findMany']?.['get']?.security).toHaveLength(0); + }); + + it('security operation level override', async () => { + const { model, dmmf, modelFile } = await loadZModelAndDmmf(` +plugin openapi { + provider = '${process.cwd()}/dist' + securitySchemes = { + myBasic: { type: 'http', scheme: 'basic' } + } +} + +model User { + id String @id + + @@allow('read', true) + + @@openapi.meta({ + security: [], + findMany: { + security: [{ myBasic: [] }] + } + }) +} + `); + + const { name: output } = tmp.fileSync({ postfix: '.yaml' }); + const options = buildOptions(model, modelFile, output); + await generate(model, options, dmmf); + + console.log('OpenAPI specification generated:', output); + + const api = await OpenAPIParser.validate(output); + expect(api.paths?.['/user/findMany']?.['get']?.security).toHaveLength(1); + }); + + it('security inferred', async () => { + const { model, dmmf, modelFile } = await loadZModelAndDmmf(` +plugin openapi { + provider = '${process.cwd()}/dist' + securitySchemes = { + myBasic: { type: 'http', scheme: 'basic' } + } +} + +model User { + id String @id + @@allow('create', true) +} + `); + + const { name: output } = tmp.fileSync({ postfix: '.yaml' }); + const options = buildOptions(model, modelFile, output); + await generate(model, options, dmmf); + + console.log('OpenAPI specification generated:', output); + + const api = await OpenAPIParser.validate(output); + expect(api.paths?.['/user/create']?.['post']?.security).toHaveLength(0); + expect(api.paths?.['/user/findMany']?.['get']?.security).toBeUndefined(); + }); + + it('v3.1.0 fields', async () => { + const { model, dmmf, modelFile } = await loadZModelAndDmmf(` +plugin openapi { + provider = '${process.cwd()}/dist' + summary = 'awesome api' +} + +model User { + id String @id +} + `); + + const { name: output } = tmp.fileSync({ postfix: '.yaml' }); + const options = buildOptions(model, modelFile, output); + await generate(model, options, dmmf); + + console.log('OpenAPI specification generated:', output); + await OpenAPIParser.validate(output); + + const parsed = YAML.parse(fs.readFileSync(output, 'utf-8')); + expect(parsed.openapi).toBe('3.1.0'); + expect(parsed.info.summary).toEqual('awesome api'); + }); }); + +function buildOptions(model: Model, modelFile: string, output: string) { + const optionFields = model.declarations.find((d): d is Plugin => isPlugin(d))?.fields || []; + const options: any = { schemaPath: modelFile, output }; + optionFields.forEach((f) => (options[f.name] = getLiteral(f.value) ?? getObjectLiteral(f.value))); + return options; +} diff --git a/packages/plugins/react/package.json b/packages/plugins/react/package.json index 21670fdcd..70ab3752d 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.87", + "version": "1.0.0-alpha.102", "description": "ZenStack plugin and runtime for ReactJS", "main": "index.js", "repository": { @@ -29,22 +29,27 @@ "change-case": "^4.1.2", "decimal.js": "^10.4.2", "superjson": "^1.11.0", - "swr": "^2.0.3", "ts-morph": "^16.0.0" }, "peerDependencies": { "react": "^17.0.2 || ^18", - "react-dom": "^17.0.2 || ^18" + "react-dom": "^17.0.2 || ^18", + "swr": "2.x", + "@tanstack/react-query": "4.x" }, "devDependencies": { + "@tanstack/react-query": "^4.28.0", "@types/jest": "^29.5.0", "@types/react": "^18.0.26", "@types/tmp": "^0.2.3", + "@zenstackhq/testtools": "workspace:*", "copyfiles": "^2.4.1", "jest": "^29.5.0", "rimraf": "^3.0.2", + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18", + "swr": "^2.0.3", "ts-jest": "^29.0.5", - "typescript": "^4.9.4", - "@zenstackhq/testtools": "workspace:*" + "typescript": "^4.9.4" } } diff --git a/packages/plugins/react/src/generator/index.ts b/packages/plugins/react/src/generator/index.ts new file mode 100644 index 000000000..c16cfaafd --- /dev/null +++ b/packages/plugins/react/src/generator/index.ts @@ -0,0 +1,17 @@ +import type { DMMF } from '@prisma/generator-helper'; +import { PluginError, type PluginOptions } from '@zenstackhq/sdk'; +import type { Model } from '@zenstackhq/sdk/ast'; +import { generate as reactQueryGenerate } from './react-query'; +import { generate as swrGenerate } from './swr'; + +export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { + const fetcher = (options.fetcher as string) ?? 'swr'; + switch (fetcher) { + case 'swr': + return swrGenerate(model, options, dmmf); + case 'react-query': + return reactQueryGenerate(model, options, dmmf); + default: + throw new PluginError(`Unknown "fetcher" option: ${fetcher}, use "swr" or "react-query"`); + } +} diff --git a/packages/plugins/react/src/generator/react-query.ts b/packages/plugins/react/src/generator/react-query.ts new file mode 100644 index 000000000..ca2c379bd --- /dev/null +++ b/packages/plugins/react/src/generator/react-query.ts @@ -0,0 +1,327 @@ +import { DMMF } from '@prisma/generator-helper'; +import { PluginError, PluginOptions, createProject, getDataModels, saveProject } from '@zenstackhq/sdk'; +import { DataModel, Model } from '@zenstackhq/sdk/ast'; +import { camelCase, paramCase, pascalCase } from 'change-case'; +import * as path from 'path'; +import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph'; + +export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { + let outDir = options.output as string; + if (!outDir) { + throw new PluginError('"output" option is required'); + } + + if (!path.isAbsolute(outDir)) { + // output dir is resolved relative to the schema file path + outDir = path.join(path.dirname(options.schemaPath), outDir); + } + + const project = createProject(); + const warnings: string[] = []; + const models = getDataModels(model); + + generateIndex(project, outDir, models); + + models.forEach((dataModel) => { + const mapping = dmmf.mappings.modelOperations.find((op) => op.model === dataModel.name); + if (!mapping) { + warnings.push(`Unable to find mapping for model ${dataModel.name}`); + return; + } + generateModelHooks(project, outDir, dataModel, mapping); + }); + + await saveProject(project); + return warnings; +} + +function generateQueryHook( + sf: SourceFile, + model: string, + operation: string, + returnArray: boolean, + optionalInput: boolean, + overrideReturnType?: string, + overrideInputType?: string, + overrideTypeParameters?: string[] +) { + const capOperation = pascalCase(operation); + + const argsType = overrideInputType ?? `Prisma.${model}${capOperation}Args`; + const inputType = `Prisma.SelectSubset`; + const returnType = + overrideReturnType ?? (returnArray ? `Array>` : `Prisma.${model}GetPayload`); + const optionsType = `UseQueryOptions<${returnType}>`; + + const func = sf.addFunction({ + name: `use${capOperation}${model}`, + typeParameters: overrideTypeParameters ?? [`T extends ${argsType}`], + parameters: [ + { + name: optionalInput ? 'args?' : 'args', + type: inputType, + }, + { + name: 'options?', + type: optionsType, + }, + ], + isExported: true, + }); + + func.addStatements([ + 'const { endpoint } = useContext(RequestHandlerContext);', + `return request.query<${returnType}>('${model}', \`\${endpoint}/${camelCase( + model + )}/${operation}\`, args, options);`, + ]); +} + +function generateMutationHook( + sf: SourceFile, + model: string, + operation: string, + httpVerb: 'post' | 'put' | 'delete', + overrideReturnType?: string +) { + const capOperation = pascalCase(operation); + + const argsType = `Prisma.${model}${capOperation}Args`; + const inputType = `Prisma.SelectSubset`; + const returnType = overrideReturnType ?? `Prisma.CheckSelect>`; + const nonGenericOptionsType = `Omit, 'mutationFn'>`; + const optionsType = `Omit, 'mutationFn'>`; + + const func = sf.addFunction({ + name: `use${capOperation}${model}`, + isExported: true, + parameters: [ + { + name: 'options?', + type: nonGenericOptionsType, + }, + { + name: 'invalidateQueries', + type: 'boolean', + initializer: 'true', + }, + ], + }); + func.addStatements(['const { endpoint } = useContext(RequestHandlerContext);']); + + func.addVariableStatement({ + declarationKind: VariableDeclarationKind.Const, + declarations: [ + { + name: `_mutation`, + initializer: ` + request.${httpVerb}Mutation<${argsType}, ${ + overrideReturnType ?? model + }>('${model}', \`\${endpoint}/${camelCase(model)}/${operation}\`, options, invalidateQueries) + `, + }, + ], + }); + + func.addVariableStatement({ + declarationKind: VariableDeclarationKind.Const, + declarations: [ + { + name: 'mutation', + initializer: `{ + ..._mutation, + async mutateAsync( + args: Prisma.SelectSubset, + options?: ${optionsType} + ) { + return (await _mutation.mutateAsync( + args, + options as any + )) as ${returnType}; + }, + }`, + }, + ], + }); + + func.addStatements('return mutation;'); +} + +function generateModelHooks(project: Project, outDir: string, model: DataModel, mapping: DMMF.ModelMapping) { + const fileName = paramCase(model.name); + const sf = project.createSourceFile(path.join(outDir, `${fileName}.ts`), undefined, { overwrite: true }); + + sf.addStatements('/* eslint-disable */'); + + sf.addImportDeclaration({ + namedImports: ['Prisma', model.name], + isTypeOnly: true, + moduleSpecifier: '@prisma/client', + }); + sf.addStatements([ + `import { useContext } from 'react';`, + `import { RequestHandlerContext } from '@zenstackhq/react/runtime';`, + `import * as request from '@zenstackhq/react/runtime/react-query';`, + `import type { UseMutationOptions, UseQueryOptions } from '@tanstack/react-query';`, + ]); + + // create is somehow named "createOne" in the DMMF + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (mapping.create || (mapping as any).createOne) { + generateMutationHook(sf, model.name, 'create', 'post'); + } + + // createMany + if (mapping.createMany) { + generateMutationHook(sf, model.name, 'createMany', 'post', 'Prisma.BatchPayload'); + } + + // findMany + if (mapping.findMany) { + generateQueryHook(sf, model.name, 'findMany', true, true); + } + + // findUnique + if (mapping.findUnique) { + generateQueryHook(sf, model.name, 'findUnique', false, false); + } + + // findFirst + if (mapping.findFirst) { + generateQueryHook(sf, model.name, 'findFirst', false, true); + } + + // update + // update is somehow named "updateOne" in the DMMF + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (mapping.update || (mapping as any).updateOne) { + generateMutationHook(sf, model.name, 'update', 'put'); + } + + // updateMany + if (mapping.updateMany) { + generateMutationHook(sf, model.name, 'updateMany', 'put', 'Prisma.BatchPayload'); + } + + // upsert + // upsert is somehow named "upsertOne" in the DMMF + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (mapping.upsert || (mapping as any).upsertOne) { + generateMutationHook(sf, model.name, 'upsert', 'post'); + } + + // del + // delete is somehow named "deleteOne" in the DMMF + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (mapping.delete || (mapping as any).deleteOne) { + generateMutationHook(sf, model.name, 'delete', 'delete'); + } + + // deleteMany + if (mapping.deleteMany) { + generateMutationHook(sf, model.name, 'deleteMany', 'delete', 'Prisma.BatchPayload'); + } + + // aggregate + if (mapping.aggregate) { + generateQueryHook(sf, model.name, 'aggregate', false, false, `Prisma.Get${model.name}AggregateType`); + } + + // groupBy + if (mapping.groupBy) { + const typeParameters = [ + `T extends Prisma.${model.name}GroupByArgs`, + `HasSelectOrTake extends Prisma.Or>, Prisma.Extends<'take', Prisma.Keys>>`, + `OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.${model.name}GroupByArgs['orderBy'] }: { orderBy?: Prisma.${model.name}GroupByArgs['orderBy'] },`, + `OrderFields extends Prisma.ExcludeUnderscoreKeys>>`, + `ByFields extends Prisma.TupleToUnion`, + `ByValid extends Prisma.Has`, + `HavingFields extends Prisma.GetHavingFields`, + `HavingValid extends Prisma.Has`, + `ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False`, + `InputErrors extends ByEmpty extends Prisma.True + ? \`Error: "by" must not be empty.\` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? \`Error: Field "\${P}" used in "having" needs to be provided in "by".\` + : [ + Error, + 'Field ', + P, + \` in "having" needs to be provided in "by"\`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` + }[OrderFields]`, + ]; + + const returnType = `{} extends InputErrors ? + Array & + { + [P in ((keyof T) & (keyof Prisma.${model.name}GroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > : InputErrors`; + + generateQueryHook( + sf, + model.name, + 'groupBy', + false, + false, + returnType, + `Prisma.SubsetIntersection & InputErrors`, + typeParameters + ); + } + + // somehow dmmf doesn't contain "count" operation, so we unconditionally add it here + { + generateQueryHook( + sf, + model.name, + 'count', + false, + true, + `T extends { select: any; } ? T['select'] extends true ? number : Prisma.GetScalarType : number` + ); + } +} + +function generateIndex(project: Project, outDir: string, models: DataModel[]) { + const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true }); + sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`)); +} diff --git a/packages/plugins/react/src/react-hooks-generator.ts b/packages/plugins/react/src/generator/swr.ts similarity index 93% rename from packages/plugins/react/src/react-hooks-generator.ts rename to packages/plugins/react/src/generator/swr.ts index 1efda5bdf..5ad536ae7 100644 --- a/packages/plugins/react/src/react-hooks-generator.ts +++ b/packages/plugins/react/src/generator/swr.ts @@ -1,5 +1,12 @@ import { DMMF } from '@prisma/generator-helper'; -import { CrudFailureReason, getDataModels, PluginError, PluginOptions } from '@zenstackhq/sdk'; +import { + CrudFailureReason, + PluginError, + PluginOptions, + createProject, + getDataModels, + saveProject, +} from '@zenstackhq/sdk'; import { DataModel, Model } from '@zenstackhq/sdk/ast'; import { camelCase, paramCase } from 'change-case'; import * as path from 'path'; @@ -16,7 +23,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. outDir = path.join(path.dirname(options.schemaPath), outDir); } - const project = new Project(); + const project = createProject(); const warnings: string[] = []; const models = getDataModels(model); @@ -31,7 +38,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. generateModelHooks(project, outDir, dataModel, mapping); }); - await project.save(); + await saveProject(project); return warnings; } @@ -61,8 +68,9 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, }); sf.addStatements([ `import { useContext } from 'react';`, - `import { RequestHandlerContext, type RequestOptions } from '@zenstackhq/react/runtime';`, - `import * as request from '@zenstackhq/react/runtime';`, + `import { RequestHandlerContext } from '@zenstackhq/react/runtime';`, + `import { type RequestOptions } from '@zenstackhq/react/runtime/swr';`, + `import * as request from '@zenstackhq/react/runtime/swr';`, ]); const useFunc = sf.addFunction({ @@ -310,7 +318,7 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, typeParameters: [`T extends ${argsType}`], parameters: [ { - name: 'args?', + name: 'args', type: inputType, }, ], @@ -377,14 +385,23 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, // groupBy if (mapping.groupBy) { methods.push('groupBy'); - const returnType = `{} extends InputErrors ? Prisma.Get${model.name}GroupByPayload : InputErrors`; + const returnType = `{} extends InputErrors ? + Array & + { + [P in ((keyof T) & (keyof Prisma.${model.name}GroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > : InputErrors`; useFunc .addFunction({ name: 'groupBy', typeParameters: [ `T extends Prisma.${model.name}GroupByArgs`, `HasSelectOrTake extends Prisma.Or>, Prisma.Extends<'take', Prisma.Keys>>`, - `OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.UserGroupByArgs['orderBy'] }: { orderBy?: Prisma.UserGroupByArgs['orderBy'] },`, + `OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.${model.name}GroupByArgs['orderBy'] }: { orderBy?: Prisma.${model.name}GroupByArgs['orderBy'] },`, `OrderFields extends Prisma.ExcludeUnderscoreKeys>>`, `ByFields extends Prisma.TupleToUnion`, `ByValid extends Prisma.Has`, @@ -451,8 +468,8 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, ]); } - // count - if (mapping.count) { + // somehow dmmf doesn't contain "count" operation, so we unconditionally add it here + { methods.push('count'); const argsType = `Prisma.${model.name}CountArgs`; const inputType = `Prisma.Subset`; @@ -479,14 +496,9 @@ function generateModelHooks(project: Project, outDir: string, model: DataModel, } useFunc.addStatements([`return { ${methods.join(', ')} };`]); - - sf.formatText(); } function generateIndex(project: Project, outDir: string, models: DataModel[]) { const sf = project.createSourceFile(path.join(outDir, 'index.ts'), undefined, { overwrite: true }); - sf.addStatements(models.map((d) => `export * from './${paramCase(d.name)}';`)); - - sf.formatText(); } diff --git a/packages/plugins/react/src/index.ts b/packages/plugins/react/src/index.ts index 44b6149c3..3baa559a9 100644 --- a/packages/plugins/react/src/index.ts +++ b/packages/plugins/react/src/index.ts @@ -1,7 +1,7 @@ -import { DMMF } from '@prisma/generator-helper'; -import { PluginOptions } from '@zenstackhq/sdk'; -import { Model } from '@zenstackhq/sdk/ast'; -import { generate } from './react-hooks-generator'; +import type { DMMF } from '@prisma/generator-helper'; +import type { PluginOptions } from '@zenstackhq/sdk'; +import type { Model } from '@zenstackhq/sdk/ast'; +import { generate } from './generator'; export const name = 'React'; diff --git a/packages/plugins/react/src/runtime/index.ts b/packages/plugins/react/src/runtime/index.ts new file mode 100644 index 000000000..e1ff8d2d0 --- /dev/null +++ b/packages/plugins/react/src/runtime/index.ts @@ -0,0 +1,20 @@ +import { createContext } from 'react'; + +/** + * Context type for configuring react hooks. + */ +export type RequestHandlerContext = { + endpoint: string; +}; + +/** + * Context for configuring react hooks. + */ +export const RequestHandlerContext = createContext({ + endpoint: '/api/model', +}); + +/** + * Context provider. + */ +export const Provider = RequestHandlerContext.Provider; diff --git a/packages/plugins/react/src/runtime/react-query.ts b/packages/plugins/react/src/runtime/react-query.ts new file mode 100644 index 000000000..6fd04f329 --- /dev/null +++ b/packages/plugins/react/src/runtime/react-query.ts @@ -0,0 +1,141 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + useMutation, + useQuery, + useQueryClient, + type MutateFunction, + type QueryClient, + type UseMutationOptions, + type UseQueryOptions, +} from '@tanstack/react-query'; +import { marshal, registerSerializers } from '../serialization-utils'; +import { fetcher, makeUrl } from './utils'; + +// register superjson custom serializers +registerSerializers(); + +const QUERY_KEY_PREFIX = 'zenstack:'; + +/** + * Creates a react-query query. + * + * @param model The name of the model under query. + * @param url The request URL. + * @param args The request args object, which will be superjson-stringified and appended as "?q=" parameter + * @param options The react-query options object + * @returns useQuery hook + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function query(model: string, url: string, args?: unknown, options?: UseQueryOptions) { + const reqUrl = makeUrl(url, args); + return useQuery({ + queryKey: [QUERY_KEY_PREFIX + model, url, args], + queryFn: () => fetcher(reqUrl), + ...options, + }); +} + +/** + * Creates a POST mutation with react-query. + * + * @param model The name of the model under mutation. + * @param url The request URL. + * @param options The react-query options. + * @param invalidateQueries Whether to invalidate queries after mutation. + * @returns useMutation hooks + */ +export function postMutation( + model: string, + url: string, + options?: Omit, 'mutationFn'>, + invalidateQueries = true +) { + const queryClient = useQueryClient(); + const mutationFn = (data: any) => + fetcher(url, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: marshal(data), + }); + + const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); + const mutation = useMutation(finalOptions); + return mutation; +} + +/** + * Creates a PUT mutation with react-query. + * + * @param model The name of the model under mutation. + * @param url The request URL. + * @param options The react-query options. + * @param invalidateQueries Whether to invalidate queries after mutation. + * @returns useMutation hooks + */ +export function putMutation( + model: string, + url: string, + options?: Omit, 'mutationFn'>, + invalidateQueries = true +) { + const queryClient = useQueryClient(); + const mutationFn = (data: any) => + fetcher(url, { + method: 'PUT', + headers: { + 'content-type': 'application/json', + }, + body: marshal(data), + }); + + const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); + const mutation = useMutation(finalOptions); + return mutation; +} + +/** + * Creates a DELETE mutation with react-query. + * + * @param model The name of the model under mutation. + * @param url The request URL. + * @param options The react-query options. + * @param invalidateQueries Whether to invalidate queries after mutation. + * @returns useMutation hooks + */ +export function deleteMutation( + model: string, + url: string, + options?: Omit, 'mutationFn'>, + invalidateQueries = true +) { + const queryClient = useQueryClient(); + const mutationFn = (data: any) => + fetcher(makeUrl(url, data), { + method: 'DELETE', + }); + + const finalOptions = mergeOptions(model, options, invalidateQueries, mutationFn, queryClient); + const mutation = useMutation(finalOptions); + return mutation; +} + +function mergeOptions( + model: string, + options: Omit, 'mutationFn'> | undefined, + invalidateQueries: boolean, + mutationFn: MutateFunction, + queryClient: QueryClient +): UseMutationOptions { + const result = { ...options, mutationFn }; + if (options?.onSuccess || invalidateQueries) { + result.onSuccess = (...args) => { + if (invalidateQueries) { + queryClient.invalidateQueries([QUERY_KEY_PREFIX + model]); + } + return options?.onSuccess?.(...args); + }; + } + return result; +} diff --git a/packages/plugins/react/src/runtime.ts b/packages/plugins/react/src/runtime/swr.ts similarity index 69% rename from packages/plugins/react/src/runtime.ts rename to packages/plugins/react/src/runtime/swr.ts index 19559d40d..ce84702ec 100644 --- a/packages/plugins/react/src/runtime.ts +++ b/packages/plugins/react/src/runtime/swr.ts @@ -1,8 +1,7 @@ -import { createContext } from 'react'; -import superjson from 'superjson'; -import useSWR, { useSWRConfig } from 'swr'; import type { MutatorCallback, MutatorOptions, SWRResponse } from 'swr'; -import { registerSerializers } from './serialization-utils'; +import useSWR, { useSWRConfig } from 'swr'; +import { marshal, registerSerializers } from '../serialization-utils'; +import { fetcher, makeUrl } from './utils'; /** * Client request options @@ -16,39 +15,6 @@ export type RequestOptions = { // register superjson custom serializers registerSerializers(); -const fetcher = async (url: string, options?: RequestInit) => { - const res = await fetch(url, options); - if (!res.ok) { - const error: Error & { info?: unknown; status?: number } = new Error( - 'An error occurred while fetching the data.' - ); - error.info = await res.json(); - error.status = res.status; - throw error; - } - - const textResult = await res.text(); - try { - return unmarshal(textResult); - } catch (err) { - console.error(`Unable to deserialize data:`, textResult); - throw err; - } -}; - -function marshal(value: unknown) { - return superjson.stringify(value); -} - -function unmarshal(value: string) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return superjson.parse(value); -} - -function makeUrl(url: string, args: unknown) { - return args ? url + `?q=${encodeURIComponent(marshal(args))}` : url; -} - /** * Makes a GET request with SWR. * @@ -144,22 +110,3 @@ export function getMutate(prefixes: string[]): Mutator { return Promise.all(mutations); }; } - -/** - * Context type for configuring react hooks. - */ -export type RequestHandlerContext = { - endpoint: string; -}; - -/** - * Context for configuring react hooks. - */ -export const RequestHandlerContext = createContext({ - endpoint: '/api/model', -}); - -/** - * Context provider. - */ -export const Provider = RequestHandlerContext.Provider; diff --git a/packages/plugins/react/src/runtime/utils.ts b/packages/plugins/react/src/runtime/utils.ts new file mode 100644 index 000000000..ec92a0e2b --- /dev/null +++ b/packages/plugins/react/src/runtime/utils.ts @@ -0,0 +1,25 @@ +import { marshal, unmarshal } from '../serialization-utils'; + +export function makeUrl(url: string, args: unknown) { + return args ? url + `?q=${encodeURIComponent(marshal(args))}` : url; +} + +export async function fetcher(url: string, options?: RequestInit) { + const res = await fetch(url, options); + if (!res.ok) { + const error: Error & { info?: unknown; status?: number } = new Error( + 'An error occurred while fetching the data.' + ); + error.info = await res.json(); + error.status = res.status; + throw error; + } + + const textResult = await res.text(); + try { + return unmarshal(textResult) as R; + } catch (err) { + console.error(`Unable to deserialize data:`, textResult); + throw err; + } +} diff --git a/packages/plugins/react/src/serialization-utils.ts b/packages/plugins/react/src/serialization-utils.ts index 15442900b..2889fe851 100644 --- a/packages/plugins/react/src/serialization-utils.ts +++ b/packages/plugins/react/src/serialization-utils.ts @@ -19,3 +19,12 @@ export function registerSerializers() { 'decimal.js' ); } + +export function marshal(value: unknown) { + return superjson.stringify(value); +} + +export function unmarshal(value: string) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return superjson.parse(value); +} diff --git a/packages/plugins/react/tests/react-hooks.test.ts b/packages/plugins/react/tests/react-hooks.test.ts index 01b8e0209..e27c454a1 100644 --- a/packages/plugins/react/tests/react-hooks.test.ts +++ b/packages/plugins/react/tests/react-hooks.test.ts @@ -13,14 +13,7 @@ describe('React Hooks Plugin Tests', () => { process.chdir(origDir); }); - it('run plugin', async () => { - await loadSchema( - ` -plugin react { - provider = '${process.cwd()}/dist' - output = '$projectRoot/hooks' -} - + const sharedModel = ` model User { id String @id createdAt DateTime @default(now()) @@ -45,10 +38,39 @@ model Foo { id String @id @@ignore } + `; + + it('swr generator', async () => { + await loadSchema( + ` +plugin react { + provider = '${process.cwd()}/dist' + output = '$projectRoot/hooks' +} + +${sharedModel} + `, + true, + false, + [`${origDir}/dist`, 'react', '@types/react', 'swr'], + true + ); + }); + + it('react-query generator', async () => { + await loadSchema( + ` +plugin react { + provider = '${process.cwd()}/dist' + output = '$projectRoot/hooks' + fetcher = 'react-query' +} + +${sharedModel} `, true, false, - [`${origDir}/dist`, 'react', '@types/react'], + [`${origDir}/dist`, 'react', '@types/react', '@tanstack/react-query'], true ); }); diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index 50a05903c..d553e6e96 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.87", + "version": "1.0.0-alpha.102", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { @@ -31,7 +31,7 @@ "prettier": "^2.8.3", "ts-morph": "^16.0.0", "tslib": "^2.4.1", - "zod": "^3.19.1" + "zod": "3.21.1" }, "devDependencies": { "@types/jest": "^29.5.0", diff --git a/packages/plugins/trpc/src/generator.ts b/packages/plugins/trpc/src/generator.ts index 1ef8873e1..29bf6c743 100644 --- a/packages/plugins/trpc/src/generator.ts +++ b/packages/plugins/trpc/src/generator.ts @@ -1,14 +1,20 @@ import { DMMF } from '@prisma/generator-helper'; -import { PluginError, PluginOptions } from '@zenstackhq/sdk'; +import { CrudFailureReason, PluginError, PluginOptions, RUNTIME_PACKAGE, saveProject } from '@zenstackhq/sdk'; import { Model } from '@zenstackhq/sdk/ast'; +import { camelCase } from 'change-case'; import { promises as fs } from 'fs'; import path from 'path'; -import { generate as PrismaZodGenerator } from './zod/generator'; -import { generateProcedure, generateRouterSchemaImports, getInputTypeByOpName, resolveModelsComments } from './helpers'; +import { Project } from 'ts-morph'; +import { + generateHelperImport, + generateProcedure, + generateRouterSchemaImports, + getInputTypeByOpName, + resolveModelsComments, +} from './helpers'; import { project } from './project'; import removeDir from './utils/removeDir'; -import { camelCase } from 'change-case'; -import { Project } from 'ts-morph'; +import { generate as PrismaZodGenerator } from './zod/generator'; export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { let outDir = options.output as string; @@ -33,6 +39,13 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. const hiddenModels: string[] = []; resolveModelsComments(models, hiddenModels); + createAppRouter(outDir, modelOperations, hiddenModels); + createHelper(outDir); + + await saveProject(project); +} + +function createAppRouter(outDir: string, modelOperations: DMMF.ModelMapping[], hiddenModels: string[]) { const appRouter = project.createSourceFile(path.resolve(outDir, 'routers', `index.ts`), undefined, { overwrite: true, }); @@ -110,7 +123,6 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. }); appRouter.formatText(); - await project.save(); } function generateModelCreateRouter( @@ -133,6 +145,7 @@ function generateModelCreateRouter( ]); generateRouterSchemaImports(modelRouter, model); + generateHelperImport(modelRouter); modelRouter .addFunction({ @@ -162,3 +175,108 @@ function generateModelCreateRouter( modelRouter.formatText(); } + +function createHelper(outDir: string) { + const sf = project.createSourceFile(path.resolve(outDir, 'helper.ts'), undefined, { + overwrite: true, + }); + + sf.addStatements(`import { TRPCError } from '@trpc/server';`); + sf.addStatements(`import { isPrismaClientKnownRequestError } from '${RUNTIME_PACKAGE}';`); + + const checkMutate = sf.addFunction({ + name: 'checkMutate', + typeParameters: [{ name: 'T' }], + parameters: [ + { + name: 'promise', + type: 'Promise', + }, + ], + isAsync: true, + isExported: true, + returnType: 'Promise', + }); + + checkMutate.setBodyText( + `try { + return await promise; + } catch (err: any) { + if (isPrismaClientKnownRequestError(err)) { + if (err.code === 'P2004') { + if (err.meta?.reason === '${CrudFailureReason.RESULT_NOT_READABLE}') { + // unable to readback data + return undefined; + } else { + // rejected by policy + throw new TRPCError({ + code: 'FORBIDDEN', + message: err.message, + cause: err, + }); + } + } else { + // request error + throw new TRPCError({ + code: 'BAD_REQUEST', + message: err.message, + cause: err, + }); + } + } else { + throw err; + } + } + ` + ); + checkMutate.formatText(); + + const checkRead = sf.addFunction({ + name: 'checkRead', + typeParameters: [{ name: 'T' }], + parameters: [ + { + name: 'promise', + type: 'Promise', + }, + ], + isAsync: true, + isExported: true, + returnType: 'Promise', + }); + + checkRead.setBodyText( + `try { + return await promise; + } catch (err: any) { + if (isPrismaClientKnownRequestError(err)) { + if (err.code === 'P2004') { + // rejected by policy + throw new TRPCError({ + code: 'FORBIDDEN', + message: err.message, + cause: err, + }); + } else if (err.code === 'P2025') { + // not found + throw new TRPCError({ + code: 'NOT_FOUND', + message: err.message, + cause: err, + }); + } else { + // request error + throw new TRPCError({ + code: 'BAD_REQUEST', + message: err.message, + cause: err, + }) + } + } else { + throw err; + } + } + ` + ); + checkRead.formatText(); +} diff --git a/packages/plugins/trpc/src/helpers.ts b/packages/plugins/trpc/src/helpers.ts index b7be4dc29..3972c79e0 100644 --- a/packages/plugins/trpc/src/helpers.ts +++ b/packages/plugins/trpc/src/helpers.ts @@ -1,22 +1,7 @@ import { DMMF } from '@prisma/generator-helper'; -import { CrudFailureReason } from '@zenstackhq/sdk'; import { CodeBlockWriter, SourceFile } from 'ts-morph'; import { uncapitalizeFirstLetter } from './utils/uncapitalizeFirstLetter'; -export const generatetRPCImport = (sourceFile: SourceFile) => { - sourceFile.addImportDeclaration({ - moduleSpecifier: '@trpc/server', - namespaceImport: 'trpc', - }); -}; - -export const generateRouterImport = (sourceFile: SourceFile, modelNamePlural: string, modelNameCamelCase: string) => { - sourceFile.addImportDeclaration({ - moduleSpecifier: `./${modelNameCamelCase}.router`, - namedImports: [`${modelNamePlural}Router`], - }); -}; - export function generateProcedure( writer: CodeBlockWriter, opType: string, @@ -29,24 +14,15 @@ export function generateProcedure( if (procType === 'query') { writer.write(` - ${opType}: procedure.input(${typeName}).query(({ctx, input}) => db(ctx).${uncapitalizeFirstLetter( + ${opType}: procedure.input(${typeName}).query(({ctx, input}) => checkRead(db(ctx).${uncapitalizeFirstLetter( modelName - )}.${prismaMethod}(input)), + )}.${prismaMethod}(input))), `); } else if (procType === 'mutation') { writer.write(` - ${opType}: procedure.input(${typeName}).mutation(async ({ctx, input}) => { - try { - return await db(ctx).${uncapitalizeFirstLetter(modelName)}.${prismaMethod}(input); - } catch (err: any) { - if (err.code === 'P2004' && err.meta?.reason === '${CrudFailureReason.RESULT_NOT_READABLE}') { - // unable to readback data - return undefined; - } else { - throw err; - } - } - }), + ${opType}: procedure.input(${typeName}).mutation(async ({ctx, input}) => checkMutate(db(ctx).${uncapitalizeFirstLetter( + modelName + )}.${prismaMethod}(input))), `); } } @@ -55,6 +31,10 @@ export function generateRouterSchemaImports(sourceFile: SourceFile, name: string sourceFile.addStatements(`import { ${name}Schema } from '../schemas/${name}.schema';`); } +export function generateHelperImport(sourceFile: SourceFile) { + sourceFile.addStatements(`import { checkRead, checkMutate } from '../helper';`); +} + export const getInputTypeByOpName = (opName: string, modelName: string) => { let inputType; switch (opName) { diff --git a/packages/plugins/trpc/src/zod/generator.ts b/packages/plugins/trpc/src/zod/generator.ts index a1cd1018b..d480ac646 100644 --- a/packages/plugins/trpc/src/zod/generator.ts +++ b/packages/plugins/trpc/src/zod/generator.ts @@ -1,18 +1,21 @@ import { ConnectorType, DMMF } from '@prisma/generator-helper'; import { Dictionary } from '@prisma/internals'; -import { getLiteral, PluginOptions } from '@zenstackhq/sdk'; -import { DataSource, isDataSource, Model } from '@zenstackhq/sdk/ast'; +import { PluginOptions, getLiteral } from '@zenstackhq/sdk'; +import { DataSource, Model, isDataSource } from '@zenstackhq/sdk/ast'; import { - addMissingInputObjectTypes, AggregateOperationSupport, + addMissingInputObjectTypes, resolveAggregateOperationSupport, } from '@zenstackhq/sdk/dmmf-helpers'; import { promises as fs } from 'fs'; +import path from 'path'; import Transformer from './transformer'; import removeDir from './utils/removeDir'; +import { writeFileSafely } from './utils/writeFileSafely'; export async function generate(model: Model, options: PluginOptions, dmmf: DMMF.Document) { - await handleGeneratorOutputValue((options.output as string) ?? './generated'); + const output = (options.output as string) ?? './generated'; + await handleGeneratorOutputValue(output); const prismaClientDmmf = dmmf; @@ -38,7 +41,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. const aggregateOperationSupport = resolveAggregateOperationSupport(inputObjectTypes); - await generateObjectSchemas(inputObjectTypes); + await generateObjectSchemas(inputObjectTypes, output); await generateModelSchemas(models, modelOperations, aggregateOperationSupport); } @@ -61,13 +64,19 @@ async function generateEnumSchemas(prismaSchemaEnum: DMMF.SchemaEnum[], modelSch await transformer.generateEnumSchemas(); } -async function generateObjectSchemas(inputObjectTypes: DMMF.InputType[]) { +async function generateObjectSchemas(inputObjectTypes: DMMF.InputType[], output: string) { + const moduleNames: string[] = []; 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 }); - await transformer.generateObjectSchema(); + const moduleName = await transformer.generateObjectSchema(); + moduleNames.push(moduleName); } + await writeFileSafely( + path.join(output, `schemas/objects/index.ts`), + moduleNames.map((name) => `export * from './${name}';`).join('\n') + ); } async function generateModelSchemas( diff --git a/packages/plugins/trpc/src/zod/transformer.ts b/packages/plugins/trpc/src/zod/transformer.ts index ca14fb2f4..58b39386c 100644 --- a/packages/plugins/trpc/src/zod/transformer.ts +++ b/packages/plugins/trpc/src/zod/transformer.ts @@ -59,6 +59,11 @@ export default class Transformer { )}` ); } + + await writeFileSafely( + path.join(Transformer.outputPath, `schemas/enums/index.ts`), + this.enumTypes.map((enumType) => `export * from './${enumType.name}.schema';`).join('\n') + ); } generateImportZodStatement() { @@ -77,6 +82,7 @@ export default class Transformer { path.join(Transformer.outputPath, `schemas/objects/${this.name}.schema.ts`), '/* eslint-disable */\n' + objectSchema ); + return `${this.name}.schema`; } generateObjectSchemaFields() { @@ -113,10 +119,11 @@ export default class Transformer { } 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)); + result.push(this.wrapWithZodValidators(['z.date()', 'z.string().datetime()'], field, inputType)); + } else if (inputType.type === 'Bytes') { + result.push(this.wrapWithZodValidators('z.number().array()', 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)); @@ -158,19 +165,29 @@ export default class Transformer { } wrapWithZodValidators( - mainValidator: string, + mainValidators: string | string[], field: PrismaDMMF.SchemaArg, inputType: PrismaDMMF.SchemaArgInputType ) { let line = ''; - line = mainValidator; - if (inputType.isList) { - line += '.array()'; - } + const base = Array.isArray(mainValidators) ? mainValidators : [mainValidators]; + + line += base + .map((validator) => { + let r = validator; + if (inputType.isList) { + r += '.array()'; + } + if (!field.isRequired) { + r += '.optional()'; + } + return r; + }) + .join(', '); - if (!field.isRequired) { - line += '.optional()'; + if (base.length > 1) { + line = `z.union([${line}])`; } return line; @@ -356,8 +373,7 @@ export default class Transformer { } async generateModelSchemas() { - const globalImports: string[] = []; - let globalExport = ''; + const globalExports: string[] = []; for (const modelOperation of this.modelOperations) { const { @@ -381,8 +397,7 @@ export default class Transformer { // eslint-disable-next-line @typescript-eslint/no-explicit-any } = modelOperation; - globalImports.push(`import { ${modelName}Schema } from './${modelName}.schema'`); - globalExport += `${modelName}: ${modelName}Schema,`; + globalExports.push(`export { ${modelName}Schema as ${modelName} } from './${modelName}.schema'`); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const model = findModelByName(this.models, modelName)!; @@ -547,13 +562,7 @@ ${indentString(codeBody, 4)} path.join(Transformer.outputPath, 'schemas/index.ts'), ` /* eslint-disable */ -${globalImports.join(';\n')} - -const schemas = { -${indentString(globalExport, 4)} -}; - -export default schemas; +${globalExports.join(';\n')} ` ); } diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 770391323..20919fd4c 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.87", + "version": "1.0.0-alpha.102", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", @@ -20,18 +20,18 @@ "linkDirectory": true }, "dependencies": { + "@paralleldrive/cuid2": "^2.2.0", "@types/bcryptjs": "^2.4.2", "@zenstackhq/sdk": "workspace:*", "bcryptjs": "^2.4.3", "change-case": "^4.1.2", "colors": "1.4.0", - "cuid": "^2.1.8", "decimal.js": "^10.4.2", "deepcopy": "^2.1.0", "pluralize": "^8.0.0", "superjson": "^1.11.0", "tslib": "^2.4.1", - "zod": "^3.19.1", + "zod": "3.21.1", "zod-validation-error": "^0.2.1" }, "peerDependencies": { @@ -44,7 +44,7 @@ "license": "MIT", "devDependencies": { "@types/bcryptjs": "^2.4.2", - "@types/jest": "^29.0.3", + "@types/jest": "^29.5.0", "@types/node": "^14.18.29", "@types/pluralize": "^0.0.29", "copyfiles": "^2.4.1", diff --git a/packages/runtime/src/enhancements/policy/policy-utils.ts b/packages/runtime/src/enhancements/policy/policy-utils.ts index ac00978a2..e279d1044 100644 --- a/packages/runtime/src/enhancements/policy/policy-utils.ts +++ b/packages/runtime/src/enhancements/policy/policy-utils.ts @@ -3,8 +3,8 @@ import { PrismaClientKnownRequestError, PrismaClientUnknownRequestError } from '@prisma/client/runtime'; import { AUXILIARY_FIELDS, CrudFailureReason, GUARD_FIELD_NAME, TRANSACTION_FIELD_NAME } from '@zenstackhq/sdk'; import { camelCase } from 'change-case'; -import cuid from 'cuid'; import deepcopy from 'deepcopy'; +import pluralize from 'pluralize'; import { fromZodError } from 'zod-validation-error'; import { AuthUser, @@ -20,7 +20,7 @@ import { NestedWriteVisitor, VisitorContext } from '../nested-write-vistor'; import { ModelMeta, PolicyDef, PolicyFunc } from '../types'; import { enumerate, formatObject, getModelFields } from '../utils'; import { Logger } from './logger'; -import pluralize from 'pluralize'; +import { createId } from '@paralleldrive/cuid2'; /** * Access policy enforcement utilities @@ -400,7 +400,7 @@ export class PolicyUtil { // use a transaction to conduct write, so in case any create or nested create // fails access policies, we can roll back the entire operation - const transactionId = cuid(); + const transactionId = createId(); // args processor for create const processCreate = async (model: string, args: any) => { diff --git a/packages/runtime/src/enhancements/proxy.ts b/packages/runtime/src/enhancements/proxy.ts index 19c799fee..8dd386030 100644 --- a/packages/runtime/src/enhancements/proxy.ts +++ b/packages/runtime/src/enhancements/proxy.ts @@ -165,7 +165,7 @@ export function makeProxy( name = 'unnamed_enhancer', inTransaction = false ) { - const models = Object.keys(modelMeta.fields); + const models = Object.keys(modelMeta.fields).map((k) => k.toLowerCase()); const proxy = new Proxy(prisma, { get: (target: any, prop: string | symbol, receiver: any) => { // enhancer metadata @@ -206,7 +206,7 @@ export function makeProxy( } } - if (typeof prop !== 'string' || prop.startsWith('$') || !models.includes(prop)) { + if (typeof prop !== 'string' || prop.startsWith('$') || !models.includes(prop.toLowerCase())) { // skip non-model fields return Reflect.get(target, prop, receiver); } diff --git a/packages/runtime/src/version.ts b/packages/runtime/src/version.ts index 9bf48c342..854e5eab6 100644 --- a/packages/runtime/src/version.ts +++ b/packages/runtime/src/version.ts @@ -4,6 +4,7 @@ export function getVersion() { return require('./package.json').version; } catch { try { + // dev environment return require('../package.json').version; } catch { return 'unknown'; diff --git a/packages/schema/package.json b/packages/schema/package.json index f54bd8a8d..458ad0e89 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.87", + "version": "1.0.0-alpha.102", "author": { "name": "ZenStack Team" }, @@ -66,7 +66,7 @@ "main": "./bundle/extension.js", "scripts": { "vscode:publish": "vsce publish --no-dependencies", - "vscode:prepublish": "pnpm lint && pnpm bundle", + "vscode:prepublish": "pnpm bundle", "vscode:package": "vsce package --no-dependencies", "clean": "rimraf bundle dist", "build": "pnpm clean && pnpm lint && tsc && copyfiles -F \"bin/*\" dist && copyfiles ./README-global.md ./LICENSE ./package.json dist && renamer --replace \"README.md\" dist/README-global.md && copyfiles -u 1 \"src/res/*\" dist && node build/post-build.js", @@ -78,24 +78,26 @@ "publish-dev": "pnpm publish --tag dev", "postinstall": "node bin/post-install.js" }, + "peerDependencies": { + "prisma": "^4.0.0" + }, "dependencies": { - "@prisma/generator-helper": "^4.7.1", - "@prisma/internals": "^4.7.1", + "@paralleldrive/cuid2": "^2.2.0", + "@prisma/generator-helper": "^4.0.0", + "@prisma/internals": "^4.0.0", "@zenstackhq/language": "workspace:*", - "@zenstackhq/runtime": "workspace:*", "@zenstackhq/sdk": "workspace:*", "async-exit-hook": "^2.0.1", "change-case": "^4.1.2", "chevrotain": "^9.1.0", "colors": "1.4.0", "commander": "^8.3.0", - "cuid": "^2.1.8", + "get-latest-version": "^5.0.1", "langium": "1.1.0", "mixpanel": "^0.17.0", "node-machine-id": "^1.1.12", "ora": "^5.4.1", "pluralize": "^8.0.0", - "prisma": "~4.7.0", "promisify": "^0.0.3", "semver": "^7.3.8", "sleep-promise": "^9.1.0", @@ -106,11 +108,12 @@ "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", "vscode-uri": "^3.0.6", - "zod": "^3.19.1" + "zod": "3.21.1", + "zod-validation-error": "^0.2.1" }, "devDependencies": { "@types/async-exit-hook": "^2.0.0", - "@types/jest": "^29.2.0", + "@types/jest": "^29.5.0", "@types/node": "^14.18.32", "@types/pluralize": "^0.0.29", "@types/semver": "^7.3.13", @@ -119,6 +122,8 @@ "@types/vscode": "^1.56.0", "@typescript-eslint/eslint-plugin": "^5.42.0", "@typescript-eslint/parser": "^5.42.0", + "@vscode/vsce": "^2.19.0", + "@zenstackhq/runtime": "workspace:*", "@zenstackhq/testtools": "workspace:*", "concurrently": "^7.4.0", "copyfiles": "^2.4.1", @@ -126,8 +131,8 @@ "esbuild": "^0.15.12", "eslint": "^8.27.0", "eslint-plugin-jest": "^27.1.7", - "jest": "^29.2.1", - "langium-cli": "^1.0.0", + "jest": "^29.5.0", + "prisma": "^4.0.0", "renamer": "^4.0.0", "rimraf": "^3.0.2", "tmp": "^0.2.1", @@ -135,7 +140,6 @@ "ts-node": "^10.9.1", "tsc-alias": "^1.7.0", "typescript": "^4.8.4", - "vitest": "^0.29.7", - "vsce": "^2.13.0" + "vitest": "^0.29.7" } } diff --git a/packages/schema/src/cli/cli-util.ts b/packages/schema/src/cli/cli-util.ts index ff3034fea..ea02495d1 100644 --- a/packages/schema/src/cli/cli-util.ts +++ b/packages/schema/src/cli/cli-util.ts @@ -1,16 +1,20 @@ -import { isPlugin, Model } from '@zenstackhq/language/ast'; +import { isDataSource, isPlugin, Model } from '@zenstackhq/language/ast'; import { getLiteral, PluginError } from '@zenstackhq/sdk'; import colors from 'colors'; import fs from 'fs'; -import { LangiumDocument } from 'langium'; +import getLatestVersion from 'get-latest-version'; +import { getDocument, LangiumDocument, LangiumDocuments } from 'langium'; import { NodeFileSystem } from 'langium/node'; +import ora from 'ora'; import path from 'path'; +import semver from 'semver'; import { URI } from 'vscode-uri'; 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 { mergeBaseModel } from '../utils/ast-utils'; +import { mergeBaseModel, resolveImport, resolveTransitiveImports } from '../utils/ast-utils'; import { ensurePackage, installPackage, PackageManagers } from '../utils/pkg-utils'; +import { getVersion } from '../utils/version-utils'; import { CliError } from './cli-error'; import { PluginRunner } from './plugin-runner'; @@ -21,7 +25,7 @@ export async function initProject( projectPath: string, prismaSchema: string | undefined, packageManager: PackageManagers | undefined, - tag: string + tag?: string ) { if (!fs.existsSync(projectPath)) { console.error(`Path does not exist: ${projectPath}`); @@ -57,6 +61,8 @@ export async function initProject( ensurePackage('prisma', true, packageManager, 'latest', projectPath); ensurePackage('@prisma/client', false, packageManager, 'latest', projectPath); + + tag = tag ?? getVersion(); installPackage('zenstack', true, packageManager, tag, projectPath); installPackage('@zenstackhq/runtime', false, packageManager, tag, projectPath); @@ -102,13 +108,22 @@ export async function loadDocument(fileName: string): Promise { // load documents provided by plugins const pluginDocuments = await getPluginDocuments(services, fileName); + const langiumDocuments = services.shared.workspace.LangiumDocuments; // load the document - const document = services.shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName))); + const document = langiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName))); + + // load all imports + const importedURIs = eagerLoadAllImports(document, langiumDocuments); + + const importedDocuments = importedURIs.map((uri) => langiumDocuments.getOrCreateDocument(uri)); // build the document together with standard library and plugin modules - await services.shared.workspace.DocumentBuilder.build([stdLib, ...pluginDocuments, document], { - validationChecks: 'all', - }); + await services.shared.workspace.DocumentBuilder.build( + [stdLib, ...pluginDocuments, document, ...importedDocuments], + { + validationChecks: 'all', + } + ); const validationErrors = (document.diagnostics ?? []).filter((e) => e.severity === 1); if (validationErrors.length > 0) { @@ -125,11 +140,56 @@ export async function loadDocument(fileName: string): Promise { throw new CliError('schema validation errors'); } - const result = document.parseResult.value as Model; + const model = document.parseResult.value as Model; - mergeBaseModel(result); + mergeImportsDeclarations(langiumDocuments, model); - return result; + validationAfterMerge(model); + + mergeBaseModel(model); + + return model; +} + +// check global unique thing after merge imports +function validationAfterMerge(model: Model) { + const dataSources = model.declarations.filter((d) => isDataSource(d)); + if (dataSources.length == 0) { + console.error(colors.red('Validation errors: Model must define a datasource')); + throw new CliError('schema validation errors'); + } else if (dataSources.length > 1) { + console.error(colors.red('Validation errors: Multiple datasource declarations are not allowed')); + throw new CliError('schema validation errors'); + } +} + +export function eagerLoadAllImports( + document: LangiumDocument, + documents: LangiumDocuments, + uris: Set = new Set() +) { + const uriString = document.uri.toString(); + if (!uris.has(uriString)) { + uris.add(uriString); + const model = document.parseResult.value as Model; + + for (const imp of model.imports) { + const importedModel = resolveImport(documents, imp); + if (importedModel) { + const importedDoc = getDocument(importedModel); + eagerLoadAllImports(importedDoc, documents, uris); + } + } + } + + return Array.from(uris) + .filter((x) => uriString != x) + .map((e) => URI.parse(e)); +} + +export function mergeImportsDeclarations(documents: LangiumDocuments, model: Model) { + const importedModels = resolveTransitiveImports(documents, model); + model.declarations.push(...importedModels.flatMap((m) => m.declarations)); } export async function getPluginDocuments(services: ZModelServices, fileName: string): Promise { @@ -156,7 +216,7 @@ export async function getPluginDocuments(services: ZModelServices, fileName: str ); } } catch { - console.warn(`Unable to load plugin from ${provider}, skipping`); + // noop } } } @@ -185,3 +245,54 @@ export async function runPlugins(options: { schema: string; packageManager: Pack } } } + +export async function dumpInfo(projectPath: string) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let pkgJson: any; + const resolvedPath = path.resolve(projectPath); + try { + pkgJson = require(path.join(resolvedPath, 'package.json')); + } catch { + console.error('Unable to locate package.json. Are you in a valid project directory?'); + return; + } + const packages = [ + 'zenstack', + ...Object.keys(pkgJson.dependencies ?? {}).filter((p) => p.startsWith('@zenstackhq/')), + ...Object.keys(pkgJson.devDependencies ?? {}).filter((p) => p.startsWith('@zenstackhq/')), + ]; + + const versions = new Set(); + for (const pkg of packages) { + try { + const resolved = require.resolve(`${pkg}/package.json`, { paths: [resolvedPath] }); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const version = require(resolved).version; + versions.add(version); + console.log(` ${colors.green(pkg.padEnd(20))}\t${version}`); + } catch { + // noop + } + } + + if (versions.size > 1) { + console.warn(colors.yellow('WARNING: Multiple versions of Zenstack packages detected. This may cause issues.')); + } else if (versions.size > 0) { + const spinner = ora('Checking npm registry').start(); + const latest = await getLatestVersion('zenstack'); + + if (!latest) { + spinner.fail('unable to check for latest version'); + } else { + spinner.succeed(); + const version = [...versions][0]; + if (semver.gt(latest, version)) { + console.log(`A newer version of Zenstack is available: ${latest}.`); + } else if (semver.gt(version, latest)) { + console.log('You are using a pre-release version of Zenstack.'); + } else { + console.log('You are using the latest version of Zenstack.'); + } + } + } +} diff --git a/packages/schema/src/cli/config.ts b/packages/schema/src/cli/config.ts new file mode 100644 index 000000000..99aa66a2e --- /dev/null +++ b/packages/schema/src/cli/config.ts @@ -0,0 +1,40 @@ +import { GUARD_FIELD_NAME, TRANSACTION_FIELD_NAME } from '@zenstackhq/sdk'; +import fs from 'fs'; +import z, { ZodError } from 'zod'; +import { fromZodError } from 'zod-validation-error'; +import { CliError } from './cli-error'; + +const schema = z + .object({ + guardFieldName: z.string().default(GUARD_FIELD_NAME), + transactionFieldName: z.string().default(TRANSACTION_FIELD_NAME), + }) + .strict(); + +export type ConfigType = z.infer; + +export let config: ConfigType = schema.parse({}); + +/** + * Loads and validates CLI configuration file. + * @returns + */ +export function loadConfig(filename: string) { + try { + const fileData = fs.readFileSync(filename, `utf-8`); + const content = JSON.parse(fileData); + config = schema.parse(content); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + if (err?.code === `ENOENT`) { + throw new CliError(`Config file could not be found: ${filename}`); + } + if (err instanceof SyntaxError) { + throw new CliError(`Config is not a valid JSON file: ${filename}`); + } + if (err instanceof ZodError) { + throw new CliError(`Config file ${filename} is not valid: ${fromZodError(err)}`); + } + throw new CliError(`Error loading config: ${filename}`); + } +} diff --git a/packages/schema/src/cli/index.ts b/packages/schema/src/cli/index.ts index 17557638b..a3b511340 100644 --- a/packages/schema/src/cli/index.ts +++ b/packages/schema/src/cli/index.ts @@ -2,22 +2,26 @@ import { ZModelLanguageMetaData } from '@zenstackhq/language/module'; import colors from 'colors'; import { Command, Option } from 'commander'; +import fs from 'fs'; import * as semver from 'semver'; import telemetry from '../telemetry'; import { PackageManagers } from '../utils/pkg-utils'; import { getVersion } from '../utils/version-utils'; import { CliError } from './cli-error'; -import { initProject, runPlugins } from './cli-util'; +import { dumpInfo, initProject, runPlugins } from './cli-util'; +import { loadConfig } from './config'; // required minimal version of Prisma export const requiredPrismaVersion = '4.0.0'; +const DEFAULT_CONFIG_FILE = 'zenstack.config.json'; + export const initAction = async ( projectPath: string, options: { prisma: string | undefined; packageManager: PackageManagers | undefined; - tag: string; + tag?: string; } ): Promise => { await telemetry.trackSpan( @@ -29,6 +33,16 @@ export const initAction = async ( ); }; +export const infoAction = async (projectPath: string): Promise => { + await telemetry.trackSpan( + 'cli:command:start', + 'cli:command:complete', + 'cli:command:error', + { command: 'info' }, + () => dumpInfo(projectPath) + ); +}; + export const generateAction = async (options: { schema: string; packageManager: PackageManagers | undefined; @@ -87,6 +101,8 @@ export function createProgram() { './schema.zmodel' ); + const configOption = new Option('-c, --config [file]', 'config file'); + const pmOption = new Option('-p, --package-manager ', 'package manager to use').choices([ 'npm', 'yarn', @@ -95,16 +111,19 @@ export function createProgram() { const noDependencyCheck = new Option('--no-dependency-check', 'do not check if dependencies are installed'); + program + .command('info') + .description('Get information of installed ZenStack and related packages.') + .argument('[path]', 'project path', '.') + .action(infoAction); + program .command('init') .description('Initialize an existing project for ZenStack.') + .addOption(configOption) .addOption(pmOption) .addOption(new Option('--prisma ', 'location of Prisma schema file to bootstrap from')) - .addOption( - new Option('--tag ', 'the NPM package tag to use when installing dependencies').default( - '' - ) - ) + .addOption(new Option('--tag [tag]', 'the NPM package tag to use when installing dependencies')) .argument('[path]', 'project path', '.') .action(initAction); @@ -112,9 +131,24 @@ export function createProgram() { .command('generate') .description('Run code generation.') .addOption(schemaOption) + .addOption(configOption) .addOption(pmOption) .addOption(noDependencyCheck) .action(generateAction); + + // make sure config is loaded before actions run + program.hook('preAction', async (_, actionCommand) => { + let configFile: string | undefined = actionCommand.opts().config; + + if (!configFile && fs.existsSync(DEFAULT_CONFIG_FILE)) { + configFile = DEFAULT_CONFIG_FILE; + } + + if (configFile) { + loadConfig(configFile); + } + }); + return program; } diff --git a/packages/schema/src/cli/plugin-runner.ts b/packages/schema/src/cli/plugin-runner.ts index 7cf7b999d..909dce993 100644 --- a/packages/schema/src/cli/plugin-runner.ts +++ b/packages/schema/src/cli/plugin-runner.ts @@ -1,15 +1,16 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -import { DMMF } from '@prisma/generator-helper'; +import type { DMMF } from '@prisma/generator-helper'; import { getDMMF } from '@prisma/internals'; -import { Plugin, isPlugin } from '@zenstackhq/language/ast'; -import { PluginFunction, PluginOptions, getLiteral, getLiteralArray } from '@zenstackhq/sdk'; +import { isPlugin, Plugin } from '@zenstackhq/language/ast'; +import { getLiteral, getLiteralArray, PluginError, PluginFunction, PluginOptions } from '@zenstackhq/sdk'; import colors from 'colors'; import fs from 'fs'; import ora from 'ora'; import path from 'path'; import telemetry from '../telemetry'; -import { Context } from '../types'; -import { CliError } from './cli-error'; +import type { Context } from '../types'; +import { getVersion } from '../utils/version-utils'; +import { config } from './config'; /** * ZenStack code generator @@ -19,7 +20,7 @@ export class PluginRunner { * Runs a series of nested generators */ async run(context: Context): Promise { - const version = require('../package.json').version; + const version = getVersion(); console.log(colors.bold(`⌛️ ZenStack CLI v${version}, running plugins`)); const plugins: Array<{ @@ -44,9 +45,9 @@ export class PluginRunner { const options: PluginOptions = { schemaPath: context.schemaPath }; plugin.fields.forEach((f) => { - const value = getLiteral(f.value) || getLiteralArray(f.value); - if (!value) { - throw new CliError(`Invalid plugin value for ${f.name}`); + const value = getLiteral(f.value) ?? getLiteralArray(f.value); + if (value === undefined) { + throw new PluginError(`Invalid plugin value for ${f.name}`); } options[f.name] = value; }); @@ -58,12 +59,12 @@ export class PluginRunner { pluginModule = require(pluginModulePath); } catch (err) { console.error(`Unable to load plugin module ${pluginProvider}: ${pluginModulePath}, ${err}`); - throw new CliError(`Unable to load plugin module ${pluginProvider}`); + throw new PluginError(`Unable to load plugin module ${pluginProvider}`); } if (!pluginModule.default || typeof pluginModule.default !== 'function') { console.error(`Plugin provider ${pluginProvider} is missing a default function export`); - throw new CliError(`Plugin provider ${pluginProvider} is missing a default function export`); + throw new PluginError(`Plugin provider ${pluginProvider} is missing a default function export`); } plugins.push({ name: this.getPluginName(pluginModule, pluginProvider), @@ -134,7 +135,7 @@ export class PluginRunner { plugin: name, }, async () => { - let result = run(context.schema, options, dmmf); + let result = run(context.schema, options, dmmf, config); if (result instanceof Promise) { result = await result; } diff --git a/packages/schema/src/language-server/validator/datamodel-validator.ts b/packages/schema/src/language-server/validator/datamodel-validator.ts index 5448f8807..42f0c3f76 100644 --- a/packages/schema/src/language-server/validator/datamodel-validator.ts +++ b/packages/schema/src/language-server/validator/datamodel-validator.ts @@ -6,13 +6,12 @@ import { isLiteralExpr, ReferenceExpr, } from '@zenstackhq/language/ast'; +import { analyzePolicies, getLiteral } from '@zenstackhq/sdk'; import { ValidationAcceptor } from 'langium'; -import { analyzePolicies } from '../../utils/ast-utils'; import { IssueCodes, SCALAR_TYPES } from '../constants'; import { AstValidator } from '../types'; import { getIdFields, getUniqueFields } from '../utils'; import { validateAttributeApplication, validateDuplicatedDeclarations } from './utils'; -import { getLiteral } from '@zenstackhq/sdk'; /** * Validates data model declarations. @@ -73,6 +72,10 @@ export default class DataModelValidator implements AstValidator { accept('error', 'Optional lists are not supported. Use either `Type[]` or `Type?`', { node: field.type }); } + if (field.type.unsupported && typeof field.type.unsupported.value.value !== 'string') { + accept('error', 'Unsupported type argument must be a string literal', { node: field.type.unsupported }); + } + field.attributes.forEach((attr) => validateAttributeApplication(attr, accept)); } diff --git a/packages/schema/src/language-server/validator/expression-validator.ts b/packages/schema/src/language-server/validator/expression-validator.ts index 64cdec539..18c826158 100644 --- a/packages/schema/src/language-server/validator/expression-validator.ts +++ b/packages/schema/src/language-server/validator/expression-validator.ts @@ -1,6 +1,6 @@ -import { BinaryExpr, Expression, isArrayExpr, isBinaryExpr, isEnum, isLiteralExpr } from '@zenstackhq/language/ast'; +import { BinaryExpr, Expression, isBinaryExpr, isEnum } from '@zenstackhq/language/ast'; import { ValidationAcceptor } from 'langium'; -import { getDataModelFieldReference, isAuthInvocation, isEnumFieldReference } from '../../utils/ast-utils'; +import { isAuthInvocation } from '../../utils/ast-utils'; import { AstValidator } from '../types'; /** @@ -22,6 +22,10 @@ export default class ExpressionValidator implements AstValidator { } } + if (expr.$resolvedType?.decl === 'Unsupported') { + accept('error', 'Field of "Unsupported" type cannot be used in expressions', { node: expr }); + } + // extra validations by expression type switch (expr.$type) { case 'BinaryExpr': @@ -33,21 +37,12 @@ export default class ExpressionValidator implements AstValidator { private validateBinaryExpr(expr: BinaryExpr, accept: ValidationAcceptor) { switch (expr.operator) { case 'in': { - if (!getDataModelFieldReference(expr.left)) { - accept('error', 'left operand of "in" must be a field reference', { node: expr.left }); - } - if (typeof expr.left.$resolvedType?.decl !== 'string' && !isEnum(expr.left.$resolvedType?.decl)) { accept('error', 'left operand of "in" must be of scalar type', { node: expr.left }); } - if ( - !( - isArrayExpr(expr.right) && - expr.right.items.every((item) => isLiteralExpr(item) || isEnumFieldReference(item)) - ) - ) { - accept('error', 'right operand of "in" must be an array of literals or enum values', { + if (!expr.right.$resolvedType?.array) { + accept('error', 'right operand of "in" must be an array', { node: expr.right, }); } diff --git a/packages/schema/src/language-server/validator/schema-validator.ts b/packages/schema/src/language-server/validator/schema-validator.ts index 3d3dc5836..098489939 100644 --- a/packages/schema/src/language-server/validator/schema-validator.ts +++ b/packages/schema/src/language-server/validator/schema-validator.ts @@ -1,16 +1,31 @@ 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'; +import { LangiumDocuments, ValidationAcceptor } from 'langium'; import { validateDuplicatedDeclarations } from './utils'; +import { getAllDeclarationsFromImports, resolveTransitiveImports } from '../../utils/ast-utils'; /** * Validates toplevel schema. */ export default class SchemaValidator implements AstValidator { + constructor(protected readonly documents: LangiumDocuments) {} validate(model: Model, accept: ValidationAcceptor): void { validateDuplicatedDeclarations(model.declarations, accept); + const importedModels = resolveTransitiveImports(this.documents, model); + + const importedNames = new Set(importedModels.flatMap((m) => m.declarations.map((d) => d.name))); + + for (const declaration of model.declarations) { + if (importedNames.has(declaration.name)) { + accept('error', `A ${declaration.name} already exists in an imported module`, { + node: declaration, + property: 'name', + }); + } + } + if ( !model.$document?.uri.path.endsWith(STD_LIB_MODULE_NAME) && !model.$document?.uri.path.endsWith(PLUGIN_MODULE_NAME) @@ -20,10 +35,8 @@ export default class SchemaValidator implements AstValidator { } private validateDataSources(model: Model, accept: ValidationAcceptor) { - const dataSources = model.declarations.filter((d) => isDataSource(d)); - if (dataSources.length === 0) { - accept('error', 'Model must define a datasource', { node: model }); - } else if (dataSources.length > 1) { + const dataSources = getAllDeclarationsFromImports(this.documents, model).filter((d) => isDataSource(d)); + if (dataSources.length > 1) { accept('error', 'Multiple datasource declarations are not allowed', { node: dataSources[1] }); } } diff --git a/packages/schema/src/language-server/validator/utils.ts b/packages/schema/src/language-server/validator/utils.ts index e4514693f..7b2f88b62 100644 --- a/packages/schema/src/language-server/validator/utils.ts +++ b/packages/schema/src/language-server/validator/utils.ts @@ -91,7 +91,9 @@ export function typeAssignable(destType: ExpressionType, sourceType: ExpressionT /** * Maps a ZModel builtin type to expression type */ -export function mapBuiltinTypeToExpressionType(type: BuiltinType | 'Any' | 'Object' | 'Null'): ExpressionType | 'Any' { +export function mapBuiltinTypeToExpressionType( + type: BuiltinType | 'Any' | 'Object' | 'Null' | 'Unsupported' +): ExpressionType | 'Any' { switch (type) { case 'Any': case 'Boolean': @@ -110,6 +112,8 @@ export function mapBuiltinTypeToExpressionType(type: BuiltinType | 'Any' | 'Obje return 'Any'; case 'Object': return 'Object'; + case 'Unsupported': + return 'Unsupported'; } } diff --git a/packages/schema/src/language-server/validator/zmodel-validator.ts b/packages/schema/src/language-server/validator/zmodel-validator.ts index 4ea4c59dd..3f4f740b7 100644 --- a/packages/schema/src/language-server/validator/zmodel-validator.ts +++ b/packages/schema/src/language-server/validator/zmodel-validator.ts @@ -42,6 +42,7 @@ export class ZModelValidationRegistry extends ValidationRegistry { * Implementation of custom validations. */ export class ZModelValidator { + constructor(protected readonly services: ZModelServices) {} private shouldCheck(node: AstNode) { let doc: LangiumDocument | undefined; let currNode: AstNode | undefined = node; @@ -57,7 +58,8 @@ export class ZModelValidator { } checkModel(node: Model, accept: ValidationAcceptor): void { - this.shouldCheck(node) && new SchemaValidator().validate(node, accept); + this.shouldCheck(node) && + new SchemaValidator(this.services.shared.workspace.LangiumDocuments).validate(node, accept); } checkDataSource(node: DataSource, accept: ValidationAcceptor): void { diff --git a/packages/schema/src/language-server/zmodel-code-action.ts b/packages/schema/src/language-server/zmodel-code-action.ts index a9b714721..92ce9f3a2 100644 --- a/packages/schema/src/language-server/zmodel-code-action.ts +++ b/packages/schema/src/language-server/zmodel-code-action.ts @@ -4,6 +4,7 @@ import { CodeActionProvider, findDeclarationNodeAtOffset, getContainerOfType, + getDocument, IndexManager, LangiumDocument, LangiumServices, @@ -112,6 +113,9 @@ export class ZModelCodeActionProvider implements CodeActionProvider { newText = '\n' + indent + `${fieldName} ${typeName}[]`; } + // the opposite model might be in the imported file + const targetDocument = getDocument(oppositeModel); + return { title: `Add opposite relation fields on ${oppositeModel.name}`, kind: CodeActionKind.QuickFix, @@ -119,7 +123,7 @@ export class ZModelCodeActionProvider implements CodeActionProvider { isPreferred: false, edit: { changes: { - [document.textDocument.uri]: [ + [targetDocument.textDocument.uri]: [ { range: { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index d56ecb6ce..743f24c43 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -19,7 +19,6 @@ import { isReferenceExpr, LiteralExpr, MemberAccessExpr, - Model, NullExpr, ObjectExpr, ReferenceExpr, @@ -39,11 +38,11 @@ import { LangiumDocument, LangiumServices, LinkingError, - Mutable, Reference, streamContents, } from 'langium'; import { CancellationToken } from 'vscode-jsonrpc'; +import { getAllDeclarationsFromImports } from '../utils/ast-utils'; import { getContainingModel, isFromStdlib } from './utils'; import { mapBuiltinTypeToExpressionType } from './validator/utils'; @@ -72,8 +71,6 @@ export class ZModelLinker extends DefaultLinker { return; } - this.resolveBaseModels(document); - for (const node of streamContents(document.parseResult.value)) { await interruptAndCheck(cancelToken); this.resolve(node, document); @@ -96,34 +93,6 @@ export class ZModelLinker extends DefaultLinker { //#endregion - //#region abstract DataModel - - private resolveBaseModels(document: LangiumDocument) { - const model = document.parseResult.value as Model; - - model.declarations.forEach((decl) => { - if (decl.$type === 'DataModel') { - const dataModel = decl as DataModel; - dataModel.$resolvedFields = [...dataModel.fields]; - dataModel.superTypes.forEach((superType) => { - const superTypeDecl = superType.ref; - if (superTypeDecl) { - superTypeDecl.fields.forEach((field) => { - const cloneField = Object.assign({}, field); - cloneField.$isInherited = true; - const mutable = cloneField as Mutable; - // update container - mutable.$container = dataModel; - dataModel.$resolvedFields.push(cloneField); - }); - } - }); - } - }); - } - - //#endregion - //#region Expression type resolving private resolveFromScopeProviders( @@ -280,9 +249,14 @@ export class ZModelLinker extends DefaultLinker { if (funcDecl.name === 'auth' && isFromStdlib(funcDecl)) { // auth() function is resolved to User model in the current document const model = getContainingModel(node); - const userModel = model?.declarations.find((d) => isDataModel(d) && d.name === 'User'); - if (userModel) { - node.$resolvedType = { decl: userModel, nullable: true }; + + if (model) { + const userModel = getAllDeclarationsFromImports(this.langiumDocuments(), model).find( + (d) => isDataModel(d) && d.name === 'User' + ); + if (userModel) { + node.$resolvedType = { decl: userModel, nullable: true }; + } } } else if (funcDecl.name === 'future' && isFromStdlib(funcDecl)) { // future() function is resolved to current model @@ -483,7 +457,14 @@ export class ZModelLinker extends DefaultLinker { let nullable = false; if (isDataModelFieldType(type)) { nullable = type.optional; + + // referencing a field of 'Unsupported' type + if (type.unsupported) { + node.$resolvedType = { decl: 'Unsupported', array: type.array, nullable }; + return; + } } + if (type.type) { const mappedType = mapBuiltinTypeToExpressionType(type.type); node.$resolvedType = { decl: mappedType, array: type.array, nullable: nullable }; diff --git a/packages/schema/src/language-server/zmodel-module.ts b/packages/schema/src/language-server/zmodel-module.ts index 077675ed5..e25f7015f 100644 --- a/packages/schema/src/language-server/zmodel-module.ts +++ b/packages/schema/src/language-server/zmodel-module.ts @@ -23,7 +23,7 @@ import { ZModelValidationRegistry, ZModelValidator } from './validator/zmodel-va import { ZModelCodeActionProvider } from './zmodel-code-action'; import { ZModelFormatter } from './zmodel-formatter'; import { ZModelLinker } from './zmodel-linker'; -import { ZModelScopeComputation } from './zmodel-scope'; +import { ZModelScopeComputation, ZModelScopeProvider } from './zmodel-scope'; import ZModelWorkspaceManager from './zmodel-workspace-manager'; /** @@ -50,10 +50,11 @@ export const ZModelModule: Module new ZModelScopeComputation(services), Linker: (services) => new ZModelLinker(services), + ScopeProvider: (services) => new ZModelScopeProvider(services), }, validation: { ValidationRegistry: (services) => new ZModelValidationRegistry(services), - ZModelValidator: () => new ZModelValidator(), + ZModelValidator: (services) => new ZModelValidator(services), }, lsp: { Formatter: () => new ZModelFormatter(), diff --git a/packages/schema/src/language-server/zmodel-scope.ts b/packages/schema/src/language-server/zmodel-scope.ts index 51c7ae184..95fecacc7 100644 --- a/packages/schema/src/language-server/zmodel-scope.ts +++ b/packages/schema/src/language-server/zmodel-scope.ts @@ -1,14 +1,26 @@ -import { isEnumField } from '@zenstackhq/language/ast'; +import { isEnumField, isModel, Model, DataModel } from '@zenstackhq/language/ast'; import { AstNode, AstNodeDescription, DefaultScopeComputation, + DefaultScopeProvider, + EMPTY_SCOPE, + equalURI, + getContainerOfType, interruptAndCheck, LangiumDocument, LangiumServices, + Mutable, + PrecomputedScopes, + ReferenceInfo, + Scope, + stream, streamAllContents, + StreamScope, } from 'langium'; import { CancellationToken } from 'vscode-jsonrpc'; +import { resolveImportUri } from '../utils/ast-utils'; +import { PLUGIN_MODULE_NAME, STD_LIB_MODULE_NAME } from './constants'; /** * Custom Langium ScopeComputation implementation which adds enum fields into global scope @@ -41,4 +53,67 @@ export class ZModelScopeComputation extends DefaultScopeComputation { return result; } + + override computeLocalScopes( + document: LangiumDocument, + cancelToken?: CancellationToken | undefined + ): Promise { + const result = super.computeLocalScopes(document, cancelToken); + + //the $resolvedFields would be used in Linking stage for all the documents + //so we need to set it at the end of the scope computation + this.resolveBaseModels(document); + return result; + } + + private resolveBaseModels(document: LangiumDocument) { + const model = document.parseResult.value as Model; + + model.declarations.forEach((decl) => { + if (decl.$type === 'DataModel') { + const dataModel = decl as DataModel; + dataModel.$resolvedFields = [...dataModel.fields]; + dataModel.superTypes.forEach((superType) => { + const superTypeDecl = superType.ref; + if (superTypeDecl) { + superTypeDecl.fields.forEach((field) => { + const cloneField = Object.assign({}, field); + cloneField.$isInherited = true; + const mutable = cloneField as Mutable; + // update container + mutable.$container = dataModel; + dataModel.$resolvedFields.push(cloneField); + }); + } + }); + } + }); + } +} + +export class ZModelScopeProvider extends DefaultScopeProvider { + constructor(services: LangiumServices) { + super(services); + } + + protected override getGlobalScope(referenceType: string, context: ReferenceInfo): Scope { + const model = getContainerOfType(context.container, isModel); + if (!model) { + return EMPTY_SCOPE; + } + + const importedUris = stream(model.imports).map(resolveImportUri).nonNullable(); + const importedElements = this.indexManager.allElements(referenceType).filter( + (des) => + // allow current document + equalURI(des.documentUri, model.$document?.uri) || + // allow stdlib + des.documentUri.path.endsWith(STD_LIB_MODULE_NAME) || + // allow plugin models + des.documentUri.path.endsWith(PLUGIN_MODULE_NAME) || + // allow imported documents + importedUris.some((importedUri) => (des.documentUri, importedUri)) + ); + return new StreamScope(importedElements); + } } diff --git a/packages/schema/src/language-server/zmodel-workspace-manager.ts b/packages/schema/src/language-server/zmodel-workspace-manager.ts index b4146cfd0..79b5bfb5e 100644 --- a/packages/schema/src/language-server/zmodel-workspace-manager.ts +++ b/packages/schema/src/language-server/zmodel-workspace-manager.ts @@ -1,13 +1,17 @@ -import { DefaultWorkspaceManager, LangiumDocument } from 'langium'; +import { isPlugin, Model } from '@zenstackhq/language/ast'; +import { getLiteral } from '@zenstackhq/sdk'; +import { DefaultWorkspaceManager, interruptAndCheck, LangiumDocument } from 'langium'; import path from 'path'; -import { WorkspaceFolder } from 'vscode-languageserver'; -import { URI } from 'vscode-uri'; -import { STD_LIB_MODULE_NAME } from './constants'; +import { CancellationToken, WorkspaceFolder } from 'vscode-languageserver'; +import { URI, Utils } from 'vscode-uri'; +import { PLUGIN_MODULE_NAME, STD_LIB_MODULE_NAME } from './constants'; /** * Custom Langium WorkspaceManager implementation which automatically loads stdlib.zmodel */ export default class ZModelWorkspaceManager extends DefaultWorkspaceManager { + public pluginModels = new Set(); + protected async loadAdditionalDocuments( _folders: WorkspaceFolder[], _collector: (document: LangiumDocument) => void @@ -18,4 +22,115 @@ export default class ZModelWorkspaceManager extends DefaultWorkspaceManager { const stdlib = this.langiumDocuments.getOrCreateDocument(stdLibUri); _collector(stdlib); } + + override async initializeWorkspace( + folders: WorkspaceFolder[], + cancelToken = CancellationToken.None + ): Promise { + const fileExtensions = this.serviceRegistry.all.flatMap((e) => e.LanguageMetaData.fileExtensions); + const documents: LangiumDocument[] = []; + const collector = (document: LangiumDocument) => { + documents.push(document); + if (!this.langiumDocuments.hasDocument(document.uri)) { + this.langiumDocuments.addDocument(document); + } + }; + + await this.loadAdditionalDocuments(folders, collector); + await Promise.all( + folders + .map((wf) => [wf, this.getRootFolder(wf)] as [WorkspaceFolder, URI]) + .map(async (entry) => this.traverseFolder(...entry, fileExtensions, collector)) + ); + + // find plugin models + documents.forEach((doc) => { + const parsed = doc.parseResult.value as Model; + 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) { + this.pluginModels.add(provider); + } + } + } + }); + }); + + if (this.pluginModels.size > 0) { + console.log(`Used plugin documents: ${Array.from(this.pluginModels)}`); + + // the loaded plugin models would be removed from the set + const unLoadedPluginModels = new Set(this.pluginModels); + + await Promise.all( + folders + .map((wf) => [wf, this.getRootFolder(wf)] as [WorkspaceFolder, URI]) + .map(async (entry) => this.loadPluginModels(...entry, unLoadedPluginModels, collector)) + ); + + if (unLoadedPluginModels.size > 0) { + console.warn(`The following plugin documents could not be loaded: ${Array.from(unLoadedPluginModels)}`); + } + } + + // Only after creating all documents do we check whether we need to cancel the initialization + // The document builder will later pick up on all unprocessed documents + await interruptAndCheck(cancelToken); + await this.documentBuilder.build(documents, undefined, cancelToken); + } + + protected async loadPluginModels( + workspaceFolder: WorkspaceFolder, + folderPath: URI, + pluginModels: Set, + collector: (document: LangiumDocument) => void + ): Promise { + const content = await ( + await this.fileSystemProvider.readDirectory(folderPath) + ).sort((a, b) => { + // make sure the node_moudules folder is always the first one to be checked + // so it could be early exited if the plugin is found + if (a.isDirectory && b.isDirectory) { + const aName = Utils.basename(a.uri); + if (aName === 'node_modules') { + return -1; + } else { + return 1; + } + } else { + return 0; + } + }); + + for (const entry of content) { + if (entry.isDirectory) { + const name = Utils.basename(entry.uri); + if (name === 'node_modules') { + for (const plugin of Array.from(pluginModels)) { + const path = Utils.joinPath(entry.uri, plugin, PLUGIN_MODULE_NAME); + try { + this.fileSystemProvider.readFileSync(path); + const document = this.langiumDocuments.getOrCreateDocument(path); + collector(document); + console.log(`Adding plugin document from ${path.path}`); + + pluginModels.delete(plugin); + // early exit if all plugins are loaded + if (pluginModels.size === 0) { + return; + } + } catch { + // no-op. The module might be found in another node_modules folder + // will show the warning message eventually if not found + } + } + } else { + await this.loadPluginModels(workspaceFolder, entry.uri, pluginModels, collector); + } + } + } + } } diff --git a/packages/schema/src/plugins/access-policy/expression-writer.ts b/packages/schema/src/plugins/access-policy/expression-writer.ts index 03fe11fb1..238eed6c1 100644 --- a/packages/schema/src/plugins/access-policy/expression-writer.ts +++ b/packages/schema/src/plugins/access-policy/expression-writer.ts @@ -153,14 +153,35 @@ export class ExpressionWriter { } private writeIn(expr: BinaryExpr) { + const leftIsFieldAccess = this.isFieldAccess(expr.left); + const rightIsFieldAccess = this.isFieldAccess(expr.right); + this.block(() => { - this.writeFieldCondition( - expr.left, - () => { - this.plain(expr.right); - }, - 'in' - ); + if (!leftIsFieldAccess && !rightIsFieldAccess) { + // 'in' without referencing fields + this.guard(() => this.plain(expr)); + } else if (leftIsFieldAccess && !rightIsFieldAccess) { + // 'in' with left referencing a field, right is an array literal + this.writeFieldCondition( + expr.left, + () => { + this.plain(expr.right); + }, + 'in' + ); + } else if (!leftIsFieldAccess && rightIsFieldAccess) { + // 'in' with right referencing an array field, left is a literal + // transform it into a 'has' filter + this.writeFieldCondition( + expr.right, + () => { + this.plain(expr.left); + }, + 'has' + ); + } else { + throw new PluginError('"in" operator cannot be used with field references on both sides'); + } }); } @@ -520,6 +541,12 @@ export class ExpressionWriter { } if (FILTER_OPERATOR_FUNCTIONS.includes(funcDecl.name)) { + if (!expr.args.some((arg) => this.isFieldAccess(arg.value))) { + // filter functions without referencing fields + this.block(() => this.guard(() => this.plain(expr))); + return; + } + let valueArg = expr.args[1]?.value; // isEmpty function is zero arity, it's mapped to a boolean literal diff --git a/packages/schema/src/plugins/access-policy/policy-guard-generator.ts b/packages/schema/src/plugins/access-policy/policy-guard-generator.ts index 06a70336b..62079cef0 100644 --- a/packages/schema/src/plugins/access-policy/policy-guard-generator.ts +++ b/packages/schema/src/plugins/access-policy/policy-guard-generator.ts @@ -13,16 +13,28 @@ import { MemberAccessExpr, Model, } from '@zenstackhq/language/ast'; -import { PolicyKind, PolicyOperationKind } from '@zenstackhq/runtime'; -import { getDataModels, getLiteral, GUARD_FIELD_NAME, PluginError, PluginOptions, resolved } from '@zenstackhq/sdk'; +import type { PolicyKind, PolicyOperationKind } from '@zenstackhq/runtime'; +import { + analyzePolicies, + createProject, + emitProject, + getDataModels, + getLiteral, + GUARD_FIELD_NAME, + PluginError, + PluginOptions, + resolved, + RUNTIME_PACKAGE, + saveProject, +} from '@zenstackhq/sdk'; import { camelCase } from 'change-case'; import { streamAllContents } from 'langium'; import path from 'path'; -import { FunctionDeclaration, Project, SourceFile, VariableDeclarationKind } from 'ts-morph'; +import { FunctionDeclaration, SourceFile, VariableDeclarationKind } from 'ts-morph'; import { name } from '.'; import { isFromStdlib } from '../../language-server/utils'; -import { analyzePolicies, getIdFields } from '../../utils/ast-utils'; -import { ALL_OPERATION_KINDS, getDefaultOutputFolder, RUNTIME_PACKAGE } from '../plugin-utils'; +import { getIdFields } from '../../utils/ast-utils'; +import { ALL_OPERATION_KINDS, getDefaultOutputFolder } from '../plugin-utils'; import { ExpressionWriter } from './expression-writer'; import { isFutureExpr } from './utils'; import { ZodSchemaGenerator } from './zod-schema-generator'; @@ -38,19 +50,15 @@ export default class PolicyGenerator { return; } - const project = new Project(); + const project = createProject(); const sf = project.createSourceFile(path.join(output, 'policy.ts'), undefined, { overwrite: true }); + sf.addStatements('/* eslint-disable */'); sf.addImportDeclaration({ namedImports: [{ name: 'type QueryContext' }, { name: 'hasAllFields' }], moduleSpecifier: `${RUNTIME_PACKAGE}`, }); - sf.addImportDeclaration({ - namedImports: [{ name: 'z' }], - moduleSpecifier: 'zod', - }); - // import enums for (const e of model.declarations.filter((d) => isEnum(d))) { sf.addImportDeclaration({ @@ -68,6 +76,8 @@ export default class PolicyGenerator { const zodGenerator = new ZodSchemaGenerator(); + let fieldSchemaGenerated = false; + sf.addVariableStatement({ declarationKind: VariableDeclarationKind.Const, declarations: [ @@ -95,18 +105,33 @@ export default class PolicyGenerator { writer.writeLine(','); writer.write('schema:'); - zodGenerator.generate(writer, models); + if (zodGenerator.generate(writer, models)) { + fieldSchemaGenerated = true; + } }); }, }, ], }); + if (fieldSchemaGenerated) { + sf.addImportDeclaration({ + namedImports: [{ name: 'z' }], + moduleSpecifier: 'zod', + }); + } + sf.addStatements('export default policy'); - sf.formatText(); - await project.save(); - await project.emit(); + // emit if generated into standard location or compilation is forced + const shouldCompile = !options.output || options.compile === true; + if (!shouldCompile || options.preserveTsFiles === true) { + // save ts files + await saveProject(project); + } + if (shouldCompile) { + await emitProject(project); + } } private getPolicyExpressions(model: DataModel, kind: PolicyKind, operation: PolicyOperationKind) { @@ -337,7 +362,7 @@ export default class PolicyGenerator { func.addStatements( `const user = hasAllFields(context.user, [${userIdFields .map((f) => "'" + f.name + "'") - .join(', ')}]) ? context.user : null;` + .join(', ')}]) ? context.user as any : null;` ); } diff --git a/packages/schema/src/plugins/access-policy/typescript-expression-transformer.ts b/packages/schema/src/plugins/access-policy/typescript-expression-transformer.ts index cb6dfba5e..98dde9004 100644 --- a/packages/schema/src/plugins/access-policy/typescript-expression-transformer.ts +++ b/packages/schema/src/plugins/access-policy/typescript-expression-transformer.ts @@ -12,7 +12,8 @@ import { ThisExpr, UnaryExpr, } from '@zenstackhq/language/ast'; -import { PluginError } from '@zenstackhq/sdk'; +import { getLiteral, PluginError } from '@zenstackhq/sdk'; +import { FILTER_OPERATOR_FUNCTIONS } from '../../language-server/constants'; import { isAuthInvocation } from '../../utils/ast-utils'; import { isFutureExpr } from './utils'; @@ -28,17 +29,17 @@ export default class TypeScriptExpressionTransformer { constructor(private readonly isPostGuard = false) {} /** - * - * @param expr + * Transforms the given expression to a TypeScript expression. + * @param normalizeUndefined if undefined values should be normalized to null * @returns */ - transform(expr: Expression): string { + transform(expr: Expression, normalizeUndefined = true): string { switch (expr.$type) { case LiteralExpr: return this.literal(expr as LiteralExpr); case ArrayExpr: - return this.array(expr as ArrayExpr); + return this.array(expr as ArrayExpr, normalizeUndefined); case NullExpr: return this.null(); @@ -50,16 +51,16 @@ export default class TypeScriptExpressionTransformer { return this.reference(expr as ReferenceExpr); case InvocationExpr: - return this.invocation(expr as InvocationExpr); + return this.invocation(expr as InvocationExpr, normalizeUndefined); case MemberAccessExpr: - return this.memberAccess(expr as MemberAccessExpr); + return this.memberAccess(expr as MemberAccessExpr, normalizeUndefined); case UnaryExpr: - return this.unary(expr as UnaryExpr); + return this.unary(expr as UnaryExpr, normalizeUndefined); case BinaryExpr: - return this.binary(expr as BinaryExpr); + return this.binary(expr as BinaryExpr, normalizeUndefined); default: throw new PluginError(`Unsupported expression type: ${expr.$type}`); @@ -72,7 +73,7 @@ export default class TypeScriptExpressionTransformer { return 'id'; } - private memberAccess(expr: MemberAccessExpr) { + private memberAccess(expr: MemberAccessExpr, normalizeUndefined: boolean) { if (!expr.member.ref) { throw new PluginError(`Unresolved MemberAccessExpr`); } @@ -85,14 +86,71 @@ export default class TypeScriptExpressionTransformer { } return expr.member.ref.name; } else { - // normalize field access to null instead of undefined to avoid accidentally use undefined in filter - return `(${this.transform(expr.operand)}?.${expr.member.ref.name} ?? null)`; + if (normalizeUndefined) { + // normalize field access to null instead of undefined to avoid accidentally use undefined in filter + return `(${this.transform(expr.operand, normalizeUndefined)}?.${expr.member.ref.name} ?? null)`; + } else { + return `${this.transform(expr.operand, normalizeUndefined)}?.${expr.member.ref.name}`; + } } } - private invocation(expr: InvocationExpr) { + private invocation(expr: InvocationExpr, normalizeUndefined: boolean) { + if (!expr.function.ref) { + throw new PluginError(`Unresolved InvocationExpr`); + } + if (isAuthInvocation(expr)) { return 'user'; + } else if (FILTER_OPERATOR_FUNCTIONS.includes(expr.function.ref.name)) { + // arguments are already type-checked + + const arg0 = this.transform(expr.args[0].value, false); + let result: string; + switch (expr.function.ref.name) { + case 'contains': { + const caseInsensitive = getLiteral(expr.args[2]?.value) === true; + if (caseInsensitive) { + result = `${arg0}?.toLowerCase().includes(${this.transform( + expr.args[1].value, + normalizeUndefined + )}?.toLowerCase())`; + } else { + result = `${arg0}?.includes(${this.transform(expr.args[1].value, normalizeUndefined)})`; + } + break; + } + case 'search': + throw new PluginError('"search" function must be used against a field'); + case 'startsWith': + result = `${arg0}?.startsWith(${this.transform(expr.args[1].value, normalizeUndefined)})`; + break; + case 'endsWith': + result = `${arg0}?.endsWith(${this.transform(expr.args[1].value, normalizeUndefined)})`; + break; + case 'has': + result = `${arg0}?.includes(${this.transform(expr.args[1].value, normalizeUndefined)})`; + break; + case 'hasEvery': + result = `${this.transform( + expr.args[1].value, + normalizeUndefined + )}?.every((item) => ${arg0}?.includes(item))`; + break; + case 'hasSome': + result = `${this.transform( + expr.args[1].value, + normalizeUndefined + )}?.some((item) => ${arg0}?.includes(item))`; + break; + case 'isEmpty': + result = `${arg0}?.length === 0`; + break; + default: + throw new PluginError(`Function invocation is not supported: ${expr.function.ref?.name}`); + } + + return `(${result} ?? false)`; } else { throw new PluginError(`Function invocation is not supported: ${expr.function.ref?.name}`); } @@ -121,8 +179,8 @@ export default class TypeScriptExpressionTransformer { return 'null'; } - private array(expr: ArrayExpr) { - return `[${expr.items.map((item) => this.transform(item)).join(', ')}]`; + private array(expr: ArrayExpr, normalizeUndefined: boolean) { + return `[${expr.items.map((item) => this.transform(item, normalizeUndefined)).join(', ')}]`; } private literal(expr: LiteralExpr) { @@ -133,11 +191,18 @@ export default class TypeScriptExpressionTransformer { } } - private unary(expr: UnaryExpr): string { - return `(${expr.operator} ${this.transform(expr.operand)})`; + private unary(expr: UnaryExpr, normalizeUndefined: boolean): string { + return `(${expr.operator} ${this.transform(expr.operand, normalizeUndefined)})`; } - private binary(expr: BinaryExpr): string { - return `(${this.transform(expr.left)} ${expr.operator} ${this.transform(expr.right)})`; + private binary(expr: BinaryExpr, normalizeUndefined: boolean): string { + if (expr.operator === 'in') { + return `(${this.transform(expr.right, false)}?.includes(${this.transform( + expr.left, + normalizeUndefined + )}) ?? false)`; + } else { + return `(${this.transform(expr.left)} ${expr.operator} ${this.transform(expr.right, normalizeUndefined)})`; + } } } diff --git a/packages/schema/src/plugins/access-policy/zod-schema-generator.ts b/packages/schema/src/plugins/access-policy/zod-schema-generator.ts index 222e17186..a6f94a19b 100644 --- a/packages/schema/src/plugins/access-policy/zod-schema-generator.ts +++ b/packages/schema/src/plugins/access-policy/zod-schema-generator.ts @@ -1,14 +1,14 @@ import { DataModel, DataModelField, DataModelFieldAttribute, isDataModelField } from '@zenstackhq/language/ast'; -import { AUXILIARY_FIELDS, getLiteral } from '@zenstackhq/sdk'; +import { AUXILIARY_FIELDS, VALIDATION_ATTRIBUTES, getLiteral } from '@zenstackhq/sdk'; import { camelCase } from 'change-case'; import { CodeBlockWriter } from 'ts-morph'; -import { VALIDATION_ATTRIBUTES } from '../../utils/ast-utils'; /** * Writes Zod schema for data models. */ export class ZodSchemaGenerator { generate(writer: CodeBlockWriter, models: DataModel[]) { + let generated = false; writer.inlineBlock(() => { models.forEach((model) => { const fields = model.fields.filter( @@ -23,6 +23,7 @@ export class ZodSchemaGenerator { return; } + generated = true; writer.write(`${camelCase(model.name)}: z.object(`); writer.inlineBlock(() => { fields.forEach((field) => { @@ -32,6 +33,7 @@ export class ZodSchemaGenerator { writer.writeLine(').partial(),'); }); }); + return generated; } private hasValidationAttributes(field: DataModelField) { diff --git a/packages/schema/src/plugins/model-meta/index.ts b/packages/schema/src/plugins/model-meta/index.ts index 7ff2f918a..61a539e78 100644 --- a/packages/schema/src/plugins/model-meta/index.ts +++ b/packages/schema/src/plugins/model-meta/index.ts @@ -7,11 +7,21 @@ import { Model, ReferenceExpr, } from '@zenstackhq/language/ast'; -import { RuntimeAttribute } from '@zenstackhq/runtime'; -import { getAttributeArgs, getDataModels, getLiteral, hasAttribute, PluginOptions, resolved } from '@zenstackhq/sdk'; +import type { RuntimeAttribute } from '@zenstackhq/runtime'; +import { + createProject, + emitProject, + getAttributeArgs, + getDataModels, + getLiteral, + hasAttribute, + PluginOptions, + resolved, + saveProject, +} from '@zenstackhq/sdk'; import { camelCase } from 'change-case'; import path from 'path'; -import { CodeBlockWriter, Project, VariableDeclarationKind } from 'ts-morph'; +import { CodeBlockWriter, VariableDeclarationKind } from 'ts-morph'; import { getIdFields } from '../../language-server/utils'; import { ensureNodeModuleFolder, getDefaultOutputFolder } from '../plugin-utils'; @@ -26,23 +36,29 @@ export default async function run(model: Model, options: PluginOptions) { const dataModels = getDataModels(model); - const project = new Project(); + const project = createProject(); if (!options.output) { ensureNodeModuleFolder(output); } const sf = project.createSourceFile(path.join(output, 'model-meta.ts'), undefined, { overwrite: true }); + sf.addStatements('/* eslint-disable */'); sf.addVariableStatement({ declarationKind: VariableDeclarationKind.Const, declarations: [{ name: 'metadata', initializer: (writer) => generateModelMetadata(dataModels, writer) }], }); sf.addStatements('export default metadata;'); - sf.formatText(); - - await project.save(); - await project.emit(); + // emit if generated into standard location or compilation is forced + const shouldCompile = !options.output || options.compile === true; + if (!shouldCompile || options.preserveTsFiles === true) { + // save ts files + await saveProject(project); + } + if (shouldCompile) { + await emitProject(project); + } } function generateModelMetadata(dataModels: DataModel[], writer: CodeBlockWriter) { diff --git a/packages/schema/src/plugins/plugin-utils.ts b/packages/schema/src/plugins/plugin-utils.ts index c70364438..d10be7c94 100644 --- a/packages/schema/src/plugins/plugin-utils.ts +++ b/packages/schema/src/plugins/plugin-utils.ts @@ -1,8 +1,7 @@ -import { PolicyOperationKind } from '@zenstackhq/runtime'; +import type { PolicyOperationKind } from '@zenstackhq/runtime'; import fs from 'fs'; import path from 'path'; -export const RUNTIME_PACKAGE = '@zenstackhq/runtime'; export const ALL_OPERATION_KINDS: PolicyOperationKind[] = ['create', 'update', 'postUpdate', 'read', 'delete']; /** diff --git a/packages/schema/src/plugins/prisma/index.ts b/packages/schema/src/plugins/prisma/index.ts index c9bb7ac08..6ffc0a6a8 100644 --- a/packages/schema/src/plugins/prisma/index.ts +++ b/packages/schema/src/plugins/prisma/index.ts @@ -1,9 +1,15 @@ +import type { DMMF } from '@prisma/generator-helper'; import { Model } from '@zenstackhq/language/ast'; import { PluginOptions } from '@zenstackhq/sdk'; import PrismaSchemaGenerator from './schema-generator'; export const name = 'Prisma'; -export default async function run(model: Model, options: PluginOptions) { - return new PrismaSchemaGenerator().generate(model, options); +export default async function run( + model: Model, + options: PluginOptions, + _dmmf?: DMMF.Document, + config?: Record +) { + return new PrismaSchemaGenerator().generate(model, options, config); } diff --git a/packages/schema/src/plugins/prisma/schema-generator.ts b/packages/schema/src/plugins/prisma/schema-generator.ts index 4066270e3..5ddc9bd27 100644 --- a/packages/schema/src/plugins/prisma/schema-generator.ts +++ b/packages/schema/src/plugins/prisma/schema-generator.ts @@ -6,6 +6,7 @@ import { DataModelAttribute, DataModelField, DataModelFieldAttribute, + DataModelFieldType, DataSource, Enum, EnumField, @@ -20,6 +21,7 @@ import { Model, } from '@zenstackhq/language/ast'; import { + analyzePolicies, getLiteral, getLiteralArray, GUARD_FIELD_NAME, @@ -31,13 +33,14 @@ import { import fs from 'fs'; import { writeFile } from 'fs/promises'; import path from 'path'; -import { analyzePolicies } from '../../utils/ast-utils'; +import { getStringLiteral } from '../../language-server/validator/utils'; import { execSync } from '../../utils/exec-utils'; import { + ModelFieldType, AttributeArg as PrismaAttributeArg, AttributeArgValue as PrismaAttributeArgValue, - ContainerAttribute as PrismaModelAttribute, ContainerDeclaration as PrismaContainerDeclaration, + Model as PrismaDataModel, DataSourceUrl as PrismaDataSourceUrl, Enum as PrismaEnum, FieldAttribute as PrismaFieldAttribute, @@ -45,10 +48,9 @@ import { FieldReferenceArg as PrismaFieldReferenceArg, FunctionCall as PrismaFunctionCall, FunctionCallArg as PrismaFunctionCallArg, - Model as PrismaDataModel, - ModelFieldType, - PassThroughAttribute as PrismaPassThroughAttribute, PrismaModel, + ContainerAttribute as PrismaModelAttribute, + PassThroughAttribute as PrismaPassThroughAttribute, SimpleField, } from './prisma-builder'; import ZModelCodeGenerator from './zmodel-code-generator'; @@ -69,7 +71,7 @@ export default class PrismaSchemaGenerator { `; - async generate(model: Model, options: PluginOptions) { + async generate(model: Model, options: PluginOptions, config?: Record) { const prisma = new PrismaModel(); for (const decl of model.declarations) { @@ -83,7 +85,7 @@ export default class PrismaSchemaGenerator { break; case DataModel: - this.generateModel(prisma, decl as DataModel); + this.generateModel(prisma, decl as DataModel, config); break; case GeneratorDecl: @@ -191,26 +193,33 @@ export default class PrismaSchemaGenerator { ); } - private generateModel(prisma: PrismaModel, decl: DataModel) { + private generateModel(prisma: PrismaModel, decl: DataModel, config?: Record) { const model = prisma.addModel(decl.name); for (const field of decl.fields) { this.generateModelField(model, field); } // add an "zenstack_guard" field for dealing with boolean conditions - model.addField(GUARD_FIELD_NAME, 'Boolean', [ + const guardField = model.addField(GUARD_FIELD_NAME, 'Boolean', [ new PrismaFieldAttribute('@default', [ new PrismaAttributeArg(undefined, new PrismaAttributeArgValue('Boolean', true)), ]), ]); + if (config?.guardFieldName && config?.guardFieldName !== GUARD_FIELD_NAME) { + // generate a @map to rename field in the database + guardField.addAttribute('@map', [ + new PrismaAttributeArg(undefined, new PrismaAttributeArgValue('String', config.guardFieldName)), + ]); + } + const { allowAll, denyAll, hasFieldValidation } = analyzePolicies(decl); if ((!allowAll && !denyAll) || hasFieldValidation) { // generate auxiliary fields for policy check // add an "zenstack_transaction" field for tracking records created/updated with nested writes - model.addField(TRANSACTION_FIELD_NAME, 'String?'); + const transactionField = model.addField(TRANSACTION_FIELD_NAME, 'String?'); // create an index for "zenstack_transaction" field model.addAttribute('@@index', [ @@ -221,6 +230,16 @@ export default class PrismaSchemaGenerator { ]) ), ]); + + if (config?.transactionFieldName && config?.transactionFieldName !== TRANSACTION_FIELD_NAME) { + // generate a @map to rename field in the database + transactionField.addAttribute('@map', [ + new PrismaAttributeArg( + undefined, + new PrismaAttributeArgValue('String', config.transactionFieldName) + ), + ]); + } } for (const attr of decl.attributes.filter((attr) => this.isPrismaAttribute(attr))) { @@ -248,8 +267,22 @@ export default class PrismaSchemaGenerator { ); } + private getUnsupportedFieldType(fieldType: DataModelFieldType) { + if (fieldType.unsupported) { + const value = getStringLiteral(fieldType.unsupported.value); + if (value) { + return `Unsupported("${value}")`; + } else { + return undefined; + } + } else { + return undefined; + } + } + private generateModelField(model: PrismaDataModel, field: DataModelField) { - const fieldType = field.type.type || field.type.reference?.ref?.name; + const fieldType = + field.type.type || field.type.reference?.ref?.name || this.getUnsupportedFieldType(field.type); if (!fieldType) { throw new PluginError(`Field type is not resolved: ${field.$container.name}.${field.name}`); } diff --git a/packages/schema/src/plugins/zod/generator.ts b/packages/schema/src/plugins/zod/generator.ts index 50d135101..310f78918 100644 --- a/packages/schema/src/plugins/zod/generator.ts +++ b/packages/schema/src/plugins/zod/generator.ts @@ -1,10 +1,10 @@ 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 { PluginOptions, createProject, emitProject, getLiteral, saveProject } from '@zenstackhq/sdk'; +import { DataSource, Model, isDataSource } from '@zenstackhq/sdk/ast'; import { - addMissingInputObjectTypes, AggregateOperationSupport, + addMissingInputObjectTypes, resolveAggregateOperationSupport, } from '@zenstackhq/sdk/dmmf-helpers'; import { promises as fs } from 'fs'; @@ -33,7 +33,7 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. const outputObjectTypes = prismaClientDmmf.schema.outputObjectTypes.prisma; const models: DMMF.Model[] = prismaClientDmmf.datamodel.models; - const project = new Project(); + const project = createProject(); await generateEnumSchemas( prismaClientDmmf.schema.enumTypes.prisma, @@ -56,10 +56,18 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. const aggregateOperationSupport = resolveAggregateOperationSupport(inputObjectTypes); - await generateObjectSchemas(inputObjectTypes, project); + await generateObjectSchemas(inputObjectTypes, project, output); await generateModelSchemas(models, modelOperations, aggregateOperationSupport, project); - await emitProject(project); + // emit if generated into standard location or compilation is forced + const shouldCompile = !options.output || options.compile === true; + if (!shouldCompile || options.preserveTsFiles === true) { + // save ts files + await saveProject(project); + } + if (shouldCompile) { + await emitProject(project); + } } async function handleGeneratorOutputValue(output: string) { @@ -86,13 +94,20 @@ async function generateEnumSchemas( await transformer.generateEnumSchemas(); } -async function generateObjectSchemas(inputObjectTypes: DMMF.InputType[], project: Project) { +async function generateObjectSchemas(inputObjectTypes: DMMF.InputType[], project: Project, output: string) { + const moduleNames: string[] = []; 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(); + const moduleName = transformer.generateObjectSchema(); + moduleNames.push(moduleName); } + project.createSourceFile( + path.join(output, 'objects/index.ts'), + moduleNames.map((name) => `export * from './${name}';`).join('\n'), + { overwrite: true } + ); } async function generateModelSchemas( diff --git a/packages/schema/src/plugins/zod/transformer.ts b/packages/schema/src/plugins/zod/transformer.ts index 8b73fe825..087c7dc75 100644 --- a/packages/schema/src/plugins/zod/transformer.ts +++ b/packages/schema/src/plugins/zod/transformer.ts @@ -60,6 +60,11 @@ export default class Transformer { )}`; this.project.createSourceFile(filePath, content, { overwrite: true }); } + this.project.createSourceFile( + path.join(Transformer.outputPath, `enums/index.ts`), + this.enumTypes.map((enumType) => `export * from './${enumType.name}.schema';`).join('\n'), + { overwrite: true } + ); } generateImportZodStatement() { @@ -70,7 +75,7 @@ export default class Transformer { return `export const ${name}Schema = ${schema}`; } - async generateObjectSchema() { + generateObjectSchema() { const zodObjectSchemaFields = this.generateObjectSchemaFields(); const objectSchema = this.prepareObjectSchema(zodObjectSchemaFields); const objectSchemaName = this.resolveObjectSchemaName(); @@ -78,6 +83,7 @@ export default class Transformer { const filePath = path.join(Transformer.outputPath, `objects/${objectSchemaName}.schema.ts`); const content = '/* eslint-disable */\n' + objectSchema; this.project.createSourceFile(filePath, content, { overwrite: true }); + return `${objectSchemaName}.schema`; } generateObjectSchemaFields() { @@ -114,10 +120,11 @@ export default class Transformer { } 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)); + result.push(this.wrapWithZodValidators(['z.date()', 'z.string().datetime()'], field, inputType)); + } else if (inputType.type === 'Bytes') { + result.push(this.wrapWithZodValidators('z.number().array()', 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)); @@ -159,19 +166,29 @@ export default class Transformer { } wrapWithZodValidators( - mainValidator: string, + mainValidators: string | string[], field: PrismaDMMF.SchemaArg, inputType: PrismaDMMF.SchemaArgInputType ) { let line = ''; - line = mainValidator; - if (inputType.isList) { - line += '.array()'; - } + const base = Array.isArray(mainValidators) ? mainValidators : [mainValidators]; + + line += base + .map((validator) => { + let r = validator; + if (inputType.isList) { + r += '.array()'; + } + if (!field.isRequired) { + r += '.optional()'; + } + return r; + }) + .join(', '); - if (!field.isRequired) { - line += '.optional()'; + if (base.length > 1) { + line = `z.union([${line}])`; } return line; @@ -361,8 +378,7 @@ export default class Transformer { } async generateModelSchemas() { - const globalImports: string[] = []; - let globalExport = ''; + const globalExports: string[] = []; for (const modelOperation of this.modelOperations) { const { @@ -386,8 +402,7 @@ export default class Transformer { // eslint-disable-next-line @typescript-eslint/no-explicit-any } = modelOperation; - globalImports.push(`import { ${modelName}Schema } from './${modelName}.schema'`); - globalExport += `${modelName}: ${modelName}Schema,`; + globalExports.push(`export { ${modelName}Schema as ${modelName} } from './${modelName}.schema'`); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const model = findModelByName(this.models, modelName)!; @@ -554,13 +569,7 @@ export default class Transformer { const indexFilePath = path.join(Transformer.outputPath, 'index.ts'); const indexContent = ` /* eslint-disable */ -${globalImports.join(';\n')} - -const schemas = { -${indentString(globalExport, 4)} -}; - -export default schemas; +${globalExports.join(';\n')} `; this.project.createSourceFile(indexFilePath, indexContent, { overwrite: true }); } diff --git a/packages/schema/src/res/starter.zmodel b/packages/schema/src/res/starter.zmodel index 75207fc1e..08c952610 100644 --- a/packages/schema/src/res/starter.zmodel +++ b/packages/schema/src/res/starter.zmodel @@ -7,7 +7,7 @@ */ datasource db { provider = 'sqlite' - url = 'file:./todo.db' + url = 'file:./dev.db' } generator client { diff --git a/packages/schema/src/res/stdlib.zmodel b/packages/schema/src/res/stdlib.zmodel index f76ef7251..8787f6e57 100644 --- a/packages/schema/src/res/stdlib.zmodel +++ b/packages/schema/src/res/stdlib.zmodel @@ -99,9 +99,10 @@ function future(): Any { } /* - * If the field value contains the search string + * If the field value contains the search string. By default, the search is case-sensitive, + * but you can override the behavior with the "caseInSensitive" argument. */ -function contains(field: String, search: String, caseSensitive: Boolean?): Boolean { +function contains(field: String, search: String, caseInSensitive: Boolean?): Boolean { } /* diff --git a/packages/schema/src/telemetry.ts b/packages/schema/src/telemetry.ts index 9d4b9f66a..e066d5831 100644 --- a/packages/schema/src/telemetry.ts +++ b/packages/schema/src/telemetry.ts @@ -1,6 +1,6 @@ +import { createId } from '@paralleldrive/cuid2'; import exitHook from 'async-exit-hook'; import { CommanderError } from 'commander'; -import cuid from 'cuid'; import { init, Mixpanel } from 'mixpanel'; import { machineIdSync } from 'node-machine-id'; import * as os from 'os'; @@ -16,6 +16,7 @@ export type TelemetryEvents = | 'cli:start' | 'cli:complete' | 'cli:error' + | 'cli:crash' | 'cli:command:start' | 'cli:command:complete' | 'cli:command:error' @@ -29,7 +30,7 @@ export type TelemetryEvents = export class Telemetry { private readonly mixpanel: Mixpanel | undefined; private readonly hostId = machineIdSync(); - private readonly sessionid = cuid(); + private readonly sessionid = createId(); private readonly _os = os.platform(); private readonly version = getVersion(); private exitWait = 200; @@ -50,19 +51,26 @@ export class Telemetry { }); const errorHandler = async (err: Error) => { - this.track('cli:error', { - message: err.message, - stack: err.stack, - }); - if (this.mixpanel) { - // a small delay to ensure telemetry is sent - await sleep(this.exitWait); - } - if (err instanceof CliError || err instanceof CommanderError) { + this.track('cli:error', { + message: err.message, + stack: err.stack, + }); + if (this.mixpanel) { + // a small delay to ensure telemetry is sent + await sleep(this.exitWait); + } // error already logged } else { console.error('\nAn unexpected error occurred:\n', err); + this.track('cli:crash', { + message: err.message, + stack: err.stack, + }); + if (this.mixpanel) { + // a small delay to ensure telemetry is sent + await sleep(this.exitWait); + } } process.exit(1); diff --git a/packages/schema/src/utils/ast-utils.ts b/packages/schema/src/utils/ast-utils.ts index 37a562ab8..15409eb76 100644 --- a/packages/schema/src/utils/ast-utils.ts +++ b/packages/schema/src/utils/ast-utils.ts @@ -9,14 +9,18 @@ import { isEnumField, isInvocationExpr, isMemberAccessExpr, + isModel, isReferenceExpr, Model, + ModelImport, ReferenceExpr, } from '@zenstackhq/language/ast'; import { PolicyOperationKind } from '@zenstackhq/runtime'; import { getLiteral } from '@zenstackhq/sdk'; import { AstNode, Mutable } from 'langium'; import { isFromStdlib } from '../language-server/utils'; +import { getDocument, LangiumDocuments } from 'langium'; +import { URI, Utils } from 'vscode-uri'; export function extractDataModelsWithAllowRules(model: Model): DataModel[] { return model.declarations.filter( @@ -168,3 +172,63 @@ export function getDataModelFieldReference(expr: Expression): DataModelField | u return undefined; } } + +export function resolveImportUri(imp: ModelImport): URI | undefined { + if (imp.path === undefined || imp.path.length === 0) { + return undefined; + } + const dirUri = Utils.dirname(getDocument(imp).uri); + let grammarPath = imp.path; + if (!grammarPath.endsWith('.zmodel')) { + grammarPath += '.zmodel'; + } + return Utils.resolvePath(dirUri, grammarPath); +} + +export function resolveTransitiveImports(documents: LangiumDocuments, model: Model): Model[] { + return resolveTransitiveImportsInternal(documents, model); +} + +function resolveTransitiveImportsInternal( + documents: LangiumDocuments, + model: Model, + initialModel = model, + visited: Set = new Set(), + models: Set = new Set() +): Model[] { + const doc = getDocument(model); + if (initialModel !== model) { + models.add(model); + } + if (!visited.has(doc.uri)) { + visited.add(doc.uri); + for (const imp of model.imports) { + const importedGrammar = resolveImport(documents, imp); + if (importedGrammar) { + resolveTransitiveImportsInternal(documents, importedGrammar, initialModel, visited, models); + } + } + } + return Array.from(models); +} + +export function resolveImport(documents: LangiumDocuments, imp: ModelImport): Model | undefined { + const resolvedUri = resolveImportUri(imp); + try { + if (resolvedUri) { + const resolvedDocument = documents.getOrCreateDocument(resolvedUri); + const node = resolvedDocument.parseResult.value; + if (isModel(node)) { + return node; + } + } + } catch { + // NOOP + } + return undefined; +} + +export function getAllDeclarationsFromImports(documents: LangiumDocuments, model: Model) { + const imports = resolveTransitiveImports(documents, model); + return model.declarations.concat(...imports.map((imp) => imp.declarations)); +} diff --git a/packages/schema/src/utils/pkg-utils.ts b/packages/schema/src/utils/pkg-utils.ts index 84b8d4553..4fe73d145 100644 --- a/packages/schema/src/utils/pkg-utils.ts +++ b/packages/schema/src/utils/pkg-utils.ts @@ -39,21 +39,34 @@ export function installPackage( dev: boolean, pkgManager: PackageManagers | undefined = undefined, tag = 'latest', - projectPath = '.' + projectPath = '.', + exactVersion = true ) { const manager = pkgManager ?? getPackageManager(projectPath); - console.log(`Installing package "${pkg}" with ${manager}`); + console.log(`Installing package "${pkg}@${tag}" with ${manager}`); switch (manager) { case 'yarn': - execSync(`yarn --cwd "${projectPath}" add ${pkg}@${tag} ${dev ? ' --dev' : ''} --ignore-engines`); + execSync( + `yarn --cwd "${projectPath}" add ${exactVersion ? '--exact' : ''} ${pkg}@${tag} ${ + dev ? ' --dev' : '' + } --ignore-engines` + ); break; case 'pnpm': - execSync(`pnpm add -C "${projectPath}" ${dev ? ' --save-dev' : ''} ${pkg}@${tag}`); + execSync( + `pnpm add -C "${projectPath}" ${exactVersion ? '--save-exact' : ''} ${ + dev ? ' --save-dev' : '' + } ${pkg}@${tag}` + ); break; default: - execSync(`npm install --prefix "${projectPath}" ${dev ? ' --save-dev' : ''} ${pkg}@${tag}`); + execSync( + `npm install --prefix "${projectPath}" ${exactVersion ? '--save-exact' : ''} ${ + dev ? ' --save-dev' : '' + } ${pkg}@${tag}` + ); break; } } @@ -63,11 +76,13 @@ export function ensurePackage( dev: boolean, pkgManager: PackageManagers | undefined = undefined, tag = 'latest', - projectPath = '.' + projectPath = '.', + exactVersion = false ) { + const resolvePath = path.resolve(projectPath); try { - require(pkg); - } catch { - installPackage(pkg, dev, pkgManager, tag, projectPath); + require.resolve(pkg, { paths: [resolvePath] }); + } catch (err) { + installPackage(pkg, dev, pkgManager, tag, resolvePath, exactVersion); } } diff --git a/packages/schema/src/utils/version-utils.ts b/packages/schema/src/utils/version-utils.ts index a0b783c3b..5ebc41bee 100644 --- a/packages/schema/src/utils/version-utils.ts +++ b/packages/schema/src/utils/version-utils.ts @@ -3,6 +3,7 @@ export function getVersion() { try { return require('../package.json').version; } catch { + // dev environment return require('../../package.json').version; } } diff --git a/packages/schema/tests/cli/cli.test.ts b/packages/schema/tests/cli/cli.test.ts deleted file mode 100644 index f0cb52fd2..000000000 --- a/packages/schema/tests/cli/cli.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { getWorkspaceNpmCacheFolder } from '@zenstackhq/testtools'; -import * as fs from 'fs'; -import * as tmp from 'tmp'; -import { createProgram } from '../../src/cli'; -import { execSync } from '../../src/utils/exec-utils'; - -describe('CLI Tests', () => { - let projDir: string; - let origDir: string; - - beforeEach(() => { - origDir = process.cwd(); - const r = tmp.dirSync(); - projDir = r.name; - console.log(`Project dir: ${projDir}`); - process.chdir(projDir); - }); - - afterEach(() => { - fs.rmSync(projDir, { recursive: true, force: true }); - process.chdir(origDir); - }); - - function createNpmrc() { - 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: getWorkspaceNpmCacheFolder(__dirname), - }); - createNpmrc(); - - const program = createProgram(); - program.parse(['init', '--tag', 'latest'], { from: 'user' }); - - expect(fs.readFileSync('schema.zmodel', 'utf-8')).toEqual(fs.readFileSync('prisma/schema.prisma', 'utf-8')); - }); - - 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: getWorkspaceNpmCacheFolder(__dirname), - }); - createNpmrc(); - fs.renameSync('prisma/schema.prisma', 'prisma/my.prisma'); - - const program = createProgram(); - program.parse(['init', '--tag', 'latest', '--prisma', 'prisma/my.prisma'], { from: 'user' }); - - expect(fs.readFileSync('schema.zmodel', 'utf-8')).toEqual(fs.readFileSync('prisma/my.prisma', 'utf-8')); - }); - - it('init project empty project', async () => { - fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); - createNpmrc(); - const program = createProgram(); - program.parse(['init', '--tag', 'latest'], { from: 'user' }); - expect(fs.readFileSync('schema.zmodel', 'utf-8')).toBeTruthy(); - }); - - it('init project existing zmodel', async () => { - fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); - const origZModelContent = ` - datasource db { - provider = 'sqlite' - url = 'file:./todo.db' - } - `; - fs.writeFileSync('schema.zmodel', origZModelContent); - createNpmrc(); - const program = createProgram(); - program.parse(['init', '--tag', 'latest'], { from: 'user' }); - expect(fs.readFileSync('schema.zmodel', 'utf-8')).toEqual(origZModelContent); - }); -}); diff --git a/packages/schema/tests/cli/command.test.ts b/packages/schema/tests/cli/command.test.ts new file mode 100644 index 000000000..9ef4de1d0 --- /dev/null +++ b/packages/schema/tests/cli/command.test.ts @@ -0,0 +1,129 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/// + +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'; + +describe('CLI Command Tests', () => { + let origDir: string; + + beforeEach(() => { + origDir = process.cwd(); + const r = tmp.dirSync({ unsafeCleanup: true }); + console.log(`Project dir: ${r.name}`); + process.chdir(r.name); + }); + + afterEach(() => { + process.chdir(origDir); + }); + + function createNpmrc() { + fs.writeFileSync('.npmrc', `cache=${getWorkspaceNpmCacheFolder(__dirname)}`); + } + + it('init project t3 npm std', async () => { + execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', 'inherit', { + npm_config_user_agent: 'npm', + npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + }); + createNpmrc(); + + const program = createProgram(); + await program.parseAsync(['init', '--tag', 'latest'], { from: 'user' }); + + expect(fs.readFileSync('schema.zmodel', 'utf-8')).toEqual(fs.readFileSync('prisma/schema.prisma', 'utf-8')); + + checkDependency('zenstack', true, true); + checkDependency('@zenstackhq/runtime', false, true); + }); + + it('init project t3 yarn std', async () => { + execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', 'inherit', { + npm_config_user_agent: 'yarn', + npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + }); + createNpmrc(); + + const program = createProgram(); + await program.parseAsync(['init', '--tag', 'latest'], { from: 'user' }); + + expect(fs.readFileSync('schema.zmodel', 'utf-8')).toEqual(fs.readFileSync('prisma/schema.prisma', 'utf-8')); + + checkDependency('zenstack', true, true); + checkDependency('@zenstackhq/runtime', false, true); + }); + + it('init project t3 pnpm std', async () => { + execSync('npx --yes create-t3-app@latest --prisma --CI --noGit .', 'inherit', { + npm_config_user_agent: 'pnpm', + npm_config_cache: getWorkspaceNpmCacheFolder(__dirname), + }); + createNpmrc(); + + const program = createProgram(); + await program.parseAsync(['init', '--tag', 'latest'], { from: 'user' }); + + expect(fs.readFileSync('schema.zmodel', 'utf-8')).toEqual(fs.readFileSync('prisma/schema.prisma', 'utf-8')); + + checkDependency('zenstack', true, true); + checkDependency('@zenstackhq/runtime', false, true); + }); + + 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: getWorkspaceNpmCacheFolder(__dirname), + }); + createNpmrc(); + fs.renameSync('prisma/schema.prisma', 'prisma/my.prisma'); + + const program = createProgram(); + await program.parseAsync(['init', '--tag', 'latest', '--prisma', 'prisma/my.prisma'], { from: 'user' }); + + expect(fs.readFileSync('schema.zmodel', 'utf-8')).toEqual(fs.readFileSync('prisma/my.prisma', 'utf-8')); + }); + + it('init project empty project', async () => { + fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); + createNpmrc(); + const program = createProgram(); + await program.parseAsync(['init', '--tag', 'latest'], { from: 'user' }); + expect(fs.readFileSync('schema.zmodel', 'utf-8')).toBeTruthy(); + }); + + it('init project existing zmodel', async () => { + fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); + const origZModelContent = ` + datasource db { + provider = 'sqlite' + url = 'file:./todo.db' + } + `; + fs.writeFileSync('schema.zmodel', origZModelContent); + createNpmrc(); + const program = createProgram(); + await program.parseAsync(['init', '--tag', 'latest'], { from: 'user' }); + expect(fs.readFileSync('schema.zmodel', 'utf-8')).toEqual(origZModelContent); + }); +}); + +function checkDependency(pkg: string, isDev: boolean, requireExactVersion = true) { + const pkgJson = require(path.resolve('./package.json')); + + if (isDev) { + expect(pkgJson.devDependencies[pkg]).toBeTruthy(); + if (requireExactVersion) { + expect(pkgJson.devDependencies[pkg]).not.toMatch(/^[\^~].*/); + } + } else { + expect(pkgJson.dependencies[pkg]).toBeTruthy(); + if (requireExactVersion) { + expect(pkgJson.dependencies[pkg]).not.toMatch(/^[\^~].*/); + } + } +} diff --git a/packages/schema/tests/cli/config.test.ts b/packages/schema/tests/cli/config.test.ts new file mode 100644 index 000000000..3a8b22b75 --- /dev/null +++ b/packages/schema/tests/cli/config.test.ts @@ -0,0 +1,101 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/// + +import * as fs from 'fs'; +import * as tmp from 'tmp'; +import { createProgram } from '../../src/cli'; +import { CliError } from '../../src/cli/cli-error'; +import { config } from '../../src/cli/config'; +import { GUARD_FIELD_NAME, TRANSACTION_FIELD_NAME } from '@zenstackhq/sdk'; + +describe('CLI Config Tests', () => { + let origDir: string; + + beforeEach(() => { + origDir = process.cwd(); + const r = tmp.dirSync({ unsafeCleanup: true }); + console.log(`Project dir: ${r.name}`); + process.chdir(r.name); + + fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); + }); + + afterEach(() => { + process.chdir(origDir); + }); + + it('invalid default config', async () => { + fs.writeFileSync('zenstack.config.json', JSON.stringify({ abc: 'def' })); + + const program = createProgram(); + await expect(program.parseAsync(['init', '--tag', 'latest'], { from: 'user' })).rejects.toBeInstanceOf( + CliError + ); + }); + + it('valid default config empty', async () => { + fs.writeFileSync('zenstack.config.json', JSON.stringify({})); + + const program = createProgram(); + await program.parseAsync(['init', '--tag', 'latest'], { from: 'user' }); + + // custom config + expect(config.guardFieldName).toBe(GUARD_FIELD_NAME); + + // default value + expect(config.transactionFieldName).toBe(TRANSACTION_FIELD_NAME); + }); + + it('valid default config non-empty', async () => { + fs.writeFileSync( + 'zenstack.config.json', + JSON.stringify({ guardFieldName: 'myGuardField', transactionFieldName: 'myTransactionField' }) + ); + + const program = createProgram(); + await program.parseAsync(['init', '--tag', 'latest'], { from: 'user' }); + + // custom config + expect(config.guardFieldName).toBe('myGuardField'); + + // default value + expect(config.transactionFieldName).toBe('myTransactionField'); + }); + + it('custom config file does not exist', async () => { + const program = createProgram(); + const configFile = `my.config.json`; + await expect( + program.parseAsync(['init', '--tag', 'latest', '--config', configFile], { from: 'user' }) + ).rejects.toThrow(/Config file could not be found/i); + }); + + it('custom config file is not json', async () => { + const program = createProgram(); + const configFile = `my.config.json`; + fs.writeFileSync(configFile, ` 😬 😬 😬`); + await expect( + program.parseAsync(['init', '--tag', 'latest', '--config', configFile], { from: 'user' }) + ).rejects.toThrow(/Config is not a valid JSON file/i); + }); + + it('valid custom config file', async () => { + fs.writeFileSync('my.config.json', JSON.stringify({ guardFieldName: 'myGuardField' })); + const program = createProgram(); + await program.parseAsync(['init', '--tag', 'latest', '--config', 'my.config.json'], { from: 'user' }); + + // custom config + expect(config.guardFieldName).toBe('myGuardField'); + + // default value + expect(config.transactionFieldName).toBe(TRANSACTION_FIELD_NAME); + }); + + it('invalid custom config file', async () => { + fs.writeFileSync('my.config.json', JSON.stringify({ abc: 'def' })); + const program = createProgram(); + await expect( + program.parseAsync(['init', '--tag', 'latest', '--config', 'my.config.json'], { from: 'user' }) + ).rejects.toThrow(/Config file my.config.json is not valid/i); + }); +}); diff --git a/packages/schema/tests/cli/plugins.test.ts b/packages/schema/tests/cli/plugins.test.ts new file mode 100644 index 000000000..4d1520f2e --- /dev/null +++ b/packages/schema/tests/cli/plugins.test.ts @@ -0,0 +1,84 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/// + +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'; + +describe('CLI Plugins Tests', () => { + let origDir: string; + + beforeEach(() => { + origDir = process.cwd(); + const r = tmp.dirSync({ unsafeCleanup: true }); + console.log(`Project dir: ${r.name}`); + process.chdir(r.name); + }); + + afterEach(() => { + process.chdir(origDir); + }); + + function createNpmrc() { + fs.writeFileSync('.npmrc', `cache=${getWorkspaceNpmCacheFolder(__dirname)}`); + } + + const plugins = [ + `plugin prisma { + provider = '@core/prisma' + output = 'prisma/my.prisma' + generateClient = true + }`, + `plugin meta { + provider = '@core/model-meta' + output = 'model-meta' + } + `, + `plugin policy { + provider = '@core/access-policy' + output = 'policy' + }`, + `plugin zod { + provider = '@core/zod' + output = 'zod' + }`, + `plugin react { + provider = '${path.join(__dirname, '../../../plugins/react/dist')}' + output = 'lib/default-hooks' + }`, + `plugin trpc { + provider = '${path.join(__dirname, '../../../plugins/trpc/dist')}' + output = 'lib/trpc' + }`, + `plugin openapi { + provider = '${path.join(__dirname, '../../../plugins/openapi/dist')}' + output = 'myapi.yaml' + specVersion = '3.0.0' + title = 'My Awesome API' + version = '1.0.0' + description = 'awesome api' + prefix = '/myapi' + securitySchemes = { + myBasic: { type: 'http', scheme: 'basic' }, + myBearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, + myApiKey: { type: 'apiKey', in: 'header', name: 'X-API-KEY' } + } + }`, + ]; + + it('all plugins', async () => { + fs.writeFileSync('package.json', JSON.stringify({ name: 'my app', version: '1.0.0' })); + createNpmrc(); + const program = createProgram(); + await program.parseAsync(['init', '--tag', 'latest'], { from: 'user' }); + + let schemaContent = fs.readFileSync('schema.zmodel', 'utf-8'); + for (const plugin of plugins) { + schemaContent += `\n${plugin}`; + } + fs.writeFileSync('schema.zmodel', schemaContent); + await program.parseAsync(['generate', '--no-dependency-check'], { from: 'user' }); + }); +}); diff --git a/packages/schema/tests/generator/expression-writer.test.ts b/packages/schema/tests/generator/expression-writer.test.ts index 157d4916e..178abe78e 100644 --- a/packages/schema/tests/generator/expression-writer.test.ts +++ b/packages/schema/tests/generator/expression-writer.test.ts @@ -943,7 +943,7 @@ describe('Expression Writer Tests', () => { ); }); - it('filter operators', async () => { + it('filter operators field access', async () => { await check( ` enum Role { @@ -1134,6 +1134,153 @@ describe('Expression Writer Tests', () => { }); }); +it('filter operators non-field access', async () => { + const userInit = `{ id: 'user1', email: 'test@zenstack.dev', roles: [Role.ADMIN] }`; + const prelude = ` + enum Role { + USER + ADMIN + } + + model User { + id String @id + email String + roles Role[] + } + `; + + await check( + ` + ${prelude} + model Test { + id String @id + @@allow('all', ADMIN in auth().roles) + } + `, + (model) => model.attributes[0].args[1].value, + `{zenstack_guard:(user?.roles?.includes(Role.ADMIN)??false)}`, + userInit + ); + + await check( + ` + ${prelude} + model Test { + id String @id + roles Role[] + @@allow('all', ADMIN in roles) + } + `, + (model) => model.attributes[0].args[1].value, + `{roles:{has:Role.ADMIN}}`, + userInit + ); + + await check( + ` + ${prelude} + model Test { + id String @id + @@allow('all', contains(auth().email, 'test')) + } + `, + (model) => model.attributes[0].args[1].value, + `{zenstack_guard:(user?.email?.includes('test')??false)}`, + userInit + ); + + await check( + ` + ${prelude} + model Test { + id String @id + @@allow('all', contains(auth().email, 'test', true)) + } + `, + (model) => model.attributes[0].args[1].value, + `{zenstack_guard:(user?.email?.toLowerCase().includes('test'?.toLowerCase())??false)}`, + userInit + ); + + await check( + ` + ${prelude} + model Test { + id String @id + @@allow('all', startsWith(auth().email, 'test')) + } + `, + (model) => model.attributes[0].args[1].value, + `{zenstack_guard:(user?.email?.startsWith('test')??false)}`, + userInit + ); + + await check( + ` + ${prelude} + model Test { + id String @id + @@allow('all', endsWith(auth().email, 'test')) + } + `, + (model) => model.attributes[0].args[1].value, + `{zenstack_guard:(user?.email?.endsWith('test')??false)}`, + userInit + ); + + await check( + ` + ${prelude} + model Test { + id String @id + @@allow('all', has(auth().roles, ADMIN)) + } + `, + (model) => model.attributes[0].args[1].value, + `{zenstack_guard:(user?.roles?.includes(Role.ADMIN)??false)}`, + userInit + ); + + await check( + ` + ${prelude} + model Test { + id String @id + @@allow('all', hasEvery(auth().roles, [ADMIN, USER])) + } + `, + (model) => model.attributes[0].args[1].value, + `{zenstack_guard:([Role.ADMIN,Role.USER]?.every((item)=>user?.roles?.includes(item))??false)}`, + userInit + ); + + await check( + ` + ${prelude} + model Test { + id String @id + @@allow('all', hasSome(auth().roles, [USER, ADMIN])) + } + `, + (model) => model.attributes[0].args[1].value, + `{zenstack_guard:([Role.USER,Role.ADMIN]?.some((item)=>user?.roles?.includes(item))??false)}`, + userInit + ); + + await check( + ` + ${prelude} + model Test { + id String @id + @@allow('all', isEmpty(auth().roles)) + } + `, + (model) => model.attributes[0].args[1].value, + `{zenstack_guard:(user?.roles?.length===0??false)}`, + userInit + ); +}); + async function check(schema: string, getExpr: (model: DataModel) => Expression, expected: string, userInit?: string) { if (!schema.includes('datasource ')) { schema = @@ -1155,12 +1302,6 @@ async function check(schema: string, getExpr: (model: DataModel) => Expression, overwrite: true, }); - // inject user variable - sf.addVariableStatement({ - declarationKind: VariableDeclarationKind.Const, - declarations: [{ name: 'user', initializer: userInit ?? '{ id: "user1" }' }], - }); - // inject enums model.declarations .filter((d) => isEnum(d)) @@ -1180,6 +1321,12 @@ async function check(schema: string, getExpr: (model: DataModel) => Expression, }); }); + // inject user variable + sf.addVariableStatement({ + declarationKind: VariableDeclarationKind.Const, + declarations: [{ name: 'user', initializer: userInit ?? '{ id: "user1" }' }], + }); + sf.addVariableStatement({ declarationKind: VariableDeclarationKind.Const, declarations: [ @@ -1189,7 +1336,6 @@ async function check(schema: string, getExpr: (model: DataModel) => Expression, }, ], }); - sf.formatText(); await project.save(); console.log('Source saved:', sourcePath); @@ -1198,7 +1344,6 @@ async function check(schema: string, getExpr: (model: DataModel) => Expression, for (const d of project.getPreEmitDiagnostics()) { console.warn(`${d.getLineNumber()}: ${d.getMessageText()}`); } - console.log(`Generated source: ${sourcePath}`); throw new Error('Compilation errors occurred'); } diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index 66a3e8e80..94873f3f9 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -3,10 +3,55 @@ import { getDMMF } from '@prisma/internals'; import fs from 'fs'; import tmp from 'tmp'; +import path from 'path'; +import { loadDocument } from '../../src/cli/cli-util'; import PrismaSchemaGenerator from '../../src/plugins/prisma/schema-generator'; import { loadModel } from '../utils'; describe('Prisma generator test', () => { + it('field type coverage', async () => { + const model = await loadModel(` + datasource db { + provider = 'postgresql' + url = env('DATABASE_URL') + } + + model User { + id String @id + age Int + serial BigInt + height Float + salary Decimal + activated Boolean + createdAt DateTime + metadata Json + content Bytes + unsupported Unsupported('foo') + } + `); + + const { name } = tmp.fileSync({ postfix: '.prisma' }); + await new PrismaSchemaGenerator().generate(model, { + provider: '@core/prisma', + schemaPath: 'schema.zmodel', + output: name, + }); + + const content = fs.readFileSync(name, 'utf-8'); + await getDMMF({ datamodel: content }); + + expect(content).toContain('id String'); + expect(content).toContain('age Int'); + expect(content).toContain('serial BigInt'); + expect(content).toContain('height Float'); + expect(content).toContain('salary Decimal'); + expect(content).toContain('activated Boolean'); + expect(content).toContain('createdAt DateTime'); + expect(content).toContain('metadata Json'); + expect(content).toContain('content Bytes'); + expect(content).toContain('unsupported Unsupported("foo")'); + }); + it('triple slash comments', async () => { const model = await loadModel(` datasource db { @@ -209,7 +254,6 @@ describe('Prisma generator test', () => { published Boolean @default(false) } `); - const { name } = tmp.fileSync({ postfix: '.prisma' }); await new PrismaSchemaGenerator().generate(model, { provider: '@core/prisma', @@ -226,4 +270,53 @@ describe('Prisma generator test', () => { expect(post.name).toBe('Post'); expect(post.fields.length).toBe(6); }); + + it('custom aux field names', async () => { + const model = await loadModel(` + datasource db { + provider = 'postgresql' + url = env('URL') + } + + model Foo { + id String @id + value Int + @@allow('create', value > 0) + } + `); + + const { name } = tmp.fileSync({ postfix: '.prisma' }); + await new PrismaSchemaGenerator().generate( + model, + { + provider: '@core/prisma', + schemaPath: 'schema.zmodel', + output: name, + }, + { guardFieldName: 'myGuardField', transactionFieldName: 'myTransactionField' } + ); + + const content = fs.readFileSync(name, 'utf-8'); + await getDMMF({ datamodel: content }); + expect(content).toContain('@map("myGuardField")'); + expect(content).toContain('@map("myTransactionField")'); + }); + + it('multi files', async () => { + const model = await loadDocument(path.join(__dirname, './zmodel/schema.zmodel')); + + const { name } = tmp.fileSync({ postfix: '.prisma' }); + await new PrismaSchemaGenerator().generate(model, { + provider: '@core/prisma', + schemaPath: 'schema.zmodel', + output: name, + generateClient: false, + }); + + const content = fs.readFileSync(name, 'utf-8'); + const dmmf = await getDMMF({ datamodel: content }); + + expect(dmmf.datamodel.models.length).toBe(2); + expect(dmmf.datamodel.enums[0].name).toBe('UserRole'); + }); }); diff --git a/packages/schema/tests/generator/zmodel/schema.zmodel b/packages/schema/tests/generator/zmodel/schema.zmodel new file mode 100644 index 000000000..be912e3fb --- /dev/null +++ b/packages/schema/tests/generator/zmodel/schema.zmodel @@ -0,0 +1,14 @@ +import "user/user" + +datasource db { + provider = 'postgresql' + url = env('URL') +} + +model Post { + id Int @id() @default(autoincrement()) + author User? @relation(fields: [authorId], references: [id]) + authorId Int? + // author has full access + @@allow('all', auth() == author) +} \ No newline at end of file diff --git a/packages/schema/tests/generator/zmodel/user/user.zmodel b/packages/schema/tests/generator/zmodel/user/user.zmodel new file mode 100644 index 000000000..c83eb6fbe --- /dev/null +++ b/packages/schema/tests/generator/zmodel/user/user.zmodel @@ -0,0 +1,17 @@ +import "../schema" +model User { + id Int @id() @default(autoincrement()) + email String @unique() + name String? + posts Post[] + role UserRole + + // make user profile public + @@allow('read', true) +} + + +enum UserRole { + USER + ADMIN +} \ No newline at end of file diff --git a/packages/schema/tests/schema/cal-com.zmodel b/packages/schema/tests/schema/cal-com.zmodel index c6e874304..15025f0ef 100644 --- a/packages/schema/tests/schema/cal-com.zmodel +++ b/packages/schema/tests/schema/cal-com.zmodel @@ -14,11 +14,15 @@ generator client { plugin meta { provider = '@core/model-meta' output = '.zenstack' + compile = true + preserveTsFiles = true } plugin policy { provider = '@core/access-policy' output = '.zenstack' + compile = true + preserveTsFiles = true } enum SchedulingType { diff --git a/packages/schema/tests/schema/mutil-files/multi-files.test.ts b/packages/schema/tests/schema/mutil-files/multi-files.test.ts new file mode 100644 index 000000000..b9c41ff19 --- /dev/null +++ b/packages/schema/tests/schema/mutil-files/multi-files.test.ts @@ -0,0 +1,12 @@ +import path from 'path'; +import { loadDocument } from '../../../src/cli/cli-util'; + +describe('Mutiple files Tests', () => { + it('model loading post', async () => { + await loadDocument(path.join(__dirname, './schema.zmodel')); + }); + + it('model loading user', async () => { + await loadDocument(path.join(__dirname, './user.zmodel')); + }); +}); diff --git a/packages/schema/tests/schema/mutil-files/schema.zmodel b/packages/schema/tests/schema/mutil-files/schema.zmodel new file mode 100644 index 000000000..1aa1a71db --- /dev/null +++ b/packages/schema/tests/schema/mutil-files/schema.zmodel @@ -0,0 +1,19 @@ +import "user" + +datasource db { + provider="sqlite" + url="file:./dev.db" +} + +generator client { + provider = "prisma-client-js" +} + + +model Post { + id Int @id() @default(autoincrement()) + author User? @relation(fields: [authorId], references: [id]) + authorId Int? + // author has full access + @@allow('all', auth() == author) +} \ No newline at end of file diff --git a/packages/schema/tests/schema/mutil-files/user.zmodel b/packages/schema/tests/schema/mutil-files/user.zmodel new file mode 100644 index 000000000..79fbbe969 --- /dev/null +++ b/packages/schema/tests/schema/mutil-files/user.zmodel @@ -0,0 +1,10 @@ +import "schema" +model User { + id Int @id() @default(autoincrement()) + email String @unique() + name String? + posts Post[] + + // make user profile public + @@allow('read', true) +} \ No newline at end of file diff --git a/packages/schema/tests/schema/parser.test.ts b/packages/schema/tests/schema/parser.test.ts index 322d3b5bf..0509b225d 100644 --- a/packages/schema/tests/schema/parser.test.ts +++ b/packages/schema/tests/schema/parser.test.ts @@ -76,6 +76,7 @@ describe('Parsing Tests', () => { createdAt DateTime metadata Json content Bytes + unsupported Unsupported('foo') } `; const doc = await loadModel(content, false); @@ -93,6 +94,7 @@ describe('Parsing Tests', () => { 'Bytes', ]) ); + expect(model.fields.find((f) => f.name === 'unsupported')?.type.unsupported?.value.value).toBe('foo'); }); it('model field modifiers', async () => { diff --git a/packages/schema/tests/schema/validation/attribute-validation.test.ts b/packages/schema/tests/schema/validation/attribute-validation.test.ts index d541d43a9..faf88eb9f 100644 --- a/packages/schema/tests/schema/validation/attribute-validation.test.ts +++ b/packages/schema/tests/schema/validation/attribute-validation.test.ts @@ -461,17 +461,6 @@ describe('Attribute tests', () => { `) ).toContain('argument is not assignable to parameter'); - expect( - await loadModelWithError(` - ${prelude} - model M { - id String @id - i Int[] - @@allow('all', 1 in i) - } - `) - ).toContain('left operand of "in" must be a field reference'); - expect( await loadModelWithError(` ${prelude} @@ -481,7 +470,7 @@ describe('Attribute tests', () => { @@allow('all', i in 1) } `) - ).toContain('right operand of "in" must be an array of literals or enum values'); + ).toContain('right operand of "in" must be an array'); expect( await loadModelWithError(` diff --git a/packages/schema/tests/schema/validation/datamodel-validation.test.ts b/packages/schema/tests/schema/validation/datamodel-validation.test.ts index 76c38a3f1..f9a3478fc 100644 --- a/packages/schema/tests/schema/validation/datamodel-validation.test.ts +++ b/packages/schema/tests/schema/validation/datamodel-validation.test.ts @@ -40,6 +40,41 @@ describe('Data Model Validation Tests', () => { `); }); + it('Unsupported type valid arg', async () => { + await loadModel(` + ${prelude} + model M { + id String @id + a Unsupported('foo') + } + `); + }); + + it('Unsupported type invalid arg', async () => { + expect( + await loadModelWithError(` + ${prelude} + model M { + id String @id + a Unsupported(123) + } + `) + ).toContain('Unsupported type argument must be a string literal'); + }); + + it('Unsupported type used in expression', async () => { + expect( + await loadModelWithError(` + ${prelude} + model M { + id String @id + a Unsupported('a') + @@allow('all', a == 'a') + } + `) + ).toContain('Field of "Unsupported" type cannot be used in expressions'); + }); + it('mix array and optional', async () => { expect( await loadModelWithError(` diff --git a/packages/schema/tests/schema/validation/schema-validation.test.ts b/packages/schema/tests/schema/validation/schema-validation.test.ts index 52d8bc731..0c063b30d 100644 --- a/packages/schema/tests/schema/validation/schema-validation.test.ts +++ b/packages/schema/tests/schema/validation/schema-validation.test.ts @@ -1,10 +1,6 @@ import { loadModelWithError } from '../../utils'; describe('Toplevel Schema Validation Tests', () => { - it('no datasource', async () => { - expect(await loadModelWithError('')).toContain('Model must define a datasource'); - }); - it('too many datasources', async () => { expect( await loadModelWithError(` diff --git a/packages/sdk/package.json b/packages/sdk/package.json index aa93e7bac..4dee33831 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "1.0.0-alpha.87", + "version": "1.0.0-alpha.102", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/sdk/src/code-gen.ts b/packages/sdk/src/code-gen.ts index f58e6c9fd..02dd348b1 100644 --- a/packages/sdk/src/code-gen.ts +++ b/packages/sdk/src/code-gen.ts @@ -1,5 +1,6 @@ import prettier from 'prettier'; -import { Project, SourceFile } from 'ts-morph'; +import { CompilerOptions, DiagnosticCategory, ModuleKind, Project, ScriptTarget, SourceFile } from 'ts-morph'; +import { PluginError } from './types'; const formatOptions = { trailingComma: 'all', @@ -23,16 +24,55 @@ async function formatFile(sourceFile: SourceFile) { } } +/** + * Creates a TS code generation project + */ +export function createProject(options?: CompilerOptions) { + return new Project({ + compilerOptions: { + target: ScriptTarget.ES2016, + module: ModuleKind.CommonJS, + esModuleInterop: true, + declaration: true, + strict: true, + skipLibCheck: true, + noEmitOnError: true, + ...options, + }, + }); +} + +/** + * Persists a TS project to disk. + */ +export async function saveProject(project: Project) { + await Promise.all( + project.getSourceFiles().map(async (sf) => { + await formatFile(sf); + }) + ); + await project.save(); +} + /** * 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); - }) - ); +export async function emitProject(project: Project) { + const errors = project.getPreEmitDiagnostics().filter((d) => d.getCategory() === DiagnosticCategory.Error); + if (errors.length > 0) { + console.error('Error compiling generated code:'); + console.error(project.formatDiagnosticsWithColorAndContext(errors.slice(0, 10))); + await project.save(); + throw new PluginError(`Error compiling generated code`); + } + + const result = await project.emit(); + + const emitErrors = result.getDiagnostics().filter((d) => d.getCategory() === DiagnosticCategory.Error); + if (emitErrors.length > 0) { + console.error('Some generated code is not emitted:'); + console.error(project.formatDiagnosticsWithColorAndContext(emitErrors.slice(0, 10))); + await project.save(); + throw new PluginError(`Error emitting generated code`); } - await project.emit(); } diff --git a/packages/sdk/src/constants.ts b/packages/sdk/src/constants.ts index 038a7cebe..da7d620e4 100644 --- a/packages/sdk/src/constants.ts +++ b/packages/sdk/src/constants.ts @@ -22,3 +22,8 @@ export enum CrudFailureReason { */ RESULT_NOT_READABLE = 'RESULT_NOT_READABLE', } + +/** + * @zenstackhq/runtime package name + */ +export const RUNTIME_PACKAGE = '@zenstackhq/runtime'; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 130e9ea8d..e82960d44 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -2,3 +2,4 @@ export * from './code-gen'; export * from './constants'; export * from './types'; export * from './utils'; +export * from './policy'; diff --git a/packages/sdk/src/policy.ts b/packages/sdk/src/policy.ts new file mode 100644 index 000000000..ef10fa633 --- /dev/null +++ b/packages/sdk/src/policy.ts @@ -0,0 +1,82 @@ +import type { DataModel, DataModelAttribute } from './ast'; +import { getLiteral } from './utils'; + +export const VALIDATION_ATTRIBUTES = [ + '@length', + '@regex', + '@startsWith', + '@endsWith', + '@email', + '@url', + '@datetime', + '@gt', + '@gte', + '@lt', + '@lte', +]; + +export function analyzePolicies(dataModel: DataModel) { + const allows = dataModel.attributes.filter((attr) => attr.decl.ref?.name === '@@allow'); + const denies = dataModel.attributes.filter((attr) => attr.decl.ref?.name === '@@deny'); + + const create = toStaticPolicy('create', allows, denies); + const read = toStaticPolicy('read', allows, denies); + const update = toStaticPolicy('update', allows, denies); + const del = toStaticPolicy('delete', allows, denies); + const hasFieldValidation = dataModel.fields.some((field) => + field.attributes.some((attr) => VALIDATION_ATTRIBUTES.includes(attr.decl.$refText)) + ); + + return { + allows, + denies, + create, + read, + update, + delete: del, + allowAll: create === true && read === true && update === true && del === true, + denyAll: create === false && read === false && update === false && del === false, + hasFieldValidation, + }; +} + +function toStaticPolicy( + operation: string, + allows: DataModelAttribute[], + denies: DataModelAttribute[] +): boolean | undefined { + const filteredDenies = forOperation(operation, denies); + if (filteredDenies.some((rule) => getLiteral(rule.args[1].value) === true)) { + // any constant true deny rule + return false; + } + + const filteredAllows = forOperation(operation, allows); + if (filteredAllows.length === 0) { + // no allow rule + return false; + } + + if ( + filteredDenies.length === 0 && + filteredAllows.some((rule) => getLiteral(rule.args[1].value) === true) + ) { + // any constant true allow rule + return true; + } + return undefined; +} + +function forOperation(operation: string, rules: DataModelAttribute[]) { + return rules.filter((rule) => { + const ops = getLiteral(rule.args[0].value); + if (!ops) { + return false; + } + if (ops === 'all') { + return true; + } + const splitOps = ops.split(',').map((p) => p.trim()); + return splitOps.includes(operation); + }); +} diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index bf9d2c3c0..bb967d234 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -17,7 +17,8 @@ export type PluginOptions = { provider?: string; schemaPath: string } & Record ) => Promise | string[] | Promise | void; /** diff --git a/packages/sdk/src/utils.ts b/packages/sdk/src/utils.ts index d27a35da2..741e0fc99 100644 --- a/packages/sdk/src/utils.ts +++ b/packages/sdk/src/utils.ts @@ -34,7 +34,7 @@ export function getLiteral( expr: Expression | undefined ): T | undefined { if (!isLiteralExpr(expr)) { - return undefined; + return getObjectLiteral(expr); } return expr.value as T; } diff --git a/packages/server/package.json b/packages/server/package.json index 813a99f41..0ee698f03 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "1.0.0-alpha.87", + "version": "1.0.0-alpha.102", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", @@ -33,7 +33,7 @@ "devDependencies": { "@types/body-parser": "^1.19.2", "@types/express": "^4.17.17", - "@types/jest": "^29.4.0", + "@types/jest": "^29.5.0", "@types/supertest": "^2.0.12", "@zenstackhq/testtools": "workspace:*", "body-parser": "^1.20.2", @@ -41,7 +41,7 @@ "express": "^4.18.2", "fastify": "^4.14.1", "fastify-plugin": "^4.5.0", - "jest": "^29.4.3", + "jest": "^29.5.0", "rimraf": "^3.0.2", "supertest": "^6.3.3", "ts-jest": "^29.0.5", diff --git a/packages/server/src/express/middleware.ts b/packages/server/src/express/middleware.ts index 54d6e86be..214b30b64 100644 --- a/packages/server/src/express/middleware.ts +++ b/packages/server/src/express/middleware.ts @@ -19,7 +19,8 @@ export interface MiddlewareOptions { logger?: LoggerConfig; /** - * Zod schemas for validating request input. Pass `true` to load from standard location (need to enable `@core/zod` plugin in schema.zmodel). + * Zod schemas for validating request input. Pass `true` to load from standard location + * (need to enable `@core/zod` plugin in schema.zmodel) or omit to disable input validation. */ zodSchemas?: ModelZodSchema | boolean; } diff --git a/packages/server/src/fastify/plugin.ts b/packages/server/src/fastify/plugin.ts index 4999b5d8a..c22d64a29 100644 --- a/packages/server/src/fastify/plugin.ts +++ b/packages/server/src/fastify/plugin.ts @@ -25,7 +25,8 @@ export interface PluginOptions { logger?: LoggerConfig; /** - * Zod schemas for validating request input. Pass `true` to load from standard location (need to enable `@core/zod` plugin in schema.zmodel). + * Zod schemas for validating request input. Pass `true` to load from standard location + * (need to enable `@core/zod` plugin in schema.zmodel) or omit to disable input validation. */ zodSchemas?: ModelZodSchema | boolean; } diff --git a/packages/server/src/openapi/index.ts b/packages/server/src/openapi/index.ts index 42f47e9bf..3ecfa1e5d 100644 --- a/packages/server/src/openapi/index.ts +++ b/packages/server/src/openapi/index.ts @@ -6,7 +6,7 @@ import { isPrismaClientValidationError, } from '@zenstackhq/runtime'; import type { ModelZodSchema } from '@zenstackhq/runtime/zod'; -import { capitalCase } from 'change-case'; +import { pascalCase } from 'change-case'; import { fromZodError } from 'zod-validation-error'; import { stripAuxFields } from './utils'; @@ -57,8 +57,8 @@ export type Response = { function getZodSchema(zodSchemas: ModelZodSchema, model: string, operation: keyof DbOperations) { if (zodSchemas[model]) { return zodSchemas[model][operation]; - } else if (zodSchemas[capitalCase(model)]) { - return zodSchemas[capitalCase(model)][operation]; + } else if (zodSchemas[pascalCase(model)]) { + return zodSchemas[pascalCase(model)][operation]; } else { return undefined; } diff --git a/packages/server/tests/open-api.test.ts b/packages/server/tests/open-api.test.ts index d651d68b8..487303477 100644 --- a/packages/server/tests/open-api.test.ts +++ b/packages/server/tests/open-api.test.ts @@ -139,7 +139,9 @@ describe('OpenAPI server tests', () => { prisma, }); expect(r.status).toBe(400); + expect((r.body as any).message).toContain('Argument where is missing'); + // with validation r = await handleRequest({ method: 'get', path: '/post/findUnique', diff --git a/packages/server/tests/utils.ts b/packages/server/tests/utils.ts index 48b00c7a5..34f7e48b6 100644 --- a/packages/server/tests/utils.ts +++ b/packages/server/tests/utils.ts @@ -1,12 +1,16 @@ export const schema = ` model User { id String @id @default(cuid()) + createdAt DateTime @default (now()) + updatedAt DateTime @updatedAt email String @unique posts Post[] } model Post { id String @id @default(cuid()) + createdAt DateTime @default (now()) + updatedAt DateTime @updatedAt title String author User? @relation(fields: [authorId], references: [id]) authorId String? diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 4fb2eadae..9f813c240 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "1.0.0-alpha.87", + "version": "1.0.0-alpha.102", "description": "ZenStack Test Tools", "main": "index.js", "publishConfig": { @@ -21,8 +21,8 @@ "@prisma/client": "^4.7.0", "@prisma/generator-helper": "^4.7.1", "@prisma/internals": "^4.7.1", - "@zenstackhq/runtime": "workspace:*", "@zenstackhq/language": "workspace:*", + "@zenstackhq/runtime": "workspace:*", "prisma": "~4.7.0", "tmp": "^0.2.1", "zenstack": "workspace:*" diff --git a/packages/testtools/src/package.template.json b/packages/testtools/src/package.template.json index e264f0bd0..ca8aa031e 100644 --- a/packages/testtools/src/package.template.json +++ b/packages/testtools/src/package.template.json @@ -15,6 +15,6 @@ "prisma": "^4.0.0", "typescript": "^4.9.3", "zenstack": "file:/packages/schema/dist", - "zod": "^3.19.1" + "zod": "3.21.1" } } diff --git a/packages/testtools/src/schema.ts b/packages/testtools/src/schema.ts index 9aa684d95..40271f069 100644 --- a/packages/testtools/src/schema.ts +++ b/packages/testtools/src/schema.ts @@ -34,13 +34,17 @@ export function run(cmd: string, env?: Record, cwd?: string) { }); } +function normalizePath(p: string) { + return p ? p.split(path.sep).join(path.posix.sep) : p; +} + export function getWorkspaceRoot(start: string) { - let curr = start; + let curr = normalizePath(start); while (curr && curr !== '/') { if (fs.existsSync(path.join(curr, 'pnpm-workspace.yaml'))) { return curr; } else { - curr = path.dirname(curr); + curr = normalizePath(path.dirname(curr)); } } return undefined; @@ -65,16 +69,22 @@ generator js { plugin meta { provider = '@core/model-meta' output = '.zenstack' + compile = true + preserveTsFiles = true } plugin policy { provider = '@core/access-policy' output = '.zenstack' + compile = true + preserveTsFiles = true } plugin zod { provider = '@core/zod' output = '.zenstack/zod' + compile = true + preserveTsFiles = true } `; @@ -90,61 +100,68 @@ export async function loadSchema( extraDependencies: string[] = [], compile = false ) { - const { name: projectRoot } = tmp.dirSync(); + const { name: projectRoot } = tmp.dirSync({ unsafeCleanup: true }); - const root = getWorkspaceRoot(__dirname); - if (!root) { - throw new Error('Could not find workspace root'); - } - console.log('Workspace root:', root); + try { + const root = getWorkspaceRoot(__dirname); - const pkgContent = fs.readFileSync(path.join(__dirname, 'package.template.json'), { encoding: 'utf-8' }); - fs.writeFileSync(path.join(projectRoot, 'package.json'), pkgContent.replaceAll('', root)); + if (!root) { + throw new Error('Could not find workspace root'); + } - const npmrcContent = fs.readFileSync(path.join(__dirname, '.npmrc.template'), { encoding: 'utf-8' }); - fs.writeFileSync(path.join(projectRoot, '.npmrc'), npmrcContent.replaceAll('', root)); + console.log('Workspace root:', root); - console.log('Workdir:', projectRoot); - process.chdir(projectRoot); + const pkgContent = fs.readFileSync(path.join(__dirname, 'package.template.json'), { encoding: 'utf-8' }); + fs.writeFileSync(path.join(projectRoot, 'package.json'), pkgContent.replaceAll('', root)); - schema = schema.replaceAll('$projectRoot', projectRoot); + const npmrcContent = fs.readFileSync(path.join(__dirname, '.npmrc.template'), { encoding: 'utf-8' }); + fs.writeFileSync(path.join(projectRoot, '.npmrc'), npmrcContent.replaceAll('', root)); - const content = addPrelude ? `${MODEL_PRELUDE}\n${schema}` : schema; - fs.writeFileSync('schema.zmodel', content); - run('npm install'); - run('npx zenstack generate --no-dependency-check', { NODE_PATH: './node_modules' }); + console.log('Workdir:', projectRoot); + process.chdir(projectRoot); - if (pushDb) { - run('npx prisma db push'); - } + schema = schema.replaceAll('$projectRoot', projectRoot); - const PrismaClient = require(path.join(projectRoot, 'node_modules/.prisma/client')).PrismaClient; - const prisma = new PrismaClient({ log: ['info', 'warn', 'error'] }); + const content = addPrelude ? `${MODEL_PRELUDE}\n${schema}` : schema; + fs.writeFileSync('schema.zmodel', content); + run('npm install'); + run('npx zenstack generate --no-dependency-check', { NODE_PATH: './node_modules' }); - extraDependencies.forEach((dep) => { - console.log(`Installing dependency ${dep}`); - run(`npm install ${dep}`); - }); + if (pushDb) { + run('npx prisma db push'); + } - if (compile) { - console.log('Compiling...'); - run('npx tsc --init'); - run('npx tsc --project tsconfig.json'); - } + const PrismaClient = require(path.join(projectRoot, 'node_modules/.prisma/client')).PrismaClient; + const prisma = new PrismaClient({ log: ['info', 'warn', 'error'] }); + + extraDependencies.forEach((dep) => { + console.log(`Installing dependency ${dep}`); + run(`npm install ${dep}`); + }); - const policy = require(path.join(projectRoot, '.zenstack/policy')).default; - const modelMeta = require(path.join(projectRoot, '.zenstack/model-meta')).default; - const zodSchemas = require(path.join(projectRoot, '.zenstack/zod')).default; - - return { - projectDir: projectRoot, - prisma, - withPolicy: (user?: AuthUser) => withPolicy(prisma, { user }, policy, modelMeta), - withOmit: () => withOmit(prisma, modelMeta), - withPassword: () => withPassword(prisma, modelMeta), - withPresets: (user?: AuthUser) => withPresets(prisma, { user }, policy, modelMeta), - zodSchemas, - }; + if (compile) { + console.log('Compiling...'); + run('npx tsc --init'); + run('npx tsc --project tsconfig.json'); + } + + const policy = require(path.join(projectRoot, '.zenstack/policy')).default; + const modelMeta = require(path.join(projectRoot, '.zenstack/model-meta')).default; + const zodSchemas = require(path.join(projectRoot, '.zenstack/zod')); + + return { + projectDir: projectRoot, + prisma, + withPolicy: (user?: AuthUser) => withPolicy(prisma, { user }, policy, modelMeta), + withOmit: () => withOmit(prisma, modelMeta), + withPassword: () => withPassword(prisma, modelMeta), + withPresets: (user?: AuthUser) => withPresets(prisma, { user }, policy, modelMeta), + zodSchemas, + }; + } catch (err) { + fs.rmSync(projectRoot, { recursive: true, force: true }); + throw err; + } } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9aef7549d..7bacedc5b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,13 +28,13 @@ importers: packages/next: specifiers: - '@types/jest': ^29.4.0 + '@types/jest': ^29.5.0 '@types/react': ^18.0.26 '@types/supertest': ^2.0.12 '@zenstackhq/runtime': workspace:* '@zenstackhq/testtools': workspace:* copyfiles: ^2.4.1 - jest: ^29.4.3 + jest: ^29.5.0 next: ^12.3.1 || ^13 react: ^17.0.2 || ^18 rimraf: ^3.0.2 @@ -45,20 +45,20 @@ importers: typescript: ^4.9.4 dependencies: '@zenstackhq/runtime': link:../runtime/dist - '@zenstackhq/testtools': link:../testtools/dist next: 12.3.1_672uxklweod7ene3nqtsh262ca tmp: 0.2.1 devDependencies: - '@types/jest': 29.4.0 + '@types/jest': 29.5.0 '@types/react': 18.0.26 '@types/supertest': 2.0.12 + '@zenstackhq/testtools': link:../testtools/dist copyfiles: 2.4.1 - jest: 29.4.3 + jest: 29.5.0 react: 18.2.0 rimraf: 3.0.2 superjson: 1.11.0 supertest: 6.3.3 - ts-jest: 29.0.5_itrzs5lisv6omhzjwqg6f7v6de + ts-jest: 29.0.5_xv37nfqpz6757dg24cw6wqnoey typescript: 4.9.4 publishDirectory: dist @@ -67,7 +67,7 @@ importers: '@prisma/generator-helper': ^4.7.1 '@prisma/internals': ^4.7.1 '@readme/openapi-parser': ^2.4.0 - '@types/jest': ^29.4.0 + '@types/jest': ^29.5.0 '@types/tmp': ^0.2.3 '@typescript-eslint/eslint-plugin': ^5.54.0 '@typescript-eslint/parser': ^5.54.0 @@ -77,7 +77,7 @@ importers: change-case: ^4.1.2 copyfiles: ^2.4.1 eslint: ^8.35.0 - jest: ^29.4.3 + jest: ^29.5.0 openapi-types: ^12.1.0 rimraf: ^3.0.2 tiny-invariant: ^1.3.1 @@ -86,6 +86,8 @@ importers: typescript: ^4.9.5 yaml: ^2.2.1 zenstack: workspace:* + zod: 3.21.1 + zod-validation-error: ^0.2.1 dependencies: '@prisma/generator-helper': 4.7.1 '@zenstackhq/runtime': link:../../runtime/dist @@ -94,20 +96,22 @@ importers: openapi-types: 12.1.0 tiny-invariant: 1.3.1 yaml: 2.2.1 + zod: 3.21.1 + zod-validation-error: 0.2.1_zod@3.21.1 devDependencies: '@prisma/internals': 4.7.1 '@readme/openapi-parser': 2.4.0_openapi-types@12.1.0 - '@types/jest': 29.4.0 + '@types/jest': 29.5.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 + jest: 29.5.0 rimraf: 3.0.2 tmp: 0.2.1 - ts-jest: 29.0.5_62v6np7q4ngunzmn4ekotpxy7y + ts-jest: 29.0.5_alvbhmn3fmdinfvcr37idtrz64 typescript: 4.9.5 zenstack: link:../../schema/dist publishDirectory: dist @@ -115,6 +119,7 @@ importers: packages/plugins/react: specifiers: '@prisma/generator-helper': ^4.7.1 + '@tanstack/react-query': ^4.28.0 '@types/jest': ^29.5.0 '@types/react': ^18.0.26 '@types/tmp': ^0.2.3 @@ -137,19 +142,20 @@ importers: '@zenstackhq/sdk': link:../../sdk/dist change-case: 4.1.2 decimal.js: 10.4.2 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 superjson: 1.12.1 - swr: 2.0.3_react@18.2.0 ts-morph: 16.0.0 devDependencies: + '@tanstack/react-query': 4.28.0_biqbaboplfbrettd7655fr4n2y '@types/jest': 29.5.0 '@types/react': 18.0.26 '@types/tmp': 0.2.3 '@zenstackhq/testtools': link:../../testtools/dist copyfiles: 2.4.1 jest: 29.5.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 rimraf: 3.0.2 + swr: 2.0.3_react@18.2.0 ts-jest: 29.0.5_xv37nfqpz6757dg24cw6wqnoey typescript: 4.9.4 publishDirectory: dist @@ -171,7 +177,7 @@ importers: ts-morph: ^16.0.0 tslib: ^2.4.1 typescript: ^4.9.4 - zod: ^3.19.1 + zod: 3.21.1 dependencies: '@prisma/generator-helper': 4.7.1 '@prisma/internals': 4.7.1 @@ -180,7 +186,7 @@ importers: prettier: 2.8.3 ts-morph: 16.0.0 tslib: 2.4.1 - zod: 3.19.1 + zod: 3.21.1 devDependencies: '@types/jest': 29.5.0 '@types/prettier': 2.7.2 @@ -194,9 +200,10 @@ importers: packages/runtime: specifiers: + '@paralleldrive/cuid2': ^2.2.0 '@prisma/client': ^4.0.0 '@types/bcryptjs': ^2.4.2 - '@types/jest': ^29.0.3 + '@types/jest': ^29.5.0 '@types/node': ^14.18.29 '@types/pluralize': ^0.0.29 '@zenstackhq/sdk': workspace:* @@ -204,7 +211,6 @@ importers: change-case: ^4.1.2 colors: 1.4.0 copyfiles: ^2.4.1 - cuid: ^2.1.8 decimal.js: ^10.4.2 deepcopy: ^2.1.0 pluralize: ^8.0.0 @@ -212,25 +218,25 @@ importers: superjson: ^1.11.0 tslib: ^2.4.1 typescript: ^4.9.3 - zod: ^3.19.1 + zod: 3.21.1 zod-validation-error: ^0.2.1 dependencies: + '@paralleldrive/cuid2': 2.2.0 '@prisma/client': 4.7.1 '@types/bcryptjs': 2.4.2 '@zenstackhq/sdk': link:../sdk/dist bcryptjs: 2.4.3 change-case: 4.1.2 colors: 1.4.0 - cuid: 2.1.8 decimal.js: 10.4.2 deepcopy: 2.1.0 pluralize: 8.0.0 superjson: 1.11.0 tslib: 2.4.1 - zod: 3.19.1 - zod-validation-error: 0.2.1_zod@3.19.1 + zod: 3.21.1 + zod-validation-error: 0.2.1_zod@3.21.1 devDependencies: - '@types/jest': 29.2.0 + '@types/jest': 29.5.0 '@types/node': 14.18.32 '@types/pluralize': 0.0.29 copyfiles: 2.4.1 @@ -240,10 +246,11 @@ importers: packages/schema: specifiers: - '@prisma/generator-helper': ^4.7.1 - '@prisma/internals': ^4.7.1 + '@paralleldrive/cuid2': ^2.2.0 + '@prisma/generator-helper': ^4.0.0 + '@prisma/internals': ^4.0.0 '@types/async-exit-hook': ^2.0.0 - '@types/jest': ^29.2.0 + '@types/jest': ^29.5.0 '@types/node': ^14.18.32 '@types/pluralize': ^0.0.29 '@types/semver': ^7.3.13 @@ -252,6 +259,7 @@ importers: '@types/vscode': ^1.56.0 '@typescript-eslint/eslint-plugin': ^5.42.0 '@typescript-eslint/parser': ^5.42.0 + '@vscode/vsce': ^2.19.0 '@zenstackhq/language': workspace:* '@zenstackhq/runtime': workspace:* '@zenstackhq/sdk': workspace:* @@ -263,19 +271,18 @@ importers: commander: ^8.3.0 concurrently: ^7.4.0 copyfiles: ^2.4.1 - cuid: ^2.1.8 dotenv: ^16.0.3 esbuild: ^0.15.12 eslint: ^8.27.0 eslint-plugin-jest: ^27.1.7 - jest: ^29.2.1 + get-latest-version: ^5.0.1 + jest: ^29.5.0 langium: 1.1.0 - langium-cli: ^1.0.0 mixpanel: ^0.17.0 node-machine-id: ^1.1.12 ora: ^5.4.1 pluralize: ^8.0.0 - prisma: ~4.7.0 + prisma: ^4.0.0 promisify: ^0.0.3 renamer: ^4.0.0 rimraf: ^3.0.2 @@ -289,31 +296,30 @@ importers: typescript: ^4.8.4 uuid: ^9.0.0 vitest: ^0.29.7 - vsce: ^2.13.0 vscode-jsonrpc: ^8.0.2 vscode-languageclient: ^8.0.2 vscode-languageserver: ^8.0.2 vscode-languageserver-textdocument: ^1.0.7 vscode-uri: ^3.0.6 - zod: ^3.19.1 + zod: 3.21.1 + zod-validation-error: ^0.2.1 dependencies: + '@paralleldrive/cuid2': 2.2.0 '@prisma/generator-helper': 4.7.1 '@prisma/internals': 4.7.1 '@zenstackhq/language': link:../language/dist - '@zenstackhq/runtime': link:../runtime/dist '@zenstackhq/sdk': link:../sdk/dist async-exit-hook: 2.0.1 change-case: 4.1.2 chevrotain: 9.1.0 colors: 1.4.0 commander: 8.3.0 - cuid: 2.1.8 + get-latest-version: 5.0.1 langium: 1.1.0 mixpanel: 0.17.0 node-machine-id: 1.1.12 ora: 5.4.1 pluralize: 8.0.0 - prisma: 4.7.0 promisify: 0.0.3 semver: 7.3.8 sleep-promise: 9.1.0 @@ -324,10 +330,11 @@ importers: vscode-languageserver: 8.0.2 vscode-languageserver-textdocument: 1.0.7 vscode-uri: 3.0.6 - zod: 3.19.1 + zod: 3.21.1 + zod-validation-error: 0.2.1_zod@3.21.1 devDependencies: '@types/async-exit-hook': 2.0.0 - '@types/jest': 29.2.0 + '@types/jest': 29.5.0 '@types/node': 14.18.32 '@types/pluralize': 0.0.29 '@types/semver': 7.3.13 @@ -336,24 +343,25 @@ importers: '@types/vscode': 1.72.0 '@typescript-eslint/eslint-plugin': 5.42.0_ofgjrzjuekeo7s3hdyz2yuzw34 '@typescript-eslint/parser': 5.42.0_rmayb2veg2btbq6mbmnyivgasy + '@vscode/vsce': 2.19.0 + '@zenstackhq/runtime': link:../runtime/dist '@zenstackhq/testtools': link:../testtools/dist concurrently: 7.4.0 copyfiles: 2.4.1 dotenv: 16.0.3 esbuild: 0.15.12 eslint: 8.27.0 - eslint-plugin-jest: 27.1.7_pdoqwsgoaseomjcjdinsmwbl4q - jest: 29.2.1_4f2ldd7um3b3u4eyvetyqsphze - langium-cli: 1.0.0 + eslint-plugin-jest: 27.1.7_bqsz7mnzvjsobat2umlw76do7a + jest: 29.5.0_4f2ldd7um3b3u4eyvetyqsphze + prisma: 4.7.0 renamer: 4.0.0 rimraf: 3.0.2 tmp: 0.2.1 - ts-jest: 29.0.3_yf6yylffq7wy4s6j6no7bitnpa + ts-jest: 29.0.3_i42we4lxl4f3wyuuvscuqjoe7i ts-node: 10.9.1_jcmx33t3olsvcxopqdljsohpme tsc-alias: 1.7.0 typescript: 4.8.4 vitest: 0.29.7 - vsce: 2.15.0 publishDirectory: dist packages/sdk: @@ -380,7 +388,7 @@ importers: specifiers: '@types/body-parser': ^1.19.2 '@types/express': ^4.17.17 - '@types/jest': ^29.4.0 + '@types/jest': ^29.5.0 '@types/supertest': ^2.0.12 '@zenstackhq/openapi': workspace:* '@zenstackhq/runtime': workspace:* @@ -392,7 +400,7 @@ importers: express: ^4.18.2 fastify: ^4.14.1 fastify-plugin: ^4.5.0 - jest: ^29.4.3 + jest: ^29.5.0 rimraf: ^3.0.2 supertest: ^6.3.3 tiny-invariant: ^1.3.1 @@ -405,11 +413,11 @@ importers: '@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 + zod-validation-error: 0.2.1_zod@3.21.1 devDependencies: '@types/body-parser': 1.19.2 '@types/express': 4.17.17 - '@types/jest': 29.4.0 + '@types/jest': 29.5.0 '@types/supertest': 2.0.12 '@zenstackhq/testtools': link:../testtools/dist body-parser: 1.20.2 @@ -417,10 +425,10 @@ importers: express: 4.18.2 fastify: 4.14.1 fastify-plugin: 4.5.0 - jest: 29.4.3 + jest: 29.5.0 rimraf: 3.0.2 supertest: 6.3.3 - ts-jest: 29.0.5_62v6np7q4ngunzmn4ekotpxy7y + ts-jest: 29.0.5_alvbhmn3fmdinfvcr37idtrz64 typescript: 4.9.5 publishDirectory: dist @@ -440,7 +448,7 @@ importers: typescript: ^4.9.5 zenstack: workspace:* dependencies: - '@prisma/client': 4.7.1_prisma@4.7.0 + '@prisma/client': 4.12.0_prisma@4.7.0 '@prisma/generator-helper': 4.7.1 '@prisma/internals': 4.7.1 '@zenstackhq/language': link:../language/dist @@ -461,7 +469,7 @@ importers: '@prisma/client': ^4.7.0 '@types/bcryptjs': ^2.4.2 '@types/fs-extra': ^11.0.1 - '@types/jest': ^29.0.3 + '@types/jest': ^29.5.0 '@types/node': ^14.18.29 '@types/supertest': ^2.0.12 '@types/tmp': ^0.2.3 @@ -476,7 +484,7 @@ importers: eslint: ^8.30.0 eslint-plugin-jest: ^27.1.7 fs-extra: ^11.1.0 - jest: ^29.0.3 + jest: ^29.5.0 jest-fetch-mock: ^3.0.3 next: ^12.3.1 prisma: ~4.7.0 @@ -499,7 +507,7 @@ importers: devDependencies: '@types/bcryptjs': 2.4.2 '@types/fs-extra': 11.0.1 - '@types/jest': 29.0.3 + '@types/jest': 29.5.0 '@types/supertest': 2.0.12 '@types/tmp': 0.2.3 '@types/uuid': 8.3.4 @@ -508,14 +516,14 @@ importers: '@zenstackhq/runtime': link:../../packages/runtime/dist '@zenstackhq/trpc': link:../../packages/plugins/trpc/dist eslint: 8.30.0 - eslint-plugin-jest: 27.1.7_mqrelppxlcv42tihlyzz2qch5q + eslint-plugin-jest: 27.1.7_zoe3f4dgrzvdh6rxdjodmtuft4 fs-extra: 11.1.0 - jest: 29.0.3_johvxhudwcpndp4mle25vwrlq4 + jest: 29.5.0_johvxhudwcpndp4mle25vwrlq4 jest-fetch-mock: 3.0.3 next: 12.3.1_672uxklweod7ene3nqtsh262ca prisma: 4.7.0 tmp: 0.2.1 - ts-jest: 29.0.1_57hartw4ijnqan4zaqtggk6uaa + ts-jest: 29.0.1_rlomzopfumxmkrybxmhq4yglo4 ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa typescript: 4.8.3 uuid: 9.0.0 @@ -545,38 +553,10 @@ packages: dependencies: '@babel/highlight': 7.18.6 - /@babel/compat-data/7.19.4: - resolution: {integrity: sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw==} - engines: {node: '>=6.9.0'} - dev: true - /@babel/compat-data/7.20.5: resolution: {integrity: sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==} engines: {node: '>=6.9.0'} - /@babel/core/7.19.3: - resolution: {integrity: sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.19.5 - '@babel/helper-compilation-targets': 7.19.3_@babel+core@7.19.3 - '@babel/helper-module-transforms': 7.19.0 - '@babel/helpers': 7.19.4 - '@babel/parser': 7.19.4 - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.4 - '@babel/types': 7.19.4 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.1 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/core/7.20.5: resolution: {integrity: sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==} engines: {node: '>=6.9.0'} @@ -599,24 +579,6 @@ packages: transitivePeerDependencies: - supports-color - /@babel/generator/7.19.5: - resolution: {integrity: sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.4 - '@jridgewell/gen-mapping': 0.3.2 - jsesc: 2.5.2 - dev: true - - /@babel/generator/7.19.6: - resolution: {integrity: sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.4 - '@jridgewell/gen-mapping': 0.3.2 - jsesc: 2.5.2 - dev: true - /@babel/generator/7.20.5: resolution: {integrity: sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==} engines: {node: '>=6.9.0'} @@ -625,19 +587,6 @@ packages: '@jridgewell/gen-mapping': 0.3.2 jsesc: 2.5.2 - /@babel/helper-compilation-targets/7.19.3_@babel+core@7.19.3: - resolution: {integrity: sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.19.4 - '@babel/core': 7.19.3 - '@babel/helper-validator-option': 7.18.6 - browserslist: 4.21.4 - semver: 6.3.0 - dev: true - /@babel/helper-compilation-targets/7.20.0_@babel+core@7.20.5: resolution: {integrity: sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==} engines: {node: '>=6.9.0'} @@ -673,22 +622,6 @@ packages: dependencies: '@babel/types': 7.20.5 - /@babel/helper-module-transforms/7.19.0: - resolution: {integrity: sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-simple-access': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.4 - '@babel/types': 7.19.4 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helper-module-transforms/7.20.2: resolution: {integrity: sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==} engines: {node: '>=6.9.0'} @@ -714,13 +647,6 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/helper-simple-access/7.18.6: - resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.19.4 - dev: true - /@babel/helper-simple-access/7.20.2: resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} engines: {node: '>=6.9.0'} @@ -745,17 +671,6 @@ packages: resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} engines: {node: '>=6.9.0'} - /@babel/helpers/7.19.4: - resolution: {integrity: sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.18.10 - '@babel/traverse': 7.19.6 - '@babel/types': 7.19.4 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/helpers/7.20.6: resolution: {integrity: sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==} engines: {node: '>=6.9.0'} @@ -774,22 +689,6 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 - /@babel/parser/7.19.4: - resolution: {integrity: sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.19.4 - dev: true - - /@babel/parser/7.19.6: - resolution: {integrity: sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.19.4 - dev: true - /@babel/parser/7.20.5: resolution: {integrity: sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==} engines: {node: '>=6.0.0'} @@ -797,15 +696,6 @@ packages: dependencies: '@babel/types': 7.20.5 - /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.19.3: - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.20.5: resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -815,15 +705,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-bigint/7.8.3_@babel+core@7.19.3: - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 - dev: true - /@babel/plugin-syntax-bigint/7.8.3_@babel+core@7.20.5: resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: @@ -833,15 +714,6 @@ packages: '@babel/helper-plugin-utils': 7.19.0 dev: true - /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.19.3: - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.20.5: resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: @@ -851,15 +723,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.19.3: - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 - dev: true - /@babel/plugin-syntax-import-meta/7.10.4_@babel+core@7.20.5: resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -869,15 +732,6 @@ packages: '@babel/helper-plugin-utils': 7.19.0 dev: true - /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.19.3: - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.20.5: resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: @@ -887,16 +741,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.19.3: - resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 - dev: true - /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.20.5: resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} engines: {node: '>=6.9.0'} @@ -907,15 +751,6 @@ packages: '@babel/helper-plugin-utils': 7.19.0 dev: true - /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.19.3: - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.20.5: resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: @@ -925,15 +760,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.19.3: - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.20.5: resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: @@ -943,15 +769,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.19.3: - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.20.5: resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: @@ -961,15 +778,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.19.3: - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.20.5: resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: @@ -979,15 +787,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.19.3: - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.20.5: resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: @@ -997,15 +796,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.19.3: - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.20.5: resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: @@ -1015,16 +805,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.19.3: - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.20.5: resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} @@ -1035,26 +815,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-typescript/7.18.6_@babel+core@7.19.3: - resolution: {integrity: sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 - dev: true - - /@babel/plugin-syntax-typescript/7.18.6_@babel+core@7.20.5: - resolution: {integrity: sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.5 - '@babel/helper-plugin-utils': 7.19.0 - dev: true - /@babel/plugin-syntax-typescript/7.20.0_@babel+core@7.20.5: resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} engines: {node: '>=6.9.0'} @@ -1080,42 +840,6 @@ packages: '@babel/parser': 7.20.5 '@babel/types': 7.20.5 - /@babel/traverse/7.19.4: - resolution: {integrity: sha512-w3K1i+V5u2aJUOXBFFC5pveFLmtq1s3qcdDNC2qRI6WPBQIDaKFqXxDEqDO/h1dQ3HjsZoZMyIy6jGLq0xtw+g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.19.5 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.19.4 - '@babel/types': 7.19.4 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/traverse/7.19.6: - resolution: {integrity: sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.18.6 - '@babel/generator': 7.19.6 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.19.6 - '@babel/types': 7.19.4 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/traverse/7.20.5: resolution: {integrity: sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==} engines: {node: '>=6.9.0'} @@ -1133,15 +857,6 @@ packages: transitivePeerDependencies: - supports-color - /@babel/types/7.19.4: - resolution: {integrity: sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.19.4 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 - dev: true - /@babel/types/7.20.5: resolution: {integrity: sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==} engines: {node: '>=6.9.0'} @@ -1718,140 +1433,20 @@ packages: engines: {node: '>=8'} dev: true - /@jest/console/29.0.3: - resolution: {integrity: sha512-cGg0r+klVHSYnfE977S9wmpuQ9L+iYuYgL+5bPXiUlUynLLYunRxswEmhBzvrSKGof5AKiHuTTmUKAqRcDY9dg==} + /@jest/console/29.5.0: + resolution: {integrity: sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.0.3 + '@jest/types': 29.5.0 '@types/node': 18.14.2 chalk: 4.1.2 - jest-message-util: 29.0.3 - jest-util: 29.0.3 + jest-message-util: 29.5.0 + jest-util: 29.5.0 slash: 3.0.0 dev: true - /@jest/console/29.2.1: - resolution: {integrity: sha512-MF8Adcw+WPLZGBiNxn76DOuczG3BhODTcMlDCA4+cFi41OkaY/lyI0XUUhi73F88Y+7IHoGmD80pN5CtxQUdSw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.2.1 - '@types/node': 18.14.2 - chalk: 4.1.2 - jest-message-util: 29.2.1 - jest-util: 29.2.1 - slash: 3.0.0 - dev: true - - /@jest/console/29.4.3: - resolution: {integrity: sha512-W/o/34+wQuXlgqlPYTansOSiBnuxrTv61dEVkA6HNmpcgHLUjfaUbdqt6oVvOzaawwo9IdW9QOtMgQ1ScSZC4A==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.4.3 - '@types/node': 18.14.2 - chalk: 4.1.2 - jest-message-util: 29.4.3 - jest-util: 29.4.3 - slash: 3.0.0 - dev: true - - /@jest/console/29.5.0: - resolution: {integrity: sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.5.0 - '@types/node': 18.14.2 - chalk: 4.1.2 - jest-message-util: 29.5.0 - jest-util: 29.5.0 - slash: 3.0.0 - dev: true - - /@jest/core/29.0.3_ts-node@10.9.1: - resolution: {integrity: sha512-1d0hLbOrM1qQE3eP3DtakeMbKTcXiXP3afWxqz103xPyddS2NhnNghS7MaXx1dcDt4/6p4nlhmeILo2ofgi8cQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/console': 29.0.3 - '@jest/reporters': 29.0.3 - '@jest/test-result': 29.0.3 - '@jest/transform': 29.0.3 - '@jest/types': 29.0.3 - '@types/node': 18.14.2 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.4.0 - exit: 0.1.2 - graceful-fs: 4.2.10 - jest-changed-files: 29.0.0 - jest-config: 29.0.3_jboh4c3iv3wxuja4m36ecyac7e - jest-haste-map: 29.0.3 - jest-message-util: 29.0.3 - jest-regex-util: 29.0.0 - jest-resolve: 29.0.3 - jest-resolve-dependencies: 29.0.3 - jest-runner: 29.0.3 - jest-runtime: 29.0.3 - jest-snapshot: 29.0.3 - jest-util: 29.0.3 - jest-validate: 29.0.3 - jest-watcher: 29.0.3 - micromatch: 4.0.5 - pretty-format: 29.0.3 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - supports-color - - ts-node - dev: true - - /@jest/core/29.2.1_ts-node@10.9.1: - resolution: {integrity: sha512-kuLKYqnqgerXkBUwlHVxeSuhSnd+JMnMCLfU98bpacBSfWEJPegytDh3P2m15/JHzet32hGGld4KR4OzMb6/Tg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/console': 29.2.1 - '@jest/reporters': 29.2.1 - '@jest/test-result': 29.2.1 - '@jest/transform': 29.2.1 - '@jest/types': 29.2.1 - '@types/node': 18.14.2 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.5.0 - exit: 0.1.2 - graceful-fs: 4.2.10 - jest-changed-files: 29.2.0 - jest-config: 29.2.1_jboh4c3iv3wxuja4m36ecyac7e - jest-haste-map: 29.2.1 - jest-message-util: 29.2.1 - jest-regex-util: 29.2.0 - jest-resolve: 29.2.1 - jest-resolve-dependencies: 29.2.1 - jest-runner: 29.2.1 - jest-runtime: 29.2.1 - jest-snapshot: 29.2.1 - jest-util: 29.2.1 - jest-validate: 29.2.1 - jest-watcher: 29.2.1 - micromatch: 4.0.5 - pretty-format: 29.2.1 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - supports-color - - ts-node - dev: true - - /@jest/core/29.4.3: - resolution: {integrity: sha512-56QvBq60fS4SPZCuM7T+7scNrkGIe7Mr6PVIXUpu48ouvRaWOFqRPV91eifvFM0ay2HmfswXiGf97NGUN5KofQ==} + /@jest/core/29.5.0: + resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -1859,32 +1454,32 @@ packages: node-notifier: optional: true dependencies: - '@jest/console': 29.4.3 - '@jest/reporters': 29.4.3 - '@jest/test-result': 29.4.3 - '@jest/transform': 29.4.3 - '@jest/types': 29.4.3 + '@jest/console': 29.5.0 + '@jest/reporters': 29.5.0 + '@jest/test-result': 29.5.0 + '@jest/transform': 29.5.0 + '@jest/types': 29.5.0 '@types/node': 18.14.2 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.7.1 exit: 0.1.2 graceful-fs: 4.2.10 - jest-changed-files: 29.4.3 - jest-config: 29.4.3_@types+node@18.14.2 - jest-haste-map: 29.4.3 - jest-message-util: 29.4.3 + jest-changed-files: 29.5.0 + jest-config: 29.5.0_@types+node@18.14.2 + jest-haste-map: 29.5.0 + jest-message-util: 29.5.0 jest-regex-util: 29.4.3 - jest-resolve: 29.4.3 - jest-resolve-dependencies: 29.4.3 - jest-runner: 29.4.3 - jest-runtime: 29.4.3 - jest-snapshot: 29.4.3 - jest-util: 29.4.3 - jest-validate: 29.4.3 - jest-watcher: 29.4.3 + jest-resolve: 29.5.0 + jest-resolve-dependencies: 29.5.0 + jest-runner: 29.5.0 + jest-runtime: 29.5.0 + jest-snapshot: 29.5.0 + jest-util: 29.5.0 + jest-validate: 29.5.0 + jest-watcher: 29.5.0 micromatch: 4.0.5 - pretty-format: 29.4.3 + pretty-format: 29.5.0 slash: 3.0.0 strip-ansi: 6.0.1 transitivePeerDependencies: @@ -1892,7 +1487,7 @@ packages: - ts-node dev: true - /@jest/core/29.5.0: + /@jest/core/29.5.0_ts-node@10.9.1: resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -1913,7 +1508,7 @@ packages: exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 29.5.0 - jest-config: 29.5.0_@types+node@18.14.2 + jest-config: 29.5.0_jboh4c3iv3wxuja4m36ecyac7e jest-haste-map: 29.5.0 jest-message-util: 29.5.0 jest-regex-util: 29.4.3 @@ -1934,46 +1529,6 @@ packages: - ts-node dev: true - /@jest/environment/29.0.3: - resolution: {integrity: sha512-iKl272NKxYNQNqXMQandAIwjhQaGw5uJfGXduu8dS9llHi8jV2ChWrtOAVPnMbaaoDhnI3wgUGNDvZgHeEJQCA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/fake-timers': 29.0.3 - '@jest/types': 29.0.3 - '@types/node': 18.14.2 - jest-mock: 29.0.3 - dev: true - - /@jest/environment/29.2.1: - resolution: {integrity: sha512-EutqA7T/X6zFjw6mAWRHND+ZkTPklmIEWCNbmwX6uCmOrFrWaLbDZjA+gePHJx6fFMMRvNfjXcvzXEtz54KPlg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/fake-timers': 29.2.1 - '@jest/types': 29.2.1 - '@types/node': 18.14.2 - jest-mock: 29.2.1 - dev: true - - /@jest/environment/29.3.1: - resolution: {integrity: sha512-pMmvfOPmoa1c1QpfFW0nXYtNLpofqo4BrCIk6f2kW4JFeNlHV2t3vd+3iDLf31e2ot2Mec0uqZfmI+U0K2CFag==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/fake-timers': 29.3.1 - '@jest/types': 29.3.1 - '@types/node': 18.14.2 - jest-mock: 29.3.1 - dev: true - - /@jest/environment/29.4.3: - resolution: {integrity: sha512-dq5S6408IxIa+lr54zeqce+QgI+CJT4nmmA+1yzFgtcsGK8c/EyiUb9XQOgz3BMKrRDfKseeOaxj2eO8LlD3lA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/fake-timers': 29.4.3 - '@jest/types': 29.4.3 - '@types/node': 18.14.2 - jest-mock: 29.4.3 - dev: true - /@jest/environment/29.5.0: resolution: {integrity: sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1984,34 +1539,6 @@ packages: jest-mock: 29.5.0 dev: true - /@jest/expect-utils/29.0.3: - resolution: {integrity: sha512-i1xUkau7K/63MpdwiRqaxgZOjxYs4f0WMTGJnYwUKubsNRZSeQbLorS7+I4uXVF9KQ5r61BUPAUMZ7Lf66l64Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.0.0 - dev: true - - /@jest/expect-utils/29.2.1: - resolution: {integrity: sha512-yr4aHNg5Z1CjKby5ozm7sKjgBlCOorlAoFcvrOQ/4rbZRfgZQdnmh7cth192PYIgiPZo2bBXvqdOApnAMWFJZg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.2.0 - dev: true - - /@jest/expect-utils/29.3.1: - resolution: {integrity: sha512-wlrznINZI5sMjwvUoLVk617ll/UYfGIZNxmbU+Pa7wmkL4vYzhV9R2pwVqUh4NWWuLQWkI8+8mOkxs//prKQ3g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.2.0 - dev: true - - /@jest/expect-utils/29.4.3: - resolution: {integrity: sha512-/6JWbkxHOP8EoS8jeeTd9dTfc9Uawi+43oLKHfp6zzux3U2hqOOVnV3ai4RpDYHOccL6g+5nrxpoc8DmJxtXVQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.4.3 - dev: true - /@jest/expect-utils/29.5.0: resolution: {integrity: sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2019,46 +1546,6 @@ packages: jest-get-type: 29.4.3 dev: true - /@jest/expect/29.0.3: - resolution: {integrity: sha512-6W7K+fsI23FQ01H/BWccPyDZFrnU9QlzDcKOjrNVU5L8yUORFAJJIpmyxWPW70+X624KUNqzZwPThPMX28aXEQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - expect: 29.0.3 - jest-snapshot: 29.0.3 - transitivePeerDependencies: - - supports-color - dev: true - - /@jest/expect/29.2.1: - resolution: {integrity: sha512-o14R2t2tHHHudwji43UKkzmmH49xfF5T++FQBK2tl88qwuBWQOcx7fNUYl+mA/9TPNAN0FkQ3usnpyS8FUwsvQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - expect: 29.2.1 - jest-snapshot: 29.2.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@jest/expect/29.3.1: - resolution: {integrity: sha512-QivM7GlSHSsIAWzgfyP8dgeExPRZ9BIe2LsdPyEhCGkZkoyA+kGsoIzbKAfZCvvRzfZioKwPtCZIt5SaoxYCvg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - expect: 29.3.1 - jest-snapshot: 29.3.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@jest/expect/29.4.3: - resolution: {integrity: sha512-iktRU/YsxEtumI9zsPctYUk7ptpC+AVLLk1Ax3AsA4g1C+8OOnKDkIQBDHtD5hA/+VtgMd5AWI5gNlcAlt2vxQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - expect: 29.4.3 - jest-snapshot: 29.4.3 - transitivePeerDependencies: - - supports-color - dev: true - /@jest/expect/29.5.0: resolution: {integrity: sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2069,54 +1556,6 @@ packages: - supports-color dev: true - /@jest/fake-timers/29.0.3: - resolution: {integrity: sha512-tmbUIo03x0TdtcZCESQ0oQSakPCpo7+s6+9mU19dd71MptkP4zCwoeZqna23//pgbhtT1Wq02VmA9Z9cNtvtCQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.0.3 - '@sinonjs/fake-timers': 9.1.2 - '@types/node': 18.14.2 - jest-message-util: 29.0.3 - jest-mock: 29.0.3 - jest-util: 29.0.3 - dev: true - - /@jest/fake-timers/29.2.1: - resolution: {integrity: sha512-KWil+8fef7Uj/P/PTZlPKk1Pw117wAmr71VWFV8ZDtRtkwmTG8oY4IRf0Ss44J2y5CYRy8d/zLOhxyoGRENjvA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.2.1 - '@sinonjs/fake-timers': 9.1.2 - '@types/node': 18.14.2 - jest-message-util: 29.2.1 - jest-mock: 29.2.1 - jest-util: 29.2.1 - dev: true - - /@jest/fake-timers/29.3.1: - resolution: {integrity: sha512-iHTL/XpnDlFki9Tq0Q1GGuVeQ8BHZGIYsvCO5eN/O/oJaRzofG9Xndd9HuSDBI/0ZS79pg0iwn07OMTQ7ngF2A==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.3.1 - '@sinonjs/fake-timers': 9.1.2 - '@types/node': 18.14.2 - jest-message-util: 29.3.1 - jest-mock: 29.3.1 - jest-util: 29.3.1 - dev: true - - /@jest/fake-timers/29.4.3: - resolution: {integrity: sha512-4Hote2MGcCTWSD2gwl0dwbCpBRHhE6olYEuTj8FMowdg3oQWNKr2YuxenPQYZ7+PfqPY1k98wKDU4Z+Hvd4Tiw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.4.3 - '@sinonjs/fake-timers': 10.0.2 - '@types/node': 18.14.2 - jest-message-util: 29.4.3 - jest-mock: 29.4.3 - jest-util: 29.4.3 - dev: true - /@jest/fake-timers/29.5.0: resolution: {integrity: sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2129,42 +1568,6 @@ packages: jest-util: 29.5.0 dev: true - /@jest/globals/29.2.1: - resolution: {integrity: sha512-Z4EejYPP1OPVq2abk1+9urAwJqkgw5jB2UJGlPjb5ZwzPQF8WLMcigKEfFzZb2OHhEVPP0RZD0/DbVTY1R6iQA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.2.1 - '@jest/expect': 29.2.1 - '@jest/types': 29.2.1 - jest-mock: 29.2.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@jest/globals/29.3.1: - resolution: {integrity: sha512-cTicd134vOcwO59OPaB6AmdHQMCtWOe+/DitpTZVxWgMJ+YvXL1HNAmPyiGbSHmF/mXVBkvlm8YYtQhyHPnV6Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.3.1 - '@jest/expect': 29.3.1 - '@jest/types': 29.3.1 - jest-mock: 29.3.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@jest/globals/29.4.3: - resolution: {integrity: sha512-8BQ/5EzfOLG7AaMcDh7yFCbfRLtsc+09E1RQmRBI4D6QQk4m6NSK/MXo+3bJrBN0yU8A2/VIcqhvsOLFmziioA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.4.3 - '@jest/expect': 29.4.3 - '@jest/types': 29.4.3 - jest-mock: 29.4.3 - transitivePeerDependencies: - - supports-color - dev: true - /@jest/globals/29.5.0: resolution: {integrity: sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2177,8 +1580,8 @@ packages: - supports-color dev: true - /@jest/reporters/29.0.3: - resolution: {integrity: sha512-3+QU3d4aiyOWfmk1obDerie4XNCaD5Xo1IlKNde2yGEi02WQD+ZQD0i5Hgqm1e73sMV7kw6pMlCnprtEwEVwxw==} + /@jest/reporters/29.5.0: + resolution: {integrity: sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -2187,11 +1590,11 @@ packages: optional: true dependencies: '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.0.3 - '@jest/test-result': 29.0.3 - '@jest/transform': 29.0.3 - '@jest/types': 29.0.3 - '@jridgewell/trace-mapping': 0.3.16 + '@jest/console': 29.5.0 + '@jest/test-result': 29.5.0 + '@jest/transform': 29.5.0 + '@jest/types': 29.5.0 + '@jridgewell/trace-mapping': 0.3.17 '@types/node': 18.14.2 chalk: 4.1.2 collect-v8-coverage: 1.0.1 @@ -2199,202 +1602,35 @@ packages: glob: 7.2.3 graceful-fs: 4.2.10 istanbul-lib-coverage: 3.2.0 - istanbul-lib-instrument: 5.2.0 + istanbul-lib-instrument: 5.2.1 istanbul-lib-report: 3.0.0 istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.5 - jest-message-util: 29.0.3 - jest-util: 29.0.3 - jest-worker: 29.0.3 + jest-message-util: 29.5.0 + jest-util: 29.5.0 + jest-worker: 29.5.0 slash: 3.0.0 string-length: 4.0.2 strip-ansi: 6.0.1 - terminal-link: 2.1.1 v8-to-istanbul: 9.0.1 transitivePeerDependencies: - supports-color dev: true - /@jest/reporters/29.2.1: - resolution: {integrity: sha512-sCsfUKM/yIF4nNed3e/rIgVIS58EiASGMDEPWqItfLZ9UO1ALW2ASDNJzdWkxEt0T8o2Ztj619G0KKrvK+McAw==} + /@jest/schemas/29.4.3: + resolution: {integrity: sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.2.1 - '@jest/test-result': 29.2.1 - '@jest/transform': 29.2.1 - '@jest/types': 29.2.1 - '@jridgewell/trace-mapping': 0.3.17 - '@types/node': 18.14.2 - chalk: 4.1.2 - collect-v8-coverage: 1.0.1 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.10 - istanbul-lib-coverage: 3.2.0 - istanbul-lib-instrument: 5.2.1 - istanbul-lib-report: 3.0.0 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.5 - jest-message-util: 29.2.1 - jest-util: 29.2.1 - jest-worker: 29.2.1 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.0.1 - transitivePeerDependencies: - - supports-color + '@sinclair/typebox': 0.25.24 dev: true - /@jest/reporters/29.4.3: - resolution: {integrity: sha512-sr2I7BmOjJhyqj9ANC6CTLsL4emMoka7HkQpcoMRlhCbQJjz2zsRzw0BDPiPyEFDXAbxKgGFYuQZiSJ1Y6YoTg==} + /@jest/source-map/29.4.3: + resolution: {integrity: sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.4.3 - '@jest/test-result': 29.4.3 - '@jest/transform': 29.4.3 - '@jest/types': 29.4.3 '@jridgewell/trace-mapping': 0.3.17 - '@types/node': 18.14.2 - chalk: 4.1.2 - collect-v8-coverage: 1.0.1 - exit: 0.1.2 - glob: 7.2.3 + callsites: 3.1.0 graceful-fs: 4.2.10 - istanbul-lib-coverage: 3.2.0 - istanbul-lib-instrument: 5.2.1 - istanbul-lib-report: 3.0.0 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.5 - jest-message-util: 29.4.3 - jest-util: 29.4.3 - jest-worker: 29.4.3 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.0.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@jest/reporters/29.5.0: - resolution: {integrity: sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.5.0 - '@jest/test-result': 29.5.0 - '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 - '@jridgewell/trace-mapping': 0.3.17 - '@types/node': 18.14.2 - chalk: 4.1.2 - collect-v8-coverage: 1.0.1 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.10 - istanbul-lib-coverage: 3.2.0 - istanbul-lib-instrument: 5.2.1 - istanbul-lib-report: 3.0.0 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.5 - jest-message-util: 29.5.0 - jest-util: 29.5.0 - jest-worker: 29.5.0 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.0.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@jest/schemas/29.0.0: - resolution: {integrity: sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@sinclair/typebox': 0.24.47 - dev: true - - /@jest/schemas/29.4.3: - resolution: {integrity: sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@sinclair/typebox': 0.25.24 - dev: true - - /@jest/source-map/29.0.0: - resolution: {integrity: sha512-nOr+0EM8GiHf34mq2GcJyz/gYFyLQ2INDhAylrZJ9mMWoW21mLBfZa0BUVPPMxVYrLjeiRe2Z7kWXOGnS0TFhQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jridgewell/trace-mapping': 0.3.16 - callsites: 3.1.0 - graceful-fs: 4.2.10 - dev: true - - /@jest/source-map/29.2.0: - resolution: {integrity: sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jridgewell/trace-mapping': 0.3.17 - callsites: 3.1.0 - graceful-fs: 4.2.10 - dev: true - - /@jest/source-map/29.4.3: - resolution: {integrity: sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jridgewell/trace-mapping': 0.3.17 - callsites: 3.1.0 - graceful-fs: 4.2.10 - dev: true - - /@jest/test-result/29.0.3: - resolution: {integrity: sha512-vViVnQjCgTmbhDKEonKJPtcFe9G/CJO4/Np4XwYJah+lF2oI7KKeRp8t1dFvv44wN2NdbDb/qC6pi++Vpp0Dlg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/console': 29.0.3 - '@jest/types': 29.0.3 - '@types/istanbul-lib-coverage': 2.0.4 - collect-v8-coverage: 1.0.1 - dev: true - - /@jest/test-result/29.2.1: - resolution: {integrity: sha512-lS4+H+VkhbX6z64tZP7PAUwPqhwj3kbuEHcaLuaBuB+riyaX7oa1txe0tXgrFj5hRWvZKvqO7LZDlNWeJ7VTPA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/console': 29.2.1 - '@jest/types': 29.2.1 - '@types/istanbul-lib-coverage': 2.0.4 - collect-v8-coverage: 1.0.1 - dev: true - - /@jest/test-result/29.4.3: - resolution: {integrity: sha512-Oi4u9NfBolMq9MASPwuWTlC5WvmNRwI4S8YrQg5R5Gi47DYlBe3sh7ILTqi/LGrK1XUE4XY9KZcQJTH1WJCLLA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/console': 29.4.3 - '@jest/types': 29.4.3 - '@types/istanbul-lib-coverage': 2.0.4 - collect-v8-coverage: 1.0.1 dev: true /@jest/test-result/29.5.0: @@ -2407,36 +1643,6 @@ packages: collect-v8-coverage: 1.0.1 dev: true - /@jest/test-sequencer/29.0.3: - resolution: {integrity: sha512-Hf4+xYSWZdxTNnhDykr8JBs0yBN/nxOXyUQWfotBUqqy0LF9vzcFB0jm/EDNZCx587znLWTIgxcokW7WeZMobQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/test-result': 29.0.3 - graceful-fs: 4.2.10 - jest-haste-map: 29.0.3 - slash: 3.0.0 - dev: true - - /@jest/test-sequencer/29.2.1: - resolution: {integrity: sha512-O/pnk0/xGj3lxPVNwB6HREJ7AYvUdyP2xo/s14/9Dtf091HoOeyIhWLKQE/4HzB8lNQBMo6J5mg0bHz/uCWK7w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/test-result': 29.2.1 - graceful-fs: 4.2.10 - jest-haste-map: 29.2.1 - slash: 3.0.0 - dev: true - - /@jest/test-sequencer/29.4.3: - resolution: {integrity: sha512-yi/t2nES4GB4G0mjLc0RInCq/cNr9dNwJxcGg8sslajua5Kb4kmozAc+qPLzplhBgfw1vLItbjyHzUN92UXicw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/test-result': 29.4.3 - graceful-fs: 4.2.10 - jest-haste-map: 29.4.3 - slash: 3.0.0 - dev: true - /@jest/test-sequencer/29.5.0: resolution: {integrity: sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2447,98 +1653,6 @@ packages: slash: 3.0.0 dev: true - /@jest/transform/29.0.3: - resolution: {integrity: sha512-C5ihFTRYaGDbi/xbRQRdbo5ddGtI4VSpmL6AIcZxdhwLbXMa7PcXxxqyI91vGOFHnn5aVM3WYnYKCHEqmLVGzg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.19.3 - '@jest/types': 29.0.3 - '@jridgewell/trace-mapping': 0.3.16 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 1.9.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.10 - jest-haste-map: 29.0.3 - jest-regex-util: 29.0.0 - jest-util: 29.0.3 - micromatch: 4.0.5 - pirates: 4.0.5 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@jest/transform/29.2.1: - resolution: {integrity: sha512-xup+iEuaIRSQabQaeqxaQyN0vg1Dctrp9oTObQsNf3sZEowTIa5cANYuoyi8Tqhg4GCqEVLTf18KW7ii0UeFVA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.20.5 - '@jest/types': 29.2.1 - '@jridgewell/trace-mapping': 0.3.17 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 1.9.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.10 - jest-haste-map: 29.2.1 - jest-regex-util: 29.2.0 - jest-util: 29.2.1 - micromatch: 4.0.5 - pirates: 4.0.5 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@jest/transform/29.3.1: - resolution: {integrity: sha512-8wmCFBTVGYqFNLWfcOWoVuMuKYPUBTnTMDkdvFtAYELwDOl9RGwOsvQWGPFxDJ8AWY9xM/8xCXdqmPK3+Q5Lug==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.20.5 - '@jest/types': 29.3.1 - '@jridgewell/trace-mapping': 0.3.17 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.10 - jest-haste-map: 29.3.1 - jest-regex-util: 29.2.0 - jest-util: 29.3.1 - micromatch: 4.0.5 - pirates: 4.0.5 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@jest/transform/29.4.3: - resolution: {integrity: sha512-8u0+fBGWolDshsFgPQJESkDa72da/EVwvL+II0trN2DR66wMwiQ9/CihaGfHdlLGFzbBZwMykFtxuwFdZqlKwg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.20.5 - '@jest/types': 29.4.3 - '@jridgewell/trace-mapping': 0.3.17 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.10 - jest-haste-map: 29.4.3 - jest-regex-util: 29.4.3 - jest-util: 29.4.3 - micromatch: 4.0.5 - pirates: 4.0.5 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - dev: true - /@jest/transform/29.5.0: resolution: {integrity: sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2562,54 +1676,6 @@ packages: - supports-color dev: true - /@jest/types/29.0.3: - resolution: {integrity: sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.0.0 - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 18.14.2 - '@types/yargs': 17.0.12 - chalk: 4.1.2 - dev: true - - /@jest/types/29.2.1: - resolution: {integrity: sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.0.0 - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 18.14.2 - '@types/yargs': 17.0.13 - chalk: 4.1.2 - dev: true - - /@jest/types/29.3.1: - resolution: {integrity: sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.0.0 - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 18.14.2 - '@types/yargs': 17.0.13 - chalk: 4.1.2 - dev: true - - /@jest/types/29.4.3: - resolution: {integrity: sha512-bPYfw8V65v17m2Od1cv44FH+SiKW7w2Xu7trhcdTLUmSv85rfKsP+qXSjO4KGJr4dtPSzl/gvslZBXctf1qGEA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.4.3 - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 18.14.2 - '@types/yargs': 17.0.13 - chalk: 4.1.2 - dev: true - /@jest/types/29.5.0: resolution: {integrity: sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2648,13 +1714,6 @@ packages: /@jridgewell/sourcemap-codec/1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - /@jridgewell/trace-mapping/0.3.16: - resolution: {integrity: sha512-LCQ+NeThyJ4k1W2d+vIKdxuSt9R3pQSZ4P92m7EakaYuXcVWbHuT5bjNcqLd4Rdgi6xYWYDvBJZJLZSLanjDcA==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - dev: true - /@jridgewell/trace-mapping/0.3.17: resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} dependencies: @@ -2799,6 +1858,10 @@ packages: requiresBuild: true optional: true + /@noble/hashes/1.3.0: + resolution: {integrity: sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==} + dev: false + /@nodelib/fs.scandir/2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2855,6 +1918,47 @@ packages: resolution: {integrity: sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew==} engines: {node: '>=14'} + /@paralleldrive/cuid2/2.2.0: + resolution: {integrity: sha512-CVQDpPIUHrUGGLdrMGz1NmqZvqmsB2j2rCIQEu1EvxWjlFh4fhvEGmgR409cY20/67/WlJsggenq0no3p3kYsw==} + dependencies: + '@noble/hashes': 1.3.0 + dev: false + + /@pnpm/config.env-replace/1.0.0: + resolution: {integrity: sha512-ZVPVDi1E8oeXlYqkGRtX0CkzLTwE2zt62bjWaWKaAvI8NZqHzlMvGeSNDpW+JB3+aKanYb4UETJOF1/CxGPemA==} + engines: {node: '>=12.22.0'} + dev: false + + /@pnpm/network.ca-file/1.0.2: + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + dependencies: + graceful-fs: 4.2.10 + dev: false + + /@pnpm/npm-conf/2.1.0: + resolution: {integrity: sha512-Oe6ntvgsMTE3hDIqy6sajqHF+MnzJrOF06qC2QSiUEybLL7cp6tjoKUa32gpd9+KPVl4QyMs3E3nsXrx/Vdnlw==} + engines: {node: '>=12'} + dependencies: + '@pnpm/config.env-replace': 1.0.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + dev: false + + /@prisma/client/4.12.0_prisma@4.7.0: + resolution: {integrity: sha512-j9/ighfWwux97J2dS15nqhl60tYoH8V0IuSsgZDb6bCFcQD3fXbXmxjYC8GHhIgOk3lB7Pq+8CwElz2MiDpsSg==} + engines: {node: '>=14.17'} + requiresBuild: true + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + dependencies: + '@prisma/engines-version': 4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7 + prisma: 4.7.0 + dev: false + /@prisma/client/4.7.1: resolution: {integrity: sha512-/GbnOwIPtjiveZNUzGXOdp7RxTEkHL4DZP3vBaFNadfr6Sf0RshU5EULFzVaSi9i9PIK9PYd+1Rn7z2B2npb9w==} engines: {node: '>=14.17'} @@ -2911,6 +2015,10 @@ packages: transitivePeerDependencies: - supports-color + /@prisma/engines-version/4.12.0-67.659ef412370fa3b41cd7bf6e94587c1dfb7f67e7: + resolution: {integrity: sha512-JIHNj5jlXb9mcaJwakM0vpgRYJIAurxTUqM0iX0tfEQA5XLZ9ONkIckkhuAKdAzocZ+80GYg7QSsfpjg7OxbOA==} + dev: false + /@prisma/engines-version/4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c: resolution: {integrity: sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==} dev: false @@ -3060,20 +2168,10 @@ packages: openapi-types: 12.1.0 dev: true - /@sinclair/typebox/0.24.47: - resolution: {integrity: sha512-J4Xw0xYK4h7eC34MNOPQi6IkNxGRck6n4VJpWDzXIFVTW8I/D43Gf+NfWz/v/7NHlzWOPd3+T4PJ4OqklQ2u7A==} - dev: true - /@sinclair/typebox/0.25.24: resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} dev: true - /@sinonjs/commons/1.8.3: - resolution: {integrity: sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==} - dependencies: - type-detect: 4.0.8 - dev: true - /@sinonjs/commons/2.0.0: resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} dependencies: @@ -3086,17 +2184,33 @@ packages: '@sinonjs/commons': 2.0.0 dev: true - /@sinonjs/fake-timers/9.1.2: - resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==} - dependencies: - '@sinonjs/commons': 1.8.3 - dev: true - /@swc/helpers/0.4.11: resolution: {integrity: sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==} dependencies: tslib: 2.4.1 + /@tanstack/query-core/4.27.0: + resolution: {integrity: sha512-sm+QncWaPmM73IPwFlmWSKPqjdTXZeFf/7aEmWh00z7yl2FjqophPt0dE1EHW9P1giMC5rMviv7OUbSDmWzXXA==} + dev: true + + /@tanstack/react-query/4.28.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-8cGBV5300RHlvYdS4ea+G1JcZIt5CIuprXYFnsWggkmGoC0b5JaqG0fIX3qwDL9PTNkKvG76NGThIWbpXivMrQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@tanstack/query-core': 4.27.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + use-sync-external-store: 1.2.0_react@18.2.0 + dev: true + /@tootallnate/once/2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -3153,12 +2267,6 @@ packages: '@babel/types': 7.20.5 dev: true - /@types/babel__traverse/7.18.1: - resolution: {integrity: sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==} - dependencies: - '@babel/types': 7.19.4 - dev: true - /@types/babel__traverse/7.18.2: resolution: {integrity: sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg==} dependencies: @@ -3257,27 +2365,6 @@ packages: '@types/istanbul-lib-report': 3.0.0 dev: true - /@types/jest/29.0.3: - resolution: {integrity: sha512-F6ukyCTwbfsEX5F2YmVYmM5TcTHy1q9P5rWlRbrk56KyMh3v9xRGUO3aa8+SkvMi0SHXtASJv1283enXimC0Og==} - dependencies: - expect: 29.0.3 - pretty-format: 29.0.3 - dev: true - - /@types/jest/29.2.0: - resolution: {integrity: sha512-KO7bPV21d65PKwv3LLsD8Jn3E05pjNjRZvkm+YTacWhVmykAb07wW6IkZUmQAltwQafNcDUEUrMO2h3jeBSisg==} - dependencies: - expect: 29.2.1 - pretty-format: 29.2.1 - dev: true - - /@types/jest/29.4.0: - resolution: {integrity: sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ==} - dependencies: - expect: 29.4.3 - pretty-format: 29.4.3 - dev: true - /@types/jest/29.5.0: resolution: {integrity: sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==} dependencies: @@ -3327,14 +2414,6 @@ packages: resolution: {integrity: sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==} dev: true - /@types/prettier/2.7.0: - resolution: {integrity: sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==} - dev: true - - /@types/prettier/2.7.1: - resolution: {integrity: sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==} - dev: true - /@types/prettier/2.7.2: resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==} dev: true @@ -3414,12 +2493,6 @@ packages: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: true - /@types/yargs/17.0.12: - resolution: {integrity: sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==} - dependencies: - '@types/yargs-parser': 21.0.0 - dev: true - /@types/yargs/17.0.13: resolution: {integrity: sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==} dependencies: @@ -3757,6 +2830,35 @@ packages: pretty-format: 27.5.1 dev: true + /@vscode/vsce/2.19.0: + resolution: {integrity: sha512-dAlILxC5ggOutcvJY24jxz913wimGiUrHaPkk16Gm9/PGFbz1YezWtrXsTKUtJws4fIlpX2UIlVlVESWq8lkfQ==} + engines: {node: '>= 14'} + hasBin: true + dependencies: + azure-devops-node-api: 11.2.0 + chalk: 2.4.2 + cheerio: 1.0.0-rc.12 + commander: 6.2.1 + glob: 7.2.3 + hosted-git-info: 4.1.0 + jsonc-parser: 3.2.0 + leven: 3.1.0 + markdown-it: 12.3.2 + mime: 1.6.0 + minimatch: 3.1.2 + parse-semver: 1.1.1 + read: 1.0.7 + semver: 5.7.1 + tmp: 0.2.1 + typed-rest-client: 1.8.9 + url-join: 4.0.1 + xml2js: 0.5.0 + yauzl: 2.10.0 + yazl: 2.5.1 + optionalDependencies: + keytar: 7.9.0 + dev: true + /abort-controller/3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -4016,11 +3118,6 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: true - /at-least-node/1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} - 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'} @@ -4048,60 +3145,6 @@ packages: typed-rest-client: 1.8.9 dev: true - /babel-jest/29.0.3_@babel+core@7.19.3: - resolution: {integrity: sha512-ApPyHSOhS/sVzwUOQIWJmdvDhBsMG01HX9z7ogtkp1TToHGGUWFlnXJUIzCgKPSfiYLn3ibipCYzsKSURHEwLg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - dependencies: - '@babel/core': 7.19.3 - '@jest/transform': 29.0.3 - '@types/babel__core': 7.1.19 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.0.2_@babel+core@7.19.3 - chalk: 4.1.2 - graceful-fs: 4.2.10 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - dev: true - - /babel-jest/29.2.1_@babel+core@7.20.5: - resolution: {integrity: sha512-gQJwArok0mqoREiCYhXKWOgUhElJj9DpnssW6GL8dG7ARYqHEhrM9fmPHTjdqEGRVXZAd6+imo3/Vwa8TjLcsw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - dependencies: - '@babel/core': 7.20.5 - '@jest/transform': 29.2.1 - '@types/babel__core': 7.1.19 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.2.0_@babel+core@7.20.5 - chalk: 4.1.2 - graceful-fs: 4.2.10 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - dev: true - - /babel-jest/29.4.3_@babel+core@7.20.5: - resolution: {integrity: sha512-o45Wyn32svZE+LnMVWv/Z4x0SwtLbh4FyGcYtR20kIWd+rdrDZ9Fzq8Ml3MYLD+mZvEdzCjZsCnYZ2jpJyQ+Nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - dependencies: - '@babel/core': 7.20.5 - '@jest/transform': 29.4.3 - '@types/babel__core': 7.1.19 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.4.3_@babel+core@7.20.5 - chalk: 4.1.2 - graceful-fs: 4.2.10 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - dev: true - /babel-jest/29.5.0_@babel+core@7.20.5: resolution: {integrity: sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4133,36 +3176,6 @@ packages: - supports-color dev: true - /babel-plugin-jest-hoist/29.0.2: - resolution: {integrity: sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/template': 7.18.10 - '@babel/types': 7.19.4 - '@types/babel__core': 7.1.19 - '@types/babel__traverse': 7.18.1 - dev: true - - /babel-plugin-jest-hoist/29.2.0: - resolution: {integrity: sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/template': 7.18.10 - '@babel/types': 7.20.5 - '@types/babel__core': 7.1.19 - '@types/babel__traverse': 7.18.2 - dev: true - - /babel-plugin-jest-hoist/29.4.3: - resolution: {integrity: sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/template': 7.18.10 - '@babel/types': 7.20.5 - '@types/babel__core': 7.1.19 - '@types/babel__traverse': 7.18.2 - dev: true - /babel-plugin-jest-hoist/29.5.0: resolution: {integrity: sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4173,26 +3186,6 @@ packages: '@types/babel__traverse': 7.18.2 dev: true - /babel-preset-current-node-syntax/1.0.1_@babel+core@7.19.3: - resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.19.3 - '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.19.3 - '@babel/plugin-syntax-bigint': 7.8.3_@babel+core@7.19.3 - '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.19.3 - '@babel/plugin-syntax-import-meta': 7.10.4_@babel+core@7.19.3 - '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.19.3 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.19.3 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.19.3 - '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.19.3 - '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.3 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.19.3 - '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.19.3 - '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.19.3 - dev: true - /babel-preset-current-node-syntax/1.0.1_@babel+core@7.20.5: resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: @@ -4213,39 +3206,6 @@ packages: '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.20.5 dev: true - /babel-preset-jest/29.0.2_@babel+core@7.19.3: - resolution: {integrity: sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.19.3 - babel-plugin-jest-hoist: 29.0.2 - babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.3 - dev: true - - /babel-preset-jest/29.2.0_@babel+core@7.20.5: - resolution: {integrity: sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.20.5 - babel-plugin-jest-hoist: 29.2.0 - babel-preset-current-node-syntax: 1.0.1_@babel+core@7.20.5 - dev: true - - /babel-preset-jest/29.4.3_@babel+core@7.20.5: - resolution: {integrity: sha512-gWx6COtSuma6n9bw+8/F+2PCXrIgxV/D1TJFnp6OyBK2cxPWg0K9p/sriNYeifKjpUkMViWQ09DSWtzJQRETsw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.20.5 - babel-plugin-jest-hoist: 29.4.3 - babel-preset-current-node-syntax: 1.0.1_@babel+core@7.20.5 - dev: true - /babel-preset-jest/29.5.0_@babel+core@7.20.5: resolution: {integrity: sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4602,14 +3562,11 @@ packages: /chownr/1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} dev: true + optional: true /ci-info/3.3.0: resolution: {integrity: sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==} - /ci-info/3.4.0: - resolution: {integrity: sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==} - dev: true - /ci-info/3.5.0: resolution: {integrity: sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==} dev: true @@ -4761,6 +3718,7 @@ packages: /commander/8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} + dev: false /commander/9.4.1: resolution: {integrity: sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==} @@ -4802,6 +3760,13 @@ packages: yargs: 17.6.0 dev: true + /config-chain/1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + dev: false + /constant-case/3.0.4: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} dependencies: @@ -4949,10 +3914,6 @@ packages: stream-transform: 2.1.3 dev: true - /cuid/2.1.8: - resolution: {integrity: sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==} - dev: false - /current-module-paths/1.1.0: resolution: {integrity: sha512-HGhLUszcgprjKmzvQoCQda8iEWsQn3sWVzPdttyJVR5cjfVDYcoyozQA5D1YXgab9v84SPMpSuD+YrPX6i1IMQ==} engines: {node: '>=12.17'} @@ -5008,6 +3969,14 @@ packages: dependencies: mimic-response: 3.1.0 dev: true + optional: true + + /decompress-response/7.0.0: + resolution: {integrity: sha512-6IvPrADQyyPGLpMnUh6kfKiqy7SrbXbjoUuZ90WMBJKErzv2pCiwlGEXjRX9/54OnTq+XFVnkOnOMzclLI5aEA==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + dev: false /dedent/0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} @@ -5023,7 +3992,6 @@ packages: /deep-extend/0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} - dev: true /deep-is/0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -5090,6 +4058,7 @@ packages: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} dev: true + optional: true /detect-newline/3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} @@ -5103,21 +4072,6 @@ packages: wrappy: 1.0.2 dev: true - /diff-sequences/29.0.0: - resolution: {integrity: sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - - /diff-sequences/29.2.0: - resolution: {integrity: sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - - /diff-sequences/29.3.1: - resolution: {integrity: sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - /diff-sequences/29.4.3: resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5195,11 +4149,6 @@ packages: /electron-to-chromium/1.4.284: resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} - /emittery/0.10.2: - resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==} - engines: {node: '>=12'} - dev: true - /emittery/0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} @@ -5571,7 +4520,7 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - /eslint-plugin-jest/27.1.7_mqrelppxlcv42tihlyzz2qch5q: + /eslint-plugin-jest/27.1.7_bqsz7mnzvjsobat2umlw76do7a: resolution: {integrity: sha512-0QVzf+og4YI1Qr3UoprkqqhezAZjFffdi62b0IurkCXMqPtRW84/UT4CKsYT80h/D82LA9avjO/80Ou1LdgbaQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -5584,15 +4533,16 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/utils': 5.42.0_yvgxsonm4ikac5pm5z5getdq5e - eslint: 8.30.0 - jest: 29.0.3_johvxhudwcpndp4mle25vwrlq4 + '@typescript-eslint/eslint-plugin': 5.42.0_ofgjrzjuekeo7s3hdyz2yuzw34 + '@typescript-eslint/utils': 5.42.0_rmayb2veg2btbq6mbmnyivgasy + eslint: 8.27.0 + jest: 29.5.0_4f2ldd7um3b3u4eyvetyqsphze transitivePeerDependencies: - supports-color - typescript dev: true - /eslint-plugin-jest/27.1.7_pdoqwsgoaseomjcjdinsmwbl4q: + /eslint-plugin-jest/27.1.7_zoe3f4dgrzvdh6rxdjodmtuft4: resolution: {integrity: sha512-0QVzf+og4YI1Qr3UoprkqqhezAZjFffdi62b0IurkCXMqPtRW84/UT4CKsYT80h/D82LA9avjO/80Ou1LdgbaQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -5605,10 +4555,9 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.42.0_ofgjrzjuekeo7s3hdyz2yuzw34 - '@typescript-eslint/utils': 5.42.0_rmayb2veg2btbq6mbmnyivgasy - eslint: 8.27.0 - jest: 29.2.1_4f2ldd7um3b3u4eyvetyqsphze + '@typescript-eslint/utils': 5.42.0_yvgxsonm4ikac5pm5z5getdq5e + eslint: 8.30.0 + jest: 29.5.0_johvxhudwcpndp4mle25vwrlq4 transitivePeerDependencies: - supports-color - typescript @@ -5904,50 +4853,7 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} dev: true - - /expect/29.0.3: - resolution: {integrity: sha512-t8l5DTws3212VbmPL+tBFXhjRHLmctHB0oQbL8eUc6S7NzZtYUhycrFO9mkxA0ZUC6FAWdNi7JchJSkODtcu1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/expect-utils': 29.0.3 - jest-get-type: 29.0.0 - jest-matcher-utils: 29.0.3 - jest-message-util: 29.0.3 - jest-util: 29.0.3 - dev: true - - /expect/29.2.1: - resolution: {integrity: sha512-BJtA754Fba0YWRWHgjKUMTA3ltWarKgITXHQnbZ2mTxTXC4yMQlR0FI7HkB3fJYkhWBf4qjNiqvg3LDtXCcVRQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/expect-utils': 29.2.1 - jest-get-type: 29.2.0 - jest-matcher-utils: 29.2.1 - jest-message-util: 29.2.1 - jest-util: 29.2.1 - dev: true - - /expect/29.3.1: - resolution: {integrity: sha512-gGb1yTgU30Q0O/tQq+z30KBWv24ApkMgFUpvKBkyLUBL68Wv8dHdJxTBZFl/iT8K/bqDHvUYRH6IIN3rToopPA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/expect-utils': 29.3.1 - jest-get-type: 29.2.0 - jest-matcher-utils: 29.3.1 - jest-message-util: 29.3.1 - jest-util: 29.3.1 - dev: true - - /expect/29.4.3: - resolution: {integrity: sha512-uC05+Q7eXECFpgDrHdXA4k2rpMyStAYPItEDLyQDo5Ta7fVkJnNA/4zh/OIVkVVNZ1oOK1PipQoyNjuZ6sz6Dg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/expect-utils': 29.4.3 - jest-get-type: 29.4.3 - jest-matcher-utils: 29.4.3 - jest-message-util: 29.4.3 - jest-util: 29.4.3 - dev: true + optional: true /expect/29.5.0: resolution: {integrity: sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==} @@ -6215,6 +5121,18 @@ packages: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true + /follow-redirects/1.15.2_debug@4.3.4: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dependencies: + debug: 4.3.4 + dev: false + /for-each/0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: @@ -6252,6 +5170,13 @@ packages: engines: {node: '>= 0.6'} dev: true + /from2/2.3.0: + resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.7 + dev: false + /fs-constants/1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -6290,16 +5215,6 @@ packages: universalify: 0.1.2 dev: true - /fs-extra/9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} - dependencies: - at-least-node: 1.0.0 - graceful-fs: 4.2.10 - jsonfile: 6.1.0 - universalify: 2.0.0 - dev: true - /fs-jetpack/5.1.0: resolution: {integrity: sha512-Xn4fDhLydXkuzepZVsr02jakLlmoARPy+YWIclo4kh0GyNGUHnTqeH/w/qIsVn50dFxtp8otPL2t/HcPJBbxUA==} dependencies: @@ -6354,26 +5269,57 @@ packages: has-symbols: 1.0.3 dev: true - /get-package-type/0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - dev: true - - /get-stream/6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - /get-symbol-description/1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} - engines: {node: '>= 0.4'} + /get-it/8.1.1: + resolution: {integrity: sha512-83P2+3V/3E+KSdlHnGlOr4vCrlV8wDsT580AyJkMtkK/8LtZc0TOCI9bjQXH1sgYnmQTzCoPoPaOAE+a8JZqLQ==} + engines: {node: '>=14.0.0'} dependencies: - call-bind: 1.0.2 + debug: 4.3.4 + decompress-response: 7.0.0 + follow-redirects: 1.15.2_debug@4.3.4 + into-stream: 6.0.0 + is-plain-object: 5.0.0 + is-retry-allowed: 2.2.0 + is-stream: 2.0.1 + parse-headers: 2.0.5 + progress-stream: 2.0.0 + tunnel-agent: 0.6.0 + transitivePeerDependencies: + - supports-color + dev: false + + /get-latest-version/5.0.1: + resolution: {integrity: sha512-oonDx2gj9GzP+b1dxrd4KJwnTEFPjugNVALey5ze7cBkwjI/2BQ6X93hzE5cTVHV3DOPTB8U9HlbpEZVLjI1MQ==} + engines: {node: '>=14.18'} + dependencies: + get-it: 8.1.1 + registry-auth-token: 5.0.2 + registry-url: 5.1.0 + semver: 7.3.8 + transitivePeerDependencies: + - supports-color + dev: false + + /get-package-type/0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + dev: true + + /get-stream/6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + /get-symbol-description/1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 get-intrinsic: 1.1.3 dev: true /github-from-package/0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} dev: true + optional: true /glob-parent/5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -6649,7 +5595,6 @@ packages: /ini/1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: true /ini/2.0.0: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} @@ -6664,6 +5609,14 @@ packages: side-channel: 1.0.4 dev: true + /into-stream/6.0.0: + resolution: {integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==} + engines: {node: '>=10'} + dependencies: + from2: 2.3.0 + p-is-promise: 3.0.0 + dev: false + /ipaddr.js/1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -6787,6 +5740,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-plain-object/5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + dev: false + /is-regex/1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -6795,6 +5753,11 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-retry-allowed/2.2.0: + resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==} + engines: {node: '>=10'} + dev: false + /is-shared-array-buffer/1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: @@ -6876,19 +5839,6 @@ packages: engines: {node: '>=8'} dev: true - /istanbul-lib-instrument/5.2.0: - resolution: {integrity: sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==} - engines: {node: '>=8'} - dependencies: - '@babel/core': 7.19.3 - '@babel/parser': 7.19.4 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.0 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: true - /istanbul-lib-instrument/5.2.1: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} @@ -6930,30 +5880,6 @@ packages: istanbul-lib-report: 3.0.0 dev: true - /jest-changed-files/29.0.0: - resolution: {integrity: sha512-28/iDMDrUpGoCitTURuDqUzWQoWmOmOKOFST1mi2lwh62X4BFf6khgH3uSuo1e49X/UDjuApAj3w0wLOex4VPQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - execa: 5.1.1 - p-limit: 3.1.0 - dev: true - - /jest-changed-files/29.2.0: - resolution: {integrity: sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - execa: 5.1.1 - p-limit: 3.1.0 - dev: true - - /jest-changed-files/29.4.3: - resolution: {integrity: sha512-Vn5cLuWuwmi2GNNbokPOEcvrXGSGrqVnPEZV7rC6P7ck07Dyw9RFnvWglnupSh+hGys0ajGtw/bc2ZgweljQoQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - execa: 5.1.1 - p-limit: 3.1.0 - dev: true - /jest-changed-files/29.5.0: resolution: {integrity: sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -6962,87 +5888,6 @@ packages: p-limit: 3.1.0 dev: true - /jest-circus/29.0.3: - resolution: {integrity: sha512-QeGzagC6Hw5pP+df1+aoF8+FBSgkPmraC1UdkeunWh0jmrp7wC0Hr6umdUAOELBQmxtKAOMNC3KAdjmCds92Zg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.0.3 - '@jest/expect': 29.0.3 - '@jest/test-result': 29.0.3 - '@jest/types': 29.0.3 - '@types/node': 18.14.2 - chalk: 4.1.2 - co: 4.6.0 - dedent: 0.7.0 - is-generator-fn: 2.1.0 - jest-each: 29.0.3 - jest-matcher-utils: 29.0.3 - jest-message-util: 29.0.3 - jest-runtime: 29.0.3 - jest-snapshot: 29.0.3 - jest-util: 29.0.3 - p-limit: 3.1.0 - pretty-format: 29.0.3 - slash: 3.0.0 - stack-utils: 2.0.5 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-circus/29.2.1: - resolution: {integrity: sha512-W+ZQQ5ln4Db2UZNM4NJIeasnhCdDhSuYW4eLgNAUi0XiSSpF634Kc5wiPvGiHvTgXMFVn1ZgWIijqhi9+kLNLg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.2.1 - '@jest/expect': 29.2.1 - '@jest/test-result': 29.2.1 - '@jest/types': 29.2.1 - '@types/node': 18.14.2 - chalk: 4.1.2 - co: 4.6.0 - dedent: 0.7.0 - is-generator-fn: 2.1.0 - jest-each: 29.2.1 - jest-matcher-utils: 29.2.1 - jest-message-util: 29.2.1 - jest-runtime: 29.2.1 - jest-snapshot: 29.2.1 - jest-util: 29.2.1 - p-limit: 3.1.0 - pretty-format: 29.2.1 - slash: 3.0.0 - stack-utils: 2.0.5 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-circus/29.4.3: - resolution: {integrity: sha512-Vw/bVvcexmdJ7MLmgdT3ZjkJ3LKu8IlpefYokxiqoZy6OCQ2VAm6Vk3t/qHiAGUXbdbJKJWnc8gH3ypTbB/OBw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.4.3 - '@jest/expect': 29.4.3 - '@jest/test-result': 29.4.3 - '@jest/types': 29.4.3 - '@types/node': 18.14.2 - chalk: 4.1.2 - co: 4.6.0 - dedent: 0.7.0 - is-generator-fn: 2.1.0 - jest-each: 29.4.3 - jest-matcher-utils: 29.4.3 - jest-message-util: 29.4.3 - jest-runtime: 29.4.3 - jest-snapshot: 29.4.3 - jest-util: 29.4.3 - p-limit: 3.1.0 - pretty-format: 29.4.3 - slash: 3.0.0 - stack-utils: 2.0.5 - transitivePeerDependencies: - - supports-color - dev: true - /jest-circus/29.5.0: resolution: {integrity: sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7071,36 +5916,8 @@ packages: - supports-color dev: true - /jest-cli/29.0.3_johvxhudwcpndp4mle25vwrlq4: - resolution: {integrity: sha512-aUy9Gd/Kut1z80eBzG10jAn6BgS3BoBbXyv+uXEqBJ8wnnuZ5RpNfARoskSrTIy1GY4a8f32YGuCMwibtkl9CQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/core': 29.0.3_ts-node@10.9.1 - '@jest/test-result': 29.0.3 - '@jest/types': 29.0.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.10 - import-local: 3.1.0 - jest-config: 29.0.3_johvxhudwcpndp4mle25vwrlq4 - jest-util: 29.0.3 - jest-validate: 29.0.3 - prompts: 2.4.2 - yargs: 17.5.1 - transitivePeerDependencies: - - '@types/node' - - supports-color - - ts-node - dev: true - - /jest-cli/29.2.1_4f2ldd7um3b3u4eyvetyqsphze: - resolution: {integrity: sha512-UIMD5aNqvPKpdlJSaeUAoLfxsh9TZvOkaMETx5qXnkboc317bcbb0eLHbIj8sFBHdcJAIAM+IRKnIU7Wi61MBw==} + /jest-cli/29.5.0: + resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true peerDependencies: @@ -7109,26 +5926,26 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 29.2.1_ts-node@10.9.1 - '@jest/test-result': 29.2.1 - '@jest/types': 29.2.1 + '@jest/core': 29.5.0 + '@jest/test-result': 29.5.0 + '@jest/types': 29.5.0 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.1.0 - jest-config: 29.2.1_4f2ldd7um3b3u4eyvetyqsphze - jest-util: 29.2.1 - jest-validate: 29.2.1 + jest-config: 29.5.0 + jest-util: 29.5.0 + jest-validate: 29.5.0 prompts: 2.4.2 - yargs: 17.6.0 + yargs: 17.6.2 transitivePeerDependencies: - '@types/node' - supports-color - ts-node dev: true - /jest-cli/29.4.3: - resolution: {integrity: sha512-PiiAPuFNfWWolCE6t3ZrDXQc6OsAuM3/tVW0u27UWc1KE+n/HSn5dSE6B2juqN7WP+PP0jAcnKtGmI4u8GMYCg==} + /jest-cli/29.5.0_4f2ldd7um3b3u4eyvetyqsphze: + resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true peerDependencies: @@ -7137,16 +5954,16 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 29.4.3 - '@jest/test-result': 29.4.3 - '@jest/types': 29.4.3 + '@jest/core': 29.5.0_ts-node@10.9.1 + '@jest/test-result': 29.5.0 + '@jest/types': 29.5.0 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.1.0 - jest-config: 29.4.3 - jest-util: 29.4.3 - jest-validate: 29.4.3 + jest-config: 29.5.0_4f2ldd7um3b3u4eyvetyqsphze + jest-util: 29.5.0 + jest-validate: 29.5.0 prompts: 2.4.2 yargs: 17.6.2 transitivePeerDependencies: @@ -7155,7 +5972,7 @@ packages: - ts-node dev: true - /jest-cli/29.5.0: + /jest-cli/29.5.0_johvxhudwcpndp4mle25vwrlq4: resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -7165,14 +5982,14 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 29.5.0 + '@jest/core': 29.5.0_ts-node@10.9.1 '@jest/test-result': 29.5.0 '@jest/types': 29.5.0 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.1.0 - jest-config: 29.5.0 + jest-config: 29.5.0_johvxhudwcpndp4mle25vwrlq4 jest-util: 29.5.0 jest-validate: 29.5.0 prompts: 2.4.2 @@ -7183,8 +6000,8 @@ packages: - ts-node dev: true - /jest-config/29.0.3_jboh4c3iv3wxuja4m36ecyac7e: - resolution: {integrity: sha512-U5qkc82HHVYe3fNu2CRXLN4g761Na26rWKf7CjM8LlZB3In1jadEkZdMwsE37rd9RSPV0NfYaCjHdk/gu3v+Ew==} + /jest-config/29.5.0: + resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@types/node': '*' @@ -7195,36 +6012,34 @@ packages: ts-node: optional: true dependencies: - '@babel/core': 7.19.3 - '@jest/test-sequencer': 29.0.3 - '@jest/types': 29.0.3 - '@types/node': 18.14.2 - babel-jest: 29.0.3_@babel+core@7.19.3 + '@babel/core': 7.20.5 + '@jest/test-sequencer': 29.5.0 + '@jest/types': 29.5.0 + babel-jest: 29.5.0_@babel+core@7.20.5 chalk: 4.1.2 - ci-info: 3.4.0 + ci-info: 3.7.1 deepmerge: 4.2.2 glob: 7.2.3 graceful-fs: 4.2.10 - jest-circus: 29.0.3 - jest-environment-node: 29.0.3 - jest-get-type: 29.0.0 - jest-regex-util: 29.0.0 - jest-resolve: 29.0.3 - jest-runner: 29.0.3 - jest-util: 29.0.3 - jest-validate: 29.0.3 + jest-circus: 29.5.0 + jest-environment-node: 29.5.0 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.5.0 + jest-runner: 29.5.0 + jest-util: 29.5.0 + jest-validate: 29.5.0 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.0.3 + pretty-format: 29.5.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa transitivePeerDependencies: - supports-color dev: true - /jest-config/29.0.3_johvxhudwcpndp4mle25vwrlq4: - resolution: {integrity: sha512-U5qkc82HHVYe3fNu2CRXLN4g761Na26rWKf7CjM8LlZB3In1jadEkZdMwsE37rd9RSPV0NfYaCjHdk/gu3v+Ew==} + /jest-config/29.5.0_4f2ldd7um3b3u4eyvetyqsphze: + resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@types/node': '*' @@ -7235,36 +6050,36 @@ packages: ts-node: optional: true dependencies: - '@babel/core': 7.19.3 - '@jest/test-sequencer': 29.0.3 - '@jest/types': 29.0.3 - '@types/node': 14.18.29 - babel-jest: 29.0.3_@babel+core@7.19.3 + '@babel/core': 7.20.5 + '@jest/test-sequencer': 29.5.0 + '@jest/types': 29.5.0 + '@types/node': 14.18.32 + babel-jest: 29.5.0_@babel+core@7.20.5 chalk: 4.1.2 - ci-info: 3.4.0 + ci-info: 3.7.1 deepmerge: 4.2.2 glob: 7.2.3 graceful-fs: 4.2.10 - jest-circus: 29.0.3 - jest-environment-node: 29.0.3 - jest-get-type: 29.0.0 - jest-regex-util: 29.0.0 - jest-resolve: 29.0.3 - jest-runner: 29.0.3 - jest-util: 29.0.3 - jest-validate: 29.0.3 + jest-circus: 29.5.0 + jest-environment-node: 29.5.0 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.5.0 + jest-runner: 29.5.0 + jest-util: 29.5.0 + jest-validate: 29.5.0 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.0.3 + pretty-format: 29.5.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa + ts-node: 10.9.1_jcmx33t3olsvcxopqdljsohpme transitivePeerDependencies: - supports-color dev: true - /jest-config/29.2.1_4f2ldd7um3b3u4eyvetyqsphze: - resolution: {integrity: sha512-EV5F1tQYW/quZV2br2o88hnYEeRzG53Dfi6rSG3TZBuzGQ6luhQBux/RLlU5QrJjCdq3LXxRRM8F1LP6DN1ycA==} + /jest-config/29.5.0_@types+node@18.14.2: + resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@types/node': '*' @@ -7276,35 +6091,34 @@ packages: optional: true dependencies: '@babel/core': 7.20.5 - '@jest/test-sequencer': 29.2.1 - '@jest/types': 29.2.1 - '@types/node': 14.18.32 - babel-jest: 29.2.1_@babel+core@7.20.5 + '@jest/test-sequencer': 29.5.0 + '@jest/types': 29.5.0 + '@types/node': 18.14.2 + babel-jest: 29.5.0_@babel+core@7.20.5 chalk: 4.1.2 - ci-info: 3.5.0 + ci-info: 3.7.1 deepmerge: 4.2.2 glob: 7.2.3 graceful-fs: 4.2.10 - jest-circus: 29.2.1 - jest-environment-node: 29.2.1 - jest-get-type: 29.2.0 - jest-regex-util: 29.2.0 - jest-resolve: 29.2.1 - jest-runner: 29.2.1 - jest-util: 29.2.1 - jest-validate: 29.2.1 + jest-circus: 29.5.0 + jest-environment-node: 29.5.0 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.5.0 + jest-runner: 29.5.0 + jest-util: 29.5.0 + jest-validate: 29.5.0 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.2.1 + pretty-format: 29.5.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.1_jcmx33t3olsvcxopqdljsohpme transitivePeerDependencies: - supports-color dev: true - /jest-config/29.2.1_jboh4c3iv3wxuja4m36ecyac7e: - resolution: {integrity: sha512-EV5F1tQYW/quZV2br2o88hnYEeRzG53Dfi6rSG3TZBuzGQ6luhQBux/RLlU5QrJjCdq3LXxRRM8F1LP6DN1ycA==} + /jest-config/29.5.0_jboh4c3iv3wxuja4m36ecyac7e: + resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@types/node': '*' @@ -7316,26 +6130,26 @@ packages: optional: true dependencies: '@babel/core': 7.20.5 - '@jest/test-sequencer': 29.2.1 - '@jest/types': 29.2.1 + '@jest/test-sequencer': 29.5.0 + '@jest/types': 29.5.0 '@types/node': 18.14.2 - babel-jest: 29.2.1_@babel+core@7.20.5 + babel-jest: 29.5.0_@babel+core@7.20.5 chalk: 4.1.2 - ci-info: 3.5.0 + ci-info: 3.7.1 deepmerge: 4.2.2 glob: 7.2.3 graceful-fs: 4.2.10 - jest-circus: 29.2.1 - jest-environment-node: 29.2.1 - jest-get-type: 29.2.0 - jest-regex-util: 29.2.0 - jest-resolve: 29.2.1 - jest-runner: 29.2.1 - jest-util: 29.2.1 - jest-validate: 29.2.1 + jest-circus: 29.5.0 + jest-environment-node: 29.5.0 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.5.0 + jest-runner: 29.5.0 + jest-util: 29.5.0 + jest-validate: 29.5.0 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.2.1 + pretty-format: 29.5.0 slash: 3.0.0 strip-json-comments: 3.1.1 ts-node: 10.9.1_jcmx33t3olsvcxopqdljsohpme @@ -7343,8 +6157,8 @@ packages: - supports-color dev: true - /jest-config/29.4.3: - resolution: {integrity: sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==} + /jest-config/29.5.0_johvxhudwcpndp4mle25vwrlq4: + resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@types/node': '*' @@ -7356,189 +6170,35 @@ packages: optional: true dependencies: '@babel/core': 7.20.5 - '@jest/test-sequencer': 29.4.3 - '@jest/types': 29.4.3 - babel-jest: 29.4.3_@babel+core@7.20.5 + '@jest/test-sequencer': 29.5.0 + '@jest/types': 29.5.0 + '@types/node': 14.18.29 + babel-jest: 29.5.0_@babel+core@7.20.5 chalk: 4.1.2 ci-info: 3.7.1 deepmerge: 4.2.2 glob: 7.2.3 graceful-fs: 4.2.10 - jest-circus: 29.4.3 - jest-environment-node: 29.4.3 + jest-circus: 29.5.0 + jest-environment-node: 29.5.0 jest-get-type: 29.4.3 jest-regex-util: 29.4.3 - jest-resolve: 29.4.3 - jest-runner: 29.4.3 - jest-util: 29.4.3 - jest-validate: 29.4.3 + jest-resolve: 29.5.0 + jest-runner: 29.5.0 + jest-util: 29.5.0 + jest-validate: 29.5.0 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.4.3 + pretty-format: 29.5.0 slash: 3.0.0 strip-json-comments: 3.1.1 + ts-node: 10.9.1_ck2axrxkiif44rdbzjywaqjysa transitivePeerDependencies: - supports-color dev: true - /jest-config/29.4.3_@types+node@18.14.2: - resolution: {integrity: sha512-eCIpqhGnIjdUCXGtLhz4gdDoxKSWXKjzNcc5r+0S1GKOp2fwOipx5mRcwa9GB/ArsxJ1jlj2lmlD9bZAsBxaWQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.20.5 - '@jest/test-sequencer': 29.4.3 - '@jest/types': 29.4.3 - '@types/node': 18.14.2 - babel-jest: 29.4.3_@babel+core@7.20.5 - chalk: 4.1.2 - ci-info: 3.7.1 - deepmerge: 4.2.2 - glob: 7.2.3 - graceful-fs: 4.2.10 - jest-circus: 29.4.3 - jest-environment-node: 29.4.3 - jest-get-type: 29.4.3 - jest-regex-util: 29.4.3 - jest-resolve: 29.4.3 - jest-runner: 29.4.3 - jest-util: 29.4.3 - jest-validate: 29.4.3 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 29.4.3 - slash: 3.0.0 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-config/29.5.0: - resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.20.5 - '@jest/test-sequencer': 29.5.0 - '@jest/types': 29.5.0 - babel-jest: 29.5.0_@babel+core@7.20.5 - chalk: 4.1.2 - ci-info: 3.7.1 - deepmerge: 4.2.2 - glob: 7.2.3 - graceful-fs: 4.2.10 - jest-circus: 29.5.0 - jest-environment-node: 29.5.0 - jest-get-type: 29.4.3 - jest-regex-util: 29.4.3 - jest-resolve: 29.5.0 - jest-runner: 29.5.0 - jest-util: 29.5.0 - jest-validate: 29.5.0 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 29.5.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-config/29.5.0_@types+node@18.14.2: - resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.20.5 - '@jest/test-sequencer': 29.5.0 - '@jest/types': 29.5.0 - '@types/node': 18.14.2 - babel-jest: 29.5.0_@babel+core@7.20.5 - chalk: 4.1.2 - ci-info: 3.7.1 - deepmerge: 4.2.2 - glob: 7.2.3 - graceful-fs: 4.2.10 - jest-circus: 29.5.0 - jest-environment-node: 29.5.0 - jest-get-type: 29.4.3 - jest-regex-util: 29.4.3 - jest-resolve: 29.5.0 - jest-runner: 29.5.0 - jest-util: 29.5.0 - jest-validate: 29.5.0 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 29.5.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-diff/29.0.3: - resolution: {integrity: sha512-+X/AIF5G/vX9fWK+Db9bi9BQas7M9oBME7egU7psbn4jlszLFCu0dW63UgeE6cs/GANq4fLaT+8sGHQQ0eCUfg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - diff-sequences: 29.0.0 - jest-get-type: 29.0.0 - pretty-format: 29.0.3 - dev: true - - /jest-diff/29.2.1: - resolution: {integrity: sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - diff-sequences: 29.2.0 - jest-get-type: 29.2.0 - pretty-format: 29.2.1 - dev: true - - /jest-diff/29.3.1: - resolution: {integrity: sha512-vU8vyiO7568tmin2lA3r2DP8oRvzhvRcD4DjpXc6uGveQodyk7CKLhQlCSiwgx3g0pFaE88/KLZ0yaTWMc4Uiw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - diff-sequences: 29.3.1 - jest-get-type: 29.2.0 - pretty-format: 29.3.1 - dev: true - - /jest-diff/29.4.3: - resolution: {integrity: sha512-YB+ocenx7FZ3T5O9lMVMeLYV4265socJKtkwgk/6YUz/VsEzYDkiMuMhWzZmxm3wDRQvayJu/PjkjjSkjoHsCA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - diff-sequences: 29.4.3 - jest-get-type: 29.4.3 - pretty-format: 29.4.3 - dev: true - - /jest-diff/29.5.0: - resolution: {integrity: sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==} + /jest-diff/29.5.0: + resolution: {integrity: sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 @@ -7547,20 +6207,6 @@ packages: pretty-format: 29.5.0 dev: true - /jest-docblock/29.0.0: - resolution: {integrity: sha512-s5Kpra/kLzbqu9dEjov30kj1n4tfu3e7Pl8v+f8jOkeWNqM6Ds8jRaJfZow3ducoQUrf2Z4rs2N5S3zXnb83gw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - detect-newline: 3.1.0 - dev: true - - /jest-docblock/29.2.0: - resolution: {integrity: sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - detect-newline: 3.1.0 - dev: true - /jest-docblock/29.4.3: resolution: {integrity: sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7568,39 +6214,6 @@ packages: detect-newline: 3.1.0 dev: true - /jest-each/29.0.3: - resolution: {integrity: sha512-wILhZfESURHHBNvPMJ0lZlYZrvOQJxAo3wNHi+ycr90V7M+uGR9Gh4+4a/BmaZF0XTyZsk4OiYEf3GJN7Ltqzg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.0.3 - chalk: 4.1.2 - jest-get-type: 29.0.0 - jest-util: 29.0.3 - pretty-format: 29.0.3 - dev: true - - /jest-each/29.2.1: - resolution: {integrity: sha512-sGP86H/CpWHMyK3qGIGFCgP6mt+o5tu9qG4+tobl0LNdgny0aitLXs9/EBacLy3Bwqy+v4uXClqJgASJWcruYw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.2.1 - chalk: 4.1.2 - jest-get-type: 29.2.0 - jest-util: 29.2.1 - pretty-format: 29.2.1 - dev: true - - /jest-each/29.4.3: - resolution: {integrity: sha512-1ElHNAnKcbJb/b+L+7j0/w7bDvljw4gTv1wL9fYOczeJrbTbkMGQ5iQPFJ3eFQH19VPTx1IyfePdqSpePKss7Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.4.3 - chalk: 4.1.2 - jest-get-type: 29.4.3 - jest-util: 29.4.3 - pretty-format: 29.4.3 - dev: true - /jest-each/29.5.0: resolution: {integrity: sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7612,42 +6225,6 @@ packages: pretty-format: 29.5.0 dev: true - /jest-environment-node/29.0.3: - resolution: {integrity: sha512-cdZqRCnmIlTXC+9vtvmfiY/40Cj6s2T0czXuq1whvQdmpzAnj4sbqVYuZ4zFHk766xTTJ+Ij3uUqkk8KCfXoyg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.0.3 - '@jest/fake-timers': 29.0.3 - '@jest/types': 29.0.3 - '@types/node': 18.14.2 - jest-mock: 29.0.3 - jest-util: 29.0.3 - dev: true - - /jest-environment-node/29.2.1: - resolution: {integrity: sha512-PulFKwEMz6nTAdLUwglFKei3b/LixwlRiqTN6nvPE1JtrLtlnpd6LXnFI1NFHYJGlTmIWilMP2n9jEtPPKX50g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.2.1 - '@jest/fake-timers': 29.2.1 - '@jest/types': 29.2.1 - '@types/node': 18.14.2 - jest-mock: 29.2.1 - jest-util: 29.2.1 - dev: true - - /jest-environment-node/29.4.3: - resolution: {integrity: sha512-gAiEnSKF104fsGDXNkwk49jD/0N0Bqu2K9+aMQXA6avzsA9H3Fiv1PW2D+gzbOSR705bWd2wJZRFEFpV0tXISg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.4.3 - '@jest/fake-timers': 29.4.3 - '@jest/types': 29.4.3 - '@types/node': 18.14.2 - jest-mock: 29.4.3 - jest-util: 29.4.3 - dev: true - /jest-environment-node/29.5.0: resolution: {integrity: sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7669,97 +6246,11 @@ packages: - encoding dev: true - /jest-get-type/29.0.0: - resolution: {integrity: sha512-83X19z/HuLKYXYHskZlBAShO7UfLFXu/vWajw9ZNJASN32li8yHMaVGAQqxFW1RCFOkB7cubaL6FaJVQqqJLSw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - - /jest-get-type/29.2.0: - resolution: {integrity: sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - /jest-get-type/29.4.3: resolution: {integrity: sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true - /jest-haste-map/29.0.3: - resolution: {integrity: sha512-uMqR99+GuBHo0RjRhOE4iA6LmsxEwRdgiIAQgMU/wdT2XebsLDz5obIwLZm/Psj+GwSEQhw9AfAVKGYbh2G55A==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.0.3 - '@types/graceful-fs': 4.1.5 - '@types/node': 18.14.2 - anymatch: 3.1.2 - fb-watchman: 2.0.2 - graceful-fs: 4.2.10 - jest-regex-util: 29.0.0 - jest-util: 29.0.3 - jest-worker: 29.0.3 - micromatch: 4.0.5 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /jest-haste-map/29.2.1: - resolution: {integrity: sha512-wF460rAFmYc6ARcCFNw4MbGYQjYkvjovb9GBT+W10Um8q5nHq98jD6fHZMDMO3tA56S8XnmNkM8GcA8diSZfnA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.2.1 - '@types/graceful-fs': 4.1.5 - '@types/node': 18.14.2 - anymatch: 3.1.2 - fb-watchman: 2.0.2 - graceful-fs: 4.2.10 - jest-regex-util: 29.2.0 - jest-util: 29.2.1 - jest-worker: 29.2.1 - micromatch: 4.0.5 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /jest-haste-map/29.3.1: - resolution: {integrity: sha512-/FFtvoG1xjbbPXQLFef+WSU4yrc0fc0Dds6aRPBojUid7qlPqZvxdUBA03HW0fnVHXVCnCdkuoghYItKNzc/0A==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.3.1 - '@types/graceful-fs': 4.1.5 - '@types/node': 18.14.2 - anymatch: 3.1.2 - fb-watchman: 2.0.2 - graceful-fs: 4.2.10 - jest-regex-util: 29.2.0 - jest-util: 29.3.1 - jest-worker: 29.3.1 - micromatch: 4.0.5 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /jest-haste-map/29.4.3: - resolution: {integrity: sha512-eZIgAS8tvm5IZMtKlR8Y+feEOMfo2pSQkmNbufdbMzMSn9nitgGxF1waM/+LbryO3OkMcKS98SUb+j/cQxp/vQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.4.3 - '@types/graceful-fs': 4.1.5 - '@types/node': 18.14.2 - anymatch: 3.1.2 - fb-watchman: 2.0.2 - graceful-fs: 4.2.10 - jest-regex-util: 29.4.3 - jest-util: 29.4.3 - jest-worker: 29.4.3 - micromatch: 4.0.5 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.2 - dev: true - /jest-haste-map/29.5.0: resolution: {integrity: sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7779,30 +6270,6 @@ packages: fsevents: 2.3.2 dev: true - /jest-leak-detector/29.0.3: - resolution: {integrity: sha512-YfW/G63dAuiuQ3QmQlh8hnqLDe25WFY3eQhuc/Ev1AGmkw5zREblTh7TCSKLoheyggu6G9gxO2hY8p9o6xbaRQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.0.0 - pretty-format: 29.0.3 - dev: true - - /jest-leak-detector/29.2.1: - resolution: {integrity: sha512-1YvSqYoiurxKOJtySc+CGVmw/e1v4yNY27BjWTVzp0aTduQeA7pdieLiW05wTYG/twlKOp2xS/pWuikQEmklug==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.2.0 - pretty-format: 29.2.1 - dev: true - - /jest-leak-detector/29.4.3: - resolution: {integrity: sha512-9yw4VC1v2NspMMeV3daQ1yXPNxMgCzwq9BocCwYrRgXe4uaEJPAN0ZK37nFBhcy3cUwEVstFecFLaTHpF7NiGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.4.3 - pretty-format: 29.4.3 - dev: true - /jest-leak-detector/29.5.0: resolution: {integrity: sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7811,46 +6278,6 @@ packages: pretty-format: 29.5.0 dev: true - /jest-matcher-utils/29.0.3: - resolution: {integrity: sha512-RsR1+cZ6p1hDV4GSCQTg+9qjeotQCgkaleIKLK7dm+U4V/H2bWedU3RAtLm8+mANzZ7eDV33dMar4pejd7047w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - jest-diff: 29.0.3 - jest-get-type: 29.0.0 - pretty-format: 29.0.3 - dev: true - - /jest-matcher-utils/29.2.1: - resolution: {integrity: sha512-hUTBh7H/Mnb6GTpihbLh8uF5rjAMdekfW/oZNXUMAXi7bbmym2HiRpzgqf/zzkjgejMrVAkPdVSQj+32enlUww==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - jest-diff: 29.2.1 - jest-get-type: 29.2.0 - pretty-format: 29.2.1 - dev: true - - /jest-matcher-utils/29.3.1: - resolution: {integrity: sha512-fkRMZUAScup3txIKfMe3AIZZmPEjWEdsPJFK3AIy5qRohWqQFg1qrmKfYXR9qEkNc7OdAu2N4KPHibEmy4HPeQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - jest-diff: 29.3.1 - jest-get-type: 29.2.0 - pretty-format: 29.3.1 - dev: true - - /jest-matcher-utils/29.4.3: - resolution: {integrity: sha512-TTciiXEONycZ03h6R6pYiZlSkvYgT0l8aa49z/DLSGYjex4orMUcafuLXYyyEDWB1RKglq00jzwY00Ei7yFNVg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - jest-diff: 29.4.3 - jest-get-type: 29.4.3 - pretty-format: 29.4.3 - dev: true - /jest-matcher-utils/29.5.0: resolution: {integrity: sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7861,66 +6288,6 @@ packages: pretty-format: 29.5.0 dev: true - /jest-message-util/29.0.3: - resolution: {integrity: sha512-7T8JiUTtDfppojosORAflABfLsLKMLkBHSWkjNQrjIltGoDzNGn7wEPOSfjqYAGTYME65esQzMJxGDjuLBKdOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/code-frame': 7.18.6 - '@jest/types': 29.0.3 - '@types/stack-utils': 2.0.1 - chalk: 4.1.2 - graceful-fs: 4.2.10 - micromatch: 4.0.5 - pretty-format: 29.0.3 - slash: 3.0.0 - stack-utils: 2.0.5 - dev: true - - /jest-message-util/29.2.1: - resolution: {integrity: sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/code-frame': 7.18.6 - '@jest/types': 29.2.1 - '@types/stack-utils': 2.0.1 - chalk: 4.1.2 - graceful-fs: 4.2.10 - micromatch: 4.0.5 - pretty-format: 29.2.1 - slash: 3.0.0 - stack-utils: 2.0.5 - dev: true - - /jest-message-util/29.3.1: - resolution: {integrity: sha512-lMJTbgNcDm5z+6KDxWtqOFWlGQxD6XaYwBqHR8kmpkP+WWWG90I35kdtQHY67Ay5CSuydkTBbJG+tH9JShFCyA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/code-frame': 7.18.6 - '@jest/types': 29.3.1 - '@types/stack-utils': 2.0.1 - chalk: 4.1.2 - graceful-fs: 4.2.10 - micromatch: 4.0.5 - pretty-format: 29.3.1 - slash: 3.0.0 - stack-utils: 2.0.5 - dev: true - - /jest-message-util/29.4.3: - resolution: {integrity: sha512-1Y8Zd4ZCN7o/QnWdMmT76If8LuDv23Z1DRovBj/vcSFNlGCJGoO8D1nJDw1AdyAGUk0myDLFGN5RbNeJyCRGCw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/code-frame': 7.18.6 - '@jest/types': 29.5.0 - '@types/stack-utils': 2.0.1 - chalk: 4.1.2 - graceful-fs: 4.2.10 - micromatch: 4.0.5 - pretty-format: 29.4.3 - slash: 3.0.0 - stack-utils: 2.0.5 - dev: true - /jest-message-util/29.5.0: resolution: {integrity: sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7936,41 +6303,6 @@ packages: stack-utils: 2.0.5 dev: true - /jest-mock/29.0.3: - resolution: {integrity: sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.0.3 - '@types/node': 18.14.2 - dev: true - - /jest-mock/29.2.1: - resolution: {integrity: sha512-NDphaY/GqyQpTfnTZiTqqpMaw4Z0I7XnB7yBgrT6IwYrLGxpOhrejYr4ANY4YvO2sEGdd8Tx/6D0+WLQy7/qDA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.2.1 - '@types/node': 18.14.2 - jest-util: 29.2.1 - dev: true - - /jest-mock/29.3.1: - resolution: {integrity: sha512-H8/qFDtDVMFvFP4X8NuOT3XRDzOUTz+FeACjufHzsOIBAxivLqkB1PoLCaJx9iPPQ8dZThHPp/G3WRWyMgA3JA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.3.1 - '@types/node': 18.14.2 - jest-util: 29.3.1 - dev: true - - /jest-mock/29.4.3: - resolution: {integrity: sha512-LjFgMg+xed9BdkPMyIJh+r3KeHt1klXPJYBULXVVAkbTaaKjPX1o1uVCAZADMEp/kOxGTwy/Ot8XbvgItOrHEg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.4.3 - '@types/node': 18.14.2 - jest-util: 29.4.3 - dev: true - /jest-mock/29.5.0: resolution: {integrity: sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -7980,42 +6312,6 @@ packages: jest-util: 29.5.0 dev: true - /jest-pnp-resolver/1.2.2_jest-resolve@29.0.3: - resolution: {integrity: sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - dependencies: - jest-resolve: 29.0.3 - dev: true - - /jest-pnp-resolver/1.2.2_jest-resolve@29.2.1: - resolution: {integrity: sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - dependencies: - jest-resolve: 29.2.1 - dev: true - - /jest-pnp-resolver/1.2.2_jest-resolve@29.4.3: - resolution: {integrity: sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - dependencies: - jest-resolve: 29.4.3 - dev: true - /jest-pnp-resolver/1.2.2_jest-resolve@29.5.0: resolution: {integrity: sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==} engines: {node: '>=6'} @@ -8023,54 +6319,14 @@ packages: jest-resolve: '*' peerDependenciesMeta: jest-resolve: - optional: true - dependencies: - jest-resolve: 29.5.0 - dev: true - - /jest-regex-util/29.0.0: - resolution: {integrity: sha512-BV7VW7Sy0fInHWN93MMPtlClweYv2qrSCwfeFWmpribGZtQPWNvRSq9XOVgOEjU1iBGRKXUZil0o2AH7Iy9Lug==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - - /jest-regex-util/29.2.0: - resolution: {integrity: sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - - /jest-regex-util/29.4.3: - resolution: {integrity: sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dev: true - - /jest-resolve-dependencies/29.0.3: - resolution: {integrity: sha512-KzuBnXqNvbuCdoJpv8EanbIGObk7vUBNt/PwQPPx2aMhlv/jaXpUJsqWYRpP/0a50faMBY7WFFP8S3/CCzwfDw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-regex-util: 29.0.0 - jest-snapshot: 29.0.3 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-resolve-dependencies/29.2.1: - resolution: {integrity: sha512-o3mUGX2j08usj1jIAIE8KmUVpqVAn54k80kI27ldbZf2oJn6eghhB6DvJxjrcH40va9CQgWTfU5f2Ag/MoUqgQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + optional: true dependencies: - jest-regex-util: 29.2.0 - jest-snapshot: 29.2.1 - transitivePeerDependencies: - - supports-color + jest-resolve: 29.5.0 dev: true - /jest-resolve-dependencies/29.4.3: - resolution: {integrity: sha512-uvKMZAQ3nmXLH7O8WAOhS5l0iWyT3WmnJBdmIHiV5tBbdaDZ1wqtNX04FONGoaFvSOSHBJxnwAVnSn1WHdGVaw==} + /jest-regex-util/29.4.3: + resolution: {integrity: sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-regex-util: 29.4.3 - jest-snapshot: 29.4.3 - transitivePeerDependencies: - - supports-color dev: true /jest-resolve-dependencies/29.5.0: @@ -8083,51 +6339,6 @@ packages: - supports-color dev: true - /jest-resolve/29.0.3: - resolution: {integrity: sha512-toVkia85Y/BPAjJasTC9zIPY6MmVXQPtrCk8SmiheC4MwVFE/CMFlOtMN6jrwPMC6TtNh8+sTMllasFeu1wMPg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.10 - jest-haste-map: 29.0.3 - jest-pnp-resolver: 1.2.2_jest-resolve@29.0.3 - jest-util: 29.0.3 - jest-validate: 29.0.3 - resolve: 1.22.1 - resolve.exports: 1.1.0 - slash: 3.0.0 - dev: true - - /jest-resolve/29.2.1: - resolution: {integrity: sha512-1dJTW76Z9622Viq4yRcwBuEXuzGtE9B2kdl05RC8Om/lAzac9uEgC+M8Q5osVidbuBPmxm8wSrcItYhca2ZAtQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.10 - jest-haste-map: 29.2.1 - jest-pnp-resolver: 1.2.2_jest-resolve@29.2.1 - jest-util: 29.2.1 - jest-validate: 29.2.1 - resolve: 1.22.1 - resolve.exports: 1.1.0 - slash: 3.0.0 - dev: true - - /jest-resolve/29.4.3: - resolution: {integrity: sha512-GPokE1tzguRyT7dkxBim4wSx6E45S3bOQ7ZdKEG+Qj0Oac9+6AwJPCk0TZh5Vu0xzeX4afpb+eDmgbmZFFwpOw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.10 - jest-haste-map: 29.4.3 - jest-pnp-resolver: 1.2.2_jest-resolve@29.4.3 - jest-util: 29.4.3 - jest-validate: 29.4.3 - resolve: 1.22.1 - resolve.exports: 2.0.0 - slash: 3.0.0 - dev: true - /jest-resolve/29.5.0: resolution: {integrity: sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8143,93 +6354,6 @@ packages: slash: 3.0.0 dev: true - /jest-runner/29.0.3: - resolution: {integrity: sha512-Usu6VlTOZlCZoNuh3b2Tv/yzDpKqtiNAetG9t3kJuHfUyVMNW7ipCCJOUojzKkjPoaN7Bl1f7Buu6PE0sGpQxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/console': 29.0.3 - '@jest/environment': 29.0.3 - '@jest/test-result': 29.0.3 - '@jest/transform': 29.0.3 - '@jest/types': 29.0.3 - '@types/node': 18.14.2 - chalk: 4.1.2 - emittery: 0.10.2 - graceful-fs: 4.2.10 - jest-docblock: 29.0.0 - jest-environment-node: 29.0.3 - jest-haste-map: 29.0.3 - jest-leak-detector: 29.0.3 - jest-message-util: 29.0.3 - jest-resolve: 29.0.3 - jest-runtime: 29.0.3 - jest-util: 29.0.3 - jest-watcher: 29.0.3 - jest-worker: 29.0.3 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-runner/29.2.1: - resolution: {integrity: sha512-PojFI+uVhQ4u4YZKCN/a3yU0/l/pJJXhq1sW3JpCp8CyvGBYGddRFPKZ1WihApusxqWRTHjBJmGyPWv6Av2lWA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/console': 29.2.1 - '@jest/environment': 29.2.1 - '@jest/test-result': 29.2.1 - '@jest/transform': 29.2.1 - '@jest/types': 29.2.1 - '@types/node': 18.14.2 - chalk: 4.1.2 - emittery: 0.10.2 - graceful-fs: 4.2.10 - jest-docblock: 29.2.0 - jest-environment-node: 29.2.1 - jest-haste-map: 29.2.1 - jest-leak-detector: 29.2.1 - jest-message-util: 29.2.1 - jest-resolve: 29.2.1 - jest-runtime: 29.2.1 - jest-util: 29.2.1 - jest-watcher: 29.2.1 - jest-worker: 29.2.1 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-runner/29.4.3: - resolution: {integrity: sha512-GWPTEiGmtHZv1KKeWlTX9SIFuK19uLXlRQU43ceOQ2hIfA5yPEJC7AMkvFKpdCHx6pNEdOD+2+8zbniEi3v3gA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/console': 29.4.3 - '@jest/environment': 29.4.3 - '@jest/test-result': 29.4.3 - '@jest/transform': 29.4.3 - '@jest/types': 29.4.3 - '@types/node': 18.14.2 - chalk: 4.1.2 - emittery: 0.13.1 - graceful-fs: 4.2.10 - jest-docblock: 29.4.3 - jest-environment-node: 29.4.3 - jest-haste-map: 29.4.3 - jest-leak-detector: 29.4.3 - jest-message-util: 29.4.3 - jest-resolve: 29.4.3 - jest-runtime: 29.4.3 - jest-util: 29.4.3 - jest-watcher: 29.4.3 - jest-worker: 29.4.3 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color - dev: true - /jest-runner/29.5.0: resolution: {integrity: sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8259,96 +6383,6 @@ packages: - supports-color dev: true - /jest-runtime/29.0.3: - resolution: {integrity: sha512-12gZXRQ7ozEeEHKTY45a+YLqzNDR/x4c//X6AqwKwKJPpWM8FY4vwn4VQJOcLRS3Nd1fWwgP7LU4SoynhuUMHQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.0.3 - '@jest/fake-timers': 29.0.3 - '@jest/globals': 29.3.1 - '@jest/source-map': 29.0.0 - '@jest/test-result': 29.0.3 - '@jest/transform': 29.0.3 - '@jest/types': 29.0.3 - '@types/node': 18.14.2 - chalk: 4.1.2 - cjs-module-lexer: 1.2.2 - collect-v8-coverage: 1.0.1 - glob: 7.2.3 - graceful-fs: 4.2.10 - jest-haste-map: 29.0.3 - jest-message-util: 29.0.3 - jest-mock: 29.0.3 - jest-regex-util: 29.0.0 - jest-resolve: 29.0.3 - jest-snapshot: 29.0.3 - jest-util: 29.0.3 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-runtime/29.2.1: - resolution: {integrity: sha512-PSQ880OoIW9y8E6/jjhGn3eQNgNc6ndMzCZaKqy357bv7FqCfSyYepu3yDC6Sp1Vkt+GhP2M/PVgldS2uZSFZg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.2.1 - '@jest/fake-timers': 29.2.1 - '@jest/globals': 29.2.1 - '@jest/source-map': 29.2.0 - '@jest/test-result': 29.2.1 - '@jest/transform': 29.2.1 - '@jest/types': 29.2.1 - '@types/node': 18.14.2 - chalk: 4.1.2 - cjs-module-lexer: 1.2.2 - collect-v8-coverage: 1.0.1 - glob: 7.2.3 - graceful-fs: 4.2.10 - jest-haste-map: 29.2.1 - jest-message-util: 29.2.1 - jest-mock: 29.2.1 - jest-regex-util: 29.2.0 - jest-resolve: 29.2.1 - jest-snapshot: 29.2.1 - jest-util: 29.2.1 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-runtime/29.4.3: - resolution: {integrity: sha512-F5bHvxSH+LvLV24vVB3L8K467dt3y3dio6V3W89dUz9nzvTpqd/HcT9zfYKL2aZPvD63vQFgLvaUX/UpUhrP6Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.4.3 - '@jest/fake-timers': 29.4.3 - '@jest/globals': 29.4.3 - '@jest/source-map': 29.4.3 - '@jest/test-result': 29.4.3 - '@jest/transform': 29.4.3 - '@jest/types': 29.4.3 - '@types/node': 18.14.2 - chalk: 4.1.2 - cjs-module-lexer: 1.2.2 - collect-v8-coverage: 1.0.1 - glob: 7.2.3 - graceful-fs: 4.2.10 - jest-haste-map: 29.4.3 - jest-message-util: 29.4.3 - jest-mock: 29.4.3 - jest-regex-util: 29.4.3 - jest-resolve: 29.4.3 - jest-snapshot: 29.4.3 - jest-util: 29.4.3 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - dev: true - /jest-runtime/29.5.0: resolution: {integrity: sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8379,134 +6413,6 @@ packages: - supports-color dev: true - /jest-snapshot/29.0.3: - resolution: {integrity: sha512-52q6JChm04U3deq+mkQ7R/7uy7YyfVIrebMi6ZkBoDJ85yEjm/sJwdr1P0LOIEHmpyLlXrxy3QP0Zf5J2kj0ew==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.19.3 - '@babel/generator': 7.19.5 - '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.19.3 - '@babel/traverse': 7.19.4 - '@babel/types': 7.19.4 - '@jest/expect-utils': 29.0.3 - '@jest/transform': 29.0.3 - '@jest/types': 29.0.3 - '@types/babel__traverse': 7.18.1 - '@types/prettier': 2.7.0 - babel-preset-current-node-syntax: 1.0.1_@babel+core@7.19.3 - chalk: 4.1.2 - expect: 29.0.3 - graceful-fs: 4.2.10 - jest-diff: 29.0.3 - jest-get-type: 29.0.0 - jest-haste-map: 29.0.3 - jest-matcher-utils: 29.0.3 - jest-message-util: 29.0.3 - jest-util: 29.0.3 - natural-compare: 1.4.0 - pretty-format: 29.0.3 - semver: 7.3.8 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-snapshot/29.2.1: - resolution: {integrity: sha512-KZdLD7iEz5M4ZYd+ezZ/kk73z+DtNbk/yJ4Qx7408Vb0CCuclJIZPa/HmIwSsCfIlOBNcYTKufr7x/Yv47oYlg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.20.5 - '@babel/generator': 7.19.6 - '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.20.5 - '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.20.5 - '@babel/traverse': 7.19.6 - '@babel/types': 7.19.4 - '@jest/expect-utils': 29.2.1 - '@jest/transform': 29.2.1 - '@jest/types': 29.2.1 - '@types/babel__traverse': 7.18.2 - '@types/prettier': 2.7.1 - babel-preset-current-node-syntax: 1.0.1_@babel+core@7.20.5 - chalk: 4.1.2 - expect: 29.2.1 - graceful-fs: 4.2.10 - jest-diff: 29.2.1 - jest-get-type: 29.2.0 - jest-haste-map: 29.2.1 - jest-matcher-utils: 29.2.1 - jest-message-util: 29.2.1 - jest-util: 29.2.1 - natural-compare: 1.4.0 - pretty-format: 29.2.1 - semver: 7.3.8 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-snapshot/29.3.1: - resolution: {integrity: sha512-+3JOc+s28upYLI2OJM4PWRGK9AgpsMs/ekNryUV0yMBClT9B1DF2u2qay8YxcQd338PPYSFNb0lsar1B49sLDA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.20.5 - '@babel/generator': 7.20.5 - '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.20.5 - '@babel/plugin-syntax-typescript': 7.20.0_@babel+core@7.20.5 - '@babel/traverse': 7.20.5 - '@babel/types': 7.20.5 - '@jest/expect-utils': 29.3.1 - '@jest/transform': 29.3.1 - '@jest/types': 29.3.1 - '@types/babel__traverse': 7.18.2 - '@types/prettier': 2.7.2 - babel-preset-current-node-syntax: 1.0.1_@babel+core@7.20.5 - chalk: 4.1.2 - expect: 29.3.1 - graceful-fs: 4.2.10 - jest-diff: 29.3.1 - jest-get-type: 29.2.0 - jest-haste-map: 29.3.1 - jest-matcher-utils: 29.3.1 - jest-message-util: 29.3.1 - jest-util: 29.3.1 - natural-compare: 1.4.0 - pretty-format: 29.3.1 - semver: 7.3.8 - transitivePeerDependencies: - - supports-color - dev: true - - /jest-snapshot/29.4.3: - resolution: {integrity: sha512-NGlsqL0jLPDW91dz304QTM/SNO99lpcSYYAjNiX0Ou+sSGgkanKBcSjCfp/pqmiiO1nQaOyLp6XQddAzRcx3Xw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.20.5 - '@babel/generator': 7.20.5 - '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.20.5 - '@babel/plugin-syntax-typescript': 7.20.0_@babel+core@7.20.5 - '@babel/traverse': 7.20.5 - '@babel/types': 7.20.5 - '@jest/expect-utils': 29.4.3 - '@jest/transform': 29.4.3 - '@jest/types': 29.4.3 - '@types/babel__traverse': 7.18.2 - '@types/prettier': 2.7.2 - babel-preset-current-node-syntax: 1.0.1_@babel+core@7.20.5 - chalk: 4.1.2 - expect: 29.4.3 - graceful-fs: 4.2.10 - jest-diff: 29.4.3 - jest-get-type: 29.4.3 - jest-haste-map: 29.4.3 - jest-matcher-utils: 29.4.3 - jest-message-util: 29.4.3 - jest-util: 29.4.3 - natural-compare: 1.4.0 - pretty-format: 29.4.3 - semver: 7.3.8 - transitivePeerDependencies: - - supports-color - dev: true - /jest-snapshot/29.5.0: resolution: {integrity: sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8541,151 +6447,61 @@ packages: /jest-util/29.0.3: resolution: {integrity: sha512-Q0xaG3YRG8QiTC4R6fHjHQPaPpz9pJBEi0AeOE4mQh/FuWOijFjGXMMOfQEaU9i3z76cNR7FobZZUQnL6IyfdQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.0.3 - '@types/node': 18.14.2 - chalk: 4.1.2 - ci-info: 3.4.0 - graceful-fs: 4.2.10 - picomatch: 2.3.1 - dev: true - - /jest-util/29.2.1: - resolution: {integrity: sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.2.1 - '@types/node': 18.14.2 - chalk: 4.1.2 - ci-info: 3.5.0 - graceful-fs: 4.2.10 - picomatch: 2.3.1 - dev: true - - /jest-util/29.3.1: - resolution: {integrity: sha512-7YOVZaiX7RJLv76ZfHt4nbNEzzTRiMW/IiOG7ZOKmTXmoGBxUDefgMAxQubu6WPVqP5zSzAdZG0FfLcC7HOIFQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.3.1 - '@types/node': 18.14.2 - chalk: 4.1.2 - ci-info: 3.7.1 - graceful-fs: 4.2.10 - picomatch: 2.3.1 - dev: true - - /jest-util/29.4.3: - resolution: {integrity: sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.5.0 - '@types/node': 18.14.2 - chalk: 4.1.2 - ci-info: 3.7.1 - graceful-fs: 4.2.10 - picomatch: 2.3.1 - dev: true - - /jest-util/29.5.0: - resolution: {integrity: sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 '@types/node': 18.14.2 chalk: 4.1.2 - ci-info: 3.7.1 - graceful-fs: 4.2.10 - picomatch: 2.3.1 - dev: true - - /jest-validate/29.0.3: - resolution: {integrity: sha512-OebiqqT6lK8cbMPtrSoS3aZP4juID762lZvpf1u+smZnwTEBCBInan0GAIIhv36MxGaJvmq5uJm7dl5gVt+Zrw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.0.3 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 29.0.0 - leven: 3.1.0 - pretty-format: 29.0.3 - dev: true - - /jest-validate/29.2.1: - resolution: {integrity: sha512-DZVX5msG6J6DL5vUUw+++6LEkXUsPwB5R7fsfM7BXdz2Ipr0Ib046ak+8egrwAR++pvSM/5laxLK977ieIGxkQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.2.1 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 29.2.0 - leven: 3.1.0 - pretty-format: 29.2.1 - dev: true - - /jest-validate/29.4.3: - resolution: {integrity: sha512-J3u5v7aPQoXPzaar6GndAVhdQcZr/3osWSgTeKg5v574I9ybX/dTyH0AJFb5XgXIB7faVhf+rS7t4p3lL9qFaw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.4.3 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 29.4.3 - leven: 3.1.0 - pretty-format: 29.4.3 + ci-info: 3.7.1 + graceful-fs: 4.2.10 + picomatch: 2.3.1 dev: true - /jest-validate/29.5.0: - resolution: {integrity: sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==} + /jest-util/29.2.1: + resolution: {integrity: sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.5.0 - camelcase: 6.3.0 + '@types/node': 18.14.2 chalk: 4.1.2 - jest-get-type: 29.4.3 - leven: 3.1.0 - pretty-format: 29.5.0 + ci-info: 3.5.0 + graceful-fs: 4.2.10 + picomatch: 2.3.1 dev: true - /jest-watcher/29.0.3: - resolution: {integrity: sha512-tQX9lU91A+9tyUQKUMp0Ns8xAcdhC9fo73eqA3LFxP2bSgiF49TNcc+vf3qgGYYK9qRjFpXW9+4RgF/mbxyOOw==} + /jest-util/29.4.3: + resolution: {integrity: sha512-ToSGORAz4SSSoqxDSylWX8JzkOQR7zoBtNRsA7e+1WUX5F8jrOwaNpuh1YfJHJKDHXLHmObv5eOjejUd+/Ws+Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/test-result': 29.0.3 - '@jest/types': 29.0.3 + '@jest/types': 29.5.0 '@types/node': 18.14.2 - ansi-escapes: 4.3.2 chalk: 4.1.2 - emittery: 0.10.2 - jest-util: 29.0.3 - string-length: 4.0.2 + ci-info: 3.7.1 + graceful-fs: 4.2.10 + picomatch: 2.3.1 dev: true - /jest-watcher/29.2.1: - resolution: {integrity: sha512-7jFaHUaRq50l4w/f6RuY713bvI5XskMmjWCE54NGYcY74fLkShS8LucXJke1QfGnwDSCoIqGnGGGKPwdaBYz2Q==} + /jest-util/29.5.0: + resolution: {integrity: sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/test-result': 29.2.1 - '@jest/types': 29.2.1 + '@jest/types': 29.5.0 '@types/node': 18.14.2 - ansi-escapes: 4.3.2 chalk: 4.1.2 - emittery: 0.10.2 - jest-util: 29.2.1 - string-length: 4.0.2 + ci-info: 3.7.1 + graceful-fs: 4.2.10 + picomatch: 2.3.1 dev: true - /jest-watcher/29.4.3: - resolution: {integrity: sha512-zwlXH3DN3iksoIZNk73etl1HzKyi5FuQdYLnkQKm5BW4n8HpoG59xSwpVdFrnh60iRRaRBGw0gcymIxjJENPcA==} + /jest-validate/29.5.0: + resolution: {integrity: sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/test-result': 29.4.3 - '@jest/types': 29.4.3 - '@types/node': 18.14.2 - ansi-escapes: 4.3.2 + '@jest/types': 29.5.0 + camelcase: 6.3.0 chalk: 4.1.2 - emittery: 0.13.1 - jest-util: 29.4.3 - string-length: 4.0.2 + jest-get-type: 29.4.3 + leven: 3.1.0 + pretty-format: 29.5.0 dev: true /jest-watcher/29.5.0: @@ -8702,45 +6518,6 @@ packages: string-length: 4.0.2 dev: true - /jest-worker/29.0.3: - resolution: {integrity: sha512-Tl/YWUugQOjoTYwjKdfJWkSOfhufJHO5LhXTSZC3TRoQKO+fuXnZAdoXXBlpLXKGODBL3OvdUasfDD4PcMe6ng==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@types/node': 18.14.2 - merge-stream: 2.0.0 - supports-color: 8.1.1 - dev: true - - /jest-worker/29.2.1: - resolution: {integrity: sha512-ROHTZ+oj7sBrgtv46zZ84uWky71AoYi0vEV9CdEtc1FQunsoAGe5HbQmW76nI5QWdvECVPrSi1MCVUmizSavMg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@types/node': 18.14.2 - jest-util: 29.2.1 - merge-stream: 2.0.0 - supports-color: 8.1.1 - dev: true - - /jest-worker/29.3.1: - resolution: {integrity: sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@types/node': 18.14.2 - jest-util: 29.3.1 - merge-stream: 2.0.0 - supports-color: 8.1.1 - dev: true - - /jest-worker/29.4.3: - resolution: {integrity: sha512-GLHN/GTAAMEy5BFdvpUfzr9Dr80zQqBrh0fz1mtRMe05hqP45+HfQltu7oTBfduD0UeZs09d+maFtFYAXFWvAA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@types/node': 18.14.2 - jest-util: 29.4.3 - merge-stream: 2.0.0 - supports-color: 8.1.1 - dev: true - /jest-worker/29.5.0: resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8751,28 +6528,8 @@ packages: supports-color: 8.1.1 dev: true - /jest/29.0.3_johvxhudwcpndp4mle25vwrlq4: - resolution: {integrity: sha512-ElgUtJBLgXM1E8L6K1RW1T96R897YY/3lRYqq9uVcPWtP2AAl/nQ16IYDh/FzQOOQ12VEuLdcPU83mbhG2C3PQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/core': 29.0.3_ts-node@10.9.1 - '@jest/types': 29.0.3 - import-local: 3.1.0 - jest-cli: 29.0.3_johvxhudwcpndp4mle25vwrlq4 - transitivePeerDependencies: - - '@types/node' - - supports-color - - ts-node - dev: true - - /jest/29.2.1_4f2ldd7um3b3u4eyvetyqsphze: - resolution: {integrity: sha512-K0N+7rx+fv3Us3KhuwRSJt55MMpZPs9Q3WSO/spRZSnsalX8yEYOTQ1PiSN7OvqzoRX4JEUXCbOJRlP4n8m5LA==} + /jest/29.5.0: + resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true peerDependencies: @@ -8781,18 +6538,18 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 29.2.1_ts-node@10.9.1 - '@jest/types': 29.2.1 + '@jest/core': 29.5.0 + '@jest/types': 29.5.0 import-local: 3.1.0 - jest-cli: 29.2.1_4f2ldd7um3b3u4eyvetyqsphze + jest-cli: 29.5.0 transitivePeerDependencies: - '@types/node' - supports-color - ts-node dev: true - /jest/29.4.3: - resolution: {integrity: sha512-XvK65feuEFGZT8OO0fB/QAQS+LGHvQpaadkH5p47/j3Ocqq3xf2pK9R+G0GzgfuhXVxEv76qCOOcMb5efLk6PA==} + /jest/29.5.0_4f2ldd7um3b3u4eyvetyqsphze: + resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true peerDependencies: @@ -8801,17 +6558,17 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 29.4.3 - '@jest/types': 29.4.3 + '@jest/core': 29.5.0_ts-node@10.9.1 + '@jest/types': 29.5.0 import-local: 3.1.0 - jest-cli: 29.4.3 + jest-cli: 29.5.0_4f2ldd7um3b3u4eyvetyqsphze transitivePeerDependencies: - '@types/node' - supports-color - ts-node dev: true - /jest/29.5.0: + /jest/29.5.0_johvxhudwcpndp4mle25vwrlq4: resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -8821,10 +6578,10 @@ packages: node-notifier: optional: true dependencies: - '@jest/core': 29.5.0 + '@jest/core': 29.5.0_ts-node@10.9.1 '@jest/types': 29.5.0 import-local: 3.1.0 - jest-cli: 29.5.0 + jest-cli: 29.5.0_johvxhudwcpndp4mle25vwrlq4 transitivePeerDependencies: - '@types/node' - supports-color @@ -8925,6 +6682,7 @@ packages: node-addon-api: 4.3.0 prebuild-install: 7.1.1 dev: true + optional: true /kind-of/6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} @@ -8940,19 +6698,6 @@ packages: engines: {node: '>=6'} dev: true - /langium-cli/1.0.0: - resolution: {integrity: sha512-F5RZu3/PrlRfebMM6irtHDk2cj5PdUzM4y3M5kNu8k5IC3gIhc4QN+aDD2XHYz8hQSA+bCRUIDwCdhy/7VN4cg==} - engines: {node: '>=12.0.0'} - hasBin: true - dependencies: - chalk: 4.1.2 - commander: 8.3.0 - fs-extra: 9.1.0 - jsonschema: 1.4.1 - langium: 1.0.1 - lodash: 4.17.21 - dev: true - /langium-cli/1.1.0: resolution: {integrity: sha512-vnv037FHqXqMeNiNF90v47VrJGiJPzH721UIbbHcu6Nfx0C1UC6SmQhGHtZIDRovT5qJsiXRIPDTZYrIkm4KJQ==} engines: {node: '>=14.0.0'} @@ -8966,17 +6711,6 @@ packages: lodash: 4.17.21 dev: true - /langium/1.0.1: - resolution: {integrity: sha512-9w5NRsspYOOXV56q1EhXeFJWkboAVKZDzIEqPLraMQPQy6fvq104wlVwgvF6w9H4IcCpDHCsHJLsfzQMWBsjAA==} - engines: {node: '>=14.0.0'} - dependencies: - chevrotain: 10.4.2 - chevrotain-allstar: 0.1.4 - vscode-languageserver: 8.0.2 - vscode-languageserver-textdocument: 1.0.7 - vscode-uri: 3.0.7 - dev: true - /langium/1.1.0: resolution: {integrity: sha512-TsWY/DIOR73se9/YaMQZpvfFWWrhWP0FQS9MrpxWEnMJR0FoKVpMF1thPWXZexLSfyEm1pn2oYzCdW4KUBqXxA==} engines: {node: '>=14.0.0'} @@ -9246,7 +6980,6 @@ packages: /mimic-response/3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - dev: true /min-indent/1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} @@ -9274,7 +7007,6 @@ packages: /minimist/1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true /mixme/0.5.4: resolution: {integrity: sha512-3KYa4m4Vlqx98GPdOHghxSdNtTvcP8E0kkaJ5Dlh+h2DRzF7zpuVVcA8B0QpKd11YJeP9QQ7ASkKzOeu195Wzw==} @@ -9293,6 +7025,7 @@ packages: /mkdirp-classic/0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} dev: true + optional: true /mkdirp/1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} @@ -9335,6 +7068,7 @@ packages: /napi-build-utils/1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} dev: true + optional: true /natural-compare-lite/1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -9410,10 +7144,12 @@ packages: dependencies: semver: 7.3.8 dev: true + optional: true /node-addon-api/4.3.0: resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} dev: true + optional: true /node-fetch/2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} @@ -9560,6 +7296,11 @@ packages: dependencies: p-map: 2.1.0 + /p-is-promise/3.0.0: + resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==} + engines: {node: '>=8'} + dev: false + /p-limit/2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -9626,6 +7367,10 @@ packages: callsites: 3.1.0 dev: true + /parse-headers/2.0.5: + resolution: {integrity: sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==} + dev: false + /parse-json/5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -9817,6 +7562,7 @@ packages: tar-fs: 2.1.1 tunnel-agent: 0.6.0 dev: true + optional: true /preferred-pm/3.0.3: resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} @@ -9847,42 +7593,6 @@ packages: react-is: 17.0.2 dev: true - /pretty-format/29.0.3: - resolution: {integrity: sha512-cHudsvQr1K5vNVLbvYF/nv3Qy/F/BcEKxGuIeMiVMRHxPOO1RxXooP8g/ZrwAp7Dx+KdMZoOc7NxLHhMrP2f9Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.0.0 - ansi-styles: 5.2.0 - react-is: 18.2.0 - dev: true - - /pretty-format/29.2.1: - resolution: {integrity: sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.0.0 - ansi-styles: 5.2.0 - react-is: 18.2.0 - dev: true - - /pretty-format/29.3.1: - resolution: {integrity: sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.0.0 - ansi-styles: 5.2.0 - react-is: 18.2.0 - dev: true - - /pretty-format/29.4.3: - resolution: {integrity: sha512-cvpcHTc42lcsvOOAzd3XuNWTcvk1Jmnzqeu+WsOuiPmxUJTnkbAcFNsRKvEpBEUFVUgy/GTZLulZDcDEi+CIlA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.4.3 - ansi-styles: 5.2.0 - react-is: 18.2.0 - dev: true - /pretty-format/29.5.0: resolution: {integrity: sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -9918,6 +7628,13 @@ packages: engines: {node: '>= 0.6.0'} dev: true + /progress-stream/2.0.0: + resolution: {integrity: sha512-xJwOWR46jcXUq6EH9yYyqp+I52skPySOeHfkxOZ2IY1AiBi/sFJhbhAKHoV3OTw/omQ45KTio9215dRJ2Yxd3Q==} + dependencies: + speedometer: 1.0.0 + through2: 2.0.5 + dev: false + /progress/2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -9939,6 +7656,10 @@ packages: kleur: 3.0.3 sisteransi: 1.0.5 + /proto-list/1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + dev: false + /proxy-addr/2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -9957,6 +7678,7 @@ packages: end-of-stream: 1.4.4 once: 1.4.0 dev: true + optional: true /punycode/2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} @@ -10023,7 +7745,6 @@ packages: ini: 1.3.8 minimist: 1.2.8 strip-json-comments: 2.0.1 - dev: true /react-dom/18.2.0_react@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} @@ -10171,6 +7892,20 @@ packages: engines: {node: '>=8'} dev: true + /registry-auth-token/5.0.2: + resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==} + engines: {node: '>=14'} + dependencies: + '@pnpm/npm-conf': 2.1.0 + dev: false + + /registry-url/5.1.0: + resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} + engines: {node: '>=8'} + dependencies: + rc: 1.2.8 + dev: false + /renamer/4.0.0: resolution: {integrity: sha512-yurufcXxbJfFBVAUoByNyDVH811zTZ/MrKo6gUH8pHGeAmdK7J5egj2lSNe57HuVIvnVzSalzeVGu8pi8UHGxg==} engines: {node: '>=12.17'} @@ -10225,11 +7960,6 @@ packages: engines: {node: '>=8'} dev: true - /resolve.exports/1.1.0: - resolution: {integrity: sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==} - engines: {node: '>=10'} - dev: true - /resolve.exports/2.0.0: resolution: {integrity: sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==} engines: {node: '>=10'} @@ -10446,6 +8176,7 @@ packages: /simple-concat/1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} dev: true + optional: true /simple-get/4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} @@ -10454,6 +8185,7 @@ packages: once: 1.4.0 simple-concat: 1.0.1 dev: true + optional: true /sisteransi/1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -10553,6 +8285,10 @@ packages: /spdx-license-ids/3.0.12: resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + /speedometer/1.0.0: + resolution: {integrity: sha512-lgxErLl/7A5+vgIIXsh9MbeukOaCb2axgQ+bKCdIE+ibNT4XNYGNCR1qFEGq6F+YDASXK3Fh/c5FgtZchFolxw==} + dev: false + /split2/4.1.0: resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} engines: {node: '>= 10.x'} @@ -10688,7 +8424,6 @@ packages: /strip-json-comments/2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - dev: true /strip-json-comments/3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} @@ -10796,7 +8531,7 @@ packages: dependencies: react: 18.2.0 use-sync-external-store: 1.2.0_react@18.2.0 - dev: false + dev: true /table-layout/1.0.2: resolution: {integrity: sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==} @@ -10816,6 +8551,7 @@ packages: pump: 3.0.0 tar-stream: 2.2.0 dev: true + optional: true /tar-stream/2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -10891,7 +8627,6 @@ packages: dependencies: readable-stream: 2.3.7 xtend: 4.0.2 - dev: true /tiny-invariant/1.3.1: resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} @@ -10961,7 +8696,7 @@ packages: engines: {node: '>=8'} dev: true - /ts-jest/29.0.1_57hartw4ijnqan4zaqtggk6uaa: + /ts-jest/29.0.1_rlomzopfumxmkrybxmhq4yglo4: resolution: {integrity: sha512-htQOHshgvhn93QLxrmxpiQPk69+M1g7govO1g6kf6GsjCv4uvRV0znVmDrrvjUrVCnTYeY4FBxTYYYD4airyJA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -10985,7 +8720,7 @@ packages: '@babel/core': 7.20.5 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.0.3_johvxhudwcpndp4mle25vwrlq4 + jest: 29.5.0_johvxhudwcpndp4mle25vwrlq4 jest-util: 29.0.3 json5: 2.2.1 lodash.memoize: 4.1.2 @@ -10995,7 +8730,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-jest/29.0.3_yf6yylffq7wy4s6j6no7bitnpa: + /ts-jest/29.0.3_i42we4lxl4f3wyuuvscuqjoe7i: resolution: {integrity: sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -11020,7 +8755,7 @@ packages: bs-logger: 0.2.6 esbuild: 0.15.12 fast-json-stable-stringify: 2.1.0 - jest: 29.2.1_4f2ldd7um3b3u4eyvetyqsphze + jest: 29.5.0_4f2ldd7um3b3u4eyvetyqsphze jest-util: 29.2.1 json5: 2.2.1 lodash.memoize: 4.1.2 @@ -11030,7 +8765,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-jest/29.0.5_62v6np7q4ngunzmn4ekotpxy7y: + /ts-jest/29.0.5_alvbhmn3fmdinfvcr37idtrz64: resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -11054,7 +8789,7 @@ packages: '@babel/core': 7.20.5 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.4.3 + jest: 29.5.0 jest-util: 29.4.3 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -11064,40 +8799,6 @@ packages: 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} - 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.4 - yargs-parser: 21.1.1 - dev: true - /ts-jest/29.0.5_xv37nfqpz6757dg24cw6wqnoey: resolution: {integrity: sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -11271,7 +8972,6 @@ packages: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: safe-buffer: 5.2.1 - dev: true /tunnel/0.0.6: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} @@ -11636,34 +9336,6 @@ packages: - terser dev: true - /vsce/2.15.0: - resolution: {integrity: sha512-P8E9LAZvBCQnoGoizw65JfGvyMqNGlHdlUXD1VAuxtvYAaHBKLBdKPnpy60XKVDAkQCfmMu53g+gq9FM+ydepw==} - engines: {node: '>= 14'} - deprecated: vsce has been renamed to @vscode/vsce. Install using @vscode/vsce instead. - hasBin: true - dependencies: - azure-devops-node-api: 11.2.0 - chalk: 2.4.2 - cheerio: 1.0.0-rc.12 - commander: 6.2.1 - glob: 7.2.3 - hosted-git-info: 4.1.0 - keytar: 7.9.0 - leven: 3.1.0 - markdown-it: 12.3.2 - mime: 1.6.0 - minimatch: 3.1.2 - parse-semver: 1.1.1 - read: 1.0.7 - semver: 5.7.1 - tmp: 0.2.1 - typed-rest-client: 1.8.9 - url-join: 4.0.1 - xml2js: 0.4.23 - yauzl: 2.10.0 - yazl: 2.5.1 - dev: true - /vscode-jsonrpc/8.0.2: resolution: {integrity: sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==} engines: {node: '>=14.0.0'} @@ -11685,6 +9357,7 @@ packages: /vscode-languageserver-textdocument/1.0.7: resolution: {integrity: sha512-bFJH7UQxlXT8kKeyiyu41r22jCZXG8kuuVVA33OEJn1diWOZK5n8zBSPZFHVBOu8kXZ6h0LIRhf5UnCo61J4Hg==} + dev: false /vscode-languageserver-textdocument/1.0.8: resolution: {integrity: sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==} @@ -11828,8 +9501,8 @@ packages: signal-exit: 3.0.7 dev: true - /xml2js/0.4.23: - resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} + /xml2js/0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} engines: {node: '>=4.0.0'} dependencies: sax: 1.2.4 @@ -11844,7 +9517,6 @@ packages: /xtend/4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - dev: true /y18n/4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -11915,19 +9587,6 @@ packages: yargs-parser: 20.2.9 dev: true - /yargs/17.5.1: - resolution: {integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==} - engines: {node: '>=12'} - dependencies: - cliui: 7.0.4 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - dev: true - /yargs/17.6.0: resolution: {integrity: sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==} engines: {node: '>=12'} @@ -11989,16 +9648,16 @@ packages: compress-commons: 4.1.1 readable-stream: 3.6.0 - /zod-validation-error/0.2.1_zod@3.19.1: + /zod-validation-error/0.2.1_zod@3.21.1: resolution: {integrity: sha512-zGg6P5EHi5V0dvyEeC8HBZd2pzp7QDKTngkSWgWunljrY+0SHkHyjI519D+u8/37BHkGHAFseWgnZ2Uq8LNFKg==} engines: {node: ^14.17 || >=16.0.0} peerDependencies: zod: ^3.18.0 dependencies: '@swc/helpers': 0.4.11 - zod: 3.19.1 + zod: 3.21.1 dev: false - /zod/3.19.1: - resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==} + /zod/3.21.1: + resolution: {integrity: sha512-+dTu2m6gmCbO9Ahm4ZBDapx2O6ZY9QSPXst2WXjcznPMwf2YNpn3RevLx4KkZp1OPW/ouFcoBtBzFz/LeY69oA==} dev: false diff --git a/tests/integration/package.json b/tests/integration/package.json index 9b6e995a8..8e8d5ead3 100644 --- a/tests/integration/package.json +++ b/tests/integration/package.json @@ -13,18 +13,18 @@ "devDependencies": { "@types/bcryptjs": "^2.4.2", "@types/fs-extra": "^11.0.1", - "@types/jest": "^29.0.3", + "@types/jest": "^29.5.0", "@types/supertest": "^2.0.12", "@types/tmp": "^0.2.3", "@types/uuid": "^8.3.4", - "@zenstackhq/runtime": "workspace:*", "@zenstackhq/next": "workspace:*", "@zenstackhq/react": "workspace:*", + "@zenstackhq/runtime": "workspace:*", "@zenstackhq/trpc": "workspace:*", "eslint": "^8.30.0", "eslint-plugin-jest": "^27.1.7", "fs-extra": "^11.1.0", - "jest": "^29.0.3", + "jest": "^29.5.0", "jest-fetch-mock": "^3.0.3", "next": "^12.3.1", "prisma": "~4.7.0", @@ -38,10 +38,10 @@ "dependencies": { "@prisma/client": "^4.7.0", "@types/node": "^14.18.29", + "@zenstackhq/testtools": "workspace:*", "bcryptjs": "^2.4.3", "decimal.js": "^10.4.2", "sleep-promise": "^9.1.0", - "superjson": "^1.11.0", - "@zenstackhq/testtools": "workspace:*" + "superjson": "^1.11.0" } } diff --git a/tests/integration/test-run/package-lock.json b/tests/integration/test-run/package-lock.json index 3f3d059d9..9f4a1932c 100644 --- a/tests/integration/test-run/package-lock.json +++ b/tests/integration/test-run/package-lock.json @@ -16,7 +16,7 @@ "swr": "^1.3.0", "typescript": "^4.9.3", "zenstack": "file:../../../packages/schema/dist", - "zod": "^3.19.1" + "zod": "3.21.1" } }, "../../../../packages/runtime/dist": { @@ -35,7 +35,7 @@ "superjson": "^1.11.0", "swr": "^1.3.0", "tslib": "^2.4.1", - "zod": "^3.19.1", + "zod": "3.21.1", "zod-validation-error": "^0.2.1" }, "devDependencies": { @@ -85,7 +85,7 @@ "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", "vscode-uri": "^3.0.6", - "zod": "^3.19.1" + "zod": "3.21.1" }, "bin": { "zenstack": "bin/cli" @@ -126,26 +126,26 @@ }, "../../../packages/runtime/dist": { "name": "@zenstackhq/runtime", - "version": "1.0.0-alpha.87", + "version": "1.0.0-alpha.102", "license": "MIT", "dependencies": { + "@paralleldrive/cuid2": "^2.2.0", "@types/bcryptjs": "^2.4.2", "@zenstackhq/sdk": "workspace:*", "bcryptjs": "^2.4.3", "change-case": "^4.1.2", "colors": "1.4.0", - "cuid": "^2.1.8", "decimal.js": "^10.4.2", "deepcopy": "^2.1.0", "pluralize": "^8.0.0", "superjson": "^1.11.0", "tslib": "^2.4.1", - "zod": "^3.19.1", + "zod": "3.21.1", "zod-validation-error": "^0.2.1" }, "devDependencies": { "@types/bcryptjs": "^2.4.2", - "@types/jest": "^29.0.3", + "@types/jest": "^29.5.0", "@types/node": "^14.18.29", "@types/pluralize": "^0.0.29", "copyfiles": "^2.4.1", @@ -158,27 +158,26 @@ }, "../../../packages/schema/dist": { "name": "zenstack", - "version": "1.0.0-alpha.87", + "version": "1.0.0-alpha.102", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@prisma/generator-helper": "^4.7.1", - "@prisma/internals": "^4.7.1", + "@paralleldrive/cuid2": "^2.2.0", + "@prisma/generator-helper": "^4.0.0", + "@prisma/internals": "^4.0.0", "@zenstackhq/language": "workspace:*", - "@zenstackhq/runtime": "workspace:*", "@zenstackhq/sdk": "workspace:*", "async-exit-hook": "^2.0.1", "change-case": "^4.1.2", "chevrotain": "^9.1.0", "colors": "1.4.0", "commander": "^8.3.0", - "cuid": "^2.1.8", + "get-latest-version": "^5.0.1", "langium": "1.1.0", "mixpanel": "^0.17.0", "node-machine-id": "^1.1.12", "ora": "^5.4.1", "pluralize": "^8.0.0", - "prisma": "~4.7.0", "promisify": "^0.0.3", "semver": "^7.3.8", "sleep-promise": "^9.1.0", @@ -189,14 +188,15 @@ "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", "vscode-uri": "^3.0.6", - "zod": "^3.19.1" + "zod": "3.21.1", + "zod-validation-error": "^0.2.1" }, "bin": { "zenstack": "bin/cli" }, "devDependencies": { "@types/async-exit-hook": "^2.0.0", - "@types/jest": "^29.2.0", + "@types/jest": "^29.5.0", "@types/node": "^14.18.32", "@types/pluralize": "^0.0.29", "@types/semver": "^7.3.13", @@ -205,6 +205,8 @@ "@types/vscode": "^1.56.0", "@typescript-eslint/eslint-plugin": "^5.42.0", "@typescript-eslint/parser": "^5.42.0", + "@vscode/vsce": "^2.19.0", + "@zenstackhq/runtime": "workspace:*", "@zenstackhq/testtools": "workspace:*", "concurrently": "^7.4.0", "copyfiles": "^2.4.1", @@ -212,8 +214,8 @@ "esbuild": "^0.15.12", "eslint": "^8.27.0", "eslint-plugin-jest": "^27.1.7", - "jest": "^29.2.1", - "langium-cli": "^1.0.0", + "jest": "^29.5.0", + "prisma": "^4.0.0", "renamer": "^4.0.0", "rimraf": "^3.0.2", "tmp": "^0.2.1", @@ -221,11 +223,13 @@ "ts-node": "^10.9.1", "tsc-alias": "^1.7.0", "typescript": "^4.8.4", - "vitest": "^0.29.7", - "vsce": "^2.13.0" + "vitest": "^0.29.7" }, "engines": { "vscode": "^1.56.0" + }, + "peerDependencies": { + "prisma": "^4.0.0" } }, "node_modules/@prisma/client": { @@ -322,8 +326,9 @@ "link": true }, "node_modules/zod": { - "version": "3.19.1", - "license": "MIT", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.1.tgz", + "integrity": "sha512-+dTu2m6gmCbO9Ahm4ZBDapx2O6ZY9QSPXst2WXjcznPMwf2YNpn3RevLx4KkZp1OPW/ouFcoBtBzFz/LeY69oA==", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -345,8 +350,9 @@ "@zenstackhq/runtime": { "version": "file:../../../packages/runtime/dist", "requires": { + "@paralleldrive/cuid2": "^2.2.0", "@types/bcryptjs": "^2.4.2", - "@types/jest": "^29.0.3", + "@types/jest": "^29.5.0", "@types/node": "^14.18.29", "@types/pluralize": "^0.0.29", "@zenstackhq/sdk": "workspace:*", @@ -354,7 +360,6 @@ "change-case": "^4.1.2", "colors": "1.4.0", "copyfiles": "^2.4.1", - "cuid": "^2.1.8", "decimal.js": "^10.4.2", "deepcopy": "^2.1.0", "pluralize": "^8.0.0", @@ -362,7 +367,7 @@ "superjson": "^1.11.0", "tslib": "^2.4.1", "typescript": "^4.9.3", - "zod": "^3.19.1", + "zod": "3.21.1", "zod-validation-error": "^0.2.1" } }, @@ -397,10 +402,11 @@ "zenstack": { "version": "file:../../../packages/schema/dist", "requires": { - "@prisma/generator-helper": "^4.7.1", - "@prisma/internals": "^4.7.1", + "@paralleldrive/cuid2": "^2.2.0", + "@prisma/generator-helper": "^4.0.0", + "@prisma/internals": "^4.0.0", "@types/async-exit-hook": "^2.0.0", - "@types/jest": "^29.2.0", + "@types/jest": "^29.5.0", "@types/node": "^14.18.32", "@types/pluralize": "^0.0.29", "@types/semver": "^7.3.13", @@ -409,6 +415,7 @@ "@types/vscode": "^1.56.0", "@typescript-eslint/eslint-plugin": "^5.42.0", "@typescript-eslint/parser": "^5.42.0", + "@vscode/vsce": "^2.19.0", "@zenstackhq/language": "workspace:*", "@zenstackhq/runtime": "workspace:*", "@zenstackhq/sdk": "workspace:*", @@ -420,19 +427,18 @@ "commander": "^8.3.0", "concurrently": "^7.4.0", "copyfiles": "^2.4.1", - "cuid": "^2.1.8", "dotenv": "^16.0.3", "esbuild": "^0.15.12", "eslint": "^8.27.0", "eslint-plugin-jest": "^27.1.7", - "jest": "^29.2.1", + "get-latest-version": "^5.0.1", + "jest": "^29.5.0", "langium": "1.1.0", - "langium-cli": "^1.0.0", "mixpanel": "^0.17.0", "node-machine-id": "^1.1.12", "ora": "^5.4.1", "pluralize": "^8.0.0", - "prisma": "~4.7.0", + "prisma": "^4.0.0", "promisify": "^0.0.3", "renamer": "^4.0.0", "rimraf": "^3.0.2", @@ -446,17 +452,19 @@ "typescript": "^4.8.4", "uuid": "^9.0.0", "vitest": "^0.29.7", - "vsce": "^2.13.0", "vscode-jsonrpc": "^8.0.2", "vscode-languageclient": "^8.0.2", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", "vscode-uri": "^3.0.6", - "zod": "^3.19.1" + "zod": "3.21.1", + "zod-validation-error": "^0.2.1" } }, "zod": { - "version": "3.19.1" + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.1.tgz", + "integrity": "sha512-+dTu2m6gmCbO9Ahm4ZBDapx2O6ZY9QSPXst2WXjcznPMwf2YNpn3RevLx4KkZp1OPW/ouFcoBtBzFz/LeY69oA==" } } } diff --git a/tests/integration/test-run/package.json b/tests/integration/test-run/package.json index 1c7963cbd..522921dae 100644 --- a/tests/integration/test-run/package.json +++ b/tests/integration/test-run/package.json @@ -17,6 +17,6 @@ "swr": "^1.3.0", "typescript": "^4.9.3", "zenstack": "file:../../../packages/schema/dist", - "zod": "^3.19.1" + "zod": "3.21.1" } } diff --git a/tests/integration/tests/e2e/filter-function-coverage.test.ts b/tests/integration/tests/e2e/filter-function-coverage.test.ts index 049c45e3b..a7d6088c4 100644 --- a/tests/integration/tests/e2e/filter-function-coverage.test.ts +++ b/tests/integration/tests/e2e/filter-function-coverage.test.ts @@ -1,7 +1,7 @@ -import { loadSchema, run, type WeakDbClientContract } from '@zenstackhq/testtools'; +import { loadSchema } from '@zenstackhq/testtools'; describe('Filter Function Coverage Tests', () => { - it('contains case-sensitive', async () => { + it('contains case-sensitive field', async () => { const { withPresets } = await loadSchema( ` model Foo { @@ -16,7 +16,27 @@ describe('Filter Function Coverage Tests', () => { await expect(withPresets().foo.create({ data: { string: 'bac' } })).toResolveTruthy(); }); - it('startsWith', async () => { + it('contains case-sensitive non-field', async () => { + const { withPresets } = await loadSchema( + ` + model User { + id String @id + name String + } + + model Foo { + id String @id @default(cuid()) + @@allow('all', contains(auth().name, 'a')) + } + ` + ); + + await expect(withPresets().foo.create({ data: {} })).toBeRejectedByPolicy(); + await expect(withPresets({ id: 'user1', name: 'bcd' }).foo.create({ data: {} })).toBeRejectedByPolicy(); + await expect(withPresets({ id: 'user1', name: 'bac' }).foo.create({ data: {} })).toResolveTruthy(); + }); + + it('startsWith field', async () => { const { withPresets } = await loadSchema( ` model Foo { @@ -31,7 +51,27 @@ describe('Filter Function Coverage Tests', () => { await expect(withPresets().foo.create({ data: { string: 'abc' } })).toResolveTruthy(); }); - it('endsWith', async () => { + it('startsWith non-field', async () => { + const { withPresets } = await loadSchema( + ` + model User { + id String @id + name String + } + + model Foo { + id String @id @default(cuid()) + @@allow('all', startsWith(auth().name, 'a')) + } + ` + ); + + await expect(withPresets().foo.create({ data: {} })).toBeRejectedByPolicy(); + await expect(withPresets({ id: 'user1', name: 'bac' }).foo.create({ data: {} })).toBeRejectedByPolicy(); + await expect(withPresets({ id: 'user1', name: 'abc' }).foo.create({ data: {} })).toResolveTruthy(); + }); + + it('endsWith field', async () => { const { withPresets } = await loadSchema( ` model Foo { @@ -46,7 +86,27 @@ describe('Filter Function Coverage Tests', () => { await expect(withPresets().foo.create({ data: { string: 'bca' } })).toResolveTruthy(); }); - it('in', async () => { + it('endsWith non-field', async () => { + const { withPresets } = await loadSchema( + ` + model User { + id String @id + name String + } + + model Foo { + id String @id @default(cuid()) + @@allow('all', endsWith(auth().name, 'a')) + } + ` + ); + + await expect(withPresets().foo.create({ data: {} })).toBeRejectedByPolicy(); + await expect(withPresets({ id: 'user1', name: 'bac' }).foo.create({ data: {} })).toBeRejectedByPolicy(); + await expect(withPresets({ id: 'user1', name: 'bca' }).foo.create({ data: {} })).toResolveTruthy(); + }); + + it('in left field', async () => { const { withPresets } = await loadSchema( ` model Foo { @@ -60,4 +120,24 @@ describe('Filter Function Coverage Tests', () => { await expect(withPresets().foo.create({ data: { string: 'c' } })).toBeRejectedByPolicy(); await expect(withPresets().foo.create({ data: { string: 'b' } })).toResolveTruthy(); }); + + it('in non-field', async () => { + const { withPresets } = await loadSchema( + ` + model User { + id String @id + name String + } + + model Foo { + id String @id @default(cuid()) + @@allow('all', auth().name in ['abc', 'bcd']) + } + ` + ); + + await expect(withPresets().foo.create({ data: {} })).toBeRejectedByPolicy(); + await expect(withPresets({ id: 'user1', name: 'abd' }).foo.create({ data: {} })).toBeRejectedByPolicy(); + await expect(withPresets({ id: 'user1', name: 'abc' }).foo.create({ data: {} })).toResolveTruthy(); + }); }); diff --git a/tests/integration/tests/e2e/prisma-methods.test.ts b/tests/integration/tests/e2e/prisma-methods.test.ts index b7eb65b6e..3212b6944 100644 --- a/tests/integration/tests/e2e/prisma-methods.test.ts +++ b/tests/integration/tests/e2e/prisma-methods.test.ts @@ -1,5 +1,5 @@ import { AuthUser } from '@zenstackhq/runtime'; -import { loadSchema, run, WeakDbClientContract } from '@zenstackhq/testtools'; +import { WeakDbClientContract, loadSchema, run } from '@zenstackhq/testtools'; describe('Prisma Methods Tests', () => { let getDb: (user?: AuthUser) => WeakDbClientContract; diff --git a/tests/integration/tests/nextjs/test-project/package.json b/tests/integration/tests/nextjs/test-project/package.json index d57d1d392..1a010be9d 100644 --- a/tests/integration/tests/nextjs/test-project/package.json +++ b/tests/integration/tests/nextjs/test-project/package.json @@ -16,7 +16,8 @@ "react": "18.2.0", "react-dom": "18.2.0", "typescript": "4.9.4", - "@prisma/client": "^4.7.0" + "@prisma/client": "^4.7.0", + "@zenstackhq/runtime": "../../../../../../packages/runtime/dist" }, "devDependencies": { "prisma": "^4.7.0" diff --git a/tests/integration/tests/nextjs/test-project/postgres.zmodel b/tests/integration/tests/nextjs/test-project/postgres.zmodel index 5ca059107..ea755a92b 100644 --- a/tests/integration/tests/nextjs/test-project/postgres.zmodel +++ b/tests/integration/tests/nextjs/test-project/postgres.zmodel @@ -7,6 +7,20 @@ generator js { provider = 'prisma-client-js' } +plugin meta { + provider = '@core/model-meta' + output = '.zenstack' + compile = true + preserveTsFiles = true +} + +plugin policy { + provider = '@core/access-policy' + output = '.zenstack' + compile = true + preserveTsFiles = true +} + plugin react { provider = '@zenstackhq/react' output = 'lib/hooks' @@ -16,7 +30,6 @@ model User { id String @id name String posts Post[] - @@allow('create,read', true) @@allow('update,delete', auth() == this) } @@ -27,7 +40,6 @@ model Post { author User? @relation(fields: [authorId], references: [id]) authorId String? published Boolean @default(false) - @@allow('all', auth() == this) @@allow('read', published) } diff --git a/tests/integration/tests/nextjs/test-project/sqlite.zmodel b/tests/integration/tests/nextjs/test-project/sqlite.zmodel index 1648827a1..7d91e20cf 100644 --- a/tests/integration/tests/nextjs/test-project/sqlite.zmodel +++ b/tests/integration/tests/nextjs/test-project/sqlite.zmodel @@ -7,6 +7,20 @@ generator js { provider = 'prisma-client-js' } +plugin meta { + provider = '@core/model-meta' + output = '.zenstack' + compile = true + preserveTsFiles = true +} + +plugin policy { + provider = '@core/access-policy' + output = '.zenstack' + compile = true + preserveTsFiles = true +} + plugin react { provider = '@zenstackhq/react' output = 'lib/hooks' @@ -16,7 +30,6 @@ model User { id String @id name String posts Post[] - @@allow('create,read', true) @@allow('update,delete', auth() == this) } @@ -27,7 +40,6 @@ model Post { author User? @relation(fields: [authorId], references: [id]) authorId String? published Boolean @default(false) - @@allow('all', auth() == this) @@allow('read', published) } diff --git a/tests/integration/tests/schema/cal-com.zmodel b/tests/integration/tests/schema/cal-com.zmodel index 583fd14ab..4e48b3102 100644 --- a/tests/integration/tests/schema/cal-com.zmodel +++ b/tests/integration/tests/schema/cal-com.zmodel @@ -14,16 +14,22 @@ generator client { plugin meta { provider = '@core/model-meta' output = '.zenstack' + compile = true + preserveTsFiles = true } plugin policy { provider = '@core/access-policy' output = '.zenstack' + compile = true + preserveTsFiles = true } plugin zod { provider = '@core/zod' output = '.zenstack/zod' + compile = true + preserveTsFiles = true } enum SchedulingType { diff --git a/tests/integration/tests/schema/todo.zmodel b/tests/integration/tests/schema/todo.zmodel index eac571be3..5c8eab6ad 100644 --- a/tests/integration/tests/schema/todo.zmodel +++ b/tests/integration/tests/schema/todo.zmodel @@ -15,16 +15,22 @@ generator js { plugin meta { provider = '@core/model-meta' output = '.zenstack' + compile = true + preserveTsFiles = true } plugin policy { provider = '@core/access-policy' output = '.zenstack' + compile = true + preserveTsFiles = true } plugin zod { provider = '@core/zod' output = '.zenstack/zod' + compile = true + preserveTsFiles = true } /* diff --git a/tests/integration/tests/trpc/test-project/package-lock.json b/tests/integration/tests/trpc/test-project/package-lock.json index fdff3569b..384172430 100644 --- a/tests/integration/tests/trpc/test-project/package-lock.json +++ b/tests/integration/tests/trpc/test-project/package-lock.json @@ -22,7 +22,7 @@ "react-dom": "18.2.0", "superjson": "^1.12.2", "typescript": "4.9.4", - "zod": "^3.20.2" + "zod": "3.21.1" }, "devDependencies": { "prisma": "^4.7.0" diff --git a/tests/integration/tests/trpc/test-project/package.json b/tests/integration/tests/trpc/test-project/package.json index 9837167fe..097b1cb86 100644 --- a/tests/integration/tests/trpc/test-project/package.json +++ b/tests/integration/tests/trpc/test-project/package.json @@ -23,7 +23,8 @@ "react-dom": "18.2.0", "superjson": "^1.12.2", "typescript": "4.9.4", - "zod": "^3.20.2" + "zod": "3.21.1", + "@zenstackhq/runtime": "../../../../../../packages/runtime/dist" }, "devDependencies": { "prisma": "^4.7.0" diff --git a/tests/integration/tests/trpc/test-project/todo.zmodel b/tests/integration/tests/trpc/test-project/todo.zmodel index 8468b6ef2..04c997427 100644 --- a/tests/integration/tests/trpc/test-project/todo.zmodel +++ b/tests/integration/tests/trpc/test-project/todo.zmodel @@ -7,6 +7,20 @@ generator js { provider = 'prisma-client-js' } +plugin meta { + provider = '@core/model-meta' + output = '.zenstack' + compile = true + preserveTsFiles = true +} + +plugin policy { + provider = '@core/access-policy' + output = '.zenstack' + compile = true + preserveTsFiles = true +} + plugin trpc { provider = '@zenstackhq/trpc' output = 'server/routers/generated' @@ -16,7 +30,6 @@ model User { id String @id name String posts Post[] - @@allow('create,read', true) @@allow('update,delete', auth() == this) } @@ -27,7 +40,6 @@ model Post { author User? @relation(fields: [authorId], references: [id]) authorId String? published Boolean @default(false) - @@allow('all', auth() == this) @@allow('read', published) } diff --git a/tests/integration/tests/with-policy/auth.test.ts b/tests/integration/tests/with-policy/auth.test.ts index 57c9fff08..fb7e0c9b5 100644 --- a/tests/integration/tests/with-policy/auth.test.ts +++ b/tests/integration/tests/with-policy/auth.test.ts @@ -3,7 +3,6 @@ import path from 'path'; describe('With Policy: auth() test', () => { let origDir: string; - const suite = 'undefined-user'; beforeAll(async () => { origDir = path.resolve('.'); diff --git a/tests/integration/tests/with-policy/deep-nested.test.ts b/tests/integration/tests/with-policy/deep-nested.test.ts index 842feddbd..86ac0b5a2 100644 --- a/tests/integration/tests/with-policy/deep-nested.test.ts +++ b/tests/integration/tests/with-policy/deep-nested.test.ts @@ -3,7 +3,6 @@ import path from 'path'; describe('With Policy:deep nested', () => { let origDir: string; - const suite = 'deep-nested'; const model = ` // M1 - M2 - M3 diff --git a/tests/integration/tests/with-policy/field-validation.test.ts b/tests/integration/tests/with-policy/field-validation.test.ts index 38925653d..4e3d8a4e8 100644 --- a/tests/integration/tests/with-policy/field-validation.test.ts +++ b/tests/integration/tests/with-policy/field-validation.test.ts @@ -1,8 +1,7 @@ -import { loadSchema, run, WeakDbClientContract } from '@zenstackhq/testtools'; +import { WeakDbClientContract, loadSchema, run } from '@zenstackhq/testtools'; describe('With Policy: field validation', () => { let db: WeakDbClientContract; - let prisma: WeakDbClientContract; beforeAll(async () => { const { withPolicy, prisma: _prisma } = await loadSchema( @@ -49,7 +48,6 @@ describe('With Policy: field validation', () => { ` ); db = withPolicy(); - prisma = _prisma; }); beforeEach(() => { diff --git a/tests/integration/tests/with-policy/multi-field-unique.test.ts b/tests/integration/tests/with-policy/multi-field-unique.test.ts index a6629357c..3dcc07850 100644 --- a/tests/integration/tests/with-policy/multi-field-unique.test.ts +++ b/tests/integration/tests/with-policy/multi-field-unique.test.ts @@ -3,7 +3,6 @@ import path from 'path'; describe('With Policy: multi-field unique', () => { let origDir: string; - const suite = 'multi-field-unique'; beforeAll(async () => { origDir = path.resolve('.'); diff --git a/tests/integration/tests/with-policy/nested-to-many.test.ts b/tests/integration/tests/with-policy/nested-to-many.test.ts index a3ae19dfb..8eedfd75a 100644 --- a/tests/integration/tests/with-policy/nested-to-many.test.ts +++ b/tests/integration/tests/with-policy/nested-to-many.test.ts @@ -3,7 +3,6 @@ import path from 'path'; describe('With Policy:nested to-many', () => { let origDir: string; - const suite = 'nested-to-many'; beforeAll(async () => { origDir = path.resolve('.'); diff --git a/tests/integration/tests/with-policy/nested-to-one.test.ts b/tests/integration/tests/with-policy/nested-to-one.test.ts index 8eff681d5..f14916667 100644 --- a/tests/integration/tests/with-policy/nested-to-one.test.ts +++ b/tests/integration/tests/with-policy/nested-to-one.test.ts @@ -3,7 +3,6 @@ import path from 'path'; describe('With Policy:nested to-one', () => { let origDir: string; - const suite = 'nested-to-one'; beforeAll(async () => { origDir = path.resolve('.'); diff --git a/tests/integration/tests/with-policy/post-update.test.ts b/tests/integration/tests/with-policy/post-update.test.ts index 73d21713b..dd55caa9c 100644 --- a/tests/integration/tests/with-policy/post-update.test.ts +++ b/tests/integration/tests/with-policy/post-update.test.ts @@ -3,7 +3,6 @@ import path from 'path'; describe('With Policy: post update', () => { let origDir: string; - const suite = 'post-update'; beforeAll(async () => { origDir = path.resolve('.'); diff --git a/tests/integration/tests/with-policy/toplevel-operations.test.ts b/tests/integration/tests/with-policy/toplevel-operations.test.ts index 72171315f..15626e1c2 100644 --- a/tests/integration/tests/with-policy/toplevel-operations.test.ts +++ b/tests/integration/tests/with-policy/toplevel-operations.test.ts @@ -3,7 +3,6 @@ import path from 'path'; describe('With Policy:toplevel operations', () => { let origDir: string; - const suite = 'toplevel'; beforeAll(async () => { origDir = path.resolve('.'); From bb4221ab620970606c91fcdc3395de0fe6b028ad Mon Sep 17 00:00:00 2001 From: JG Date: Fri, 28 Apr 2023 13:08:11 +0100 Subject: [PATCH 06/12] fix merging accident --- .../schema/src/language-server/validator/datamodel-validator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema/src/language-server/validator/datamodel-validator.ts b/packages/schema/src/language-server/validator/datamodel-validator.ts index f2e9eebcb..f2add7ad9 100644 --- a/packages/schema/src/language-server/validator/datamodel-validator.ts +++ b/packages/schema/src/language-server/validator/datamodel-validator.ts @@ -78,7 +78,7 @@ export default class DataModelValidator implements AstValidator { field.attributes.forEach((attr) => validateAttributeApplication(attr, accept)); - if (isDataModel(field.type.reference?.ref)) { + if (isDataModel(field.type.reference?.ref) && !(field.$container as DataModel).isAbstract) { this.validateRelationField(field, accept); } } From 07d55bf5521166c81948b064b681684cd8b64b40 Mon Sep 17 00:00:00 2001 From: JG Date: Fri, 28 Apr 2023 22:32:41 +0100 Subject: [PATCH 07/12] fix: fix code action --- packages/schema/src/cli/cli-util.ts | 6 ++- .../validator/datamodel-validator.ts | 49 +++++++++++++++++-- .../src/language-server/zmodel-code-action.ts | 36 ++++++++++---- 3 files changed, 77 insertions(+), 14 deletions(-) diff --git a/packages/schema/src/cli/cli-util.ts b/packages/schema/src/cli/cli-util.ts index ea02495d1..30ad3da62 100644 --- a/packages/schema/src/cli/cli-util.ts +++ b/packages/schema/src/cli/cli-util.ts @@ -125,7 +125,11 @@ export async function loadDocument(fileName: string): Promise { } ); - const validationErrors = (document.diagnostics ?? []).filter((e) => e.severity === 1); + const validationErrors = langiumDocuments.all + .flatMap((d) => d.diagnostics ?? []) + .filter((e) => e.severity === 1) + .toArray(); + if (validationErrors.length > 0) { console.error(colors.red('Validation errors:')); for (const validationError of validationErrors) { diff --git a/packages/schema/src/language-server/validator/datamodel-validator.ts b/packages/schema/src/language-server/validator/datamodel-validator.ts index f2add7ad9..7fa45b6d0 100644 --- a/packages/schema/src/language-server/validator/datamodel-validator.ts +++ b/packages/schema/src/language-server/validator/datamodel-validator.ts @@ -7,7 +7,7 @@ import { ReferenceExpr, } from '@zenstackhq/language/ast'; import { analyzePolicies, getLiteral } from '@zenstackhq/sdk'; -import { ValidationAcceptor } from 'langium'; +import { AstNode, DiagnosticInfo, getDocument, ValidationAcceptor } from 'langium'; import { IssueCodes, SCALAR_TYPES } from '../constants'; import { AstValidator } from '../types'; import { getIdFields, getUniqueFields } from '../utils'; @@ -213,17 +213,49 @@ export default class DataModelValidator implements AstValidator { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const oppositeModel = field.type.reference!.ref! as DataModel; - let oppositeFields = oppositeModel.$resolvedFields.filter((f) => f.type.reference?.ref === field.$container); + // Use name because the current document might be updated + let oppositeFields = oppositeModel.$resolvedFields.filter( + (f) => f.type.reference?.ref?.name === field.$container.name + ); oppositeFields = oppositeFields.filter((f) => { const fieldRel = this.parseRelation(f); return fieldRel.valid && fieldRel.name === thisRelation.name; }); if (oppositeFields.length === 0) { + const node = field.$isInherited ? field.$container : field; + const info: DiagnosticInfo = { node, code: IssueCodes.MissingOppositeRelation }; + + let relationFieldDocUri: string; + let relationDataModelName: string; + + if (field.$isInherited) { + info.property = 'name'; + const container = field.$container as DataModel; + const abstractContainer = container.superTypes.find((x) => + x.ref?.fields.find((f) => f.name === field.name) + )?.ref as DataModel; + + relationFieldDocUri = getDocument(abstractContainer).textDocument.uri; + relationDataModelName = abstractContainer.name; + } else { + relationFieldDocUri = getDocument(field).textDocument.uri; + relationDataModelName = field.$container.name; + } + + const data: MissingOppositeRelationData = { + relationFieldName: field.name, + relationDataModelName, + relationFieldDocUri, + dataModelName: field.$container.name, + }; + + info.data = data; + accept( 'error', `The relation field "${field.name}" on model "${field.$container.name}" is missing an opposite relation field on model "${oppositeModel.name}"`, - { node: field, code: IssueCodes.MissingOppositeRelation } + info ); return; } else if (oppositeFields.length > 1) { @@ -329,3 +361,14 @@ export default class DataModelValidator implements AstValidator { } } } + +export interface MissingOppositeRelationData { + relationDataModelName: string; + relationFieldName: string; + // it might be the abstract model in the imported document + relationFieldDocUri: string; + + // the name of DataModel that the relation field belongs to. + // the document is the same with the error node. + dataModelName: string; +} diff --git a/packages/schema/src/language-server/zmodel-code-action.ts b/packages/schema/src/language-server/zmodel-code-action.ts index 92ce9f3a2..e9e7862ec 100644 --- a/packages/schema/src/language-server/zmodel-code-action.ts +++ b/packages/schema/src/language-server/zmodel-code-action.ts @@ -1,12 +1,11 @@ -import { DataModel, DataModelField, isDataModel } from '@zenstackhq/language/ast'; +import { DataModel, DataModelField, Model, isDataModel } from '@zenstackhq/language/ast'; import { AstReflection, CodeActionProvider, - findDeclarationNodeAtOffset, - getContainerOfType, getDocument, IndexManager, LangiumDocument, + LangiumDocuments, LangiumServices, MaybePromise, } from 'langium'; @@ -14,16 +13,19 @@ import { import { CodeAction, CodeActionKind, CodeActionParams, Command, Diagnostic } from 'vscode-languageserver'; import { IssueCodes } from './constants'; import { ZModelFormatter } from './zmodel-formatter'; +import { MissingOppositeRelationData } from './validator/datamodel-validator'; export class ZModelCodeActionProvider implements CodeActionProvider { protected readonly reflection: AstReflection; protected readonly indexManager: IndexManager; protected readonly formatter: ZModelFormatter; + protected readonly documents: LangiumDocuments; constructor(services: LangiumServices) { this.reflection = services.shared.AstReflection; this.indexManager = services.shared.workspace.IndexManager; this.formatter = services.lsp.Formatter as ZModelFormatter; + this.documents = services.shared.workspace.LangiumDocuments; } getCodeActions( @@ -52,20 +54,34 @@ export class ZModelCodeActionProvider implements CodeActionProvider { } private fixMissingOppositeRelation(diagnostic: Diagnostic, document: LangiumDocument): CodeAction | undefined { - const offset = document.textDocument.offsetAt(diagnostic.range.start); - const rootCst = document.parseResult.value.$cstNode; + const data = diagnostic.data as MissingOppositeRelationData; + + const rootCst = + data.relationFieldDocUri == document.textDocument.uri + ? document.parseResult.value + : this.documents.all.find((doc) => doc.textDocument.uri === data.relationFieldDocUri)?.parseResult + .value; if (rootCst) { - const cstNode = findDeclarationNodeAtOffset(rootCst, offset); + const fieldModel = rootCst as Model; + const fieldAstNode = ( + fieldModel.declarations.find( + (x) => isDataModel(x) && x.name === data.relationDataModelName + ) as DataModel + )?.fields.find((x) => x.name === data.relationFieldName) as DataModelField; - const astNode = cstNode?.element as DataModelField; + if (!fieldAstNode) return undefined; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const oppositeModel = astNode.type.reference!.ref! as DataModel; + const oppositeModel = fieldAstNode.type.reference!.ref! as DataModel; const lastField = oppositeModel.fields[oppositeModel.fields.length - 1]; - const container = getContainerOfType(cstNode?.element, isDataModel) as DataModel; + const currentModel = document.parseResult.value as Model; + + const container = currentModel.declarations.find( + (decl) => decl.name === data.dataModelName && isDataModel(decl) + ) as DataModel; if (container && container.$cstNode) { // indent @@ -77,7 +93,7 @@ export class ZModelCodeActionProvider implements CodeActionProvider { indent = indent.repeat(this.formatter.getIndent()); let newText = ''; - if (astNode.type.array) { + if (fieldAstNode.type.array) { //post Post[] const idField = container.$resolvedFields.find((f) => f.attributes.find((attr) => attr.decl.ref?.name === '@id') From 48a0e0e9e8d2b8da45ee4811ba1f055ce70e0bb4 Mon Sep 17 00:00:00 2001 From: JG Date: Fri, 28 Apr 2023 22:57:05 +0100 Subject: [PATCH 08/12] test: add abstract multi files --- .../tests/generator/prisma-generator.test.ts | 4 ++-- .../schema/tests/generator/zmodel/schema.zmodel | 14 ++++++++------ .../schema/tests/generator/zmodel/user/user.zmodel | 13 ++++++++++++- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index 94873f3f9..dbf88071b 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -302,7 +302,7 @@ describe('Prisma generator test', () => { expect(content).toContain('@map("myTransactionField")'); }); - it('multi files', async () => { + it('abstract multi files', async () => { const model = await loadDocument(path.join(__dirname, './zmodel/schema.zmodel')); const { name } = tmp.fileSync({ postfix: '.prisma' }); @@ -316,7 +316,7 @@ describe('Prisma generator test', () => { const content = fs.readFileSync(name, 'utf-8'); const dmmf = await getDMMF({ datamodel: content }); - expect(dmmf.datamodel.models.length).toBe(2); + expect(dmmf.datamodel.models.length).toBe(3); expect(dmmf.datamodel.enums[0].name).toBe('UserRole'); }); }); diff --git a/packages/schema/tests/generator/zmodel/schema.zmodel b/packages/schema/tests/generator/zmodel/schema.zmodel index be912e3fb..7af4ebf66 100644 --- a/packages/schema/tests/generator/zmodel/schema.zmodel +++ b/packages/schema/tests/generator/zmodel/schema.zmodel @@ -5,10 +5,12 @@ datasource db { url = env('URL') } -model Post { - id Int @id() @default(autoincrement()) - author User? @relation(fields: [authorId], references: [id]) - authorId Int? - // author has full access - @@allow('all', auth() == author) +model Post extends Basic { + title String + content String? +} + +model Todo extends Basic { + title String + isCompleted Boolean } \ No newline at end of file diff --git a/packages/schema/tests/generator/zmodel/user/user.zmodel b/packages/schema/tests/generator/zmodel/user/user.zmodel index c83eb6fbe..eb351a627 100644 --- a/packages/schema/tests/generator/zmodel/user/user.zmodel +++ b/packages/schema/tests/generator/zmodel/user/user.zmodel @@ -1,9 +1,10 @@ import "../schema" model User { - id Int @id() @default(autoincrement()) + id String @id() @default(uuid()) email String @unique() name String? posts Post[] + todos Todo[] role UserRole // make user profile public @@ -14,4 +15,14 @@ model User { enum UserRole { USER ADMIN +} + +abstract model Basic { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) + ownerId String + + @@allow('read', owner == auth()) } \ No newline at end of file From f880587a36aeaa1d7bded25cd9db85415cbbdee4 Mon Sep 17 00:00:00 2001 From: JG Date: Sat, 29 Apr 2023 07:46:31 +0100 Subject: [PATCH 09/12] fix: resolve abstract attribute --- .../schema/src/language-server/zmodel-linker.ts | 15 +++++++++++++++ .../tests/generator/prisma-generator.test.ts | 2 ++ 2 files changed, 17 insertions(+) diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index 743f24c43..d6c4eda03 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -160,6 +160,10 @@ export class ZModelLinker extends DefaultLinker { this.resolveAttributeArg(node as AttributeArg, document, extraScopes); break; + case DataModel: + this.resolveDataModel(node as DataModel, document, extraScopes); + break; + default: this.resolveDefault(node, document, extraScopes); break; @@ -436,6 +440,17 @@ export class ZModelLinker extends DefaultLinker { } } + private resolveDataModel(node: DataModel, document: LangiumDocument, extraScopes: ScopeProvider[]) { + if (node.superTypes.length > 0) { + const providers = node.superTypes.map( + (superType) => (name: string) => superType.ref?.fields.find((f) => f.name === name) + ); + extraScopes = [...providers, ...extraScopes]; + } + + return this.resolveDefault(node, document, extraScopes); + } + private resolveDefault(node: AstNode, document: LangiumDocument, extraScopes: ScopeProvider[]) { for (const [property, value] of Object.entries(node)) { if (!property.startsWith('$')) { diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index dbf88071b..c644447db 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -316,6 +316,8 @@ describe('Prisma generator test', () => { const content = fs.readFileSync(name, 'utf-8'); const dmmf = await getDMMF({ datamodel: content }); + console.log(content); + expect(dmmf.datamodel.models.length).toBe(3); expect(dmmf.datamodel.enums[0].name).toBe('UserRole'); }); From fd002753849f26b79982e1ca2d708b07f8e517b2 Mon Sep 17 00:00:00 2001 From: JG Date: Sat, 29 Apr 2023 08:02:25 +0100 Subject: [PATCH 10/12] fix: remove duplicate relation error --- .../src/language-server/validator/datamodel-validator.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/schema/src/language-server/validator/datamodel-validator.ts b/packages/schema/src/language-server/validator/datamodel-validator.ts index 7fa45b6d0..79ab5b43f 100644 --- a/packages/schema/src/language-server/validator/datamodel-validator.ts +++ b/packages/schema/src/language-server/validator/datamodel-validator.ts @@ -56,7 +56,7 @@ export default class DataModelValidator implements AstValidator { }); } - dm.fields.forEach((field) => this.validateField(field, dm, accept)); + dm.fields.forEach((field) => this.validateField(field, accept)); if (!dm.isAbstract) { dm.$resolvedFields @@ -67,7 +67,7 @@ export default class DataModelValidator implements AstValidator { } } - private validateField(field: DataModelField, dataModel: DataModel, accept: ValidationAcceptor): void { + private validateField(field: DataModelField, accept: ValidationAcceptor): void { if (field.type.array && field.type.optional) { accept('error', 'Optional lists are not supported. Use either `Type[]` or `Type?`', { node: field.type }); } @@ -77,10 +77,6 @@ export default class DataModelValidator implements AstValidator { } field.attributes.forEach((attr) => validateAttributeApplication(attr, accept)); - - if (isDataModel(field.type.reference?.ref) && !(field.$container as DataModel).isAbstract) { - this.validateRelationField(field, accept); - } } private validateAttributes(dm: DataModel, accept: ValidationAcceptor) { From 58fc719988b8f7f29120b8eb1fd723896cf6e8ac Mon Sep 17 00:00:00 2001 From: JG Date: Sat, 29 Apr 2023 08:43:01 +0100 Subject: [PATCH 11/12] feat: merge attributes from abstract model --- packages/schema/src/utils/ast-utils.ts | 8 ++++++++ .../schema/tests/generator/prisma-generator.test.ts | 11 +++++++++-- packages/schema/tests/generator/zmodel/schema.zmodel | 2 ++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/schema/src/utils/ast-utils.ts b/packages/schema/src/utils/ast-utils.ts index 15409eb76..6ce870302 100644 --- a/packages/schema/src/utils/ast-utils.ts +++ b/packages/schema/src/utils/ast-utils.ts @@ -69,6 +69,14 @@ export function mergeBaseModel(model: Model) { mutable.$container = dataModel; dataModel.fields.push(mutable as DataModelField); }); + + superTypeDecl.attributes.forEach((attr) => { + const cloneAttr = Object.assign({}, attr); + const mutable = cloneAttr as Mutable; + // update container + mutable.$container = dataModel; + dataModel.attributes.push(mutable as DataModelAttribute); + }); } }); }); diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index c644447db..a65e34206 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -316,9 +316,16 @@ describe('Prisma generator test', () => { const content = fs.readFileSync(name, 'utf-8'); const dmmf = await getDMMF({ datamodel: content }); - console.log(content); - expect(dmmf.datamodel.models.length).toBe(3); expect(dmmf.datamodel.enums[0].name).toBe('UserRole'); + + const post = dmmf.datamodel.models.find((m) => m.name === 'Post'); + + expect(post!.documentation?.replace(/\s/g, '')).toBe( + `@@allow('delete', ownerId == auth()) @@allow('read', owner == auth())`.replace(/\s/g, '') + ); + + const todo = dmmf.datamodel.models.find((m) => m.name === 'Todo'); + expect(todo!.documentation?.replace(/\s/g, '')).toBe(`@@allow('read', owner == auth())`.replace(/\s/g, '')); }); }); diff --git a/packages/schema/tests/generator/zmodel/schema.zmodel b/packages/schema/tests/generator/zmodel/schema.zmodel index 7af4ebf66..9e2c6a803 100644 --- a/packages/schema/tests/generator/zmodel/schema.zmodel +++ b/packages/schema/tests/generator/zmodel/schema.zmodel @@ -8,6 +8,8 @@ datasource db { model Post extends Basic { title String content String? + + @@allow('delete', ownerId == auth()) } model Todo extends Basic { From 3e3ad22f5fae4ff4e5fff25f545d6bd0c643c669 Mon Sep 17 00:00:00 2001 From: JG Date: Sat, 29 Apr 2023 09:08:02 +0100 Subject: [PATCH 12/12] feat: validate base model must be abstract --- .../validator/datamodel-validator.ts | 12 +++++++++++ .../tests/generator/prisma-generator.test.ts | 4 ++-- .../validation/datamodel-validation.test.ts | 21 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/schema/src/language-server/validator/datamodel-validator.ts b/packages/schema/src/language-server/validator/datamodel-validator.ts index 79ab5b43f..ec305330f 100644 --- a/packages/schema/src/language-server/validator/datamodel-validator.ts +++ b/packages/schema/src/language-server/validator/datamodel-validator.ts @@ -18,6 +18,7 @@ import { validateAttributeApplication, validateDuplicatedDeclarations } from './ */ export default class DataModelValidator implements AstValidator { validate(dm: DataModel, accept: ValidationAcceptor): void { + this.validateBaseAbstractModel(dm, accept); validateDuplicatedDeclarations(dm.$resolvedFields, accept); this.validateAttributes(dm, accept); this.validateFields(dm, accept); @@ -356,6 +357,17 @@ export default class DataModelValidator implements AstValidator { }); } } + + private validateBaseAbstractModel(model: DataModel, accept: ValidationAcceptor) { + model.superTypes.forEach((superType, index) => { + if (!superType.ref?.isAbstract) + accept('error', `Model ${superType.$refText} cannot be extended because it's not abstract`, { + node: model, + property: 'superTypes', + index, + }); + }); + } } export interface MissingOppositeRelationData { diff --git a/packages/schema/tests/generator/prisma-generator.test.ts b/packages/schema/tests/generator/prisma-generator.test.ts index a65e34206..fc4bfe8de 100644 --- a/packages/schema/tests/generator/prisma-generator.test.ts +++ b/packages/schema/tests/generator/prisma-generator.test.ts @@ -321,11 +321,11 @@ describe('Prisma generator test', () => { const post = dmmf.datamodel.models.find((m) => m.name === 'Post'); - expect(post!.documentation?.replace(/\s/g, '')).toBe( + expect(post?.documentation?.replace(/\s/g, '')).toBe( `@@allow('delete', ownerId == auth()) @@allow('read', owner == auth())`.replace(/\s/g, '') ); const todo = dmmf.datamodel.models.find((m) => m.name === 'Todo'); - expect(todo!.documentation?.replace(/\s/g, '')).toBe(`@@allow('read', owner == auth())`.replace(/\s/g, '')); + expect(todo?.documentation?.replace(/\s/g, '')).toBe(`@@allow('read', owner == auth())`.replace(/\s/g, '')); }); }); diff --git a/packages/schema/tests/schema/validation/datamodel-validation.test.ts b/packages/schema/tests/schema/validation/datamodel-validation.test.ts index f9a3478fc..daa7c381e 100644 --- a/packages/schema/tests/schema/validation/datamodel-validation.test.ts +++ b/packages/schema/tests/schema/validation/datamodel-validation.test.ts @@ -590,4 +590,25 @@ describe('Data Model Validation Tests', () => { } `); }); + + it('abstract base type', async () => { + const errors = await loadModelWithError(` + ${prelude} + + abstract model Base { + id String @id + } + + model A { + a String + } + + model B extends Base,A { + b String + } + `); + expect(errors.length).toBe(1); + + expect(errors[0]).toEqual(`Model A cannot be extended because it's not abstract`); + }); });