diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 632042c5a..69d9056e7 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -15,7 +15,7 @@ permissions: jobs: build-test: - runs-on: buildjet-8vcpu-ubuntu-2204 + runs-on: ubuntu-latest services: postgres: @@ -59,7 +59,7 @@ jobs: version: 10.12.1 - name: Use Node.js ${{ matrix.node-version }} - uses: buildjet/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -76,7 +76,7 @@ jobs: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - name: Setup pnpm cache - uses: buildjet/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 3334ebc44..a15950130 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -12,12 +12,7 @@ on: jobs: claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - + if: ${{ github.event.pull_request.head.repo.fork == false }} runs-on: ubuntu-latest permissions: contents: read @@ -33,6 +28,7 @@ jobs: - name: Run Claude Code Review id: claude-review + if: ${{ !contains(github.event.pull_request.title, '[WIP]') }} uses: anthropics/claude-code-action@beta with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} @@ -71,7 +67,3 @@ jobs: # Optional: Add specific tools for running tests or linting # allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)" - # Optional: Skip review for certain conditions - # if: | - # !contains(github.event.pull_request.title, '[skip-review]') && - # !contains(github.event.pull_request.title, '[WIP]') diff --git a/package.json b/package.json index bfd38f904..b72b85dd3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-v3", - "version": "3.4.3", + "version": "3.4.4", "description": "ZenStack", "packageManager": "pnpm@10.23.0", "type": "module", diff --git a/packages/auth-adapters/better-auth/package.json b/packages/auth-adapters/better-auth/package.json index 13c02848d..0b69b2049 100644 --- a/packages/auth-adapters/better-auth/package.json +++ b/packages/auth-adapters/better-auth/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/better-auth", - "version": "3.4.3", + "version": "3.4.4", "description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.", "type": "module", "scripts": { diff --git a/packages/cli/package.json b/packages/cli/package.json index f86a5dcb8..f7dc0c589 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack CLI", "description": "FullStack database toolkit with built-in access control and automatic API generation.", - "version": "3.4.3", + "version": "3.4.4", "type": "module", "author": { "name": "ZenStack Team" diff --git a/packages/cli/src/actions/action-utils.ts b/packages/cli/src/actions/action-utils.ts index 7539c4ca0..aed343193 100644 --- a/packages/cli/src/actions/action-utils.ts +++ b/packages/cli/src/actions/action-utils.ts @@ -1,13 +1,16 @@ +import { invariant } from '@zenstackhq/common-helpers'; import { type ZModelServices, loadDocument } from '@zenstackhq/language'; -import { type Model, isDataSource } from '@zenstackhq/language/ast'; -import { PrismaSchemaGenerator } from '@zenstackhq/sdk'; +import { type Model, type Plugin, isDataSource, type LiteralExpr } from '@zenstackhq/language/ast'; +import { type CliPlugin, PrismaSchemaGenerator } from '@zenstackhq/sdk'; import colors from 'colors'; +import { createJiti } from 'jiti'; import fs from 'node:fs'; import { createRequire } from 'node:module'; import path from 'node:path'; -import { CliError } from '../cli-error'; +import { pathToFileURL } from 'node:url'; import terminalLink from 'terminal-link'; import { z } from 'zod'; +import { CliError } from '../cli-error'; export function getSchemaFile(file?: string) { if (file) { @@ -219,6 +222,89 @@ export async function getZenStackPackages( return result.filter((p) => !!p); } +export function getPluginProvider(plugin: Plugin) { + const providerField = plugin.fields.find((f) => f.name === 'provider'); + invariant(providerField, `Plugin ${plugin.name} does not have a provider field`); + const provider = (providerField.value as LiteralExpr).value as string; + return provider; +} + +export async function loadPluginModule(provider: string, basePath: string) { + if (provider.toLowerCase().endsWith('.zmodel')) { + // provider is a zmodel file, no plugin code module to load + return undefined; + } + + let moduleSpec = provider; + if (moduleSpec.startsWith('.')) { + // relative to schema's path + moduleSpec = path.resolve(basePath, moduleSpec); + } + + const importAsEsm = async (spec: string) => { + try { + const result = (await import(spec)).default as CliPlugin; + return result; + } catch (err) { + throw new CliError(`Failed to load plugin module from ${spec}: ${(err as Error).message}`); + } + }; + + const jiti = createJiti(pathToFileURL(basePath).toString()); + const importAsTs = async (spec: string) => { + try { + const result = (await jiti.import(spec, { default: true })) as CliPlugin; + return result; + } catch (err) { + throw new CliError(`Failed to load plugin module from ${spec}: ${(err as Error).message}`); + } + }; + + const esmSuffixes = ['.js', '.mjs']; + const tsSuffixes = ['.ts', '.mts']; + + if (fs.existsSync(moduleSpec) && fs.statSync(moduleSpec).isFile()) { + // try provider as ESM file + if (esmSuffixes.some((suffix) => moduleSpec.endsWith(suffix))) { + return await importAsEsm(pathToFileURL(moduleSpec).toString()); + } + + // try provider as TS file + if (tsSuffixes.some((suffix) => moduleSpec.endsWith(suffix))) { + return await importAsTs(moduleSpec); + } + } + + // try ESM index files in provider directory + for (const suffix of esmSuffixes) { + const indexPath = path.join(moduleSpec, `index${suffix}`); + if (fs.existsSync(indexPath)) { + return await importAsEsm(pathToFileURL(indexPath).toString()); + } + } + + // try TS index files in provider directory + for (const suffix of tsSuffixes) { + const indexPath = path.join(moduleSpec, `index${suffix}`); + if (fs.existsSync(indexPath)) { + return await importAsTs(indexPath); + } + } + + // last resort, try to import as esm directly + try { + const mod = await import(moduleSpec); + // plugin may not export a generator, return undefined in that case + return mod.default as CliPlugin | undefined; + } catch (err) { + const errorCode = (err as NodeJS.ErrnoException)?.code; + if (errorCode === 'ERR_MODULE_NOT_FOUND' || errorCode === 'MODULE_NOT_FOUND') { + throw new CliError(`Cannot find plugin module "${provider}". Please make sure the package exists.`); + } + throw new CliError(`Failed to load plugin module "${provider}": ${(err as Error).message}`); + } +} + const FETCH_CLI_MAX_TIME = 1000; const CLI_CONFIG_ENDPOINT = 'https://zenstack.dev/config/cli-v3.json'; diff --git a/packages/cli/src/actions/check.ts b/packages/cli/src/actions/check.ts index 10f063149..a7f765f36 100644 --- a/packages/cli/src/actions/check.ts +++ b/packages/cli/src/actions/check.ts @@ -1,5 +1,7 @@ +import { isPlugin, type Model } from '@zenstackhq/language/ast'; import colors from 'colors'; -import { getSchemaFile, loadSchemaDocument } from './action-utils'; +import path from 'node:path'; +import { getPluginProvider, getSchemaFile, loadPluginModule, loadSchemaDocument } from './action-utils'; type Options = { schema?: string; @@ -12,7 +14,8 @@ export async function run(options: Options) { const schemaFile = getSchemaFile(options.schema); try { - await loadSchemaDocument(schemaFile); + const model = await loadSchemaDocument(schemaFile); + await checkPluginResolution(schemaFile, model); console.log(colors.green('✓ Schema validation completed successfully.')); } catch (error) { console.error(colors.red('✗ Schema validation failed.')); @@ -20,3 +23,13 @@ export async function run(options: Options) { throw error; } } + +async function checkPluginResolution(schemaFile: string, model: Model) { + const plugins = model.declarations.filter(isPlugin); + for (const plugin of plugins) { + const provider = getPluginProvider(plugin); + if (!provider.startsWith('@core/')) { + await loadPluginModule(provider, path.dirname(schemaFile)); + } + } +} diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index fe31688b8..ce499ef11 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -1,22 +1,21 @@ import { invariant, singleDebounce } from '@zenstackhq/common-helpers'; import { ZModelLanguageMetaData } from '@zenstackhq/language'; -import { isPlugin, LiteralExpr, Plugin, type AbstractDeclaration, type Model } from '@zenstackhq/language/ast'; +import { isPlugin, type AbstractDeclaration, type Model } from '@zenstackhq/language/ast'; import { getLiteral, getLiteralArray } from '@zenstackhq/language/utils'; import { type CliPlugin } from '@zenstackhq/sdk'; import { watch } from 'chokidar'; import colors from 'colors'; -import { createJiti } from 'jiti'; -import fs from 'node:fs'; import path from 'node:path'; -import { pathToFileURL } from 'node:url'; import ora, { type Ora } from 'ora'; import semver from 'semver'; import { CliError } from '../cli-error'; import * as corePlugins from '../plugins'; import { getOutputPath, + getPluginProvider, getSchemaFile, getZenStackPackages, + loadPluginModule, loadSchemaDocument, startUsageTipsFetch, } from './action-utils'; @@ -258,14 +257,7 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string, } } -function getPluginProvider(plugin: Plugin) { - const providerField = plugin.fields.find((f) => f.name === 'provider'); - invariant(providerField, `Plugin ${plugin.name} does not have a provider field`); - const provider = (providerField.value as LiteralExpr).value as string; - return provider; -} - -function getPluginOptions(plugin: Plugin): Record { +function getPluginOptions(plugin: Parameters[0]): Record { const result: Record = {}; for (const field of plugin.fields) { if (field.name === 'provider') { @@ -281,72 +273,6 @@ function getPluginOptions(plugin: Plugin): Record { return result; } -async function loadPluginModule(provider: string, basePath: string) { - let moduleSpec = provider; - if (moduleSpec.startsWith('.')) { - // relative to schema's path - moduleSpec = path.resolve(basePath, moduleSpec); - } - - const importAsEsm = async (spec: string) => { - try { - const result = (await import(spec)).default as CliPlugin; - return result; - } catch (err) { - throw new CliError(`Failed to load plugin module from ${spec}: ${(err as Error).message}`); - } - }; - - const jiti = createJiti(pathToFileURL(basePath).toString()); - const importAsTs = async (spec: string) => { - try { - const result = (await jiti.import(spec, { default: true })) as CliPlugin; - return result; - } catch (err) { - throw new CliError(`Failed to load plugin module from ${spec}: ${(err as Error).message}`); - } - }; - - const esmSuffixes = ['.js', '.mjs']; - const tsSuffixes = ['.ts', '.mts']; - - if (fs.existsSync(moduleSpec) && fs.statSync(moduleSpec).isFile()) { - // try provider as ESM file - if (esmSuffixes.some((suffix) => moduleSpec.endsWith(suffix))) { - return await importAsEsm(pathToFileURL(moduleSpec).toString()); - } - - // try provider as TS file - if (tsSuffixes.some((suffix) => moduleSpec.endsWith(suffix))) { - return await importAsTs(moduleSpec); - } - } - - // try ESM index files in provider directory - for (const suffix of esmSuffixes) { - const indexPath = path.join(moduleSpec, `index${suffix}`); - if (fs.existsSync(indexPath)) { - return await importAsEsm(pathToFileURL(indexPath).toString()); - } - } - - // try TS index files in provider directory - for (const suffix of tsSuffixes) { - const indexPath = path.join(moduleSpec, `index${suffix}`); - if (fs.existsSync(indexPath)) { - return await importAsTs(indexPath); - } - } - - // last resort, try to import as esm directly - try { - return (await import(moduleSpec)).default as CliPlugin; - } catch { - // plugin may not export a generator so we simply ignore the error here - return undefined; - } -} - async function checkForMismatchedPackages(projectPath: string) { const packages = await getZenStackPackages(projectPath); if (!packages.length) { diff --git a/packages/cli/src/actions/proxy.ts b/packages/cli/src/actions/proxy.ts index 5e4f9464d..a68521189 100644 --- a/packages/cli/src/actions/proxy.ts +++ b/packages/cli/src/actions/proxy.ts @@ -75,18 +75,18 @@ export async function run(options: Options) { const schemaModule = (await jiti.import(path.join(outputPath, 'schema'))) as any; - // Build omit configuration for computed fields + // Build omit configuration for computed fields and Unsupported fields. const schema = schemaModule.schema as SchemaDef; const omit: Record> = {}; for (const [modelName, modelDef] of Object.entries(schema.models)) { - const computedFields: Record = {}; + const omitFields: Record = {}; for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) { - if (fieldDef.computed === true) { - computedFields[fieldName] = true; + if (fieldDef.computed === true || fieldDef.type === 'Unsupported') { + omitFields[fieldName] = true; } } - if (Object.keys(computedFields).length > 0) { - omit[modelName] = computedFields; + if (Object.keys(omitFields).length > 0) { + omit[modelName] = omitFields; } } diff --git a/packages/cli/test/check.test.ts b/packages/cli/test/check.test.ts index 99d31ecda..becc9dfd7 100644 --- a/packages/cli/test/check.test.ts +++ b/packages/cli/test/check.test.ts @@ -81,6 +81,39 @@ describe('CLI validate command test', () => { expect(() => runCli('check', workDir)).not.toThrow(); }); + it('should succeed when plugin module is resolvable', async () => { + const modelWithPlugin = ` +plugin myPlugin { + provider = './my-plugin' +} + +model User { + id String @id @default(cuid()) + @@custom +} +`; + const { workDir } = await createProject(modelWithPlugin); + const pluginDir = path.join(workDir, 'zenstack/my-plugin'); + fs.mkdirSync(pluginDir, { recursive: true }); + fs.writeFileSync(path.join(pluginDir, 'index.mjs'), 'export const name = "my-plugin";'); + fs.writeFileSync(path.join(pluginDir, 'plugin.zmodel'), 'attribute @@custom()'); + expect(() => runCli('check', workDir)).not.toThrow(); + }); + + it('should report error for unresolvable plugin module', async () => { + const modelWithMissingPlugin = ` +plugin foo { + provider = '@zenstackhq/nonexistent-plugin' +} + +model User { + id String @id @default(cuid()) +} +`; + const { workDir } = await createProject(modelWithMissingPlugin); + expect(() => runCli('check', workDir)).toThrow(/Cannot find plugin module/); + }); + it('should validate schema with syntax errors', async () => { const modelWithSyntaxError = ` model User { diff --git a/packages/cli/test/generate.test.ts b/packages/cli/test/generate.test.ts index 646cbb680..291f0e734 100644 --- a/packages/cli/test/generate.test.ts +++ b/packages/cli/test/generate.test.ts @@ -199,6 +199,79 @@ model User { expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(false); }); + it('should report error for unresolvable plugin module', async () => { + const modelWithMissingPlugin = ` +plugin foo { + provider = '@zenstackhq/nonexistent-plugin' +} + +model User { + id String @id @default(cuid()) +} +`; + const { workDir } = await createProject(modelWithMissingPlugin); + expect(() => runCli('generate', workDir)).toThrow(/Cannot find plugin module/); + }); + + it('should succeed when plugin module exists but has no CLI generator', async () => { + const modelWithNoGeneratorPlugin = ` +plugin foo { + provider = './my-plugin.mjs' +} + +model User { + id String @id @default(cuid()) +} +`; + const { workDir } = await createProject(modelWithNoGeneratorPlugin); + // Create a plugin module that doesn't export a default CLI generator + fs.writeFileSync(path.join(workDir, 'zenstack/my-plugin.mjs'), 'export const name = "no-generator";'); + runCli('generate', workDir); + // Should succeed without error, generating the default typescript output + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + }); + + it('should succeed when plugin only provides a plugin.zmodel for custom attributes', async () => { + const modelWithZmodelOnlyPlugin = ` +plugin myPlugin { + provider = './my-plugin' +} + +model User { + id String @id @default(cuid()) + @@custom +} +`; + const { workDir } = await createProject(modelWithZmodelOnlyPlugin); + // Create a plugin directory with index.mjs (no default export) and a plugin.zmodel defining a custom attribute + const pluginDir = path.join(workDir, 'zenstack/my-plugin'); + fs.mkdirSync(pluginDir, { recursive: true }); + fs.writeFileSync(path.join(pluginDir, 'index.mjs'), 'export const name = "my-plugin";'); + fs.writeFileSync(path.join(pluginDir, 'plugin.zmodel'), 'attribute @@custom()'); + runCli('generate', workDir); + // Should succeed without error, generating the default typescript output + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + }); + + it('should succeed when plugin provider is a .zmodel file', async () => { + const modelWithZmodelProvider = ` +plugin myPlugin { + provider = './custom-attrs/plugin.zmodel' +} + +model User { + id String @id @default(cuid()) + @@custom +} +`; + const { workDir } = await createProject(modelWithZmodelProvider); + const pluginDir = path.join(workDir, 'zenstack/custom-attrs'); + fs.mkdirSync(pluginDir, { recursive: true }); + fs.writeFileSync(path.join(pluginDir, 'plugin.zmodel'), 'attribute @@custom()'); + runCli('generate', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + }); + it('should prefer CLI options over @core/typescript plugin settings for generateModels and generateInput', async () => { const modelWithPlugin = ` plugin typescript { diff --git a/packages/clients/client-helpers/package.json b/packages/clients/client-helpers/package.json index 7398fc668..0e30523ba 100644 --- a/packages/clients/client-helpers/package.json +++ b/packages/clients/client-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/client-helpers", - "version": "3.4.3", + "version": "3.4.4", "description": "Helpers for implementing clients that consume ZenStack's CRUD service", "type": "module", "scripts": { diff --git a/packages/clients/tanstack-query/package.json b/packages/clients/tanstack-query/package.json index 10e863990..020014b43 100644 --- a/packages/clients/tanstack-query/package.json +++ b/packages/clients/tanstack-query/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/tanstack-query", - "version": "3.4.3", + "version": "3.4.4", "description": "TanStack Query Client for consuming ZenStack v3's CRUD service", "type": "module", "scripts": { diff --git a/packages/common-helpers/package.json b/packages/common-helpers/package.json index 8547a2d16..50cc27880 100644 --- a/packages/common-helpers/package.json +++ b/packages/common-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/common-helpers", - "version": "3.4.3", + "version": "3.4.4", "description": "ZenStack Common Helpers", "type": "module", "scripts": { diff --git a/packages/config/eslint-config/package.json b/packages/config/eslint-config/package.json index 7409124f4..66b864ba2 100644 --- a/packages/config/eslint-config/package.json +++ b/packages/config/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/eslint-config", - "version": "3.4.3", + "version": "3.4.4", "type": "module", "private": true, "license": "MIT" diff --git a/packages/config/typescript-config/package.json b/packages/config/typescript-config/package.json index f835a7ff0..333a5e4c3 100644 --- a/packages/config/typescript-config/package.json +++ b/packages/config/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/typescript-config", - "version": "3.4.3", + "version": "3.4.4", "private": true, "license": "MIT" } diff --git a/packages/config/vitest-config/package.json b/packages/config/vitest-config/package.json index 43a833584..c8b22184c 100644 --- a/packages/config/vitest-config/package.json +++ b/packages/config/vitest-config/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/vitest-config", "type": "module", - "version": "3.4.3", + "version": "3.4.4", "private": true, "license": "MIT", "exports": { diff --git a/packages/create-zenstack/package.json b/packages/create-zenstack/package.json index 793339fa5..4de6891f5 100644 --- a/packages/create-zenstack/package.json +++ b/packages/create-zenstack/package.json @@ -1,6 +1,6 @@ { "name": "create-zenstack", - "version": "3.4.3", + "version": "3.4.4", "description": "Create a new ZenStack project", "type": "module", "scripts": { diff --git a/packages/ide/vscode/package.json b/packages/ide/vscode/package.json index 269949542..f4a05378f 100644 --- a/packages/ide/vscode/package.json +++ b/packages/ide/vscode/package.json @@ -1,7 +1,7 @@ { "name": "zenstack-v3", "publisher": "zenstack", - "version": "3.4.3", + "version": "3.4.4", "displayName": "ZenStack V3 Language Tools", "description": "VSCode extension for ZenStack (v3) ZModel language", "private": true, diff --git a/packages/language/package.json b/packages/language/package.json index 48dfd281e..ad73e4afe 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/language", "description": "ZenStack ZModel language specification", - "version": "3.4.3", + "version": "3.4.4", "license": "MIT", "author": "ZenStack Team", "files": [ diff --git a/packages/orm/package.json b/packages/orm/package.json index f5e06b033..d35875e55 100644 --- a/packages/orm/package.json +++ b/packages/orm/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/orm", - "version": "3.4.3", + "version": "3.4.4", "description": "ZenStack ORM", "type": "module", "scripts": { diff --git a/packages/orm/src/client/helpers/schema-db-pusher.ts b/packages/orm/src/client/helpers/schema-db-pusher.ts index 9c16e61e2..4886687ef 100644 --- a/packages/orm/src/client/helpers/schema-db-pusher.ts +++ b/packages/orm/src/client/helpers/schema-db-pusher.ts @@ -84,6 +84,10 @@ export class SchemaDbPusher { for (const field of Object.values(model.fields)) { // relation order if (field.relation && field.relation.fields && field.relation.references) { + // skip self-referential relations to avoid false cycle in toposort + if (field.type === model.name) { + continue; + } const targetModel = requireModel(this.schema, field.type); // edge: fk side -> target model graph.push([model, targetModel]); diff --git a/packages/plugins/policy/package.json b/packages/plugins/policy/package.json index d97ef6e95..5efc9d1a6 100644 --- a/packages/plugins/policy/package.json +++ b/packages/plugins/policy/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/plugin-policy", - "version": "3.4.3", + "version": "3.4.4", "description": "ZenStack Policy Plugin", "type": "module", "scripts": { diff --git a/packages/schema/package.json b/packages/schema/package.json index 57a0ef2fb..1f4ad8cca 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/schema", - "version": "3.4.3", + "version": "3.4.4", "description": "ZenStack Runtime Schema", "type": "module", "scripts": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 144c5e649..fcb6049b9 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "3.4.3", + "version": "3.4.4", "description": "ZenStack SDK", "type": "module", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 897f69b7b..c2eba36cf 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "3.4.3", + "version": "3.4.4", "description": "ZenStack automatic CRUD API handlers and server adapters", "type": "module", "scripts": { diff --git a/packages/testtools/package.json b/packages/testtools/package.json index bface5e91..9d8efb11c 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "3.4.3", + "version": "3.4.4", "description": "ZenStack Test Tools", "type": "module", "scripts": { diff --git a/packages/zod/package.json b/packages/zod/package.json index bb5f27e08..a9bf32efe 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/zod", - "version": "3.4.3", + "version": "3.4.4", "description": "ZenStack Zod integration", "type": "module", "scripts": { diff --git a/samples/orm/package.json b/samples/orm/package.json index 43d2aac3c..0e48042c4 100644 --- a/samples/orm/package.json +++ b/samples/orm/package.json @@ -1,6 +1,6 @@ { "name": "sample-orm", - "version": "3.4.3", + "version": "3.4.4", "description": "", "main": "index.js", "private": true, diff --git a/tests/e2e/package.json b/tests/e2e/package.json index be50da497..bb8a70c7a 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -1,6 +1,6 @@ { "name": "e2e", - "version": "3.4.3", + "version": "3.4.4", "private": true, "type": "module", "scripts": { diff --git a/tests/regression/package.json b/tests/regression/package.json index ac9074539..e27f0a55c 100644 --- a/tests/regression/package.json +++ b/tests/regression/package.json @@ -1,6 +1,6 @@ { "name": "regression", - "version": "3.4.3", + "version": "3.4.4", "private": true, "type": "module", "scripts": { diff --git a/tests/regression/test/issue-2435.test.ts b/tests/regression/test/issue-2435.test.ts new file mode 100644 index 000000000..944805942 --- /dev/null +++ b/tests/regression/test/issue-2435.test.ts @@ -0,0 +1,37 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +// https://github.com/zenstackhq/zenstack/issues/2435 +describe('Regression for issue 2435', () => { + it('should not throw cyclic dependency error when delegate model has a self-referential relation', async () => { + await expect( + createTestClient( + ` +enum ContentType { + POST + ARTICLE + QUESTION +} + +model Content { + id Int @id @default(autoincrement()) + type ContentType + @@delegate(type) +} + +model Post extends Content { + post1s Post1[] + replies Post[] @relation("PostReplies") + parentId Int? + parent Post? @relation("PostReplies", fields: [parentId], references: [id]) +} + +model Post1 extends Content { + post Post @relation(fields: [postId], references: [id]) + postId Int +} + `, + ), + ).toResolveTruthy(); + }); +}); diff --git a/tests/runtimes/bun/package.json b/tests/runtimes/bun/package.json index 783aea594..8513628ee 100644 --- a/tests/runtimes/bun/package.json +++ b/tests/runtimes/bun/package.json @@ -1,6 +1,6 @@ { "name": "bun-e2e", - "version": "3.4.3", + "version": "3.4.4", "private": true, "type": "module", "scripts": { diff --git a/tests/runtimes/bun/schemas/schema.zmodel b/tests/runtimes/bun/schemas/schema.zmodel index ee491c084..f9abf4160 100644 --- a/tests/runtimes/bun/schemas/schema.zmodel +++ b/tests/runtimes/bun/schemas/schema.zmodel @@ -3,7 +3,7 @@ datasource db { } plugin policy { - provider = "../../../packages/plugins/policy" + provider = "../../../../packages/plugins/policy/plugin.zmodel" } model User { diff --git a/tests/runtimes/edge-runtime/package.json b/tests/runtimes/edge-runtime/package.json index e804c148d..48cc8cea8 100644 --- a/tests/runtimes/edge-runtime/package.json +++ b/tests/runtimes/edge-runtime/package.json @@ -1,6 +1,6 @@ { "name": "edge-runtime-e2e", - "version": "3.4.3", + "version": "3.4.4", "private": true, "type": "module", "scripts": { diff --git a/tests/runtimes/edge-runtime/schemas/schema.zmodel b/tests/runtimes/edge-runtime/schemas/schema.zmodel index 7872ce263..9842dc4ca 100644 --- a/tests/runtimes/edge-runtime/schemas/schema.zmodel +++ b/tests/runtimes/edge-runtime/schemas/schema.zmodel @@ -3,7 +3,7 @@ datasource db { } plugin policy { - provider = "../../../packages/plugins/policy" + provider = "../../../../packages/plugins/policy/plugin.zmodel" } model User {