diff --git a/package.json b/package.json index 7127fc1b8..d61f79174 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "1.10.3", + "version": "1.11.0", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/ide/jetbrains/build.gradle.kts b/packages/ide/jetbrains/build.gradle.kts index 702173ecc..23dc11e99 100644 --- a/packages/ide/jetbrains/build.gradle.kts +++ b/packages/ide/jetbrains/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.zenstack" -version = "1.10.3" +version = "1.11.0" repositories { mavenCentral() diff --git a/packages/ide/jetbrains/package.json b/packages/ide/jetbrains/package.json index 1243423e7..09476b216 100644 --- a/packages/ide/jetbrains/package.json +++ b/packages/ide/jetbrains/package.json @@ -1,6 +1,6 @@ { "name": "jetbrains", - "version": "1.10.3", + "version": "1.11.0", "displayName": "ZenStack JetBrains IDE Plugin", "description": "ZenStack JetBrains IDE plugin", "homepage": "https://zenstack.dev", diff --git a/packages/language/package.json b/packages/language/package.json index a07cd6772..50758ce23 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "1.10.3", + "version": "1.11.0", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/misc/redwood/package.json b/packages/misc/redwood/package.json index 300daaced..d95ef1925 100644 --- a/packages/misc/redwood/package.json +++ b/packages/misc/redwood/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/redwood", "displayName": "ZenStack RedwoodJS Integration", - "version": "1.10.3", + "version": "1.11.0", "description": "CLI and runtime for integrating ZenStack with RedwoodJS projects.", "repository": { "type": "git", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index d0f39b473..3db67dead 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.10.3", + "version": "1.11.0", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/swr/package.json b/packages/plugins/swr/package.json index 1bd780195..0d1fb7147 100644 --- a/packages/plugins/swr/package.json +++ b/packages/plugins/swr/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/swr", "displayName": "ZenStack plugin for generating SWR hooks", - "version": "1.10.3", + "version": "1.11.0", "description": "ZenStack plugin for generating SWR hooks", "main": "index.js", "repository": { diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index 2cd3e1161..8d034e8f4 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/tanstack-query", "displayName": "ZenStack plugin for generating tanstack-query hooks", - "version": "1.10.3", + "version": "1.11.0", "description": "ZenStack plugin for generating tanstack-query hooks", "main": "index.js", "exports": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index bf8107afd..ad76965c2 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.10.3", + "version": "1.11.0", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 160341df6..5665262c1 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "1.10.3", + "version": "1.11.0", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index bae642f1a..249389e68 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "Build scalable web apps with minimum code by defining authorization and validation rules inside the data schema that closer to the database", - "version": "1.10.3", + "version": "1.11.0", "author": { "name": "ZenStack Team" }, diff --git a/packages/schema/src/utils/ast-utils.ts b/packages/schema/src/utils/ast-utils.ts index 348752fae..c752c837d 100644 --- a/packages/schema/src/utils/ast-utils.ts +++ b/packages/schema/src/utils/ast-utils.ts @@ -18,6 +18,8 @@ import { import { isFromStdlib } from '@zenstackhq/sdk'; import { AstNode, getDocument, LangiumDocuments, Mutable } from 'langium'; import { URI, Utils } from 'vscode-uri'; +import { findNodeModulesFile } from './pkg-utils'; +import {isAbsolute} from 'node:path' export function extractDataModelsWithAllowRules(model: Model): DataModel[] { return model.declarations.filter( @@ -94,15 +96,21 @@ export function getDataModelFieldReference(expr: Expression): DataModelField | u } export function resolveImportUri(imp: ModelImport): URI | undefined { - if (imp.path === undefined || imp.path.length === 0) { - return undefined; + if (!imp.path) return undefined; // This will return true if imp.path is undefined, null, or an empty string (""). + + if (!imp.path.endsWith('.zmodel')) { + imp.path += '.zmodel'; } - const dirUri = Utils.dirname(getDocument(imp).uri); - let grammarPath = imp.path; - if (!grammarPath.endsWith('.zmodel')) { - grammarPath += '.zmodel'; + + if ( + !imp.path.startsWith('.') // Respect relative paths + && !isAbsolute(imp.path) // Respect Absolute paths + ) { + imp.path = findNodeModulesFile(imp.path) ?? imp.path; } - return Utils.resolvePath(dirUri, grammarPath); + + const dirUri = Utils.dirname(getDocument(imp).uri); + return Utils.resolvePath(dirUri, imp.path); } export function resolveTransitiveImports(documents: LangiumDocuments, model: Model): Model[] { diff --git a/packages/schema/src/utils/pkg-utils.ts b/packages/schema/src/utils/pkg-utils.ts index ce41dac34..99593dcd3 100644 --- a/packages/schema/src/utils/pkg-utils.ts +++ b/packages/schema/src/utils/pkg-utils.ts @@ -5,8 +5,8 @@ import { execSync } from './exec-utils'; export type PackageManagers = 'npm' | 'yarn' | 'pnpm'; /** - * A type named FindUp that takes a type parameter e which extends boolean. - * If e extends true, it returns a union type of string[] or undefined. + * A type named FindUp that takes a type parameter e which extends boolean. + * If e extends true, it returns a union type of string[] or undefined. * If e does not extend true, it returns a union type of string or undefined. * * @export @@ -14,9 +14,9 @@ export type PackageManagers = 'npm' | 'yarn' | 'pnpm'; */ export type FindUp = e extends true ? string[] | undefined : string | undefined /** - * Find and return file paths by searching parent directories based on the given names list and current working directory (cwd) path. - * Optionally return a single path or multiple paths. - * If multiple allowed, return all paths found. + * Find and return file paths by searching parent directories based on the given names list and current working directory (cwd) path. + * Optionally return a single path or multiple paths. + * If multiple allowed, return all paths found. * If no paths are found, return undefined. * * @export @@ -37,6 +37,30 @@ export function findUp(names: string[], cwd: string = return findUp(names, up, multiple, result); } + +/** + * Find a Node module/file given its name in a specific directory, with a fallback to the current working directory. + * If the name is empty, return undefined. + * Try to resolve the module/file using require.resolve with the specified directory as the starting point. + * Return the resolved path if successful, otherwise return undefined. + * + * @export + * @param {string} name The name of the module/file to find + * @param {string} [cwd=process.cwd()] + * @returns {*} Finds a specified module or file using require.resolve starting from a specified directory path, or the current working directory if not provided. + */ +export function findNodeModulesFile(name: string, cwd: string = process.cwd()) { + if (!name) return undefined; + try { + // Use require.resolve to find the module/file. The paths option allows specifying the directory to start from. + const resolvedPath = require.resolve(name, { paths: [cwd] }) + return resolvedPath + } catch (error) { + // If require.resolve fails to find the module/file, it will throw an error. + return undefined + } +} + function getPackageManager(projectPath = '.'): PackageManagers { const lockFile = findUp(['yarn.lock', 'pnpm-lock.yaml', 'package-lock.json'], projectPath); @@ -106,7 +130,7 @@ export function ensurePackage( } /** - * A function that searches for the nearest package.json file starting from the provided search path or the current working directory if no search path is provided. + * A function that searches for the nearest package.json file starting from the provided search path or the current working directory if no search path is provided. * It iterates through the directory structure going one level up at a time until it finds a package.json file. If no package.json file is found, it returns undefined. * @deprecated Use findUp instead @see findUp */ diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 651429ef8..110fec6c0 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "1.10.3", + "version": "1.11.0", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/sdk/src/utils.ts b/packages/sdk/src/utils.ts index d32962f11..a32abc068 100644 --- a/packages/sdk/src/utils.ts +++ b/packages/sdk/src/utils.ts @@ -218,11 +218,14 @@ export function isIdField(field: DataModelField) { return true; } + // NOTE: we have to use name to match fields because the fields + // may be inherited from an abstract base and have cloned identities + const model = field.$container as DataModel; // model-level @@id attribute with a list of fields const modelLevelIds = getModelIdFields(model); - if (modelLevelIds.includes(field)) { + if (modelLevelIds.map((f) => f.name).includes(field.name)) { return true; } @@ -234,12 +237,12 @@ export function isIdField(field: DataModelField) { // then, the first field with @unique can be used as id const firstUniqueField = model.fields.find((f) => hasAttribute(f, '@unique')); if (firstUniqueField) { - return firstUniqueField === field; + return firstUniqueField.name === field.name; } // last, the first model level @@unique can be used as id const modelLevelUnique = getModelUniqueFields(model); - if (modelLevelUnique.includes(field)) { + if (modelLevelUnique.map((f) => f.name).includes(field.name)) { return true; } diff --git a/packages/server/package.json b/packages/server/package.json index 1936946b2..ea60177a5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "1.10.3", + "version": "1.11.0", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", diff --git a/packages/testtools/package.json b/packages/testtools/package.json index d8f7ce067..97e842f00 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "1.10.3", + "version": "1.11.0", "description": "ZenStack Test Tools", "main": "index.js", "private": true, diff --git a/tests/integration/tests/regression/issue-1129.test.ts b/tests/integration/tests/regression/issue-1129.test.ts new file mode 100644 index 000000000..49198a5cb --- /dev/null +++ b/tests/integration/tests/regression/issue-1129.test.ts @@ -0,0 +1,87 @@ +import { createPostgresDb, dropPostgresDb, loadSchema } from '@zenstackhq/testtools'; + +describe('Regression for issue 1129', () => { + it('regression', async () => { + let prisma; + const dbUrl = await createPostgresDb('regression-issue-1129'); + + try { + const r = await loadSchema( + ` + model Relation1 { + id String @id @default(cuid()) + field1 String + concrete Concrete[] + @@allow('all', true) + } + + model Relation2 { + id String @id @default(cuid()) + field2 String + concrete Concrete[] + @@allow('all', true) + } + + abstract model WithRelation1 { + relation1Id String + relation1 Relation1 @relation(fields: [relation1Id], references: [id]) + } + abstract model WithRelation2 { + relation2Id String + relation2 Relation2 @relation(fields: [relation2Id], references: [id]) + } + + model Concrete extends WithRelation1, WithRelation2 { + concreteField String + @@id([relation1Id, relation2Id]) + @@allow('all', true) + } + `, + { provider: 'postgresql', dbUrl } + ); + + prisma = r.prisma; + const db = r.enhance(); + + await db.$transaction(async (tx: any) => { + await tx.relation2.createMany({ + data: [ + { + id: 'relation2Id1', + field2: 'field2Value1', + }, + { + id: 'relation2Id2', + field2: 'field2Value2', + }, + ], + }); + + await tx.relation1.create({ + data: { + field1: 'field1Value', + concrete: { + createMany: { + data: [ + { + concreteField: 'concreteFieldValue1', + relation2Id: 'relation2Id1', + }, + { + concreteField: 'concreteFieldValue2', + relation2Id: 'relation2Id2', + }, + ], + }, + }, + }, + }); + }); + } finally { + if (prisma) { + await prisma.$disconnect(); + } + await dropPostgresDb('regression-issue-1129'); + } + }); +});